<big>編譯環境:python v3.5.0, mac osx 10.11.4</big>
<big>前述內容:</big>
什么是樹(Tree)
客觀事物中許多事物存在層次關系,而這種分組層次管理機制有利于高效的查找。如:
- 人類社會關系
- 圖書信息管理
- 社會組織結構
樹的定義
n(n>=0)個節點構成的有限集合
- 當n=0時,稱為空樹。
- 對于非空樹:
- 樹根(root),一個特殊的節點,用 r表示
- 其余節點可分為m(m>0)個互不相交的有限集T1,T2....,Tm,其中每個集合本身又是一棵樹,稱為原樹的子樹(SubTree)。
根據樹的定義如下事例就不是樹,因為:
- 子樹是互不相交的;
- 除了跟結點外,每個結點都有且只有一個父結點;
- 一棵含有n個結點的樹,具有n-1條邊
樹的基本術語
- 結點的度(Degree):結點的子樹個數(如:A的度數為3)
- 樹的度:樹中所有結點最大的度數(如:上述樹的度為3)
- 葉(Leaf)結點:度為0的結點(如:上述樹的葉結點為:F,L,H,M,J,K)
- 路徑和路徑長度:從結點n到結點b的路徑為一個結點序列,其路徑長度為所包含邊的個數(如:A到F的路徑為A,B,F,路徑長度為2)
- 結點的層次(Level):規定根結點在第一層,其他結點層次為其父結點層次加一。(如:A的層次為1,B的層次為2)
- 樹的深度(Depth):樹中所有結點的最大層次。(如:上述樹的深度為4)
- 森林(Forest):是m(m>=0)互不相交樹的集合。對于每個結點而言,其子樹的集合即為森林。由此,也可以森林和樹相互遞歸的定義來定義描述樹。Tree=(root,F),其中root為根結點,F是m(m>=0)棵樹的森林。
樹的表示
對于一顆指定的樹(如下),我們要怎樣在計算機中表示它的信息呢?
由于樹中的每個結點的度不統一,所以顯然,首先我們想到的是結構體加鏈表的形式對其進行表示,如下圖所示:
我們知道,當一棵樹具有n個節點時,它具有n-1條邊,而上述的表示方式則需要3n(因為樹的度為3)個存儲單元來存放樹的邊。這樣一樣,2n+1個存儲單元就被浪費了。因此為了減少上述浪費,實際編碼時我們一般采用兒子-兄弟的表示方法:
-
樹的結構體中包含:一個存放結點元素的單元,一個指向第一個兒子的指針和一個指向第一個兄弟的指針。
- 這樣一來,我們表示一顆樹,則只需要2n個存儲單元來存儲樹的邊,僅僅浪費n+1個存儲單元。
- 當我們把上述樹進行旋轉45度時,發現這就是一顆二叉樹了,因此大部分的樹都可以轉化為二叉樹的形式進行表示。綜上所述,樹的算法大多圍繞二叉樹進行。
二叉樹的表示
<big>二叉樹是一個有窮的結點集合,基本結構單元由一個包含結點元素、左孩子和右孩子指針的結構體組成。</big>
-
二叉樹有五種基本形態
- 二叉樹有左右順序之分,因此下述兩棵樹不為同一棵樹。
- 特殊二叉樹:
-
斜二叉樹(skewed binary tree):完全向一邊偏,形如單向鏈表。
- 完美二叉樹(perfect binary tree)或滿二叉樹 (full binary tree):沒有度為一的結點。(可用順式存儲方式進行存儲)
- 完全二叉樹(complete binary tree):若對結點為n的完全二叉樹從上到下、從左到右進行編號i(1<=i<=n),其編號與滿二叉樹中編號為i的結點在二叉樹中的位置相同。(可用順式存儲方式進行存儲)
二叉樹的重要性質
- 一顆二叉樹的第i層的最大結點數為2^(i-1),i>=1,不適合空樹。
- 深度為k的二叉樹擁有的最大結點樹為2^k-1,k>=1,不適合空樹。
- 對于任何非空二叉樹T,若n0表示葉結點的個數,n2表示度為2結點的個數,那么它們滿足關系n0=n2+1。
- 樹的邊數等于結點個數減一
- 二叉樹只有度為零、一和二的結點,可以分別用n0,n1,n2表示。
- n0+n1+n2則為樹結點個數,n11+n22**則為樹的邊樹
- n0+n1+n2-1=n11+n22,即:n0=n2+1
二叉樹的抽象數據數據類型
-
先序遍歷
- 先訪問其根結點(即輸出元素值)
- 再遍歷其左子樹
- 最遍歷其右子樹
-
中序遍歷
- 先遍歷其左子樹
- 再訪問其根結點(即輸出元素值)
- 最后遍歷其右子樹
-
后序遍歷
- 先遍歷其左子樹
- 再遍歷其右子樹
- 最后訪問其根結點(即輸出元素值)
-
層序遍歷
按二叉樹的在滿二叉樹上對應編號的先后順序輸出。上述樹的層序遍歷輸出結果為:ABOCSMQWK
二叉樹的順序存儲結構( tree.py)
- 根據完全二叉樹和滿二叉樹的性質,從上到下、從左到右對結點進行編號,我們可以利用數組來存儲這兩種含有n個結點的特殊二叉樹。
- 若我們對一般二叉樹也采取上述存儲方式,則會造成空間浪費。
- 生成樹:
tree =['A','B','O','C','S','M','Q','W','K'] - 是否為空樹
def isEmptyTree(tree):
return len(tree) == 0 - 遞歸算法的遍歷:
- 先序遍歷
def preOrderTraversal(tree,root): # 遞歸算法
if root <= len(tree):
if tree[root-1]:
print(tree[root-1]) # 輸出元素,訪問結點
preOrderTraversal(tree,root=2root) # 遍歷左子樹
preOrderTraversal(tree,root=2root+1) # 遍歷右子樹 - 中序遍歷
def inOrderTraversal(tree, root): # 中序遍歷
if root <= len(tree):
inOrderTraversal(tree, root=2 * root) # 遍歷左子樹
if tree[root - 1]:
print(tree[root - 1]) # 輸出元素,訪問結點
inOrderTraversal(tree, root=2 * root + 1) # 遍歷右子樹 - 后序遍歷
def postOrderTraversal(tree, root): # 后序遍歷
if root <= len(tree):
postOrderTraversal(tree, root=2 * root) # 遍歷左子樹
postOrderTraversal(tree, root=2 * root + 1) # 遍歷右子樹
if tree[root - 1]:
print(tree[root - 1]) # 輸出元素,訪問結點
- 先序遍歷
-
非遞歸算法遍歷(利用堆棧)
- 先序遍歷(入棧的時候輸出)
import stack_chain
def preOrderTraversal(tree):
store = stack_chain.stackChain() # 建立空棧
node = 1 # 指向樹根
while node <= len(tree) or not (stack_chain.isEmpty(store)): # 如果樹沒有到葉結點,或堆棧不為空
while node <= len(tree): # 遍歷左子樹
if tree[node-1]:
print(tree[node-1]) # 訪問結點,并輸出結點
stack_chain.push(store, [tree[node - 1], node]) # 將左子樹的元素壓入堆棧中
node = 2
if not (stack_chain.isEmpty(store)):
element = stack_chain.pop(store)
node = 2element[1] + 1 # 左子樹遍歷完后遍歷右子樹 - 中序遍歷(出棧的時候輸出)
import stack_chain
def preOrderTraversal(tree):
store = stack_chain.stackChain() # 建立空棧
node = 1 # 指向樹根
while node <= len(tree) or not (stack_chain.isEmpty(store)): # 如果樹沒有到葉結點,或堆棧不為空
while node <= len(tree): # 遍歷左子樹
stack_chain.push(store, [tree[node - 1], node]) # 將左子樹的元素壓入堆棧中
node = 2
if not (stack_chain.isEmpty(store)): # 訪問結點,并輸出
element = stack_chain.pop(store)
if element[0]:
print(element[0])
node = 2element[1] + 1 # 左子樹遍歷完后遍歷右子樹 - 后序遍歷(第二次出棧的時候輸出)
import stack_chain
def preOrderTraversal(tree):
store = stack_chain.stackChain() # 建立空棧
node = 1 # 指向樹根
while node <= len(tree) or not (stack_chain.isEmpty(store)): # 如果樹沒有到葉結點,或堆棧不為空
while node <= len(tree): # 遍歷左子樹
stack_chain.push(store, [tree[node - 1], node,0]) # 將左子樹的元素壓入堆棧中,增加一個tag表示元素第幾次被彈出
node = 2
if not (stack_chain.isEmpty(store)): # 訪問結點,并輸出
element = stack_chain.pop(store)
if element[0] and element[2]>0: # 元素第二次被彈出時輸入。
print(element[0])
if element[2] ==0:
element[2]=1 # 被彈出一次后改變tag
stack_chain.push(store,element)
node = 2element[1] + 1 # 左子樹遍歷完后遍歷右子樹 - 層序遍歷
def levelOrderTraversal(tree):
node = 0
while node < len(tree):
if tree[node]:
print(tree[node])
node += 1
二叉樹的鏈式存儲結構(tree_chain.py)
由包含結點元素、指向左孩子的指針和指向右孩子指針的結構體連接形成的鏈表結構。
-
創建二叉樹(可以根據先序和中序,或中序和后序,根據先序和中序創建二叉樹示意圖如下)
先序的第一個為根結點
我們根據先序的根結點可以在中序遍歷中找到,從而得到根結點的兩個兒子結點的左右順序。
因此當我們確定一顆二叉樹時,中序遍歷的順序是必須的,因為只有它才能告訴我們兒子結點的左右順序,而前和后序遍歷提供的都是根結點的信息。
class BinaryTree(): # the basical structure of binary tree
def init(self, element=None, left=None, right=None):
self.element = element
self.left = left
self.right = right
def createTree(preOrder, inOrder):
if preOrder:
p = BinaryTree() # create a tree node
site = inOrder.index(preOrder[0]) # find root in inOrder sequence and then we can know that
# which set is on the left and which set is on the right
# attach left and right set to root, according to root site in inOder sequence
p.element = preOrder[0]
del preOrder[0]
# recursive
p.left = createTree(preOrder=preOrder[0:site],inOrder=inOrder[0:site])
p.right = createTree(preOrder=preOrder[site:],inOrder=inOrder[site+1:])
return p判斷是否為空樹
def isEmptyTree(root): # hasn't either left child or right child
return root.left is None and root.right is None-
遞歸算法的遍歷:
- 先序遍歷
def preOrderTraversal(root): # 遞歸算法
if root:
print(root.element) # 輸出元素,訪問結點
preOrderTraversal(root.left) # 遍歷左子樹
preOrderTraversal(root.right) # 遍歷右子樹 - 中序遍歷
def inOrderTraversal(tree, root): # 中序遍歷
if root:
inOrderTraversal(root.left) # 遍歷左子樹
print(root.element) # 輸出元素,訪問結點
inOrderTraversal(root.element) # 遍歷右子樹 - 后序遍歷
def postOrderTraversal(tree, root): # 后序遍歷
if root:
postOrderTraversal(root.left) # 遍歷左子樹
postOrderTraversal(root.right) # 遍歷右子樹
print(root.element) # 輸出元素,訪問結點
- 先序遍歷
- 非遞歸算法遍歷(利用堆棧)
- 先序遍歷(入棧的時候輸出)
import stack_chain
def preOrderTraversal(tree):
store = stack_chain.stackChain() # 建立空棧
p = tree # 指向樹根
while p or not (stack_chain.isEmpty(store)): # 如果樹沒有到葉結點,或堆棧不為空
while p: # 遍歷左子樹
print(p.element) # 訪問結點,并輸出結點
stack_chain.push(store, p) # 將左子樹的元素壓入堆棧中
p = p.left
if not (stack_chain.isEmpty(store)):
node = stack_chain.pop(store)
p = node.right # 左子樹遍歷完后遍歷右子樹 - 中序遍歷(出棧的時候輸出)
import stack_chain
def preOrderTraversal(tree):
store = stack_chain.stackChain() # 建立空棧
p = tree # 指向樹根
while p or not (stack_chain.isEmpty(store)): # 如果樹沒有到葉結點,或堆棧不為空
while p: # 遍歷左子樹
stack_chain.push(store, p) # 將左子樹的元素壓入堆棧中
p = p.left
if not (stack_chain.isEmpty(store)): # 訪問結點,并輸出
node = stack_chain.pop(store
print(node.element)
p = node.right # 左子樹遍歷完后遍歷右子樹 - 后序遍歷(第二次出棧的時候輸出)
import stack_chain
def preOrderTraversal(tree):
store = stack_chain.stackChain() # 建立空棧
p = tree # 指向樹根
count = stack_chain.stackChain() # 記錄結點彈出的次數
while p or not (stack_chain.isEmpty(store)): # 如果樹沒有到葉結點,或堆棧不為空
while p: # 遍歷左子樹
stack_chain.push(store, p) # 將左子樹的元素壓入堆棧中
stack_chain.push(count,1) # 增加一個tag表示元素第幾次被彈出
p = p.left
if not (stack_chain.isEmpty(store)): # 訪問結點,并輸出
node = stack_chain.pop(store)
tag = stack_chain.pop(count)
if tag > 1: # 元素第二次被彈出時輸入。
print(node.element)
else:
stack_chain.push(count,2) # 被彈出一次后改變tag
stack_chain.push(store,node)
p = node.right # 左子樹遍歷完后遍歷右子樹 - 層序遍歷
def levelTraversal(tree):
store = queue_chain.queueChain() # 用隊列來暫存數據
queue_chain.inQue(store, tree) # 將根結點插入隊列中
while not(queue_chain.isEmpty(store)): # 逐個彈出訪問
node = queue_chain.outQue(store)
print(node.element)
if node.left:
queue_chain.inQue(store, node.left)
if node.right:
queue_chain.inQue(store,node.right)
二叉樹的應用
-
利用后序遍歷求二叉樹的高度
def getTreeHight(tree):
if tree: # 逐個遍歷左邊的樹和右邊的樹,取高的最大值
hl = getTreeHight(tree.left)
hr = getTreeHight(tree.right)
hight = max(hl,hr)
return hight+1
else:
return 0 -
二元運算表達式樹及其遍歷