循环和递归(二)

前言

前一篇文章讲过一些关于如何选择循环和递归来解决重复性计算的问题。总的来说,在递归层数较大时(如上万层),或者使用递归会导致很多重复性计算的情况下,应该选择循环;否则可以选择递归,让代码更简洁,并具被更好的可读性。

另外递归 其实隐藏的包含一个栈空间,每次递归调用都隐藏的包含一次入栈,每次方法返回隐藏的包含一次出栈。具体可以参考前文中,"java的vm栈与递归"一节。本篇博客再来详细分析 对递归中的隐藏栈空间的灵活运用。

首先来看一个示例:


二叉树的镜像问题

什么是二叉树的镜像呢?可以参考高中学过的镜面成像:上下关系不变,左右交换。二叉树的镜像 其实就是 除了根节点外,其他所有左右节点交换。如果使用循环来实现,代码如下:
public class BinaryTreeMirror {
    public void toMirror(TreeNode root) {
        if (root == null) {
            return;
        }

        //使用一个栈,作为辅助空间
        Stack<TreeNode> stack = new Stack<>();

        //本质上是二叉树的前序遍历
        while (root != null || !stack.isEmpty()) {
            while (root != null) {
                //交换左右子节点
                TreeNode temp = root.left;
                root.left = root.right;
                root.right = temp;

                //保存根节点,以便后续继续遍历右子节点
                stack.push(root);
                root = root.left;//继续遍历左子节点
            }

            //使用回溯法 遍历右节点
            if (!stack.isEmpty()) {
                root = stack.pop();//回溯法
                root = root.right;
            }
        }
    }
}

class TreeNode{
    TreeNode left;
    TreeNode right;
    int val;
}

可以看到这本质上是二叉树的前序遍历(先遍历根节点,再遍历左子树,最后右子树)。上述循环实现方式,使用了一个栈Stack作为辅助空间,记录下已经遍历过的父节点。再使用栈后进先出的特点,回溯到上一个父节点,继续遍历该父几点的右子节点。。

也就是说循环实现方式:是显示的使用了一个栈+回溯实现的。前文提到过 递归 其实隐藏的包含一个栈空间,这也就是为什么使用递归的代码看起来比较简洁的原因。下面来看如何使用递归实现"二叉树的镜像":

public class BinaryTreeMirror {
    public void toMirror2(TreeNode root){
        if(root == null){
            return;
        }

        //交换左右子节点
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;

        //遍历左子树
        toMirror2(root.left);
        //遍历右子树
        toMirror2(root.right);
    }
}

class TreeNode{
    TreeNode left;
    TreeNode right;
    int val;
}
可以看到使用递归实现的代码与循环实现比起来 简洁了很多。从这两种实现方式对比可以看出,使用递归我们可以直接利用其隐藏的栈空间,并在且无需手动回溯,直接利用方法的返回自动回溯,继续遍历右子树。

总结

在循环方式中,如果需要使用栈作为辅助空间(作为回溯的依据) 进行的重复性计算,此时可以考虑该用递归。二者的空间复杂度基本相同,都需要一个栈,区别是一个是显示的栈,一个是隐藏的栈。

另外需要注意的,循环方式的显示栈需要的空间是在JVM的堆内存中分配的;而递归方式中的隐藏栈是在栈内地中分配的。堆内存一般有好几个G,而栈内存一般只有几百kb。所以还是那句话,如果数据量大的情况下 还是得使用循环方式显式的栈空间。


猜你喜欢

转载自blog.csdn.net/gantianxing/article/details/79827303