二叉樹的遍歷(遞迴和迭代版)

2021-08-21 09:26:17 字數 3159 閱讀 7246

二叉樹常見的遍歷方式有先序遍歷、中序遍歷、後序遍歷、層次遍歷。

首先介紹前三種遍歷方式,對於每乙個子樹,遍歷的順序是

先序遍歷:根      ->左子樹->右子樹    ,如圖中①

中序遍歷:左子樹->根      ->右子樹    ,如圖中②

後序遍歷:左子樹->右子樹->根          ,如圖中③

先序遍歷:

按照上面的規則,遞迴版本不難給出

public void preorder(node root)
計算機執行遞迴演算法時,是通過棧來實現的,以下來自資料結構課本

當你呼叫乙個函式時,系統會將這個函式進行入棧操作,在入棧之前,通常需要完成三件事。

1、將所有的實參、返回位址等資訊傳遞給被調函式儲存。

2、為被調函式的區域性變數分配儲存區。

3、將控制轉移到被調函式入口。

當乙個函式完成之後會進行出棧操作,出棧之前同樣要完成三件事。

1、儲存被調函式的計算結果。

2、釋放被調函式的資料區。

3、依照被調函式儲存的返回位址將控制轉移到呼叫函式。

上述操作必須通過棧來實現,即將整個程式的執行空間安排在乙個棧中。每當執行乙個函式時,就在棧頂分配空間,函式退出後,釋放這塊空間。所以當前執行的函式一定在棧頂。

但是利用系統的棧會帶來很大的額外開銷,儲存了太多不必要的資料,我們可以對棧中儲存的內容進行剪裁,只保留我們需要的東西,用乙個自己建立的棧實現迭代版本的遍歷。

首先要明確的是把null節點視為葉節點,即最深層的節點也是自身和兩個null孩子形成的二叉樹的根節點。

遞迴的**裡,可以視為把lchild和rchild擺在這一層,在對lchild進行判定來進入下一層,遇到遞迴基即孩子不存在就返回上一層,對右子樹的遍歷只能在左子樹遍歷完成後開始。所以對每個子樹,檢查一下root節點並列印,有孩子就按照先右後左入棧(出棧的順序就成了先左後右),沒有就過,如此迴圈,遍歷左子樹途中更深層的node會在rchild出棧之前入棧,對右子樹的遍歷只能在左子樹遍歷完成後開始,就實現了迭代版的先序遍歷,**如下

public void preorder(node root) 

}

中序遍歷:

遞迴版本如下

public void inorder(node root)
現在改為迭代版本。中序遍歷的特點是,每個節點都是作為乙個子樹的root節點,在遍歷完左子樹之後被訪問。所以核心的點在於每個node都是作為root從下一層回來的時候被訪問的,而第一次遇到它是不訪問的。這個演算法的動作就是向左探至null、訪問父節點、從父節點的rchild起重複迴圈。整個遍歷過程實際上被劃分為乙個乙個的「向左探至null」的過程。利用棧結構先進後出的特點,把向左探的通路上的node壓棧,對於每個子樹的root,會在左子樹完成遍歷後被訪問,實現了迭代版中序遍歷。

值得注意的是,而每次push入值的順序是二叉樹的前序遍歷(根左右)。

**如下

public void inorder(node root) else 

}}

中序遍歷是非常常見的遍歷方式,如果我們可以不遍歷整個二叉樹就可以直接給出某個node在中序遍歷中的後繼就好了。想一下,對於每乙個node,如果有右子樹,那麼後繼就是從rchild開始左探的最後乙個非null節點;如果沒有右子樹,就是把它包含在左子樹中的最低的根節點。尋找節點x的後繼的函式如下

public node succ(node x) else 

return x;

} if(x.rchild != null) else

return x;

}

有了這個函式,我們就可以實現額外空間o(1)的中序遍歷。用乙個標誌變數back表示現在是不是剛從左子樹回溯上來,從root開始,判斷x有無左子樹(rchild為空 or 剛從左子樹回溯的情況統一視為無右子樹)和有無右子樹。迴圈體分為「訪問」(x無左子樹則訪問)和「換擋」(x有右子樹換到rchild,否則呼叫succ更新x為其後繼)兩部分。**如下

public void inorder(node x) else 

if(x.rchild != null) else

}}

值得注意的是這樣的中序遍歷會反覆呼叫succ(),效能有所下降。

後序遍歷:

遞迴版如下

public void postorder(node root)
先序遍歷的順序是 根->左->右,後序遍歷是左->右->根。只要保證在每乙個區域性的子樹上滿足這個順序,整個遍歷序列就滿足這個順序。在先序遍歷的演算法裡我們已經實現了在每個區域性子樹滿足 根->左->右,把左右的對調,在每個區域性子樹上滿足 根->右->左,反過來輸出就是 左->右->根。把先序遍歷的訪問改為壓棧,最後乙個個彈出來訪問,就可以實現迭代版後序遍歷。**如下

public void postorder(node root) 

while(!output.isempty())

}

總結一下,關於這三種種遍歷的迭**法的理解,重點在於

1.把null當作葉節點

2.數學歸納法,n和n+1可以,那麼任意的n都可以。

層次遍歷

層次遍歷很好理解,一層一層遍歷,在每一層上從左往右走。這裡還是一層一層進行,但是每一層都要遍歷完在進入下一層。    

還記得我們是怎麼實現迭代版先序遍歷的嗎?利用棧結構先進後出的特點,把左孩子的兩個孩子在右孩子後壓棧,實現在遍歷右子樹前完成左子樹的遍歷。這裡我們不用棧了,用先進先出的佇列結構。每次取出隊首的節點,將他的子節點按先左後右加入佇列,這樣下一層的節點永遠在上一層節點之後加入佇列,當然也在上一層所有節點之後被遍歷。從第二層開始,每一層的遍歷都是從左到右的,這一層的孩子節點自然也是從左到右的,實現了二叉樹的層次遍歷。**如下

public void levelorder(node root)

}

二叉樹遍歷(遞迴與迭代)

二叉樹遍歷演算法分為前序 preoredr 中序 inorder 後序 postorder 遍歷。並且可以設計遞迴型或者迭代型演算法。cpp view plain copy print?struct binarytreenode struct binarytreenode 1.1前序遍歷 cpp v...

二叉樹的前序遍歷(遞迴 迭代)

給定乙個二叉樹,返回它的前序遍歷。示例 輸入 1,null,2,3 1 2 3 輸出 1,2,3 高階 遞迴演算法很簡單,你可以通過迭代演算法完成嗎?遞迴確實簡單,隨便寫乙個。definition for a binary tree node.public class treenode class ...

二叉樹層次遍歷(遞迴版)

題目 分析 看到這個題目我第一反應是遞迴,不過貌似好像大概還有挺多不同的解法。本篇文章記錄一下我的遞迴思路,其他的演算法留待日後學習。力扣上給出的空方法模板的返回值為list 結合題幹可知,每一層的節點數值應按順序存入乙個集合中,所有的節點集合也要儲存在乙個集合中。那麼問題來了,對於方法而言,二叉樹...