重學資料結構之樹和二叉樹

TM0831發表於2020-05-18

一、樹和森林

1.基本概念

  樹狀圖(Tree)又稱為樹,是一種複雜的資料結構。樹是由 n(n>=0)個有限節點組成一個具有層次關係的集合,把它叫做“樹”是因為它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。當 n=0 時,稱之為空樹,否則是非空樹。

  樹具有以下的特點:

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

2.基本術語

  子女:若節點的子樹非空,則節點子樹的根即為該節點的子女。

  雙親:若節點有子女,則該節點即為子女的雙親。

  兄弟:同一節點的子女互稱為兄弟。

  :節點的子女個數即為該節點的度。

  分支節點:度不為0的節點即為分支節點。

  葉子節點:度為0的節點即為葉子節點。

  深度:節點的深度即為該節點所在的層次。

  高度:規定葉子節點的高度為1,其雙親節點的高度等於它的高度加1。

  森林:森林是 m(m>=0)顆樹的集合。

  

二、二叉樹

1.基本概念

  一顆二叉樹是節點的有限集合,該集合或者為空,或者由一個根節點和兩顆分別稱為左子樹和右子樹的、互不交叉的二叉樹組成(子樹有左右之分,不可顛倒)。下面是二叉樹的五種形態:

  

2.基本性質

  性質1:若二叉樹節點的層次從1開始,則在二叉樹的第 i 層最多有 2i-1 個節點(i>=1)。

  性質2:深度為 k 的二叉樹最少有 k 個節點,最多有 2k-1個 節點(k>=1)。

  性質3:對任意一顆二叉樹,如果其葉子節點有 m 個,度為2的非葉子節點為 n 個,則有:m = n + 1。

  設度為1的節點有 p 個,總節點數為 x,總邊數為 e,則 x = m + n + p,e  = n - 1 = 2 * n + p。因此有:

2 * n + p = m + n + p - 1

n = m - 1

m = n + 1

3.滿二叉樹和完全二叉樹

1)滿二叉樹:除了最後一層無任何子節點,每一層的所有節點都有兩個子節點,即除了葉子節點外的所有節點均有兩個子節點,這樣的二叉樹就稱為滿二叉樹。

2)完全二叉樹:設一個二叉樹的深度為 k,即共有 k 層。除了第 k 層外,其他各層的節點數都達到最大個數,且最後一層上只缺少右邊的若干節點,這樣的二叉樹就稱為完全二叉樹。

4.Python 實現

   下面是用 Python 實現的一個二叉樹的程式碼,除了實現建立二叉樹,還實現了四種遍歷二叉樹的方法,分別是:前序遍歷、中序遍歷、後序遍歷和層次遍歷。

  1 # 自定義樹節點
  2 class Node:
  3     def __init__(self, value=None, left=None, right=None):
  4         self.value = value
  5         self.left = left
  6         self.right = right
  7 
  8 
  9 # 自定義二叉樹
 10 class BinaryTree:
 11     def __init__(self, value=None, left=None, right=None):
 12         """
 13         根據傳入的引數決定樹的形態
 14         :param value: 節點值
 15         :param left: 左子樹
 16         :param right: 右子樹
 17         """
 18         self.root = Node(value, left, right) if value else None
 19 
 20     def is_empty(self):
 21         """
 22         判斷是否為空樹
 23         :return: 
 24         """
 25         return True if self.root else False
 26 
 27     def get_root(self):
 28         """
 29         獲取根節點
 30         :return: 
 31         """
 32         return self.root
 33 
 34     def get_left(self):
 35         """
 36         獲取左子樹
 37         :return: 
 38         """
 39         return self.root.left if self.root else None
 40 
 41     def get_right(self):
 42         """
 43         獲取右子樹
 44         :return: 
 45         """
 46         return self.root.right if self.root else None
 47 
 48     def set_left(self, node: Node):
 49         """
 50         設定左子樹
 51         :param node: 樹節點
 52         :return: 
 53         """
 54         self.root.left = node
 55 
 56     def set_right(self, node: Node):
 57         """
 58         設定右子樹
 59         :param node: 樹節點
 60         :return: 
 61         """
 62         self.root.right = node
 63     
 64     def pre_traverse(self, node: Node):
 65         """
 66         前序遍歷
 67         :param node: 根節點
 68         :return:
 69         """
 70         if not node:
 71             return
 72         print(node.value, end=" ")
 73         self.pre_traverse(node.left)
 74         self.pre_traverse(node.right)
 75 
 76     def mid_traverse(self, node: Node):
 77         """
 78         中序遍歷
 79         :param node: 根節點
 80         :return:
 81         """
 82         if not node:
 83             return
 84         self.mid_traverse(node.left)
 85         print(node.value, end=" ")
 86         self.mid_traverse(node.right)
 87 
 88     def after_traverse(self, node: Node):
 89         """
 90         後序遍歷
 91         :param node: 根節點
 92         :return:
 93         """
 94         if not node:
 95             return
 96         self.after_traverse(node.left)
 97         self.after_traverse(node.right)
 98         print(node.value, end=" ")
 99 
