二叉树遍历理解——递归及非递归方法中栈的利用

来源:互联网 发布:mac book怎么样 编辑:程序博客网 时间:2024/06/06 14:26

1.二叉树介绍

二叉树是每个节点最多有两个子树的树结构,遍历方法有深度优先(包括:先序、中序、后序遍历)和宽度优先(层序遍历),层序遍历通过队列可以实现。这里主要介绍深度优先遍历的方法以及其中栈的应用,帮助理解二叉树的结构、递归和非递归中栈的应用。程序python 3。

2.递归遍历

先序遍历:
    def pre_order(Tree, proc):  #proc是具体的节点数据操作        if Tree is None:            return        proc(Tree.date)             #**位置1**        pre_order(Tree.left, proc)                                    #**位置2**        pre_order(Tree.right, proc)                                    #**位置3**
这就是一个递归实现先序遍历。当把proc从位置1分别移到位置2和位置3时,就变成了中序遍历和后序遍历。注意,在递归函数中,只需要把具体的节点操作改变位置,就能实现先序、中序和后序遍历,这是因为执行递归时,递归算法中的栈存储了函数的状态,包括参数,返回地址。它在访问了左右子树和该节点的值后,栈才释放了这个节点的数据。

3.非递归遍历

    def pre_order_nonrec(Tree, proc):        s = SStack                #SStack是一个栈类        while Tree is not None or not s.is_empty():                     #当数不为空且栈不空时            while Tree:                s.push(Tree)                Tree = Tree.left                                  #**位置2**            if not s.is_empty()   #栈不空                Tree = s.pop()                proc(Tree.date)       #**位置1**                Tree = Tree.right
当proc(Tree.date) 在位置一时,实现了中序遍历,将他移到位置二时,实现了先序遍历。那能否移动proc的位置实现后序遍历呢?**这是不可能的。**首先,我们要了解先序、中序、后序遍历的实质,看下图,

树中各节点的访问路径(摘自MOOC网浙江大学数据结构一课)
树中各节点的访问路径(摘自MOOC网浙江大学数据结构一课)
可以看到,沿着整个树的外部轮廓,先序时在第一次遇见节点时访问他,中序是第二次遇到节点是访问他,后序时第三次遇到才访问。
上面的非递归遍历算法用栈保存节点,只能在入栈和出栈的时候可以访问他,出栈后(两次访问后)就将这个节点丢掉,不能第三次去访问他,而后序遍历就是在第三次访问他,所以上面的方法不能实现后序遍历。
在采用递归方法时,同样是栈,为什么他能第三次访问呢?这是因为递归函数的栈存储了函数的状态,包括参数,返回地址,是层层嵌套的,它在访问了该节点后,并没有丢掉这些数据,而是在,分别访问完左子树和右子树后才丢掉了这个节点的相关数据。
详细来说,在递归算法中实际存在的那个栈中,访问了当前节点(proc生效)之后并没有弹出参数为这个节点的函数!因为需要再次调用自身(参数为T->right),而且当前函数并未结束。
为什么这种非递归遍历方法又正确呢?以先序遍历为例说明。考虑某时刻某个节点Tree(也可以看成一棵子树),按照先序遍历规则先访问Tree,如果之后左子树为空,将Tree.right入栈。当子树Tree.right遍历完成并返回Tree这一层函数的时候,Tree这一层的函数也结尾了,那么需要pop出并将控制权交给上一层。在前序非递归算法中,Tree被访问后直接出栈,当其右子树Tree.right访问完成后,Ti本身就不在栈里面了,在这时省去了一个pop的流程。
所以,要用非递归方式实现后序遍历,则需要将节点入栈两次,这样才能他对他进行第三次访问,这可以通过给Tree增加状态量来实现第二次入栈,如有0/1/2三种状态,遇到节点时检查其状态,遇到0/1状态,入栈且状态值加一,变成1/2,遇到2状态,不需要再入栈。
最后,了解了二叉树遍历的实质后,我们可以对之前的先序遍历化简。

    def pre_order_nonrec(Tree, proc):        s = SStack                #SStack是一个栈类        while Tree is not None or not s.is_empty():                     #当数不为空且栈不空时            while Tree:                proc(Tree.date)                s.push(Tree.right)                Tree = Tree.left                                  #**位置2**            if not s.is_empty()   #栈不空                Tree = s.pop()
化简的方法就是直接将右子树入栈,而不是将该节点入栈且弹出时Tree = Tree.right。 这正是前面解释的,先序遍历只需要在第一次经过节点时访问。**总结:三种宽度优先遍历方法的实质就是在如图所示的路径中第几次访问节点。所以对于上面的方法,        递归遍历能实现第三次访问,可以改变proc位置分别实现先序、中序、后序;未化简的非递归算法,在入栈和出栈是能两次访问到该节点,所以可以改变proc位置分别实现先序、中序遍历; 而化简的非递归遍历方法,未将该节点入栈,只能访问一次,所以只能实现先序遍历。**
阅读全文
0 0