樹與二叉樹

1. 樹的概念

2. 二叉樹與基本實現

3. 二叉樹的遍歷 

 

 

1. 樹的概念

樹(英語:tree)是一種抽象數據類型(ADT)或是實做這種抽象數據類型的數據結構,用來模擬具備樹狀結構性質的數據集合。它是由n(n>=1)個有限節點組成一個具備層次關係的集合。把它叫作「樹」是由於它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。它具備如下的特色:html

  • 每一個節點有零個或多個子節點;
  • 沒有父節點的節點稱爲根節點;
  • 每個非根節點有且只有一個父節點;
  • 除了根節點外,每一個子節點能夠分爲多個不相交的子樹。

圖示:node

 樹的術語

  • 節點的度:一個節點含有的子樹的個數稱爲該節點的度;
  • 樹的度:一棵樹中,最大的節點的度稱爲樹的度;
  • 葉節點或終端節點:度爲零的節點;
  • 父親節點或父節點:若一個節點含有子節點,則這個節點稱爲其子節點的父節點;
  • 孩子節點或子節點:一個節點含有的子樹的根節點稱爲該節點的子節點;
  • 兄弟節點:具備相同父節點的節點互稱爲兄弟節點;
  • 節點的層次:從根開始定義起,根爲第1層,根的子節點爲第2層,以此類推;
  • 樹的高度或深度:樹中節點的最大層次;
  • 堂兄弟節點:父節點在同一層的節點互爲堂兄弟;
  • 節點的祖先:從根到該節點所經分支上的全部節點;
  • 子孫:以某節點爲根的子樹中任一節點都稱爲該節點的子孫;
  • 森林:由m(m>=0)棵互不相交的樹的集合稱爲森林。

樹的種類

  • 無序樹:樹中任意節點的子節點之間沒有順序關係,這種樹稱爲無序樹,也稱爲自由樹;
  • 有序樹:樹中任意節點的子節點之間有順序關係,這種樹稱爲有序樹;
    • 二叉樹:每一個節點最多含有兩個子樹的樹稱爲二叉樹;
      • 徹底二叉樹:對於一顆二叉樹,假設其深度爲d(d>1)。除了第d層外,其它各層的節點數目均已達最大值,且第d層全部節點從左向右連續地緊密排列,這樣的二叉樹被稱爲徹底二叉樹,其中滿二叉樹的定義是全部葉節點都在最底層的徹底二叉樹;
      • 平衡二叉樹(AVL樹):當且僅當任何節點的兩棵子樹的高度差不大於1的二叉樹;
      • 排序二叉樹(二叉查找樹,Binary Search Tree):也稱二叉搜索樹、有序二叉樹);
    • 霍夫曼樹(用於信息編碼):帶權路徑最短的二叉樹稱爲哈夫曼樹或最優二叉樹;
    • B樹:一種對讀寫操做進行優化的自平衡的二叉查找樹,可以保持數據有序,擁有多餘兩個子樹。

樹的存儲與表示

順序存儲

將數據結構存儲在固定的數組中,雖然在遍歷速度上有必定的優點,但因所佔空間比較大,是非主流二叉樹。二叉樹一般以鏈式存儲。mysql

鏈式存儲

 

因爲對節點的個數沒法掌握,常見樹的存儲表示都轉換成二叉樹進行處理,子節點個數最多爲2。算法

常見的一些樹的應用場景

  1. xml,html等,那麼編寫這些東西的解析器的時候,不可避免用到樹。
  2. 路由協議就是使用了樹的算法。
  3. mysql數據庫索引。
  4. 文件系統的目錄結構。
  5. 不少經典的AI算法其實都是樹搜索,此外機器學習中的decision tree也是樹結構。

 

 

 2. 二叉樹與基本實現

二叉樹的基本概念

二叉樹是每一個節點最多有兩個子樹的樹結構。一般子樹被稱做「左子樹」(left subtree)和「右子樹」(right subtree)。sql

二叉樹的性質(特性)

  • 性質1:在二叉樹的第i層上至多有2^(i-1)個結點(i>0)
  • 性質2:深度爲k的二叉樹至多有2^k - 1個結點(k>0)
  • 性質3:對於任意一棵二叉樹,若是其葉結點數爲N0,而度數爲2的結點總數爲N2,則N0=N2+1
  • 性質4:具備n個結點的徹底二叉樹的深度必爲 log2(n+1)
  • 性質5:對徹底二叉樹,若從上至下、從左至右編號,則編號爲i 的結點,其左孩子編號必爲2i,其右孩子編號必爲2i+1;其雙親的編號必爲i/2(i=1 時爲根,除外)

徹底二叉樹

若設二叉樹的高度爲h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第h層有葉子結點,而且葉子結點都是從左到右依次排布,這就是徹底二叉樹。數據庫

滿二叉樹

除了葉結點外每個結點都有左右子葉且葉子結點都處在最底層的二叉樹。數組

二叉樹的節點表示以及樹的建立

 1 class Node:
 2     """樹的節點類"""
 3     def __init__(self, item):
 4         self.elem = item
 5         self.lchild = None
 6         self.rchild = None
 7 
 8 
 9 class Tree:
10     """二叉樹"""
11     
12     def __init__(self, root=None):
13         """初始化根節點"""
14         self.root = root
15 
16     def add(self, elem):
17         """爲樹添加節點"""
18         node = Node(elem)
19         # 若是樹是空的,則爲根節點賦值
20         if self.root is None:
21             self.root = node
22         else:
23             queue = []
24             queue.append(self.root)
25             # 對已有節點進行層次遍歷
26             while queue:
27                 # 彈出隊列的第一個元素(先進先出)
28                 cur_node = queue.pop(0)
29                 if cur_node.lchild is None:
30                     cur_node.lchild = node
31                     return
32                 elif cur_node.rchild is None:
33                     cur_node.rchild = node
34                     return
35                 # 若是左右子樹都不爲空,加入隊列繼續判斷
36                 else:
37                     queue.append(cur_node.lchild)
38                     queue.append(cur_node.rchild)

 

