用Python實現二叉樹的增、刪、查

weixin_34019929發表於2019-01-23

日拱一卒,每週為你帶點 Python 乾貨。

學過資料結構的同學一定這種資料結構非常熟悉了,樹是一種非常高效的非線性儲存結構,學好樹對理解一些複雜的演算法非常有幫助。樹有以下內容需要掌握:

  • 樹、二叉樹
  • 二叉查詢樹
  • 平衡二叉查詢樹、紅黑樹
  • 遞迴樹

作為一名 Python 程式設計師,如果把基礎的資料結構與演算法都自己親自實現一遍,那麼你已經比 90% 的 Python 程式設計師更優秀了。

今天的我們的目標是使用 Python 來實現一棵二叉樹。 二叉查詢樹、平衡二叉查詢樹、紅黑樹、遞迴樹後面也會實現,請保持關注。

先來梳理下概念:樹,可以很形象的理解,有根,有葉子,對應在資料結構中就是根節點、葉子節點,同一層的葉子叫兄弟節點,鄰近不同層的叫父子節點,非常好理解。

二叉樹,就是每個節點都至多有二個子節點的樹。

滿二叉樹,就是除了葉子節點外,每個節點都有左右兩個子節點,這種二叉樹叫做滿二叉樹。

完全二叉樹,就是葉子節點都在最底下兩層,最後一層葉子節都靠左排列,並且除了最後一層,其他層的節點個數都要達到最大,這種二叉樹叫做完全二叉樹。

二叉樹即可以使用鏈式儲存,也可以使用陣列來儲存,而完全二叉樹是使用資料存最省記憶體的一種結構。

接下來我們使用 Python 實現鏈式儲存的二叉樹。

思路:
1、先定義一個節點 node 類,儲存資料 item 和左右節點指標
2、再實現二叉樹 binary_tree 的類,類應至少有以下屬性和方法:
屬性:有一個根節點(root) , 它是 node 類。

