【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化

二叉树

by.Qin3Yu

二叉树 的本质是 结构体 ,因此阅读本文需要读者先掌握结构体基础内容,关于结构体的相关内容可以参考我的往期博客:
【C++数据结构 | 结构体速通】5分钟掌握基础自定义数据类型 | 15分钟精通结构体进阶操作方法.by.Qin3Yu

文中所有代码默认已使用std命名空间

using namespace std;

概念速览

什么是二叉树?

  • 二叉树是一种常见的树状数据结构,它由节点组成,每个节点最多有两个子节点:一个左子节点和一个右子节点。二叉树可以为空,也可以只有一个根节点。每个节点都包含一个值,并且通过指针或引用与其子节点相连。

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第1张图片

二叉树具有以下特点:

  1. 每个节点最多有两个子节点,一个子节点都没有的节点称为叶子节点
  2. 子节点和子节点不能交换
  3. 二叉树的形状可以各不相同,具体取决于节点的插入顺序
  • 二叉树有多种常见的变种,包括满二叉树、完全二叉树和平衡二叉树等。它们在节点的插入和删除操作上有着不同的性质和限制。二叉树具有广泛的应用。它们常用于实现搜索和排序算法,如二叉搜索树和堆数据结构。此外,许多树状数据结构,如红黑树和B树,都可以被看作是二叉树的扩展或变种。

二叉树相比于其他数据结构具有以下优劣势:

优势

  1. 快速查找:二叉搜索树是一种常见的二叉树变种,具有快速的搜索和查找能力。
  2. 有序存储:二叉搜索树按照一定的规则进行排序,因此可以方便地实现对元素的有序存储和遍历操作。
  3. 快速操作:在平均情况下,插入和删除一个元素的时间复杂度是O(log n)。

缺点

  1. 性能退化:当二叉树变得不平衡时,二叉树可能会退化成链表,导致性能下降。
  2. 高维数据:二叉树不适用于高维数据,例如二维或多维数组,可能无法有效地表示和操作。

基本二叉树的变种

基本二叉树还具有很多用途不同的变种,例如:

  • 完全二叉树: 二叉树除最后一层外所有节点均有左右两个子节点,且最后一层的节点从左向右紧密排列。

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第2张图片

  • 满二叉树: 二叉树除最后一层外所有节点均有左右两个子节点,且最后一层的节点数量达到最大值。

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第3张图片

  • 斜树: 二叉树所有节点只有左(右)子树。

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第4张图片

  • 平衡二叉树: 左右子树的高度差不超过 1 。

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第5张图片

  • 二叉搜索树: 左子树上所有节点的值都小于根节点的值,右子树上所有节点的值都大于根节点的值。

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第6张图片

  • 此处仅作部分列举,此外还有很多二叉树的变种。

基础二叉树操作

1. 创建二叉树

  • 创建一棵二叉树,实际上就是创建多个节点,然后再将它们以特定形式连接起来。所以,我们在创建树节点时只关心他的三个属性:节点值 (value) 、左子节点 (LeftNode / LN) 和右子节点 (RightNode / RN)

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第7张图片

struct Node  //树节点
{
    int value;  //节点值
    struct Node* LN;  //左子节点
    struct Node* RN;  //右子节点
    Node(int x) : value(x), LN(nullptr), RN(nullptr) {}  //构造函数
};
  • 然后我们再依次实例化出五个树节点:

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第8张图片

Node* N1 = new Node(1);
Node* N2 = new Node(2);
Node* N3 = new Node(3);
Node* N4 = new Node(4);
Node* N5 = new Node(5);
  • 最后,我们再修改各个节点的左右子节点属性,将树节点按需相连:

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第9张图片

N1 -> LN = N2;  //N1的左子节点为N2
N1 -> RN = N3;  //N1的右子节点为N3
N2 -> LN = N4;  //N2的左子节点为N4
N2 -> RN = N5;  //N2的右子节点为N5
  • 这样,就建立了一棵最基本的二叉树。

2. 前中后序遍历二叉树

  • 前序遍历、中序遍历、后序遍历都是遍历二叉树的常用方式,原理都是依次访问所有树节点,但是它们对于二叉树的访问方式并不相同。相对于根节点来说,前序、中序、后序遍历的访问顺序可以用下图表示:

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第10张图片

  • 那么,对于上图这棵简单的二叉树,我们可以得出以下遍历结果:
遍历方法 遍历顺序 遍历结果
前序遍历 根左右 ABC
中序遍历 左根右 BAC
后序遍历 左右根 BCA
  • 不难看出,前中后分别代表了根节点的访问顺序,其次,左子节点先,右子节点后。但是在大多情况下,我们遍历的二叉树都是更复杂的情况,很可能主节点的左右方几乎都有子树

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第11张图片

  • 对于这种复杂的二叉树,我们依然使用相同的方法,我们以前序遍历为例,首先,我们访问根节点A,然后我们访问他的左节点,可以看出,A 的左节点也是一棵二叉树:

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第12张图片

  • 因此,对于这棵二叉树,我们仍然用根左右的顺序遍历,首先访问根节点B,然后访问他的左节点,B的左节点仍然是棵二叉树:

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第13张图片

  • 因此,对于这棵二叉树,我们继续用根左右的顺序遍历,首先访问根节点D,然后访问他的左节点G,最后访问他的右节点H

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第14张图片

  • 至此,B的左子树已经被遍历完,根据根左右的顺序遍历,我们再访问E节点,至此,A的左子树便被访问完了,我们再根据相同的逻辑访问A的右子树,便可获得最终的便利结果,这里我将遍历结果给出,读者可以试着自己遍历后与我的结果对照:
