樹 (tree) 是一種非常高效的非線性存儲結構。樹,可以很形象的理解,有根,有葉子,對應在數據結構中就是根節點、葉子節點,同一層的葉子叫兄弟節點,鄰近不同層的叫父子節點,非常好理解。
注:定義來自百度百科。
其他概念解釋
-
二叉樹,就是每個節點都至多有二個子節點的樹。
-
滿二叉樹,就是除了葉子節點外,每個節點都有左右兩個子節點,這種二叉樹叫做滿二叉樹。
-
完全二叉樹,就是葉子節點都在最底下兩層,最后一層葉子節都靠左排列,並且除了最后一層,其他層的節點個數都要達到最大,這種二叉樹叫做完全二叉樹。
在接下來的內容里,我們將逐步介紹二叉樹的具體功能是如何實現的。
思路:
-
先定義一個節點 node 類,存儲數據 data 和左子節點 left 以及 右子節點 right。
-
再實現二叉樹 binary_tree 的類,應至少有以下屬性和函數: 屬性:有一個根節點(root) , 它是 node 類。 函數:添加子節點 add ,返回父節點 get_parent,刪除子節點 delete。
步驟如下:
1. 創建 Node 類
創建一個 Node 的類,作為基礎數據結構:鏈點,並初始化對應的內參。
具體實現代碼如下:
class Node(object): def __init__(self,item): self.item = item #表示對應的元素 self.left=None #表示左子節點 self.right=None #表示右子節點 def __str__(self): return str(self.item) #print 一個 Node 類時會打印 __str__ 的返回值
2. 創建 Tree 類
創建一個 Tree 的類,定義根節點。
具體實現代碼如下:
class Tree(object): def __init__(self): self.root=Node('root') #根節點定義為 root 永不刪除,作為哨兵使用。
3. 添加 add 函數
添加一個 add(item) 的函數,功能是添加子節點到樹里面。
具體實現代碼如下:
def add(self,item): node = Node(item) if self.root is None: #如果二叉樹為空,那么生成的二叉樹最終為新插入樹的點 self.root = node else: q = [self.root] # 將q列表,添加二叉樹的根節點 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)
4. 添加 get_parent 函數
添加一個 get_parent(item) 函數,功能是找到 item 的父節點。
具體實現代碼如下:
def get_parent(self, item): if self.root.item == item: return None # 根節點沒有父節點 tmp = [self.root] # 將tmp列表,添加二叉樹的根節點 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 元素 tmp.append(pop_node.left) if pop_node.right is not None: tmp.append(pop_node.right) return None
5. 添加 delete 函數
添加一個 delete(item) 函數,功能是從二叉樹中刪除一個子節點。
思路如下:
先獲取待刪除節點 item 的父節點。 如果父節點不為空,判斷 item 的左右子樹: 如果左子樹為空,那么判斷 item 是父節點的左孩子,還是右孩子; 如果是左孩子,將父節點的左指針指向 item 的右子樹,反之將父節點的右指針指向 item 的右子樹。 如果右子樹為空,那么判斷 item 是父節點的左孩子,還是右孩子; 如果是左孩子,將父節點的左指針指向 item 的左子樹,反之將父節點的右指針指向 item 的左子樹。 如果左右子樹均不為空,尋找右子樹中的最左葉子節點 x ,將 x 替代要刪除的節點。 刪除成功,返回 True。 刪除失敗, 返回 False。
效果演示:對已知二叉樹刪除元素 32
具體實現代碼如下:
def delete(self, item): 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
最終完整代碼如下:
class Node(object): def __init__(self,item): self.item=item #表示對應的元素 self.left=None #表示左節點 self.right=None #表示右節點 def __str__(self): return str(self.item) #print 一個 Node 類時會打印 __str__ 的返回值 class Tree(object): def __init__(self): self.root=Node('root') #根節點定義為 root 永不刪除,作為哨兵使用。 def add(self,item): node = Node(item) if self.root is None: #如果二叉樹為空,那么生成的二叉樹最終為新插入樹的點 self.root = node else: q = [self.root] # 將q列表,添加二叉樹的根節點 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): if self.root.item == item: return None # 根節點沒有父節點 tmp = [self.root] # 將tmp列表,添加二叉樹的根節點 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 元素 tmp.append(pop_node.left) if pop_node.right is not None: tmp.append(pop_node.right) return None def delete(self, item): 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
二叉搜索樹又稱二叉查找樹,亦稱二叉排序樹,如下圖所示:
它主要用於搜索。 它或者是一棵空樹,或者是具有下列性質的二叉樹:
-
若左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
-
若右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
-
左、右子樹也分別為二叉排序樹。
平衡二叉樹(平衡二叉樹又被稱為 AVL 樹 )是基於二分法的策略提高數據的查找速度的二叉樹的數據結構。
特點:平衡二叉樹是采用二分法思維把數據按規則組裝成一個樹形結構的數據,用這個樹形結構的數據減少無關數據的檢索,大大的提升了數據檢索的速度;平衡二叉樹的數據結構組裝過程有以下規則:
-
非葉子節點只能允許最多兩個子節點存在。
-
每一個非葉子節點數據分布規則為左邊的子節點小於前節點的值,右邊的子節點大於當前節點的值(這里值是基於自己的算法規則而定的,比如 hash 值)。
注:定義來自百度百科。
遍歷原理:
二叉樹的遍歷:是指從根結點出發,按照某種次序依次訪問二叉樹中的所有結點,使得每個結點被訪問一次且僅被訪問一次。
這里有兩個關鍵詞:訪問和次序。
訪問其實是要根據實際的需要來確定具體做什么,比如對每個結點進行相關計算,輸出打印等。它算作是一個抽象操作。
二叉樹的遍歷次序不同於線性結構,最多也就是從頭到尾、循環和雙向等簡單的遍歷方式。樹的結點之間不存在唯一的前驅和后繼關系,在訪問一個結點后,下一個被訪問的結點面臨着不同的選擇。
二叉樹的遍歷方式可以有很多,如果我們限制從左到右的順序,就主要分為三種:
-
中序遍歷
-
后序遍歷
-
前序遍歷
中序遍歷
-
先處理左子樹,然后處理當前節點,再處理右子樹;
-
對於一顆二叉查找樹,所有的信息都是有序排列的,中序遍歷可以是信息有序輸出,且運行時間為 O(n);
-
遞歸實現中序遍歷。
在之前的 Tree 類里面添加 inorder 函數
參考代碼如下:
def inorder(self,node): # 中序遍歷 if node is None: return [] result = [node.item] left_item = self.inorder(node.left) right_item = self.inorder(node.right) return left_item + result + right_item
中序遍歷的效果演示:
-
先處理左右子樹,然后再處理當前節點,運行時間為 O(n)
-
遞歸實現后序遍歷
參考代碼如下:
def postorder(self,node): # 后序遍歷 if node is None: return [] result = [node.item] left_item = self.postorder(node.left) right_item = self.postorder(node.right) return left_item + right_item + result
-
先處理當前節點,再處理左右子樹;
-
遞歸實現先序遍歷。
參考代碼如下:
def preorder(self,node): # 先序遍歷 if node is None: return [] result = [node.item] left_item = self.preorder(node.left) right_item = self.preorder(node.right) return result + left_item + right_item