二叉樹的遍歷 完結

2021-08-02 16:59:42 字數 4126 閱讀 2107

學習過資料結構的同學都清楚,除了層序遍歷外,二叉樹主要有三種遍歷方式:

1. 先序遍歷

2. 中序遍歷

3. 後序遍歷

關於這三種遍歷方式,我不做贅述,網上或者書本上,大家可以去了解一下。這次我們主要總結一下二叉樹遍歷的演算法的遞迴實現和非遞迴實現。而重點又落在非遞迴實現上,因為,遞迴實現,其實也是書本上的基礎知識,只要去查閱機會有所理解。

先序遍歷的遞迴實現

我們先定義乙個二叉樹的節點結構作為本博文中的通用節點。

public class node

}

我們知道,二叉樹的遞迴遍歷無非是對一顆非空的二叉樹的每乙個節點,做相同的操作(先序、中序、後序)。二叉樹的先序遍歷就是對每乙個節點來先序遍歷的動作。**如下:

public void preorder(node head)
中序遍歷的遞迴實現

同樣的,二叉樹中序遍歷的遞迴實現,也是建立在同樣的思想上。

public void midorder(node head)
後序遍歷的遞迴實現

the same idea , not the same recipe.

public void posorder(node head)
關於遞迴實現的簡單總結

從上面的**,我們發現,在遞迴實現上,三種遍歷方式的主要區別就是輸出當前頭結點的值的時機,從而讓遍歷的順序產生區別。

用遞迴方法解決的問題都可以用非遞迴來實現,因為遞迴方法無非是實用函式棧來儲存資訊,如果用自己定義的棧來代替函式棧,就可以實現相同的功能。

先序遍歷的非遞迴實現

先來大致分析一下非遞迴方式實現先序遍歷的具體過程:

1. 申請乙個新的stack,然後把頭結點打入stack中

2. 從stack中彈出棧頂節點,記為 head ,然後列印 head 的值,然後把 head 的右孩子壓入棧中,再把 head 的左孩子壓入棧中(注意先後順序)。當然在做這些操作之前,都需要乙個非空判斷.

3. 重複步驟2,直到 stack 為空。

public void preordernorecur(node head)

}}

需要重點理解的就是彈出節點的左右孩子被壓入棧的時機。建議大家自行畫乙個圖,模擬一下程式執行的過程。

中序遍歷的非遞迴實現

同樣的,我們先分析一下中序遍歷非遞迴實現的具體過程:

1. 申請乙個新的 stack 。跟先序遍歷不同的是,我們需要兩個值,乙個cur,記錄當前遍歷到的節點的值,乙個head,記錄當前二叉樹的根節點。初始時,cur = head

2. 把cur代表的節點壓入棧中,讓cur = cur.left

3. 重複步驟2,直到 cur 為空時,從 stack 中彈出乙個節點,記為 outnode ,列印 outnode 的值。然後讓 cur = outnode.right,繼續重複步驟2。

4. 當 stack 為空且 cur 為空時,整個遍歷完成。閒話不多說,上**。

public void midordernorecur(node head)else}}

}

實際上,我們可以把這段程式中的 cur , outnode都替換成head,這樣做可以節約空間,但是在**清晰度上個人覺得會有所降低。因為是用來總結,所以覺得還是把各個東西寫清晰的好。但是也附上都替換成 head 後的**。

public void midordernorecur(node head)else}}

}

此處預留乙個位置,今天實在太晚了,困的不行,接下面的明天繼續更。good night.

更新......

後序遍歷的非遞迴實現

關於二叉樹後序遍歷的非遞迴實現,我學習了兩種方式。一種是利用兩個棧,另一種是利用乙個棧。

兩個棧實現後序遍歷

依然從具體步驟開始:

1. 申請兩個 stack,分別記為 s1,s2 ,然後將頭結點壓入 s1 中

2. 從 s1 中彈出乙個節點,記為 cur ,然後 cur 的左孩子和右孩子按順序壓入棧中

3. 把 cur 壓入 s2 中

4. 不斷重複第二步、第三步、一直到 s1 為空時停止

5. 從 s2 中依次彈出節點並列印值

public void posordernorecur(node head)

while(!s2.isempty())

system.out.print(s2.pop().value + " ");

}}

以上,我們便利用兩個棧完成了二叉樹後序遍歷的非遞迴實現,思想上很簡單,就是對每個節點做一次先序遍歷,然後再按照右孩子,左孩子的順序壓入 s2 中,因為我們知道棧的特性之一就是先進後出,按照前面所說的順序把節點壓入棧中,再遍歷時就會得到乙個正確的後序遍歷。同樣建議大家自己在稿紙上畫圖,模擬過程,幫助理解。

