二叉树的遍历的递归与非递归实现

1.递归序

抛开二叉树的遍历。我们来单看一个普通的递归函数:

public static void DiGui(Tree node){
        if(node == null){
            return;
        }
        /* 节点第一次到达DiGui函数体 */
        System.out.println("first time:" + node.val);
        DiGui(node.left);
        /* 节点第二次到达DiGui函数体 */
        System.out.println("second time :" + node.val);
        DiGui(node.right);
        /* 节点第三次到达DiGui函数体 */
        System.out.println("third time:" + node.val);
    }

从代码中我们也可以看出:我们的递归函数可以在三个地方操作这个node节点。我们从打印结果来看:
在这里插入图片描述
那么到这里,假设我们有一颗二叉树。我们对其进行这个递归函数的调用最终会打印出一串数字。这一串数字就是我们在递归函数中三个位置对接点进行打印操作带来结果。我们看图:
二叉树图例
为了好看,在打印中把前面英文删除了。
在这里插入图片描述
打印完后我们得到一串数字1.2.4.4.4.2.5.5.5.2.1.3.3.3.1。称其为该树节点的递归序
而对于二叉树的前序遍历(头左右):1.2.4.5.3
对于二叉树的中序遍历(左头右):4.2.5.1.3
对于二叉树的后序遍历(左右头):4.5.2.3.1

对比二叉树的前序遍历和递归序,不难看出:当碰到这个数字第一次出现的时候,我们就打印他,后面再出现就不做任何处理;同理,中序和后序也是:碰到第二次,第三次出现我们就处理,否则不管。

至此,我们可以得出:
1、当二叉树进行前序遍历的时候,对其递归序第一次碰到的数就打印。
2、当二叉树进行中序遍历的时候,对其递归序第二次碰到的数就打印。
3、当二叉树进行后序遍历的时候,对其递归序第三次碰到的数就打印。

我们再来看代码:

public static void DiGui(Tree node){
        if(node == null){
            return;
        }
        /* 节点第一次到达DiGui函数体 */
        System.out.println("first time:" + node.val);
        DiGui(node.left);
        /* 节点第二次到达DiGui函数体 */
        System.out.println("second time :" + node.val);
        DiGui(node.right);
        /* 节点第三次到达DiGui函数体 */
        System.out.println("third time:" + node.val);
    }

当我们需要对一颗二叉树进行前中后遍历或者涉及到遍历的操作的时候都可以如此。只需要根据前中后的需求,将我们处理节点的函数放在对应的三个地方即可。

2.递归方法遍历二叉树

在前面,我们通过递归序就已经知道了二叉树的前中后遍历的递归序操作。现在我们只需要运用在遍历二叉树上即可。

前序遍历:

public static void DiGui(Tree node){
        if(node == null){
            return;
        }
        /* 节点第一次到达DiGui函数体 */
        System.out.println (node.val);
        DiGui(node.left);
        DiGui(node.right);
    }

前序遍历:

public static void DiGui(Tree node){
        if(node == null){
            return;
        }
        DiGui(node.left);
        /* 节点第二次到达DiGui函数体 */
        System.out.println (node.val);
        DiGui(node.right);
    }

后序遍历:

public static void DiGui(Tree node){
        if(node == null){
            return;
        }
        DiGui(node.left);
        DiGui(node.right);
        /* 节点第三次到达DiGui函数体 */
        System.out.println (node.val);
    }

3.非递归方法遍历

介绍完了递归遍历二叉树,我们可以通过非递归的方法进行。因为递归调用会造成Java虚拟机栈满了,从而导致栈溢出的异常(stackofoverflow)

使用迭代的方法,我们需要一个栈空间对树的左右节点进行存储。因为栈是先进后出的数据结构,所以我们在前序遍历的时候,对于孩子节点入栈的操作先压右边,在压左边,而头节点在一开始就要压进去。然后进行一个栈是否为空的条件来决定循环。

前序遍历

图解:

1.先将头节点压进栈

在这里插入图片描述

2.如果栈不为空,就进入循环。然后执行弹出栈内头元素,并且打印。在将其右左节点按顺序压栈

在这里插入图片描述

3.重复1和2的操作
在这里插入图片描述
在这里插入图片描述

代码如下:

/* 非递归前序操作 */
    public static void ForNotUseDigui(Tree node){
        if(node == null){
            return;
        }
        Stack<Tree> stack = new Stack();
        stack.push(node);
        while(!stack.isEmpty()){
            Tree pop = stack.pop();
            System.out.println(pop.val);
            if(pop.right != null){
                stack.push(pop.right);
            }
            if(pop.left != null){
                stack.push(pop.left);
            }
        }
    }

后序遍历:

我们先抛开中序遍历不谈,先说后续遍历。我们试想,如果在前序遍历的基础上,我们将入栈的顺序改一下,变成:先压头保持不变,对于孩子节点,先压左,在压右,最后得到的顺序肯定是头右左,而我们的后续遍历打印则是左右头,只需要将头右左的结果逆序输出即可。

图解:

1.压入头节点
在这里插入图片描述

2.栈不空,弹出打印栈头元素;将其左右孩子按顺序入栈
在这里插入图片描述
3.重复1和2的操作:
在这里插入图片描述

4.栈为空,退出。
在这里插入图片描述
此时结果是1.3.2.5.4,只需要将其逆序就得到后序遍历的结果:4.5.2.3.1

示例代码:

/* 非递归后序操作 */
    public static void ForNotUseDiguiHou(Tree node){
        if(node == null){
            return;
        }
        Stack<Tree> stack = new Stack();
        stack.push(node);
        while(!stack.isEmpty()){
            Tree pop = stack.pop();
            System.out.println(pop.val);
            if(pop.left != null){
                stack.push(pop.left);
            }
            if(pop.right != null){
                stack.push(pop.right);
            }
        }
    }

中序遍历:

中序遍历稍微有点复杂。先将左边的元素一股脑的压入栈中,然后再弹出进行相应操作。这样就可以达到先左在中的效果。对于右节点来说:如果该节点有左孩子,也将左孩子一股脑的压入栈最后根据左孩子是否为空进行操作。

图解:

1.把头和左元素全部入栈
在这里插入图片描述
2.此时节点4的左孩子为空,执行第二个分支
在这里插入图片描述
3.注意此时node仍指向节点4,继续第二根分支。但是弹出的元素有右孩子,所以node指向弹出元素的右孩子。右孩子入栈
在这里插入图片描述

4.重复执行步骤123

在这里插入图片描述

代码:

/* 非递归中序操作 */
    public static void ForNotUseDiguiZhong(Tree node){
        if(node == null){
            return;
        }
        Stack<Tree> stack = new Stack();
        stack.push(node);
        while(!stack.isEmpty()){
            if(node.left != null){
                stack.push(node.left);
                node = node.left;
            }else{
                Tree tree = stack.pop();
                System.out.println(tree.val);
                if(tree.right != null){
                    node = tree.right;
                    stack.push(node);
                }
            }
        }
    }

小结:

非递归的实现还有很多。例如后序遍历还可以使用双指针来判断孩子节点是否已经操作过等。附上的代码可能会有一些误差,但是思路也还是按照那个思路保持不变,只是在实现的过程中需要考虑循环的条件边界条件等一些因素使代码健壮性更强。

©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页