美文网首页
深入浅出二叉树遍历的非递归算法 2019-11-15(未经允许,

深入浅出二叉树遍历的非递归算法 2019-11-15(未经允许,

作者: 9_SooHyun | 来源:发表于2019-11-16 01:02 被阅读0次

    1、二叉树遍历的递归算法

    递归实现二叉树的遍历非常直观,回顾一下递归的代码:

    • 前序遍历
    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)
    

    他们的直观区别仅仅在于visit(node.data)的位置或者说顺序发生了改变

    2、二叉树遍历非递归算法转化的来龙去脉

    为什么要转化为非递归算法

    递归会消耗较多的栈空间,并且不断的函数调用和返回会消耗更多的时间

    借助栈非递归化

    递归,本质上是函数帧不断出入栈的过程。那么,我们可以直接利用栈的数据结构来模拟递归的过程,转化成非递归的代码实现

    非递归化的结点入出栈过程

    入栈+出栈+访问

    • 什么时候结点入栈
    • 什么时候结点出栈
    • 什么时候访问结点

    要解决上面3个问题,可以对应递归形式的代码:

    • 递归调用导致当前函数环境入栈;
    • 递归返回导致栈顶的函数环境出栈;
    • visit行是对当前结点进行访问,并不涉及任何出入栈操作。其中,访问结点可以是打印,也可以是将结点的值加入某个列表,等等

    因为对结点的访问和出入栈没有关系,因此我们先忽略递归代码中的visit语句。此时观察三种遍历的递归代码,可以发现它们的结点出入栈顺序一模一样,重复一次,出入栈顺序一模一样

    这里的代码分析很重要!!!

    def xxxOrder(Tree node):
        if node == None:
            return 
        
        # A.因为下面要进入递归调用的xxxOrder(node.leftchild),后面还得回到node的
        # 因此只要一执行xxxOrder(node.leftchild),node就会入栈
        xxxOrder(node.leftchild)
        # B.当xxxOrder(node.leftchild)执行完毕返回,即左子树探索完了
        # 控制权自然回到结点node所在的这个函数体,那么结点node就不需要存了。所以,当xxxOrder(node.leftchild)执行完毕返回,node出栈
        
        # C.因为下面要进入递归调用的xxxOrder(node.rightchild),后面也还得回到node的
        # 因此只要一执行xxxOrder(node.rightchild),node就又会再次入栈
        xxxOrder(node.rightchild)
        # D.当xxxOrder(node.rightchild)执行完毕返回,即右子树探索完毕,node出栈
    

    可以清晰地看到,递归时,结点node要走完自己的函数体,经历了2次入栈出栈过程

    那么,我们有必要完全模仿这样的2次入出栈的过程吗?当然没有必要
    在弄清楚结点出入栈的过程之外,我们更要弄清楚结点出入栈的作用到底是什么

    结点node入出栈的作用,很重要!!!

    • 对于前序遍历,结点node在调用xxxOrder(node.leftchild)之前就被访问并入栈,在xxxOrder(node.leftchild)返回后出栈,此时node自己以及它的左子树全部都被访问了,它的出栈仅仅是为了获得接下来的node.rightchild。也就是说,node的第一次出栈,是为了接下来获得它的右孩子。这时,node没有再次入栈的必要
    • 对于中序遍历,结点node在xxxOrder(node.leftchild)返回后出栈。node出栈是为了访问node本身+获得接下来的node.rightchild,此时node的左子树和自己本身都被访问了,在获得node.rightchild之后,接下来的一切都和node无关。这时,node也没有再次入栈的必要
    • 对于后序遍历,结点node在xxxOrder(node.leftchild)返回后出栈,是为了获得接下来的node.rightchild;同时,结点node还需要再次入栈,因为它需要在xxxOrder(node.rightchild)返回后,访问自己。因此,后序遍历是比较特殊的

    3、非递归形式的基础代码

    基于上面分析,自然而然,非递归化的基本代码就可以写出来了!

    对于前序遍历和中序遍历:

    def search(TreeNode):
        
        stack = list()
        
        # A.当TreeNode不为None时, 不断入栈TreeNode
        while TreeNode != None:
            # visit(TreeNode)  # 前序
            stack.append(TreeNode)
            TreeNode = TreeNode.left
            
        # 当栈不为空时
        while stack:
            # B.栈顶元素出栈
            top = stack.pop(-1)
            # visit(top)  # 中序
            
            # C.以top.right为根结点,重复ABC
            t = top.right  # 如果是多叉树,则是 for t in top.lefted_childs:
            while t != None:
                # visit(t)  # 前序
                stack.append(t)
                t = t.left
    

    对于后序遍历:

    def search(TreeNode):
        
        stack = list()
        last_pop_node = None
        
        # 当TreeNode不为None时, 不断入栈TreeNode
        while TreeNode != None:
            stack.append(TreeNode)
            TreeNode = TreeNode.left
            
        # 当栈不为空时
        while stack:
            
            # 获取栈顶结点top,并判断是否出栈
            top = stack[-1]
            t = top.right
            # 如果top无右子树 or 上一个pop的结点是top的右孩子,说明top的右子树访问完毕,出栈top并访问
            if t is None or t == last_pop_node:
                last_pop_node = stack.pop(-1)
                visit(last_pop_node)
            
            # 否则top不出栈,top的右子树入栈
            else:
                while t != None:
                    stack.append(t)
                    t = t.left
    

    例题:

    给定一个二叉树,(以层次遍历顺序的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
            
            # 当root不为None时, 不断入栈root
            while root != None:
                stack.append(root)
                root = root.left
                
            # 当栈不为空时
            while stack:
                
                # 获取栈顶结点top,并判断是否出栈
                top = stack[-1]
                t = top.right
                # 如果top无右子树or上一个pop的结点是top的右孩子,说明top的右子树访问完毕
                if t is None or t == last_pop_node:
                    last_pop_node = stack.pop(-1)
                    result.append(last_pop_node.val)
                
                # 否则top不出栈,top的右子树入栈
                else:
                    while t != None:
                        stack.append(t)
                        t = t.left
            return result
    

    相关文章

      网友评论

          本文标题:深入浅出二叉树遍历的非递归算法 2019-11-15(未经允许,

          本文链接:https://www.haomeiwen.com/subject/ioqsictx.html