100     def level_traverse(self, nodes: list):
101         next_nodes = []
102         if nodes:
103             for node in nodes:
104                 print(node.value, end=" ")
105                 if node.left:
106                     next_nodes.append(node.left)
107                 if node.right:
108                     next_nodes.append(node.right)
109             self.level_traverse(next_nodes)

  當然光有這些方法還是不夠的,因為要一個個的建立節點還是很麻煩,我們可以將一個樹節點定義成一個三元組:

(樹節點的值,左子樹,右子樹)

  而左子樹和右子樹也都可以用這種三元組來表示,例如下面是一顆二叉樹的三元組表示:

(F, (C, A, (D, B, None)), (E, H, (G, M, None)))   

  

  因此還需要實現一個將這種三元組表達形式轉換成二叉樹的方法:

 1     def create(self, data: tuple):
 2         """
 3         建立一個二叉樹
 4         :param data: 例如(1,(2,4,5),(3,6,7))
 5         """
 6         self.root = self.parse(data)
 7 
 8     def parse(self, data: tuple):
 9         """
10         解析傳入的三元組,建立二叉樹
11         :param data: 三元組
12         :return:
13         """
14         if data:
15             node = Node(data[0])
16             node.left = self.parse(data[1]) if type(data[1]) == tuple else Node(data[1])
17             node.right = self.parse(data[2]) if type(data[2]) == tuple else Node(data[2])
18             return node

 

三、樹的應用

1.表示式計算問題

1)問題描述

  一個結構正確的二元表示式對應於一個滿二叉樹,例如下面這樣一顆二叉樹:

  

 

  這樣一顆二叉樹前序遍歷得到“-+1*23/45”,後序遍歷得到“123*+45/-”,正是波蘭表示式的形式,而其中序遍歷結果就是“1+2*3-4/5”,基本就是相應數學表示式的正確形式,只是缺少了表達計算順序的括號。輸入一個簡單的只包含四則運算的數學計算表示式,求出其計算後的結果。

2)問題分析

  對於任意一個二元表示式(如“a+b”)都可以用一個三元組來表示(如“('+', a, b)”),而像上面示例中的表示式,就可以用下面這種三元組表示:

("-", ("+", 1, ("*", 2, 3)), ("/", 4, 5))

  這種三元表示式都包含一個操作符設為 op,兩個運算元分別為 x 和 y,因而可以寫出下面的計算方法:

1 if op == "+":
2     return x + y
3 if op == "-":
4     return x - y
5 if op == "*":
6     return x * y
7 if op == "/":
8     return x / y if y else 0

  那麼現在的問題就是如何將數學表示式轉換成二叉樹,再就根據二叉樹得到三元組,最後就能使用上面的方法把表示式的值求出來了。因為我們都知道在沒有括號的情況下,乘除的優先順序是高於加減的,而利用二叉樹求解表示式時會從下往上,從葉子節點往根節點進行計算,所以要把加減號放在上面,乘除號放在下面。

3)二叉樹求解

  下面是根據輸入的表示式字串建立二叉樹的具體程式碼:

 1 def build_tree(input_string):
 2     """
 3     根據輸入的表示式字串建立二叉樹
 4     :param input_string: 輸入表示式
 5     :return:
 6     """
 7     if "+" in input_string or "-" in input_string:
 8         for i in range(len(input_string)):
 9             if input_string[i] in ["+", "-"] and "+" not in input_string[i + 1:] and "-" not in input_string[i + 1:]:
10                 node = Node(input_string[i], build_tree(input_string[:i]), build_tree(input_string[i + 1:]))
11                 return node
12     elif "*" in input_string or "/" in input_string:
13         for i in range(len(input_string)):
14             if input_string[i] in ["*", "/"]:
15                 node = Node(input_string[i], build_tree(input_string[:i]), build_tree(input_string[i + 1:]))
16                 return node
17     else:
18         return Node(input_string)

  在生成二叉樹之後,還要根據這個二叉樹得到三元組,下面就是使用遞迴演算法得到三元組的程式碼:

 1 def trans(self, node: Node, data: tuple):
 2     """
 3     將二叉樹轉換成三元組
 4     :param node: 節點
 5     :param data: 三元組
 6     :return:
 7     """
 8     left = self.trans(node.left, data) if node.left.left else node.left.value
 9     right = self.trans(node.right, data) if node.right.left else node.right.value
10     data = (node.value, left, right)
11     return data

  現在已經能生成二叉樹並得到表示式的三元組了,再就是求值了,下面是使用遞迴演算法計算三元表示式的值的程式碼:

 1 def solution(e):
 2     """
 3     計算表示式的值
 4     :param e: 轉化成三元組表達的計算表示式
 5     :return:
 6     """
 7     if type(e) == tuple:
 8         op, a, b = e[0], solution(e[1]), solution(e[2])
 9         x, y = float(a), float(b)
10         if op == "+":
11             return x + y
12         if op == "-":
13             return x - y
14         if op == "*":
15             return x * y
16         if op == "/":
17             return x / y if y else 0
18     else:
19         return e

  求解計算表示式的程式碼已經完成了,下面是使用這些程式碼來求"1+2*3-4/5"的程式碼示例:

1 s = build_tree("1+2*3-4/5")
2 tree = Tree()
3 tree.root = s
4 exp = tree.trans(s, ())
5 print(solution(exp))
6 # 6.2

相關文章