方法:

  • 插入一個元素(逐層向下插入)。
  • 刪除一個元素(按照二叉查詢樹的方式刪除,先查詢待刪除節點的父節點。 如果父節點不為空, 判斷 item 的左右子樹,如果左子樹為空,那麼判斷 item 是父節點的左孩子,還是右孩子,如果是左孩子,將父節點的左指標指向 item 的右子樹,反之將父節點的右指標指向 item 的右子樹,如果右子樹為空,那麼判斷 item 是父節點的左孩子,還是右孩子,如果是左孩子,將父節點的左指標指向 item 的左子樹,反之將父節點的右指標指向 item 的左子樹,如果左右子樹均不為空,尋找右子樹中的最左葉子節點 x ,將 x 替代要刪除的節點。 ,如果父節點為空,說明不存在該元素 ,刪除元素,返回 True ,未刪除元素, 返回 False。
  • 獲取一個元素的父節點。
  • 層序遍歷:按層輸出二叉樹的元素
  • 中序遍歷:先左子樹,再根節點,最後右子樹
  • 前序遍歷:先根節點,再左子樹,最後右子樹
  • 後序遍歷:先左子樹,再右子樹,最後根節點。

按下來就是程式設計實現了,請動手自己實現一把。

先實現一個 node 類:


class Node(object):
    def __init__(self, item):
        self.item = item
        self.left = None
        self.right = None

    def __str__(self):
        return str(self.item)

這裡的 __str__ 方法是為了方便列印。在 print 一個 Node 類時會列印 __str__ 的返回值,例如:print(Node(5)) 就會列印出字串 5 。

實現一個二叉樹類:

class Tree(object):
    def __init__(self):
        # 根節點定義為 root 永不刪除,做為哨兵使用。
        self.root = Node('root')

    def add(self, item):
        node = Node(item)
        if self.root is None:
            self.root = node
        else:
            q = [self.root]

            while True:
                pop_node = q.pop(0)
                if pop_node.left is None:
                    pop_node.left = node
                    return
                elif pop_node.right is None:
                    pop_node.right = node
                    return
                else:
                    q.append(pop_node.left)
                    q.append(pop_node.right)

    def get_parent(self, item):
        '''
        找到 Item 的父節點
        '''
        if self.root.item == item:
            return None  # 根節點沒有父節點
        tmp = [self.root]
        while tmp:
            pop_node = tmp.pop(0)
            if pop_node.left and pop_node.left.item == item:
                return pop_node
            if pop_node.right and pop_node.right.item == item:
                return pop_node
            if pop_node.left is not None:
                tmp.append(pop_node.left)
            if pop_node.right is not None:
                tmp.append(pop_node.right)
        return None

    def delete(self, item):
        '''
        從二叉樹中刪除一個元素
        先獲取 待刪除節點 item 的父節點
        如果父節點不為空,
            判斷 item 的左右子樹
            如果左子樹為空,那麼判斷 item 是父節點的左孩子,還是右孩子,如果是左孩子,將父節點的左指標指向 item 的右子樹,反之將父節點的右指標指向 item 的右子樹
            如果右子樹為空,那麼判斷 item 是父節點的左孩子,還是右孩子,如果是左孩子,將父節點的左指標指向 item 的左子樹,反之將父節點的右指標指向 item 的左子樹
            如果左右子樹均不為空,尋找右子樹中的最左葉子節點 x ,將 x 替代要刪除的節點。
        刪除成功,返回 True
        刪除失敗, 返回 False

        '''
        if self.root is None:  # 如果根為空,就什麼也不做
            return False

        parent = self.get_parent(item)
        if parent:
            del_node = parent.left if parent.left.item == item else parent.right  # 待刪除節點
            if del_node.left is None:
                if parent.left.item == item:
                    parent.left = del_node.right
                else:
                    parent.right = del_node.right
                del del_node
                return True
            elif del_node.right is None:
                if parent.left.item == item:
                    parent.left = del_node.left
                else:
                    parent.right = del_node.left
                del del_node
                return True
            else:  # 左右子樹都不為空
                tmp_pre = del_node
                tmp_next = del_node.right
                if tmp_next.left is None:
                    # 替代
                    tmp_pre.right = tmp_next.right
                    tmp_next.left = del_node.left
                    tmp_next.right = del_node.right

                else:
                    while tmp_next.left:  # 讓tmp指向右子樹的最後一個葉子
                        tmp_pre = tmp_next
                        tmp_next = tmp_next.left
                    # 替代
                    tmp_pre.left = tmp_next.right
                    tmp_next.left = del_node.left
                    tmp_next.right = del_node.right
                if parent.left.item == item:
                    parent.left = tmp_next
                else:
                    parent.right = tmp_next
                del del_node
                return True
        else:
            return False

    def traverse(self):  # 層次遍歷
        if self.root is None:
            return None
        q = [self.root]
        res = [self.root.item]
        while q != []:
            pop_node = q.pop(0)
            if pop_node.left is not None:
                q.append(pop_node.left)
                res.append(pop_node.left.item)

            if pop_node.right is not None:
                q.append(pop_node.right)
                res.append(pop_node.right.item)
        return res

    def preorder(self, root):  # 先序遍歷
        if root is None:
            return []
        result = [root.item]
        left_item = self.preorder(root.left)
        right_item = self.preorder(root.right)
        return result + left_item + right_item

    def inorder(self, root):  # 中序序遍歷
        if root is None:
            return []
        result = [root.item]
        left_item = self.inorder(root.left)
        right_item = self.inorder(root.right)
        return left_item + result + right_item

    def postorder(self, root):  # 後序遍歷
        if root is None:
            return []
        result = [root.item]
        left_item = self.postorder(root.left)
        right_item = self.postorder(root.right)
        return left_item + right_item + result

執行測試:

if __name__ == '__main__':
    t = Tree()
    for i in range(10):
        t.add(i)
    print('層序遍歷:', t.traverse())
    print('先序遍歷:', t.preorder(t.root))
    print('中序遍歷:', t.inorder(t.root))
    print('後序遍歷:', t.postorder(t.root))

    for i in range(10):
        print(i, " 的父親", t.get_parent(i))

    for i in range(0, 15, 3):
        print(f"刪除 {i}", '成功' if t.delete(i) else '失敗')
        print('層序遍歷:', t.traverse())
        print('先序遍歷:', t.preorder(t.root))
        print('中序遍歷:', t.inorder(t.root))
        print('後序遍歷:', t.postorder(t.root))

注意這裡使用了根節點 root 作為哨兵,永不刪除,簡化了 delete 函式的實現,這樣也最不容易出錯,否則還要判斷待刪除的節點是否是根節點。

執行結果如下:

層序遍歷: ['root', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
先序遍歷: ['root', 0, 2, 6, 7, 3, 8, 9, 1, 4, 5]
中序遍歷: [6, 2, 7, 0, 8, 3, 9, 'root', 4, 1, 5]
後序遍歷: [6, 7, 2, 8, 9, 3, 0, 4, 5, 1, 'root']
0  的父親 root
1  的父親 root
2  的父親 0
3  的父親 0
4  的父親 1
5  的父親 1
6  的父親 2
7  的父親 2
8  的父親 3
9  的父親 3
刪除 0 成功
層序遍歷: ['root', 8, 1, 2, 3, 4, 5, 6, 7, 9]
先序遍歷: ['root', 8, 2, 6, 7, 3, 9, 1, 4, 5]
中序遍歷: [6, 2, 7, 8, 3, 9, 'root', 4, 1, 5]
後序遍歷: [6, 7, 2, 9, 3, 8, 4, 5, 1, 'root']
刪除 3 成功
層序遍歷: ['root', 8, 1, 2, 9, 4, 5, 6, 7]
先序遍歷: ['root', 8, 2, 6, 7, 9, 1, 4, 5]
中序遍歷: [6, 2, 7, 8, 9, 'root', 4, 1, 5]
後序遍歷: [6, 7, 2, 9, 8, 4, 5, 1, 'root']
刪除 6 成功
層序遍歷: ['root', 8, 1, 2, 9, 4, 5, 7]
先序遍歷: ['root', 8, 2, 7, 9, 1, 4, 5]
中序遍歷: [2, 7, 8, 9, 'root', 4, 1, 5]
後序遍歷: [7, 2, 9, 8, 4, 5, 1, 'root']
刪除 9 成功
層序遍歷: ['root', 8, 1, 2, 4, 5, 7]
先序遍歷: ['root', 8, 2, 7, 1, 4, 5]
中序遍歷: [2, 7, 8, 'root', 4, 1, 5]
後序遍歷: [7, 2, 8, 4, 5, 1, 'root']
刪除 12 失敗
層序遍歷: ['root', 8, 1, 2, 4, 5, 7]
先序遍歷: ['root', 8, 2, 7, 1, 4, 5]
中序遍歷: [2, 7, 8, 'root', 4, 1, 5]
後序遍歷: [7, 2, 8, 4, 5, 1, 'root']
12989993-f1d6d769de86e4dc.png
個人微信公眾號.png

相關文章