乙個棧實現後序遍歷
明天繼續...

用乙個棧來實現二叉樹的後序遍歷,多少會有一些麻煩,我決定採用先貼出**的方式。

public void posordernorecur(node h)else if(c.right != null && h != c.right)else}}

}

然後再來說**的實現思路,主要是遵從這樣的步驟:

1. 首先,依然是申請乙個棧,記為 stack , 並且設定兩個變數 h 和 c。在整個過程中,h 代表最近一次彈出並列印的節點,c 代表棧頂節點,初始情況: h 為 head , c 為 null。

2. 這個時候(h 為最新彈出節點,c 為棧頂節點<並沒有彈出>)有三種情況:

如果 c 的左孩子不為空,且 h 不是 c 的左孩子也不是 c 的右孩子,那麼就把 c 的左孩子壓入棧中。

如果 c 的又孩子不為空,且 h 不是 c 的右孩子,那我們就把 c 的右孩子壓入棧中。

如果上述兩種情況都不滿足,說明 c 的左子樹和右子樹都已經列印完畢,然後讓 h = c.

結束條件: 棧 stack 為空

接下來我們解釋上述步驟的邏輯,首先,我們剛開始的時候把 h 代表的頭結點壓入 stack 中,此時,棧頂節點即為樹的根,我們另 c = stack.peek(),這個時候,按照我們對後序遍歷的理解,我們應該先遍歷頭結點的左子樹,如果 c 的左孩子不為空的話,這個時候我們的 h 肯定不等於 c 的左孩子/右孩子,因為我們並沒有把左孩子/右孩子壓入棧中,所以我們把 c 的左孩子壓入棧中,而之所以判斷一下 h 所代表的剛彈出的節點是否為 c 的左孩子/右孩子,是因為我們要判斷 c 的左右孩子是否有遍歷。如果有遍歷,那我們自然可以判斷, c 的左右孩子已經遍歷過,彈出 c 節點列印值,然後進入下一次迴圈即可。同理的,當 c 的左孩子為空,右孩子不為空時,我們自然要判斷剛彈出的是否是 c 的右孩子,然後判斷把 c 的右孩子壓入棧中。

我們講了很多關於壓入棧的操作,但是我們什麼時候輸出呢,也就是什麼時候得到 h 的值呢。最後乙個 else 包含的情況有哪些呢。很明顯,就是遍歷到葉子節點的時候。當我們碰到棧頂節點是葉子節點的時候,就會產生彈出。

本文主要講了二叉樹非常基礎的三種遍歷方式: 先序遍歷、中序遍歷、後序遍歷。在實現方式上,我們主要區分遞迴的實現方式和非遞迴的實現方式。遞迴的方式是書本上的內容,非遞迴方式的話,其實都很好理解,主要應該注意的就是壓入棧的時機,左右孩子壓入棧的順序。這些順序的判斷和分析,都是建立在先/中/後序遍歷的特點上的。只要清楚所應該遵循的規則,相信就可以寫出正確的程式。

ps: 花了四天才寫完這篇部落格。第一天太晚,第二天感冒,第三天出去浪了。是時候提高一波執行力了。

部落格位址 :部落格

二叉樹的遍歷 二叉樹遍歷與儲存

在資料結構中,二叉樹是非常重要的結構。例如 資料庫中經常用到b 樹結構。那麼資料庫是如何去單個查詢或者範圍查詢?首先得理解二叉樹的幾種遍歷順序 先序 中序 後序 層次遍歷。先序 根節點 左子樹 右子樹 中序 左子樹 根節點 右子樹 後序 左子樹 右子樹 根節點 按層級 class node if c...

構建二叉樹 遍歷二叉樹

陣列法構建二叉樹 public class main public static void main string args 用陣列的方式構建二叉樹 public static void createbintree 把linkedlist集合轉成二叉樹的形式 for int j 0 j 最後乙個父節...

玩轉二叉樹(二叉樹的遍歷)

時間限制 400 ms 記憶體限制 65536 kb 長度限制 8000 b 判題程式 standard 作者 陳越 給定一棵二叉樹的中序遍歷和前序遍歷,請你先將樹做個鏡面反轉,再輸出反轉後的層序遍歷的序列。所謂鏡面反轉,是指將所有非葉結點的左右孩子對換。這裡假設鍵值都是互不相等的正整數。輸入格式 ...