二叉樹是一種基本的資料結構,在程式設計師面試中經常會被考察。其中按一定順序遍歷所有節點是最基本的操作,很多知名的面試題目,例如求二叉樹的深度、求出和為某一值的路徑等等,本質上都是遍歷的變種。本文試圖從遞迴和非遞迴的角度來考察一下遍歷的演算法。
【遍歷定義】
在二叉樹中,每乙個節點都有左右兩個子節點(子節點可能為空,例如葉子節點)。根據節點和子節點訪問的先後順序,共有三種遍歷方式:
例如,假定有下面一棵二叉樹:
那麼各節點遍歷的順序如下:
【遞迴實現】
用遞迴實現很簡單,**如下:
# 定義node實現,用value儲存值,並且有left、right兩個節點分別指向左右兩個子節點
class node(object):
def __init__(self, value, left = none, right = none):
self.value = value
self.left = left
self.right = right
# 定義訪問node的操作,這裡直接列印節點的值
def visit(node):
print node.value
# 前序遍歷
def preorder_recursive(root):
if root is none:
return
visit(root)
preorder_recursive(root.left)
preorder_recursive(root.right)
# 中序遍歷
def inorder_recursive(root):
if root is none:
return
inorder_recurisve(root.left)
visit(root)
inorder_recursive(root.right)
# 後序遍歷
def postorder_recursive(root):
if root is none:
return
postorder_recursive(root.left)
postorder_recursive(root.right)
visit(root)
【非遞迴實現】遞迴遍歷的本質是函式的遞迴呼叫,區別只是在何時訪問節點,而函式呼叫可以用乙個棧來模擬,於是我們就有了非遞迴實現的基本思路:用棧來模擬函式呼叫。
函式呼叫的過程如下圖所示:
從圖中可以看到,每乙個節目在整個遞迴呼叫過程中都會被經過三次:
由此我們可以直觀地得出三種遍歷的非遞迴實現,中間需要儲存一些上下文資訊,例如父節點、左右子樹是否已經完成,等等:
class stacknode(object):
def __int(self, node, parent = none, is_left = false):
self.node = node # 實際節點,如父節點為葉子節點,則node為none,代表空的佔位節點
self.parent = parent # 指向父節點
self.is_left = is_left # 本節點是否為父節點的左子節點
self.left_visited = false # 左子樹是否已訪問完成
self.right_visited = false # 右子樹是否已訪問完成
def bintree_non_recrusive(root):
# 模擬函式呼叫棧
stack = stack()
# 首先將根節點入棧,並在入棧時進行前序訪問
stack.push(stacknode(root))
preorder_visit(root)
while not stack.empty():
top = stack.top()
# 第一種情況:佔位空節點,出棧,標記父節點
if top.node is none:
stack.pop()
if top.is_left:
top.parent.left_visited = true
else:
top.parent.right_visited = true
# 第二種情況:左右子樹均未完成訪問,入棧,並在入棧時進行前序訪問
elif (top.left_visited == false) and (top.right_visited == false):
while top is not none:
left_child = stacknode(top.node.left, parent = top, is_left = true)
stack.push(left_child)
preorder_visit(left_child.node)
top = left_child.node
# 第三種情況:左子樹已訪問,右子樹未訪問,說明在回溯階段,進行中序訪問,並轉向右子樹,入棧右子節點
elif (top.left_visited == true) and (top.right_visited == false):
inorder_visit(top)
right_child = stacknode(top.node.right, parent = top, is_left = false)
stack.push(right_child)
# 第四種情況:左右子樹均已訪問,進行後序訪問,出棧,並標記父節點
elif (top.left_visited == true) and (top.right_visited == true):
postorder_visit(top.node)
stack.pop()
if top.parent is not none:
if top.is_left:
top.parent.left_visited = true
else:
top.parent.right_visited = true
接下來看看如何針對每一種訪問對上述**進行簡化。對前序遍歷和中序遍歷來說,實際上我們並不關心右子樹是否已完成訪問,因此不需要將節點保留到最後,只要開始回溯時就可提前出棧,這樣**可以大大簡化:
def preorder_non_recursive(root):
stack = stack()
node = root
while (node is not none) or (not stack.empty()):
# 訪問節點,並迴圈入棧所有的左子節點
if node is not none:
visit(node)
stack.push(node)
node = node.left
# 直接出棧,並轉向右子樹,之後迭代時入棧右子樹的所有左子節點
else:
node = stack.pop()
node = node.right()
def inorder_non_recursive(root):
stack = stack()
node = root
while (node is not none) or (not stack.empty()):
# 入棧所有的左子節點
if node is not none:
stack.push(node)
node = node.left
# 出棧,訪問節點,並轉向右子樹,之後迭代時入棧所有的左子節點
else:
node = stack.pop()
visit(node)
node = node.right
對後序訪問來說,其實只要抓住關鍵的入棧、回溯和出棧的時間節點,也可以將**進行一定程度的簡化:
class stacknode(object):
def __int(self, node):
self.node = node # 實際節點
self.right_visited = false # 是否在訪問右子樹
def postorder_non_recursive(root):
stack = stack()
node = root
while (node is not none) or (not stack.empty()):
# 入棧
while node is not none:
s_node = stacknode(node)
stack.push(s_node)
node = node.left
# 出棧所有右子樹被訪問的節點
while (not stack.empty()) and (stack.top().right_visited == true):
s_node = stack.pop()
visit(s_node.node)
# 棧頂的節點左子樹已訪問完成,轉向右子樹
if (not stack.empty()):
s_node = stack.top()
s_node.right_visited = true
node = s_node.node.right
二叉樹遞迴遍歷和非遞迴遍歷
用遞迴和非遞迴實現二叉樹的前序遍歷 中序遍歷和後序遍歷並列印出相應結果。private class treenode 在遞迴呼叫時候系統自動給我們建立棧來儲存資料,而使用非遞迴時候需要我們自己實現棧來儲存資料。遞迴實現前序遍歷public void preorder treenode root sy...
二叉樹的非遞迴遍歷和遞迴遍歷
前言 二叉樹的遍歷有前序遍歷 中序遍歷 後續遍歷 層序遍歷。然後我們分別實現一下各種遍歷的遞迴與非遞迴的方式,樹節點定義如下 class treenode 前序遍歷 前序遍歷是指我們的二叉樹先遍歷root節點,然後遍歷左節點,最後是右節點 遞迴public void preorder treenod...
二叉樹遍歷(遞迴和非遞迴)
二叉樹的中序遍歷 二叉樹的後序遍歷 測試二叉樹的節點定義如下 節點 二叉樹的前序遍歷順序為 根左右。如下圖所示,前序遍歷順序為 1245367。遞迴 public static void preorder treenode root system.out.print root.val preorde...