LeetCode题解 二叉树(一):二叉树基本概念;使用递归法求解二叉树的前中后序遍历

二叉树

基础概念

在正式的刷题之前,需要了解一下二叉树的基础概念。

首先,给出链表式二叉树的结构体:

struct TreeNode {
	int val; //表示结点的值
    TreeNode *left;
    TreeNode *right;
    TreeNode(x): val(x), left(nullptr), right(nullptr) {}
};

还可以有数组形式的二叉树,此处借用一张随想录中的图:

img

数组形式的二叉树中,如果父结点的数组下标为i,那么左孩子的下标就是i * 2 + 1,右孩子的下标就是i * 2 + 2

普通的二叉树如下图所示:

img

而在解题过程中,常用到的两种树的结构有两种:满二叉树与完全二叉树

  • 满二叉树:顾名思义,这棵树是硕果累累的,结点上没有空缺,如下图所示;

    img
  • 完全二叉树:完全的意思,从中文的含以上,和满近似,但是其本质是从存储结构的角度命名的。知乎上有位博主讲得很好,文章如下完全二叉树看起来并不完全,为什么叫完全二叉树呢? - 知乎 (zhihu.com)

    简而言之,完全二叉树在存储上,空间连续且无浪费,但是非完全二叉树,会造成连续的空间内有许多地方无数可放。

    img

而从数值的角度出发,解题过程中常用到的是二叉搜索树与平衡二叉搜索树:

  • 二叉搜索树(二叉排序树、二叉查找树):数值存储上有序,

    • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
    • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
    • 它的左、右子树也分别为二叉排序树

    img

    其常用操作有:查找、插入、删除,这几个操作在之后的写题过程中都会遇到。

  • 平衡二叉搜索树:AVL(Adelson-Velsky and Landis)树,它是一棵空树,或者它的左右两个子树的高度差的绝对值不超过1,且两个左右子树都是一棵平衡二叉树

    img

    平衡二叉搜索树的常用操作有:查找,插入,删除。各操作的时间复杂度与树的高度成正比,这一点还是很好理解的。

    写到这里,想起来红黑树,在此也简单说一下:

    红黑树,是实际应用中最常用的平衡二叉搜索树,为什么叫红黑树呢?是因为与普通二叉树不同,其在每个结点增加了一个用于记录结点颜色的标志位:Red or Black。

    通过颜色的约束,任意一条从根到叶子结点路径上,红黑树可以保证最长路径不超过最短路径的二倍。

    借鉴博客红黑树、平衡二叉查找树_辉常努腻的博客-CSDN博客_java红黑树与平衡二叉树中的一张图:

    在这里插入图片描述

    最短路径是全黑结点,最长路径是红黑结点,从根节点到叶子结点的路径上黑色结点的数量相同时,最长路径就是最短路径的两倍。

    为了满足此特性,节省搜索的时间复杂度,红黑树的性质有以下几点:

    • 根是黑色,其余结点是红色或是黑色
    • 叶子结点都是黑色
    • 红色结点的子结点是黑色,其父结点也是黑色,从根结点到叶子结点的路径上不会有两个连续的红色结点
    • 同一层的任意结点到叶子结点的路径上,包含相同数量的黑色结点

    此处就不再赘述其查找、删除,插入的相关操作了。有兴趣的同学可以参考上面提到的那篇博客,博主写的很好。

接下来要讲的就是重中之重,二叉树的遍历方式:

从遍历方式上讲,分为两类:

  • 深度优先遍历(Depth First Search):从某个节点出发,一直遍历到叶子结点,然后逐步往上层返回;

    又可以分为三种遍历方式:

    • 前序遍历:“根左右”
    • 中序遍历:“左根右”
    • 后序遍历:“左右根”

    每种遍历方式都可以有两种写法:递归法和迭代法。

  • 广度优先遍历(Breath First Search):从某个节点出发,先遍历完与这个节点同层的所有节点。其对应的遍历方式为层序遍历,写法为迭代法。

以下就来介绍一下二叉树的递归遍历写法,补充一些我跟着代码随想录刷题过程中的一些想法:

递归遍历,顾名思义,往往需要在递归函数中调用递归函数,由于深度遍历的思想就类似于堆的调用,所以在求解深度遍历时,自然就会想到使用递归函数。

其实无论是在哪里使用递归函数,都需要明确三点:

  • 返回类型(其实也是返回值)和传入参数,就是函数声明的那一行
  • 处理逻辑:何时调用递归函数,前后需要做哪些处理,这是函数体内核心的那N行
  • 终止条件:由于递归函数的自身调用性,必须要判断何时终止,就是何时弹回上一层,这是初入函数的那几行

遍历方法一共有三种,类似于随想录,我们先以前序遍历二叉树的各结点并保存这一场景为例:

  • 返回类型,由于我们会将结果保存在一个vector里,所以函数无须返回任何值,只需要在函数体内将结果保存就可以;

  • 传入参数,这就简单了,一定要传入的是当前遍历到的结点,另一个就是我们用于记录结点数值的vec(传引用,避免拷贝浪费时间)

  • 处理逻辑,由于这个例子很简单,只是遍历顺序不一样,前序遍历为“中左右”,所以自然要先将当前结点数值加入vector,之后将当前结点的左、右孩子依次传入递归函数。代码近似如下:

    vec.push_back(cur->val);    // 中
    traversal(cur->left, vec);  // 左
    traversal(cur->right, vec); // 右
    
  • 确定终止条件:由于这个场景很简单,当我们遍历到空结点的时候,因为没有值,就直接返回就可以了

此场景下,前序遍历的代码类似如下:

注:随想录中使用traversal(遍历)用作递归函数的名字,而我习惯使用recursion(递归)

void recursion(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    vec.push_back(cur->val);    // 中
    recursion(cur->left, vec);  // 左
    recursion(cur->right, vec); // 右
}
vector<int> preorderTraversal(TreeNode* root) {
    vector<int> result;
    recursion(root, result);
    return result;
}

自然而然,中序遍历的递归函数如下:

void recursion(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    recursion(cur->left, vec);  // 左
    vec.push_back(cur->val);    // 中
    recursion(cur->right, vec); // 右
}

后序遍历的递归函数如下:

void recursion(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    recursion(cur->left, vec);  // 左
    recursion(cur->right, vec); // 右
    vec.push_back(cur->val);    // 中
}

此时就可以使用递归法完成LeetCode上对应的三道题,递归函数在上面已有,修改一下前序遍历的主函数即可

144 二叉树的前序遍历 easy
145 二叉树的后序遍历 easy
94 二叉树的中序遍历 easy
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值