3. 二叉樹的遍歷

樹的遍歷是樹的一種重要的運算。所謂遍歷是指對樹中全部節點的信息的訪問,即依次對樹中每一個結點訪問一次且僅訪問一次,咱們把這種對全部節點的訪問稱爲遍歷(traversal)。數據結構

樹的兩種重要的遍歷模式是深度優先遍歷和廣度優先遍歷。深度優先通常用遞歸,廣度優先通常用隊列。通常狀況下能用遞歸實現的算法大部分也能用堆棧來實現app

廣度優先遍歷(層次遍歷)

從樹的root開始,從上到下、從左到右地遍歷整個樹的節點。機器學習

深度優先遍歷

對於一顆二叉樹,深度優先搜索(Depth First Search)是沿着樹的深度遍歷樹的節點,儘量深的搜索樹的分支。

深度遍歷有重要的三種方法。這三種方式常被用於訪問樹的節點,它們之間的不一樣在於訪問每一個節點的次序不一樣。這三種遍歷分別叫作先序遍歷(preorder),中序遍歷(inorder)和後序遍歷(postorder)。

先序遍歷

在先序遍歷中,咱們先訪問根節點,而後遞歸使用先序遍歷訪問左子樹,再遞歸使用先序遍歷訪問右子樹。

  • 根節點 -> 左子樹 -> 右子樹

中序遍歷

在中序遍歷中,咱們遞歸使用中序遍歷訪問左子樹,而後訪問根節點,最後再遞歸使用中序遍歷訪問右子樹。

  • 左子樹 -> 根節點 -> 右子樹

後序遍歷

在後序遍歷中,咱們先遞歸使用後序遍歷訪問左子樹和右子樹,最後訪問根節點。

  • 左子樹 -> 右子樹 -> 根節點

示例

 代碼實現

  1 class Node:
  2     """樹的節點類"""
  3     def __init__(self, item):
  4         self.elem = item
  5         self.lchild = None
  6         self.rchild = None
  7 
  8 
  9 class Tree:
 10     """二叉樹"""
 11 
 12     def __init__(self, root=None):
 13         """初始化根節點"""
 14         self.root = root
 15 
 16     def add(self, elem):
 17         """爲樹添加節點"""
 18         node = Node(elem)
 19         # 若是樹是空的,則爲根節點賦值
 20         if self.root is None:
 21             self.root = node
 22         else:
 23             queue = []
 24             queue.append(self.root)
 25             # 對已有節點進行層次遍歷
 26             while queue:
 27                 # 彈出隊列的第一個元素(先進先出)
 28                 cur_node = queue.pop(0)
 29                 if cur_node.lchild is None:
 30                     cur_node.lchild = node
 31                     return
 32                 elif cur_node.rchild is None:
 33                     cur_node.rchild = node
 34                     return
 35                 # 若是左右子樹都不爲空,加入隊列繼續判斷
 36                 else:
 37                     queue.append(cur_node.lchild)
 38                     queue.append(cur_node.rchild)
 39 
 40     def breadth_travel(self):
 41         """廣度遍歷"""
 42         if self.root is None:
 43             return
 44         queue = [self.root]
 45         while queue:
 46             cur_node = queue.pop(0)
 47             print(cur_node.elem, end=" ")
 48             if cur_node.lchild is not None:
 49                 queue.append(cur_node.lchild)
 50             if cur_node.rchild is not None:
 51                 queue.append(cur_node.rchild)
 52 
 53     def preorder(self, node):
 54         """遞歸實現先序遍歷"""
 55         if node is None:
 56             return
 57         print(node.elem, end=" ")
 58         self.preorder(node.lchild)
 59         self.preorder(node.rchild)
 60 
 61     def midorder(self, node):
 62         """遞歸實現中序遍歷"""
 63         if node is None:
 64             return
 65         self.midorder(node.lchild)
 66         print(node.elem, end=" ")
 67         self.midorder(node.rchild)
 68 
 69     def postorder(self, node):
 70         """遞歸實現後序遍歷"""
 71         if node is None:
 72             return
 73         self.postorder(node.lchild)
 74         self.postorder(node.rchild)
 75         print(node.elem, end=" ")
 76 
 77 
 78 if __name__ == "__main__":
 79     tree = Tree()
 80     tree.add(0)
 81     tree.add(1)
 82     tree.add(2)
 83     tree.add(3)
 84     tree.add(4)
 85     tree.add(5)
 86     tree.add(6)
 87     tree.add(7)
 88     tree.add(8)
 89     tree.add(9)
 90     print("廣度遍歷:", end="")
 91     tree.breadth_travel()
 92     print()
 93     print("先序遍歷:", end="")
 94     tree.preorder(tree.root)
 95     print()
 96     print("中序遍歷:", end="")
 97     tree.midorder(tree.root)
 98     print()
 99     print("後序遍歷:", end="")
100     tree.postorder(tree.root)

運行結果:

廣度遍歷:0 1 2 3 4 5 6 7 8 9 
先序遍歷:0 1 3 7 8 4 9 2 5 6 
中序遍歷:7 3 8 1 9 4 0 5 2 6 
後序遍歷:7 8 3 9 4 1 5 6 2 0 

思考:哪兩種遍歷方式可以惟一的肯定一顆樹?

  • 先序:a b c d e f g h
  • 中序:b d c e a f h g
  • 後序:d e c b h g f a

答:先序與中序,或中序與後續

相關文章
相關標籤/搜索