遍歷方式
二叉樹的常見遍歷方式如下幾種:
- 前序遍歷: 訪問根節點,前序遍歷方式訪問左子樹,前序遍歷方式訪問右子樹;
- 中序遍歷: 中序遍歷方式訪問左子樹,訪問根節點,中序遍歷方式訪問右子樹;
- 后序遍歷: 后序遍歷方式訪問左子樹,后序遍歷方式訪問右子樹,訪問根節點;
- 層次遍歷: 按照層次遞增的順序,依次訪問樹的每層節點。
示例演示
除去層次遍歷不談,根據其他三種遍歷方式的描述可發現,其描述的內容也就是遞歸進行遍歷的過程。
二叉樹示例:
前序遍歷
前序遍歷的方式,也就是對每一棵子樹,按照根節點、左子樹、右子樹的順序進行訪問,也就是根-左-右的訪問順序。因為每一棵非空子樹,又可拆分為根節點、左子樹和右子樹,所以可以按照根-左-右的方式,遞歸訪問每棵子樹。
遞歸方式:
#recursive pre-order traversal
def recursive_pre_order_traversal(root):
if not root:
return
print(root.value,end=' ')
recursive_pre_order_traversal(root.lchild)
recursive_pre_order_traversal(root.rchild)
觀察二叉樹的前序遞歸遍歷方式,首先訪問 “5” 節點,然后是 “3” 節點,然后是 “0” 節點,然后是 “0” 節點右子樹 A (A 子樹遞歸訪問),然后是 “3” 節點的右子樹 B (即進行回溯,B 子樹同 A 子樹,遞歸訪問)。
通過以上過程可以發現:
- A、B 子樹的節點訪問方式,與以 “5” 為根節點的二叉樹訪問方式相同,即:5
3
0
A
B。
- 由 A 子樹到 B 子樹的訪問過程即為回溯,示例代碼中回溯的實現,使用了遞歸的形式,遞歸的函數會形成調用棧,每個函數的調用會保存一個棧幀,通過棧幀中數據的保存,來完成回溯。
所以這里前序遞歸遍歷規則 “根-左-右” 的體現包括兩個環節:
遞歸:前序訪問的遞歸順序即為根-左-右。
步驟:
1.訪問二叉樹的根節點
;
2.若左子樹非空,則二叉樹
指向
左子樹,執行步驟 1;
3.若左子樹為空,則二叉樹
指向
右子樹,執行步驟 1;
回溯:一個子樹的遞歸結束后,返回上一層(非父節點那一層)繼續遞歸,即為回溯。
例如以 0 為根節點,右子樹為 A 的二叉樹,完成前序遍歷后,也就相當于以 3 為根節點,{0, A} 為左子樹,B 為右子樹的二叉樹,剛剛完成根-左-右前序遍歷中的,“左”這一步,下一個訪問的二叉樹即為 B。
根據以上過程,可以推出前序遍歷的非遞歸形式。
非遞歸方式:
#non_recursive pre-order traversal
def non_recursive_pre_order_traversal(root):
stack = []
while root or len(stack) > 0:
if root:
print(root.value,end=' ')
stack.append(root)
root = root.lchild
else:
root = stack.pop()
root = root.rchild
以上非遞歸代碼中,通過循環的形式完成了類似于遞歸的調用,通過棧對象完成回溯。代碼循環執行過程為:若二叉樹根節點存在,則訪問根節點,然后以相同方式訪問根節點的左子樹;若根節點不存在,則返回上一層訪問右子樹。
tips:
1.前序和中序的回溯操作,都是訪問上一層的右子樹,因為無論是根-左-右,或者左-根-右,右子樹訪問結束后,都表示根節點和左子樹已經訪問過。
2.上一層不一定是父節點那一層,若二叉樹 是根節點
的左子樹,則左子樹訪問結束,上一層即為父節點一層,也就是根節點
這一層,下一步訪問根節點
的右子樹
;若二叉樹
訪問結束,則表示根節點
和左子樹
已經訪問結束,上一層為根節點
這一層,下一步訪問即為二叉樹
。
代碼中使用棧來保存上一層的節點,即棧中最后一個元素即為上一層的根節點,通過出棧操作來完成回溯。
中序遍歷
中序遍歷二叉樹順序為左子樹-根節點-右子樹形式。如果二叉樹為二叉搜索樹這樣的節點有序結構,則中序遍歷輸出為有序的節點列表。
遞歸方式:
#recursive in-order traversal
def recursive_in_order_traversal(root):
if not root:
return
recursive_in_order_traversal(root.lchild)
print(root.value,end=' ')
recursive_in_order_traversal(root.rchild)
在非遞歸形式中,所謂的訪問其實就是入棧和輸出根節點值的操作,因為要借用棧對象來實現回溯,所以訪問每棵二叉樹時,入棧根節點的操作是不變的。區別只在于中序遍歷時,輸出根節點的操作放在了訪問右子樹前,而非前序遍歷的入棧操作時輸出。
非遞歸方式:
#non_recursive in-order traversal
def non_recursive_in_order_traversal(root):
stack = []
while root or len(stack) > 0:
if root:
stack.append(root)
root = root.lchild
else:
root = stack.pop()
print(root.value,end=' ')
root = root.rchild
觀察前序遍歷和中序遍歷的非遞歸方式可以發現,兩者除了輸出操作 print 語句位置不同外,其他內容完全一樣。
后序遍歷
后序遍歷二叉樹順序為左子樹-右子樹-根節點形式。
遞歸方式:
#recursive post-order traversal
def recursive_post_order_traversal(root):
if not root:
return
recursive_post_order_traversal(root.lchild)
recursive_post_order_traversal(root.rchild)
print(root.value,end=' ')
因為要完成回溯,所以根節點入棧的操作不變。后序遍歷的順序為:左-右-根,也就是右子樹訪問結束后才會執行根節點的輸出操作,即右子樹遍歷結束后返回上一層繼續遍歷,后序遍歷中的上一層就是父節點一層。與前序和中序遍歷不同之處在于,后序遍歷在根節點的輸出上需要多做一些工作。
分析上圖 BT 中的兩顆二叉樹:
【1】樹 bt1 中,二叉樹 遍歷結束后,輸出上一層的根節點
的值。因為
節點是其上一層的左節點,也就是說以
為根節點的二叉樹,完成了后序遍歷的左子樹遍歷,所以再下一步訪問右子樹
。
【2】樹 bt2 中,二叉樹 遍歷結束后,輸出上一層的根節點
的值。因為
是其上一層的右節點,所以
為根節點的二叉樹遍歷結束后,下一步輸出
的上一層的根節點
的值。因為
是其上一層的左節點,也就是說以
為根節點的二叉樹,完成了后序遍歷的左子樹遍歷,所以再下一步訪問右子樹
。
根據以上兩個二叉樹的后序遍歷過程可以發現,右子樹遍歷結束后輸出根節點的值,雖然完成了一輪的左-右-根遍歷,但并不算操作結束。還要判斷根節點 是其上一層根節點
的左節點還是右節點:
- 若
是
的左節點,則說明
一層完成了左-右-根的左子樹遍歷,下一步訪問
的右子樹;
- 若
是
的右節點,則說明
一層完成了左-右-根的右子樹遍歷,下一步訪問輸出
的值,并判斷
是其上一層根節點
的左節點還是右節點。
根據遍歷完成的二叉樹的根節點是其上一層根節點的左節點或右節點的不同,進行迭代處理。其實就是迭代輸出根節點的值,直到根節點是其上一層根節點的左節點,則輸出根節點并訪問上一層根節點的右子樹。
非遞歸方式:
#non_recursive post-order traversal
def non_recursive_post_order_traversal_one(root):
stack = []
while root or len(stack) > 0:
if root:
stack.append(root)
root = root.lchild
else:
if stack[-1].rchild:
root = stack[-1].rchild
else:
while len(stack) > 1 and stack[-1] == stack[-2].rchild:
print(stack.pop().value, end=' ')
print(stack.pop().value, end=' ')
if len(stack) > 0:
root = stack[-1].rchild
觀察代碼可知,循環處理的入棧部分跟前序和中序一樣,不同之處在于二叉樹遍歷結束后,會對根節點進行迭代輸出。
后續遍歷有一種簡潔的寫法形式,后序遍歷順序為:左-右-根,可以另聲明一個列表倒序保存根-右-左順序的記錄,下面給個簡寫的示例:
非遞歸方式_簡潔形式:
# non_recursive post-order traversal
def non_recursive_post_order_traversal_two(root):
stack = []
save_list = []
while root or len(stack) > 0:
if root:
stack.append(root)
save_list.insert(0,root.value)
root = root.rchild
else:
root = stack.pop()
root = root.lchild
print(save_list,end=' ')
代碼比較簡單,跟前序遍歷形式基本一致。
層次遍歷
層次遍歷就是按層遞增的順序輸出每層的節點,即順序的輸出每層節點的左、右子節點。這里借助具有先進先出特性的隊列對象完成遍歷。
代碼示例:
#hierarchical traversal
def hierarchical_traversal(root):
node_queue = queue.Queue()
node_queue.put(root)
while node_queue.qsize() > 0:
root = node_queue.get()
print(root.value,end=' ')
if root.lchild:
node_queue.put(root.lchild)
if root.rchild:
node_queue.put(root.rchild)
層次遍歷的邏輯比較簡單,即順序記錄每層節點,主要利用了隊列先進先出的特性。
附錄
以上是四種遍歷方式的描述和代碼示例,下面給出測試和輸出。其中樹結構 引用自 二叉搜索樹 中定義。
if __name__ == '__main__':
arr = [5, 3, 4, 0, 2, 1, 8, 6, 9, 7]
T = Tree()
for i in arr:
T.insert(i)
print('BST recursive pre-order traversal:',end=' ')
recursive_pre_order_traversal(T.root)
print('\nBST non_recursive pre-order traversal:',end=' ')
non_recursive_pre_order_traversal(T.root)
print('\n\nBST recursive in-order traversal:',end=' ')
recursive_in_order_traversal(T.root)
print('\nBST non_recursive in-order traversal:',end=' ')
non_recursive_in_order_traversal(T.root)
print('\n\nBST recursive post-order traversal:',end=' ')
recursive_post_order_traversal(T.root)
print('\nBST non_recursive post-order traversal method one:',end=' ')
non_recursive_post_order_traversal_one(T.root)
print('\nBST non_recursive post-order traversal method two:',end=' ')
non_recursive_post_order_traversal_two(T.root)
print('\n\nBST hierarchical traversal:',end=' ')
hierarchical_traversal(T.root)
輸出結果為:
BST recursive pre-order traversal: 5 3 0 2 1 4 8 6 7 9
BST non_recursive pre-order traversal: 5 3 0 2 1 4 8 6 7 9
BST recursive in-order traversal: 0 1 2 3 4 5 6 7 8 9
BST non_recursive in-order traversal: 0 1 2 3 4 5 6 7 8 9
BST recursive post-order traversal: 1 2 0 4 3 7 6 9 8 5
BST non_recursive post-order traversal method one: 1 2 0 4 3 7 6 9 8 5
BST non_recursive post-order traversal method two: [1, 2, 0, 4, 3, 7, 6, 9, 8, 5]
BST hierarchical traversal: 5 3 8 0 4 6 9 2 7 1
github
鏈接:二叉樹遍歷