二叉樹(前序,中序,后序,層序)遍歷遞歸與循環的python實現


二叉樹的遍歷是在面試使比較常見的項目了。對於二叉樹的前中后層序遍歷,每種遍歷都可以遞歸和循環兩種實現方法,且每種遍歷的遞歸實現都比循環實現要簡潔。下面做一個小結。

一、中序遍歷

前中后序三種遍歷方法對於左右結點的遍歷順序都是一樣的(先左后右),唯一不同的就是根節點的出現位置。對於中序遍歷來說,根結點的遍歷位置在中間。

所以中序遍歷的順序:左中右

1.1 遞歸實現

每次遞歸,只需要判斷結點是不是None,否則按照左中右的順序打印出結點value值。

class Solution:
    def inorderTraversal(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        if not root:
            return [] 
        return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right)

1.2 循環實現

循環比遞歸要復雜得多,因為你得在一個函數中遍歷到所有結點。但是有句話很重要:

對於樹的遍歷,循環操作基本上要用到棧(stack)這個結構

對於中序遍歷的循環實現,每次將當前結點(curr)的左子結點push到棧中,直到當前結點(curr)為None。這時,pop出棧頂的第一個元素,設其為當前結點,並輸出該結點的value值,且開始遍歷該結點的右子樹。

例如,對於上圖的一個二叉樹,其循環遍歷過程如下表:

No. 輸出列表sol 棧stack 當前結點curr
1 [] [] 1
2 [] [1] 2
3 [] [1,2] 4
4 [] [1,2,4] None
5 [4] [1,2] 4 -> None(4的右結點)
6 [4,2] [1] 2 -> 5
7 [4,2] [1,5] None(5的左結點)
8 [4,2,5] [1] 5 -> None(5的右結點)
9 [4,2,5,1] [] 3
10 [4,2,5,1] [3] None
11 [4,2,5,1,3] [] None

可見,規律為:當前結點curr不為None時,每一次循環將當前結點curr入棧;當前結點curr為None時,則出棧一個結點,且打印出棧結點的value值。整個循環在stack和curr皆為None的時候結束。

class Solution:
    def inorderTraversal(self, root):
        stack = []
        sol = []
        curr = root
        while stack or curr:
            if curr:
                stack.append(curr)
                curr = curr.left
            else:
                curr = stack.pop()
                sol.append(curr.val)
                curr = curr.right
        return sol

二、前序遍歷和后序遍歷

按照上面的說法,前序遍歷指根結點在最前面輸出,所以前序遍歷的順序是:中左右

后序遍歷指根結點在最后面輸出,所以后序遍歷的順序是:左右中

2.1 遞歸實現

遞歸實現與中序遍歷幾乎完全一樣,改變一下打印的順序即可:

class Solution:
    def preorderTraversal(self, root):  ##前序遍歷
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        if not root:
            return [] 
        return  [root.val] + self.inorderTraversal(root.left) + self.inorderTraversal(root.right)
        
    def postorderTraversal(self, root):  ##后序遍歷
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        if not root:
            return [] 
        return  self.inorderTraversal(root.left) + self.inorderTraversal(root.right) + [root.val]

改動的地方只有return時函數的打印順序。

2.2 循環實現

為什么把前序遍歷和后序遍歷放在一起呢?Leetcode上前序遍歷是medium難度,后序遍歷可是hard難度呢!

實際上,后序遍歷不就是前序遍歷的“反過程”嘛!

先看前序遍歷。我們仍然使用棧stack,由於前序遍歷的順序是中左右,所以我們每次先打印當前結點curr,並將右子結點push到棧中,然后將左子結點設為當前結點。入棧和出棧條件(當前結點curr不為None時,每一次循環將當前結點curr入棧;當前結點curr為None時,則出棧一個結點)以及循環結束條件(整個循環在stack和curr皆為None的時候結束)與中序遍歷一模一樣。

再看后序遍歷。由於后序遍歷的順序是左右中,我們把它反過來,則遍歷順序變成中左右,是不是跟前序遍歷只有左右結點的差異了呢?然而左右的差異僅僅就是.left和.right的差異,在代碼上只有機械的差別。

我們來看代碼:

class Solution:
    def preorderTraversal(self, root):  ## 前序遍歷
        stack = []
        sol = []
        curr = root
        while stack or curr:
            if curr:
                sol.append(curr.val)
                stack.append(curr.right)
                curr = curr.left
            else:
                curr = stack.pop()
        return sol
        
    def postorderTraversal(self, root): ## 后序遍歷
        stack = []
        sol = []
        curr = root
        while stack or curr:
            if curr:
                sol.append(curr.val)
                stack.append(curr.left)
                curr = curr.right
            else:
                curr = stack.pop()
        return sol[::-1]

代碼的主體部分基本就是.right和.left交換了順序,且后序遍歷在最后輸出的時候進行了反向(因為要從中右左變為左右中

三、層序遍歷

層序遍歷也可以叫做寬度優先遍歷:先訪問樹的第一層結點,再訪問樹的第二層結點...然后一直訪問到最下面一層結點。在同一層結點中,以從左到右的順序依次訪問。

3.1 遞歸實現

遞歸函數需要有一個參數level,該參數表示當前結點的層數。遍歷的結果返回到一個二維列表sol=[[]]中,sol中的每一個子列表保存了對應index層的從左到右的所有結點value值。

class Solution:
    def levelOrder(self, root):
        """
        :type root: TreeNode
        :rtype: List[List[int]]
        """
        def helper(node, level):
            if not node:
                return
            else:
                sol[level-1].append(node.val)
                if len(sol) == level:  # 遍歷到新層時,只有最左邊的結點使得等式成立
                    sol.append([])
                helper(node.left, level+1)
                helper(node.right, level+1)
        sol = [[]]
        helper(root, 1)
        return sol[:-1]

PS:

Q:如果仍然按層遍歷,但是每層從右往左遍歷怎么辦呢?

A:將上面的代碼left和right互換即可

Q:如果仍然按層遍歷,但是我要第一層從左往右,第二層從右往左,第三從左往右...這種zigzag遍歷方式如何實現?

A:將sol[level-1].append(node.val)進行一個層數奇偶的判斷,一個用append(),一個用insert(0,)

    if level%2==1:
        sol[level-1].append(node.val)
    else:
        sol[level-1].insert(0, node.val)

3.2 循環實現

這里的循環實現不能用棧了,得用隊列queue。因為每一層都需要從左往右打印,而每打印一個結點都會在隊列中依次添加其左右兩個子結點,每一層的順序都是一樣的,故必須采用先進先出的數據結構。

以下代碼的打印結果為一個一維列表,沒有采用二維列表的形式。

class Solution:
    def levelOrder(self, root):
        if not root:
            return []
        sol = []
        curr = root
        queue = [curr]
        while queue:
            curr = queue.pop(0)
            sol.append(curr.val)
            if curr.left:
                queue.append(curr.left)
            if curr.right:
                queue.append(curr.right)
        return sol

其實,如果需要打印成zigzag形式(相鄰層打印順序相反),則可以采用棧stack數據結構,正好符合先進后出的形式。不過在代碼上還要進行其他改動。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM