深入淺出二叉樹(shù)遍歷的非遞歸算法 2019-11-15(未經(jīng)允許,禁止轉(zhuǎn)載)

1、二叉樹(shù)遍歷的遞歸算法

遞歸實(shí)現(xiàn)二叉樹(shù)的遍歷非常直觀,回顧一下遞歸的代碼:

  • 前序遍歷
def preOrder(Tree node):
    if node == None:
        return
    visit(node.data)
    preOrder(node.leftchild)
    preOrder(node.rightchild)
  • 中序遍歷
def inOrder(Tree node):
    if node == None:
        return 
    preOrder(node.leftchild)
    visit(node.data)
    preOrder(node.rightchild)
  • 后序遍歷
def postOrder(Tree node):
    if node == None:
        return 
    preOrder(node.leftchild)
    preOrder(node.rightchild)
    visit(node.data)

他們的直觀區(qū)別僅僅在于visit(node.data)的位置或者說(shuō)順序發(fā)生了改變

2、二叉樹(shù)遍歷非遞歸算法轉(zhuǎn)化的來(lái)龍去脈

為什么要轉(zhuǎn)化為非遞歸算法

遞歸會(huì)消耗較多的??臻g,并且不斷的函數(shù)調(diào)用和返回會(huì)消耗更多的時(shí)間

借助棧非遞歸化

遞歸,本質(zhì)上是函數(shù)幀不斷出入棧的過(guò)程。那么,我們可以直接利用棧的數(shù)據(jù)結(jié)構(gòu)來(lái)模擬遞歸的過(guò)程,轉(zhuǎn)化成非遞歸的代碼實(shí)現(xiàn)

非遞歸化的結(jié)點(diǎn)入出棧過(guò)程

入棧+出棧+訪(fǎng)問(wèn)

  • 什么時(shí)候結(jié)點(diǎn)入棧
  • 什么時(shí)候結(jié)點(diǎn)出棧
  • 什么時(shí)候訪(fǎng)問(wèn)結(jié)點(diǎn)

要解決上面3個(gè)問(wèn)題,可以對(duì)應(yīng)遞歸形式的代碼:

  • 遞歸調(diào)用導(dǎo)致當(dāng)前函數(shù)環(huán)境入棧;
  • 遞歸返回導(dǎo)致棧頂?shù)暮瘮?shù)環(huán)境出棧;
  • visit行是對(duì)當(dāng)前結(jié)點(diǎn)進(jìn)行訪(fǎng)問(wèn),并不涉及任何出入棧操作。其中,訪(fǎng)問(wèn)結(jié)點(diǎn)可以是打印,也可以是將結(jié)點(diǎn)的值加入某個(gè)列表,等等

因?yàn)閷?duì)結(jié)點(diǎn)的訪(fǎng)問(wèn)和出入棧沒(méi)有關(guān)系,因此我們先忽略遞歸代碼中的visit語(yǔ)句。此時(shí)觀察三種遍歷的遞歸代碼,可以發(fā)現(xiàn)它們的結(jié)點(diǎn)出入棧順序一模一樣,重復(fù)一次,出入棧順序一模一樣

這里的代碼分析很重要!??!

def xxxOrder(Tree node):
    if node == None:
        return 
    
    # A.因?yàn)橄旅嬉M(jìn)入遞歸調(diào)用的xxxOrder(node.leftchild),后面還得回到node的
    # 因此只要一執(zhí)行xxxOrder(node.leftchild),node就會(huì)入棧
    xxxOrder(node.leftchild)
    # B.當(dāng)xxxOrder(node.leftchild)執(zhí)行完畢返回,即左子樹(shù)探索完了
    # 控制權(quán)自然回到結(jié)點(diǎn)node所在的這個(gè)函數(shù)體,那么結(jié)點(diǎn)node就不需要存了。所以,當(dāng)xxxOrder(node.leftchild)執(zhí)行完畢返回,node出棧
    
    # C.因?yàn)橄旅嬉M(jìn)入遞歸調(diào)用的xxxOrder(node.rightchild),后面也還得回到node的
    # 因此只要一執(zhí)行xxxOrder(node.rightchild),node就又會(huì)再次入棧
    xxxOrder(node.rightchild)
    # D.當(dāng)xxxOrder(node.rightchild)執(zhí)行完畢返回,即右子樹(shù)探索完畢,node出棧

可以清晰地看到,遞歸時(shí),結(jié)點(diǎn)node要走完自己的函數(shù)體,經(jīng)歷了2次入棧出棧過(guò)程

那么,我們有必要完全模仿這樣的2次入出棧的過(guò)程嗎?當(dāng)然沒(méi)有必要
在弄清楚結(jié)點(diǎn)出入棧的過(guò)程之外,我們更要弄清楚結(jié)點(diǎn)出入棧的作用到底是什么

結(jié)點(diǎn)node入出棧的作用,很重要?。?!

  • 對(duì)于前序遍歷,結(jié)點(diǎn)node在調(diào)用xxxOrder(node.leftchild)之前就被訪(fǎng)問(wèn)并入棧,在xxxOrder(node.leftchild)返回后出棧,此時(shí)node自己以及它的左子樹(shù)全部都被訪(fǎng)問(wèn)了,它的出棧僅僅是為了獲得接下來(lái)的node.rightchild。也就是說(shuō),node的第一次出棧,是為了接下來(lái)獲得它的右孩子。這時(shí),node沒(méi)有再次入棧的必要
  • 對(duì)于中序遍歷,結(jié)點(diǎn)node在xxxOrder(node.leftchild)返回后出棧。node出棧是為了訪(fǎng)問(wèn)node本身+獲得接下來(lái)的node.rightchild,此時(shí)node的左子樹(shù)和自己本身都被訪(fǎng)問(wèn)了,在獲得node.rightchild之后,接下來(lái)的一切都和node無(wú)關(guān)。這時(shí),node也沒(méi)有再次入棧的必要
  • 對(duì)于后序遍歷,結(jié)點(diǎn)node在xxxOrder(node.leftchild)返回后出棧,是為了獲得接下來(lái)的node.rightchild;同時(shí),結(jié)點(diǎn)node還需要再次入棧,因?yàn)樗枰趚xxOrder(node.rightchild)返回后,訪(fǎng)問(wèn)自己。因此,后序遍歷是比較特殊的

3、非遞歸形式的基礎(chǔ)代碼

基于上面分析,自然而然,非遞歸化的基本代碼就可以寫(xiě)出來(lái)了!

對(duì)于前序遍歷和中序遍歷:

def search(TreeNode):
    
    stack = list()
    
    # A.當(dāng)TreeNode不為None時(shí), 不斷入棧TreeNode
    while TreeNode != None:
        # visit(TreeNode)  # 前序
        stack.append(TreeNode)
        TreeNode = TreeNode.left
        
    # 當(dāng)棧不為空時(shí)
    while stack:
        # B.棧頂元素出棧
        top = stack.pop(-1)
        # visit(top)  # 中序
        
        # C.以top.right為根結(jié)點(diǎn),重復(fù)ABC
        t = top.right  # 如果是多叉樹(shù),則是 for t in top.lefted_childs:
        while t != None:
            # visit(t)  # 前序
            stack.append(t)
            t = t.left

對(duì)于后序遍歷:

def search(TreeNode):
    
    stack = list()
    last_pop_node = None
    
    # 當(dāng)TreeNode不為None時(shí), 不斷入棧TreeNode
    while TreeNode != None:
        stack.append(TreeNode)
        TreeNode = TreeNode.left
        
    # 當(dāng)棧不為空時(shí)
    while stack:
        
        # 獲取棧頂結(jié)點(diǎn)top,并判斷是否出棧
        top = stack[-1]
        t = top.right
        # 如果top無(wú)右子樹(shù) or 上一個(gè)pop的結(jié)點(diǎn)是top的右孩子,說(shuō)明top的右子樹(shù)訪(fǎng)問(wèn)完畢,出棧top并訪(fǎng)問(wèn)
        if t is None or t == last_pop_node:
            last_pop_node = stack.pop(-1)
            visit(last_pop_node)
        
        # 否則top不出棧,top的右子樹(shù)入棧
        else:
            while t != None:
                stack.append(t)
                t = t.left

例題:

給定一個(gè)二叉樹(shù),(以層次遍歷順序的list作為輸入),返回它的 后序 遍歷
示例:
輸入: [1,null,2,3]
1
\
2
/
3

輸出: [3,2,1]

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        result = []
        stack = list()
        last_pop_node = None
        
        # 當(dāng)root不為None時(shí), 不斷入棧root
        while root != None:
            stack.append(root)
            root = root.left
            
        # 當(dāng)棧不為空時(shí)
        while stack:
            
            # 獲取棧頂結(jié)點(diǎn)top,并判斷是否出棧
            top = stack[-1]
            t = top.right
            # 如果top無(wú)右子樹(shù)or上一個(gè)pop的結(jié)點(diǎn)是top的右孩子,說(shuō)明top的右子樹(shù)訪(fǎng)問(wèn)完畢
            if t is None or t == last_pop_node:
                last_pop_node = stack.pop(-1)
                result.append(last_pop_node.val)
            
            # 否則top不出棧,top的右子樹(shù)入棧
            else:
                while t != None:
                    stack.append(t)
                    t = t.left
        return result
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容