1. 程式人生 > >LeetCode總結 -- 樹的遍歷篇

LeetCode總結 -- 樹的遍歷篇

                遍歷樹的資料結構中最常見的操作, 可以說大部分關於樹的題目都是圍繞遍歷進行變體來解決的。 一般來說面試中遇到樹的題目是用遞迴來解決的, 不過如果直接考察遍歷, 那麼一般遞迴的解法就過於簡單了, 面試官一般還會問更多問題, 比如非遞迴實現, 或者空間複雜度分析以及能否優化等等。 樹的遍歷題目在LeetCode中有以下幾個: 
Binary Tree Inorder Traversal
Binary Tree Preorder Traversal
Binary Tree Postorder Traversal
Binary Tree Level Order Traversal
Binary Tree Level Order Traversal II

Binary Tree Zigzag Level Order Traversal

樹的遍歷基本上分成兩種型別, 下面分別介紹: 

第一種是以圖的深度優先搜尋為原型的遍歷, 可以是中序, 先序和後序三種方式, 不過結點遍歷的方式是相同的, 只是訪問的時間點不同而已, 對應於Binary Tree Inorder Traversal, Binary Tree Preorder TraversalBinary Tree Postorder Traversal這三道題目。 
在這種型別中, 遞迴的實現方式是非常簡單的, 只需要遞迴左右結點, 直到結點為空作為結束條件就可以, 哪種序就取決於你訪問結點的時間。 

不過一般這不能滿足面試官的要求, 可能會接著問能不能用非遞迴實現一下, 這個說起來比較簡單, 其實就是用一個棧手動模擬遞迴的過程, Binary Tree Inorder TraversalBinary Tree Preorder Traversal比較簡單, 用一個棧來儲存前驅的分支結點(相當於圖的深度搜索的棧), 然後用一個結點來記錄當前結點就可以了。 而Binary Tree Postorder Traversal則比較複雜一些, 儲存棧和結點之後還得根據情況來判斷當前應該走的方向(往左, 往右或者回溯)。 這裡就不列舉程式碼細節, 有興趣的朋友可以看看具體題目的分析, 會更詳細一些。 

有時候非遞迴還是不能滿足面試官, 還會問一問, 上面的做法時間和空間複雜度是多少。 我們知道, 正常遍歷時間複雜度是O(n), 而空間複雜度是則是遞迴棧(或者自己維護的棧)的大小, 也就是O(logn)。 好了, 他會問能不能夠在常量空間內解決樹的遍歷問題呢? 確實還真可以, 這裡就要介紹Morris Traversal的方法。 Morris遍歷方法用了線索二叉樹,這個方法不需要為每個節點額外分配指標指向其前驅和後繼結點,而是利用葉子節點中的右空指標指向中序遍歷下的後繼節點就可以了。 這樣就節省了需要用棧來記錄前驅或者後繼結點的額外空間, 所以可以達到O(1)的空間複雜度。 不過這種方法有一個問題就是會暫時性的改動樹的結構, 這在程式設計中並不是很好的習慣, 這些在面試中都可以和麵試官討論, 一般來說問到這裡不會需要進行Morris遍歷方法的程式碼實現了, 只需要知道這種方法和他的主要優劣勢就可以了, 有興趣知道實現的朋友可以看看具體題目的實現哈。 

另一種是以圖的廣度優先搜尋為原型的, 在樹中稱為層序遍歷, LeetCode中有三種自頂向下層序, 自底向上層序和鋸齒層序遍歷, 對應於Binary Tree Level Order Traversal, Binary Tree Level Order Traversal IIBinary Tree Zigzag Level Order Traversal。 
Binary Tree Level Order Traversal其實比較簡單, 程式碼基本就是圖的廣度優先搜尋, 思路就是維護一個佇列儲存上一層的結點, 逐層訪問。 而Binary Tree Level Order Traversal II則要從最後一層倒序訪問上來, 這個我沒有想到太好的方法, 現在的實現就是把Binary Tree Level Order Traversal得到的層放入資料結構然後reverse過來, 確實沒有太大的考核意義。 至於Binary Tree Zigzag Level Order Traversal因為每一層訪問順序有所改變, 而且是每次都反轉順序, 這讓我們想到棧的資料結構, 所以這裡不用佇列對於上層結點進行, 而改用棧來儲存, 就可以滿足每層反轉訪問順序的要求了。 

樹的遍歷是一個老生常談的題目, 不過仔細研究還是有一些考點的, 對於考查對資料結構和演算法的理解還是不錯的, 所以簡單的東西也得重視哈。