二叉樹的非遞迴遍歷,還有一點黑科技

2022-09-10 21:09:28 字數 3268 閱讀 7381

二叉樹的前中後序遍歷,可以用遞迴秒解,看起來不值一提。但如果不允許採用遞迴,要怎麼實現呢?

還是先來看看遞迴演算法的實現吧:

def    visit( root):

if root is not null:

#1visit(root.left)

#2visit(root.right)

#3

上面展示的**中有三個位置(#1,#2,#3)可以用來插入訪問當前節點的**,分別對應了前中後三種遍歷。這三種不同的設定,實際上表達的是訪問節點的不同時機。

我們可以用進棧和出棧來模擬這些遞迴的過程,在跟#1,#2,#3相對應的時機訪問節點,來形成三種不同的訪問順序。先畫一棵樹當做例子吧。

1. 前序遍歷

試著手動執行以下前序遍歷。前序遍歷的過程是先訪問當前節點,再訪問當前節點的左子樹,待整個左子樹被訪問完,再回到當前節點,然後再訪問右子樹。為了使往左之後還能回到當前節點並往右,

我們要先把當前節點壓入棧中,然後才往左走。我們先以節點1為當前節點,訪問它,使它入棧,從節點1往左走,於是當前節點變為節點2。重複這個(訪問,入棧,往左)的過程,直到當前節點為空。

對應上圖的樹,這個時候已訪問節點是 1, 2, 4,並且棧中原始從棧底到棧頂也是1,2,4。此時當前節點為空,不可能再往左走,於是嘗試往右。節點4退棧,試著從節點4往右一步,失敗。

重複這個(退棧,嘗試往右一步)的操作,直到往右成功,或者棧被清空。當我們把節點2退棧,並嘗試往右時成功,於是當前節點就變成節點2的右子節點5。然後又重複上面

的(訪問,入棧,往左),(退棧,嘗試往右),一直進行下去直到當前節點為空且棧為空。

用**表達就是醬紫的:

def preorder(root):

if root is null:

return

stack, current=[ ], root

while current!=null or stack:

if current!=null:

#訪問當前節點

current=current.left #往左

else:

top=stack.pop() #退棧

if top.right: #嘗試往右

current=top.right

2. 中序遍歷

於是中序遍歷可以類似地得到。中序遍歷可以概括為:重複(入棧,往左),直到不能再往左,然後重複(退棧,訪問,嘗試往右)。

**可以類似地得到:

def inorder(root):

if root is null:

return

stack, current=[ ], root

while current!=null or stack:

if current!=null:

current=current.left #往左

else:

top=stack.pop() #退棧

#訪問top節點

if top.right: #嘗試往右

current=top.right

3. 後序遍歷

採用類似的思路實現後序遍歷是有點難度的。顯然我們應該在節點退棧之後再訪問它,而且節點應該是再它的左右子樹都被遍歷完之後才退棧並被訪問。

對於上面那棵樹的節點2,我們會有三次到達這個節點:1)在節點1往左試探時,2)在節點2的左子樹被遍歷完之後回來節點2, 3)在右子樹被遍歷完之後回到節點2。

以上三次,對應應該執行不同的操作:1)節點2入棧,2)嘗試往節點2的右子樹去遍歷,3)訪問節點2。現在的問題在於,怎麼區分後兩種情況?即怎麼知道是從左子樹回來還是從右子樹回來的?

其實也簡單,只要記錄上乙個訪問的節點是啥就可以了。例如我剛剛訪問完節點2的右子樹,於是記住上乙個被訪問的節點是節點5。那麼當從5回到2的時候,就知道應該訪問節點2,而不是從

節點2嘗試往右。所以站在節點2的角度來看,迫使我們去訪問節點2可能性有倆:要麼2的右子樹為空,要麼2的右子樹剛已經被遍歷完了。於是**應該寫成這個樣子:

def postorder(root):

if root is null:

return

stack, current, last=[ ], root, null

while current!=null or stack:

if current!=null:

current=current.left #往左

else:

top=stack.top() #檢視當前節點

if top.right is not null and top.right!=last: #嘗試往右

current=top.right

else:

stack.pop( ) #出棧

#訪問top節點

last=top

實際上,這些不同次序的遍歷之間暗藏著很多黑科技。比如,感覺上面實現的後續遍歷相比於前序和中序遍歷而言不夠簡潔呀,有別的辦法嘛?哦膚闊死!

黑科技1. 改前序遍歷。前序得到的是(根左右)的順序。而我們要得順序是(左右根)。其實吧,把前序遍歷中訪問左右子樹的順序對調一下,就得到了(根右左)的順序,

再翻轉一下,就得到了(左右根)的順序,即後續遍歷的結果。

黑科技2. 改層序遍歷。層序遍歷依靠佇列來實現,對於(根,左,右)這仨節點而言,入隊的順序是先根,然後訪問下一層,即左,右。這樣出隊的順序是,(根;左,右)。

這裡如果我們把佇列換成棧,根先入棧,被訪問,然後訪問下一層,即左右。於是出棧的順序是,(根;右,左)。再翻轉也能得到(左,右;根)的順序。

貼一下黑科技2的**,簡潔清爽,美麗新世界!

def postordertr**ersal(root):

if not root:

return

result, stack=, [root]

while stack:

current=stack.pop()

result+=curr.val,

if current.left:

stack+=current.left,

if current.right:

stack+=current.right,

return result[::-1]

二叉樹非遞迴遍歷的一點理解

二叉樹是我們必須要了解的乙個資料結構,針對這個資料結構我們可以衍生出好多知識。主要是有幾種遍歷方式,前序遍歷,中序遍歷,後續遍歷,層次遍歷。下面我們根據這個圖來詳細的說一下這幾種非遞迴遍歷的思想與做法。只有真正理解了,我們才是真的會了,遇到相似的問題我們就會迎刃而解。上面的圖是我自己畫的,以下的幾種...

二叉樹遍歷(遞迴 非遞迴)

二叉樹以及對二叉樹的三種遍歷 先根,中根,後根 的遞迴遍歷演算法實現,以及先根遍歷的非遞迴實現。node public class node public node left public node right public object value 遍歷訪問操作介面 public inte ce ...

二叉樹非遞迴遍歷

二叉樹非遞迴遍歷的幾個要點 1 不管前序 中序還是後序,它們的遍歷路線 或者說是回溯路線,先沿左邊一直走到盡頭,然後回溯到某節點,並跳轉到該節點的右孩子 如果有的話 然後又沿著這個有孩子的左邊一直走到盡頭 都是一樣的。2 明確每次回溯的目的。比如,前序回溯的目的是為了訪問右子樹 中序回溯的目的是為了...