遍历方法 遍历顺序 遍历结果
前序遍历 根左右 ABDGHECFI
中序遍历 左根右 GDHBEACIF
后序遍历 左右根 GHDEBIFCA
  • 知道了前中后序的便利逻辑,我们便能很轻松的写出代码,具体而言,我们用递归的方式完成遍历。为符合国人的阅读习惯,我们使用拼音作为函数名 (绝对不是因为我不懂英语呜呜呜)

在下面的代码中,我们使用 N 表示树的根节点

前序遍历:

void QianXu(Node* N) {
    if (N == nullptr) return;
    cout << N->value << " "; // 先输出根节点的值
    QianXu(N->LN); // 递归遍历左子树
    QianXu(N->RN); // 递归遍历右子树
}

中序遍历:

void ZhongXu(TreeNode* N) {
    if (N == nullptr) return;
    ZhongXu(N->LN); // 递归遍历左子树
    cout << N->value << " "; // 输出根节点的值
    ZhongXu(N->RN); // 递归遍历右子树
}

后序遍历:

void HouXu(TreeNode* N) {
    if (N == nullptr) return;
    HouXu(N->LN); // 递归遍历左子树
    HouXu(N->RN); // 递归遍历右子树
    cout << N->value << " "; // 输出根节点的值
}

3. 线索二叉树

什么是线索二叉树?
  • 在普通的二叉树中,要进行中序遍历时需要使用递归或栈来保存遍历的顺序,并且需要访问左子树和右子树之间的指针。而线索二叉树通过扩展二叉树节点的指针,将原本为空的指针指向该节点在中序遍历中的前驱和后继节点,从而实现对二叉树的线索化。

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第15张图片

  • 线索二叉树是在二叉树的基础上做了优化的数据结构。它的目的是在不增加额外空间的情况下,方便对二叉树进行遍历操作。线索二叉树的意义在于提高了对二叉树的遍历效率,尤其是中序遍历。不需要借助额外的空间和递归调用,可以直接找到节点的前驱和后继节点,实现快速遍历。此外,线索二叉树可以支持双向遍历,即可以通过前驱节点或后继节点反向遍历二叉树。
如何画出线索二叉树?
  • 将普通的二叉树线索化,即成为线索二叉树。要将普通二叉树线索化,需要先写出其的遍历顺序(前中后序均可)。以下图的二叉树为例,他的前序遍历顺序是ABDECF,然后,我们需要记住以下口诀:无左找前,无右找后

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第16张图片

  • 然后我们依次看每个节点是否有左右子节点,如果没有左子节点,则将此节点指向遍历顺序中的前一个节点,称为 “前驱” 。如果没有右子节点,则将此节点指向遍历顺序中的后一个节点,称为 后继
  • 如下图所示,比如节点 A 有左右节点,则不用指。节点 D 没有左节点,所以要指向遍历顺序中的前一个节点 B ,同时它也没有右节点,所以还要指向遍历顺序中的后一个节点 E ,其他节点依次类推:

ps.如果在遍历顺序中没有前驱后继可指,则指向 空地址(nullptr) 即可。

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第17张图片

  • 最后整理一下线条可空地址,可以得到下图,下图则为被线索化的二叉树

【C++数据结构 | 二叉树速通】10分钟掌握基础二叉树定义 | 2分钟上手三种遍历方法 | 3分钟秒杀线索化_第18张图片

具体代码实现
  • 以下代码可能较长且难以理解,如果您是学生,那么在一般的高校考试中不会考线索化的具体代码实现,您可以选择性阅读:
// 定义线索二叉树的节点结构体
struct Node {
    int val;
    bool LN;  // 左指针是否为线索
    bool RN;  // 右指针是否为线索
    struct Node *left;
    struct Node *right;
    Node(int x) : val(x), left(nullptr), right(nullptr), LN(false), RN(false) {}
};
// 对二叉树进行中序遍历,输出每个节点的值
void ZhongXu(Node *N) {
    if (!N) return;
    // 找到中序遍历的第一个节点
    Node *p = N;
    while (p->left) p = p->left;
    // 循环输出每个节点的值
    while (p) {
        cout << p->val << " ";
        if (p->RN) {
            p = p->right;
        } else {
            p = p->right;
            while (p && !p->LN) p = p->left;
        }
    }
}
// 将二叉树进行线索化
void XianSuoHua(Node *N) {
    if (!N) return;
    Node *prev = nullptr;
    // 对左子树进行线索化
    XianSuoHua(N->left);
    // 处理当前节点
    if (!N->left) {
        N->left = prev;
        N->LN = true;
    }
    if (prev && !prev->right) {
        prev->right = N;
        prev->RN = true;
    }
    prev = N;
    // 对右子树进行线索化
    XianSuoHua(N->right);
}
至此二叉树基础操作已讲解完毕(=

二叉树的进阶内容如哈夫曼树、最小生成树、红黑树由于篇幅较长,后续将在“算法详解”板块介绍,本文只做二叉树的基本内容讲解。

如需提问,可以在评论区留言或私信,通常在12小时内回复,免费用爱发电(=
感谢您的观看(=

你可能感兴趣的:(数据结构速通,c++,数据结构,开发语言,算法)