多叉樹深度遍歷 二叉樹的非遞迴遍歷的思考

2021-10-14 23:56:12 字數 2936 閱讀 9055

封面圖來自wikipedia

二叉樹的深度優先遍歷(前序遍歷、中序遍歷、後序遍歷)是乙個比較基本的操作。如果使用遞迴的做法,很容易寫出相應的程式;而如果使用非遞迴的做法,雖然也能寫出相應的**,但是由於三種非遞迴的遍歷沒有統一的格式,比較難記住。在這裡,介紹一種統一格式的非遞迴寫法。

先介紹一下二叉樹的三個深度優先遍歷的基本概念:

根據概念很容易寫出對應的遞迴遍歷**

2.0 資料結構定義

struct treenode;

2.1 前序遍歷

vectorpreorder(treenode* root, vector& res) 

2.2 中序遍歷

vectorinorder(treenode* root, vector& res) 

2.3 後序遍歷

vectorpostorder(treenode* root, vector& res) 

先列出**,後面再寫下**的思想以及自己的理解。

可以看出三種遍歷的寫法,除了三句執行入棧的**,順序不一樣,其他都是一致的,實現了格式的統一。

3.1 前序遍歷

void preorder(treenode *root, vector& res)

if(visited) else }}

3.2 中序遍歷

void inorder(treenode *root, vector& res)

if(visited) else }}

3.3 後序遍歷

void postorder(treenode *root, vector& res)

if(visited) else }}

4.1 簡要說明

下面以前序遍歷為例子,簡單說說我自己的理解。先總結下自己的理解:

前序遍歷的規則:「根節點-左子樹遞迴-右子樹遞迴」,等價於下面兩個規則

對於每個節點,訪問順序為:「節點-左節點-右節點」

對於每個節點,左子樹的節點全部訪問完,再開始訪問右子樹的節點。

4.2 詳細解釋

接下來嘗試對上面的話解釋一下。

回看前序遍歷的概念,可以發現它制定了遍歷的規則:先是根節點,然後遞迴遍歷左子樹,最後遞迴遍歷右子樹,我們表示成「根節點-左子樹-右子樹」。這個好像不太直觀,我們想想這個規則能不能表示成其他等價規則。首先想到的一點是:

這個很容易理解。對於乙個節點來說,它的左子節點是左子樹的根節點,右子節點是右子樹的根節點,既然要求 「節點-左子樹-右子樹」,那麼必要條件就有 「節點-左子節點-右子節點」。其次,遞迴遍歷使得對於每個節點,都有這樣的要求。

但是這個只是必要條件,並不能唯一確定節點訪問順序。舉個例子,假設有下面一棵二叉樹,那麼它的前序遍歷是 「1-2-4-5-3-6-7」。假設我們只是規定了 「節點-左子節點-右子節點」 這個規則,那麼我們便規定了下面三個序列的次序:「1-2-3」、「2-4-5」、「3-6-7」,(即:3 必須在 2 之後訪問,2 必須在 1 之後訪問...)然而我們沒有規定這三個序列之間的相對次序,那麼符合條件的次序就有很多了,比如 「1-2-3-4-5-6-7」、「1-2-3-6-7-4-5」,「1-2-4-3-6-5-7」 等等。

仔細思考了一下,出現上面這些序列的原因是:我們沒有規定左子樹 「2-4-5」 與右子樹 「3-6-7」 兩個子樹之間的相對順序。比如第乙個例子 「1-2-3-4-5-6-7」,在左子樹只訪問根節點 「2」 之後,就去訪問右子樹的根節點 「3」,之後再訪問左子樹剩下的部分,最後再訪問右子樹剩下的部分。

我們知道正確的做法是:先訪問完所有左子樹的節點,再訪問所有右子樹的節點。於是得到第二條規則:

有了上述兩條規則,遍歷順序便被唯一確定了。當然我不知道怎麼嚴謹地證明這個結論。

回頭再思考一下上面兩個規則,第乙個規則規定了節點與它的兩個子節點(子樹)之間的順序,而第二個規則規定了兩個子樹之間的順序。

來看看**怎麼實現我們上面說的兩點規則的。為了方便,我把**搬了下來。

// 前序遍歷void preorder(treenode *root, vector& res)

if(visited) else }}

下面是演算法執行的示意圖,便於大家理解演算法流程。

我們將樹的遍歷的規則轉化為兩條等價的規則,其中一條確定了節點與子節點之間的遍歷順序,另一條確定了子節點之間的遍歷順序。之後,借助棧的特性,實現了上述兩條規則,即實現了樹的遍歷。

演算法的優點是將遍歷順序與演算法邏輯之間的分離,於是使用哪一種遍歷順序,不影響演算法本身的邏輯。換一句話說,不管是哪一種遍歷順序,**的整體框架是一樣的,只需稍微改變跟順序相關的幾句**,就ok了。除此之外,很容易推廣到多叉樹。

演算法的缺點嘛,對於每個節點都需要入棧兩次,同時對於每個節點都需要分配乙個標誌位,但是我覺得瑕不掩瑜。

二叉樹非遞迴遍歷

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

非遞迴遍歷二叉樹

中序遞迴遍歷 void inordertrvdigui node pnode 然而,當樹的深度很大 比如16 時 假設為滿二叉樹 樹的節點數為 2 0 2 1 2 2 2 15 2 16 65536,遍歷整個二叉樹意味著有65536次函式呼叫,這將極大地增加程式執行時間。這時,應該採取非遞迴便利二叉...

二叉樹非遞迴遍歷

1.先序遍歷 從遞迴說起 void preorder tnode root 遞迴演算法非常的簡單。先訪問跟節點,然後訪問左節點,再訪問右節點。如果不用遞迴,那該怎麼做呢?仔細看一下遞迴程式,就會發現,其實每次都是走樹的左分支 left 直到左子樹為空,然後開始從遞迴的最深處返回,然後開始恢復遞迴現場...