码迷,mamicode.com
首页 > 其他好文 > 详细

二叉树的非递归遍历,还有一点黑科技

时间:2015-09-07 21:12:22      阅读:174      评论:0      收藏:0      [点我收藏+]

标签:

 二叉树的前中后序遍历,可以用递归秒解,看起来不值一提。但如果不允许采用递归,要怎么实现呢?

还是先来看看递归算法的实现吧:

def    visit( root):
    if  root  is  not  null:
        #1
        visit(root.left)
        #2
        visit(root.right)
        #3

上面展示的代码中有三个位置(#1,#2,#3)可以用来插入访问当前节点的代码,分别对应了前中后三种遍历。这三种不同的设定,实际上表达的是访问节点的不同时机。

我们可以用进栈和出栈来模拟这些递归的过程,在跟#1,#2,#3相对应的时机访问节点,来形成三种不同的访问顺序。先画一棵树当做例子吧。

技术分享

 

1. 前序遍历

试着手动执行以下前序遍历。前序遍历的过程是先访问当前节点,再访问当前节点的左子树,待整个左子树被访问完,再回到当前节点,然后再访问右子树。为了使往左之后还能回到当前节点并往右,

我们要先把当前节点压入栈中,然后才往左走。我们先以节点1为当前节点,访问它,使它入栈,从节点1往左走,于是当前节点变为节点2。重复这个(访问,入栈,往左)的过程,直到当前节点为空。

对应上图的树,这个时候已访问节点是 1, 2, 4,并且栈中原始从栈底到栈顶也是1,2,4。此时当前节点为空,不可能再往左走,于是尝试往右。节点4退栈,试着从节点4往右一步,失败。

重复这个(退栈,尝试往右一步)的操作,直到往右成功,或者栈被清空。当我们把节点2退栈,并尝试往右时成功,于是当前节点就变成节点2的右子节点5。然后又重复上面

的(访问,入栈,往左),(退栈,尝试往右),一直进行下去直到当前节点为空且栈为空。

用代码表达就是酱紫的:

def preOrder(root):
        if root is null:
            return
        stack, current=[ ], root
        while current!=null or stack:
            if current!=null:
                #访问当前节点
                stack.append(current)    #入栈
                current=current.left      #往左
            else:
                top=stack.pop()          #退栈
                if top.right:              #尝试往右
                    current=top.right

2. 中序遍历

于是中序遍历可以类似地得到。中序遍历可以概括为:重复(入栈,往左),直到不能再往左,然后重复(退栈,访问,尝试往右)。

代码可以类似地得到:

def inOrder(root):
        if root is null:
            return
        stack, current=[ ], root
        while current!=null or stack:
            if current!=null:
                stack.append(current)    #入栈
                current=current.left      #往左
            else:
                top=stack.pop()          #退栈
          #访问top节点
                if top.right:              #尝试往右
                    current=top.right

 

3. 后序遍历

采用类似的思路实现后序遍历是有点难度的。显然我们应该在节点退栈之后再访问它,而且节点应该是再它的左右子树都被遍历完之后才退栈并被访问。

对于上面那棵树的节点2,我们会有三次到达这个节点:1)在节点1往左试探时,2)在节点2的左子树被遍历完之后回来节点2, 3)在右子树被遍历完之后回到节点2。

以上三次,对应应该执行不同的操作:1)节点2入栈,2)尝试往节点2的右子树去遍历,3)访问节点2。现在的问题在于,怎么区分后两种情况?即怎么知道是从左子树回来还是从右子树回来的?

其实也简单,只要记录上一个访问的节点是啥就可以了。例如我刚刚访问完节点2的右子树,于是记住上一个被访问的节点是节点5。那么当从5回到2的时候,就知道应该访问节点2,而不是从

节点2尝试往右。所以站在节点2的角度来看,迫使我们去访问节点2可能性有俩:要么2的右子树为空,要么2的右子树刚已经被遍历完了。于是代码应该写成这个样子:

def postOrder(root):
        if root is null:
            return
        stack, current, last=[ ], root, null
        while current!=null or stack:
            if current!=null:
                stack.append(current)    #入栈
                current=current.left      #往左
            else:
                top=stack.top()          #查看当前节点
            if top.right is not null and top.right!=last:  #尝试往右
                current=top.right
            else:
                stack.pop( )       #出栈
                #访问top节点
                last=top

实际上,这些不同次序的遍历之间暗藏着很多黑科技。比如,感觉上面实现的后续遍历相比于前序和中序遍历而言不够简洁呀,有别的办法嘛?哦肤阔死!

黑科技1. 改前序遍历。前序得到的是(根左右)的顺序。而我们要得顺序是(左右根)。其实吧,把前序遍历中访问左右子树的顺序对调一下,就得到了(根右左)的顺序,

再翻转一下,就得到了(左右根)的顺序,即后续遍历的结果。

黑科技2. 改层序遍历。层序遍历依靠队列来实现,对于(根,左,右)这仨节点而言,入队的顺序是先根,然后访问下一层,即左,右。这样出队的顺序是,(根;左,右)。

这里如果我们把队列换成栈,根先入栈,被访问,然后访问下一层,即左右。于是出栈的顺序是,(根;右,左)。再翻转也能得到(左,右;根)的顺序。

贴一下黑科技2的代码,简洁清爽,美丽新世界!

def postorderTraversal(root):
        if not root:
            return []
        result, stack=[], [root]
        while stack:
            current=stack.pop()
            result+=curr.val,
            if current.left:
                stack+=current.left,
            if current.right:
                stack+=current.right,
        return result[::-1]

 

二叉树的非递归遍历,还有一点黑科技

标签:

原文地址:http://www.cnblogs.com/acetseng/p/4789805.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!