二叉樹常見的遍歷方式有先序遍歷、中序遍歷、後序遍歷、層次遍歷。
首先介紹前三種遍歷方式,對於每乙個子樹,遍歷的順序是
先序遍歷:根 ->左子樹->右子樹 ,如圖中①
中序遍歷:左子樹->根 ->右子樹 ,如圖中②
後序遍歷:左子樹->右子樹->根 ,如圖中③
先序遍歷:
按照上面的規則,遞迴版本不難給出
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 結合題幹可知,每一層的節點數值應按順序存入乙個集合中,所有的節點集合也要儲存在乙個集合中。那麼問題來了,對於方法而言,二叉樹...