二叉树5.深度优先遍历的递归思想

Posted 纵横千里,捭阖四方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉树5.深度优先遍历的递归思想相关的知识,希望对你有一定的参考价值。

本篇的内容还是比较简单的,根据我们的风格,不足以单独成篇,但是据我了解很多人可能对树的遍历都不熟悉,所以后面我们会将本文进一步细化和完善,让没有相关储备的读者能够一文就能彻底搞懂二叉树的遍历。

深度优先遍历有前中后三种情况,估计大部分人都能背下来,但是其中的道理是什么呢?为啥遇到自己处理边型题就搞不定了呢,为什么很多同学看递归算法都是“一看就会,一写就废”。 主要是对递归不成体系,没有方法论,每次写递归算法 ,都是靠玄学来写代码,代码能不能编过都靠运气。

这三种类型正好对应了144.二叉树的前序遍历 94.二叉树的中序遍历 145.二叉树的后序遍历三道LeetCode题。

1.前中后序遍历的递归实现

这次我们要好好谈一谈递归,本篇将介绍前后中序的递归写法,一些同学可能会感觉很简单,其实不然,我们要通过简单题目把方法论确定下来,有了方法论,后面才能应付复杂的递归。

接下来我们要处理大量的前中后遍历的题目,都是基于最原始的三种遍历拓展和延伸的,我们今天就来理一理其内在关系。

我们前面谈到递归算法的三个要素。每次写递归,都按照这三要素来写,可以更好地写出正确的递归算法。

1. 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次 递归的返回值是什么进而确定递归函数的返回类型。

2.确定终止条件:
写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不 对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存 栈必然就会溢出。

3.确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会􏰀复调用自己来实现递归的过程。

好了,我们确认了递归的三要素,接下来就来练练手:

以下以前序遍历为例:

1. 确定递归函数的参数和返回值:因为要返回遍历结果,所以返回结果定义了一个List,除了这一点就不需要再处理什么数据了,代码如下:

void preorder(TreeNode root, List<Integer> res){}

假如我们想通过前序遍历来增加、删除或者查找某个值,则就要再传一个参数进来,也就是这样子:

void preorder(TreeNode root, List<Integer> res,Integer target){}

这里你是否会有疑问 ,返回结果是否可以放在return里呢?也就是这样子:

List<Integer> preorder(TreeNode root){}

如果这么干,递归代码写不出来,我还没想好怎么解释,先放一放

 2. 确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本次递归就要要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:

if (head == null) {
        return;
    }

对于其他题目,特别是查找类的题目,终止条件的判断是非常!非常!非常!重要的的步骤,因为不一定是全部遍历完 ,满足预期的要求也会终止,所以如何确定终止条件使我们接下来训练的重点。

3. 确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数 值,代码如下:

    list.add(head);
    preOrderRecur(head.left);
    preOrderRecur(head.right);

 

单层递归的逻辑就是按照中左右的顺序来处理的,这样二叉树的前序遍历,基本就写完了。

最后还有一步就是题目给的方法不能直接进行递归,我们再写一个自己的方法包一下递归过程。

完整代码:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        preorder(root, res);
        return res;
    }

    public void preorder(TreeNode root, List<Integer> res) {
        if (root == null) {
            return;
        }
        res.add(root.val);
        preorder(root.left, res);
        preorder(root.right, res);
    }
}
 

那么前序遍历写出来之后,中序和后序遍历就不难理解了,中序是左中后,后续是后左中。代码如如下:

public static void preOrderRecur(TreeNode head) {
    if (head == null) {
        return;
    }
    preOrderRecur(head.left);
    System.out.print(head.value + " ");
    preOrderRecur(head.right);
}

后序:

public static void postOrderRecur(TreeNode head) {
    if (head == null) {
        return;
    }
    postOrderRecur(head.left);
    postOrderRecur(head.right);
    System.out.print(head.value + " ");
}

很简单是不?确实很简单,但是问题是这几行代码很多人是背下来的,而不是理解的。例如下面几个问题:
1.什么时候使用前序?中序呢?后序呢?

2.怎么确定终止条件。

3.如何确定对什么递归?

还没有刷题之前,这几个问题讨论了也比较空,我们先看一些具体的题目,后面再详细讨论。

以上是关于二叉树5.深度优先遍历的递归思想的主要内容,如果未能解决你的问题,请参考以下文章

java 二叉树 深度优先递归遍历

二叉树遍历(前序中序后序层次深度优先广度优先遍历)

二叉树的广度优先遍历深度优先遍历的递归和非递归实现方式

树二叉树遍历算法(深度优先广度优先遍历,前序中序后序层次)及Java实现

算法总结-深度优先算法

汇总|打遍天下二叉树