C++:树和二叉树是怎么被制造出来的

这里先放上一段带有注释的代码,接下来我将先讲解原理再逐行分析,后面打印二叉树的值用的是另一个数据结构 队列 这个我们现在不提及,本章内容讲的是二叉树

#include <iostream>
#include <queue>
using namespace std;

// 二叉树节点结构
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} // 构造函数,初始化节点值和左右子节点为空
};

// 创建三层完全二叉树
TreeNode* createCompleteBinaryTree() {
    TreeNode* root = new TreeNode(1); // 创建根节点
    root->left = new TreeNode(2); // 创建第二层左子节点
    root->right = new TreeNode(3); // 创建第二层右子节点
    root->left->left = new TreeNode(4); // 创建第三层左子节点
    root->left->right = new TreeNode(5); // 创建第三层右子节点
    root->right->left = new TreeNode(6); // 创建第三层左子节点
    root->right->right = new TreeNode(7); // 创建第三层右子节点
    return root;
}
// 层序遍历二叉树
void levelOrderTraversal(TreeNode* root) {
    if (root == nullptr) return; // 如果根节点为空,则直接返回
    queue<TreeNode*> q; // 创建一个队列来存放节点
    q.push(root); // 将根节点加入队列
    while (!q.empty()) { // 当队列不为空时循环
        int size = q.size(); // 获取当前队列的大小
        for (int i = 0; i < size; ++i) { // 遍历当前层的所有节点
            TreeNode* curr = q.front(); // 获取队列头部节点
            q.pop(); // 弹出队列头部节点
            cout << curr->val << " "; // 打印当前节点的值
            if (curr->left) q.push(curr->left); // 如果当前节点有左子节点,则将左子节点加入队列
            if (curr->right) q.push(curr->right); // 如果当前节点有右子节点,则将右子节点加入队列
        }
        cout << endl; // 打印换行
    }
}
int main() {
    TreeNode* root = createCompleteBinaryTree(); // 创建三层完全二叉树
    cout << "Level order traversal of the complete binary tree:" << endl;
    levelOrderTraversal(root); // 层序遍历二叉树
    return 0;
}

二叉树的形成过程

结构体

结构体(struct)是 C++ 中一种自定义的数据类型,用于将不同类型的数据组合成一个单一的数据单元。它可以包含多个成员变量,并允许使用这些成员变量来表示一个概念上的实体。下面是一个简单的结构体示例:

#include <iostream>
using namespace std;

// 定义一个结构体
struct Person {
    string name; // 姓名
    int age; // 年龄
    double height; // 身高
};

int main() {
    // 创建结构体变量并初始化
    Person p1;
    p1.name = "hewei2723";
    p1.age = 20;
    p1.height = 1.75;

    // 访问结构体成员并打印信息
    cout << "Name: " << p1.name << endl;
    cout << "Age: " << p1.age << endl;
    cout << "Height: " << p1.height << " meters" << endl;

    return 0;
}

现在来解释代码中每一行的意思:

  1. #include <iostream>:包含输入输出流库,以便使用 coutendl 输出信息。
  2. using namespace std;:使用 std 命名空间,以便直接使用标准库中的函数和对象,如 coutendl
  3. struct Person {:定义了一个结构体,名称为 Person
  4. string name;:结构体中的成员变量,表示姓名,类型为 string
  5. int age;:结构体中的成员变量,表示年龄,类型为 int
  6. double height;:结构体中的成员变量,表示身高,类型为 double
  7. };:结构体定义结束。
  8. int main() {:程序入口,定义了主函数。
  9. Person p1;:创建了一个 Person 类型的结构体变量 p1
  10. p1.name = "Alice";:给 p1name 成员变量赋值为 “Alice”。
  11. p1.age = 30;:给 p1age 成员变量赋值为 30。
  12. p1.height = 1.75;:给 p1height 成员变量赋值为 1.75。
  13. cout << "Name: " << p1.name << endl;:打印 p1 的姓名信息。
  14. cout << "Age: " << p1.age << endl;:打印 p1 的年龄信息。
  15. cout << "Height: " << p1.height << " meters" << endl;:打印 p1 的身高信息。
  16. return 0;:返回程序执行结果为 0,表示程序正常结束。
    这个结构体用图形表示就是
Person
age
name
height

age,name,height三个值组成了一个集合叫做Person,这个就是结构体。
age,name,height也可以是其他的一些东西,比如一个指针 一个指向另一个东西的指针,比如指向另一个结构体所在的位置

二叉树:一行接一行

结构体1
成员1
成员2
结构体2
成员3
成员4
结构体3
成员5
成员6

这时候你看他像什么?他是不是像一棵树,每棵树都分了两个叉。
二叉树出来了
当然,树上的二叉树一般是竖着的,就像这样
在这里插入图片描述
结构体那三个字是两个成员的集合的名字,所以图可以不看他,就变成了这样

结构体1
成员1
成员2
成员3
成员4
成员5
成员6

这就是二叉树的形成
二叉树的每个节点都包含一个值和指向左右子节点的指针。这些指针将每个节点连接到其左右子节点,形成了树状结构。

在 C++ 中,通常使用结构体或类来表示二叉树的节点。每个节点包含一个值和指向左右子节点的指针。这样的设计使得我们可以轻松地创建、操作和遍历二叉树。

用到代码上

对于树的全貌,这里展示真正的二叉树结构,包括指针,

struct TreeNode {
    int val;//数据
    TreeNode* left;//指向左节点的指针
    TreeNode* right;//指向右节点的指针
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} // 构造函数,初始化节点值和左右子节点为空
};
TreeNode
left-这是指针
val-这是储存的数据
right-这是指针

指针是可以指向某个地址的,那么我们可以让left指向另一个结构体,比如让左指针left指向下一个结构体2

TreeNode
left-这是指针
val-这是储存的数据
right-这是指针
TreeNode2
left-这是指针2
val-这是储存的数据2
right-这是指针2

依次类推,可以叠加很多层,二叉树分的叉就是这么来的,现在抽象一下,给数据和结构体名字去掉,就成了这样的图形

1
2
3
4
4
6
7

现在我们可以无限衍生,制造出三叉树,四叉树,甚至是八叉树,一个结构体放四个指针一个数据(四叉树),一个结构体放两个指针两个数据等等…但是他会使逻辑变得复杂,所以就只采用了二叉树。

A
B
C
data
D
E
理论上的一种四叉树(我猜的)

代码讲解

// 二叉树节点结构
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} // 构造函数,初始化节点值和左右子节点为空
};

创造一个结构体,里面包含了了两个指针,起一个方便人类记忆的名字,left代表在可视化数据结构图中的左边那个指针,right同理,val是储存的数据 ,这里用的是int型,所以val可以是数字,如果用char那可以用来储存汉字字符等,具体见数据类型

TreeNode* createCompleteBinaryTree() {
    TreeNode* root = new TreeNode(1); // 创建根节点
    root->left = new TreeNode(2); // 创建第二层左子节点
    root->right = new TreeNode(3); // 创建第二层右子节点
    root->left->left = new TreeNode(4); // 创建第三层左子节点
    root->left->right = new TreeNode(5); // 创建第三层右子节点
    root->right->left = new TreeNode(6); // 创建第三层左子节点
    root->right->right = new TreeNode(7); // 创建第三层右子节点
    return root;
}
  1. TreeNode* createCompleteBinaryTree() 参照int* p,*号前面是指针类型,后面是指针变量名
  2. TreeNode* root = new TreeNode(1);创建一个结构体,TreeNode(1);这个代码调用了第一块的代码TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 创建一个数值是1.左右指针是空的结构体
  3. root->left = new TreeNode(2); root->left 表示通过指针 root 访问其所指向的对象(通常是一个结构体或类的实例),并获取其名为 left 的成员。在这个上下文中,假设 root 是指向二叉树节点的指针,那么 root->left 就是访问该节点的左子节点。具体来说,如果 root 是指向 TreeNode 结构体的指针,那么 root->left 就表示 TreeNode 结构体中的 left 成员,即指向左子节点的指针。
  4. 依此类推
 	root->right = new TreeNode(3); // 创建第二层右子节点
    root->left->left = new TreeNode(4); // 创建第三层左子节点
    root->left->right = new TreeNode(5); // 创建第三层右子节点
    root->right->left = new TreeNode(6); // 创建第三层左子节点
    root->right->right = new TreeNode(7);
root->right->right

这些意思就是比如26个英文字母ABCDEFG
第一个是A
第二个是B
第三个是C
第四个是D

也可以表示
A 第一个
B 第二个是A的下一个
C 第三个是A的下一个的下一个
D 第四个是A的下一个的下一个的下一个

所以知道了A的位置就可以推出BDE的位置

return root;

就是返回root节点的地址,就像返回A的位置,有开头就可以往后移找到其他的位置

void levelOrderTraversal(TreeNode* root) {
    if (root == nullptr) return; // 如果根节点为空,则直接返回
    queue<TreeNode*> q; // 创建一个队列来存放节点
    q.push(root); // 将根节点加入队列
    while (!q.empty()) { // 当队列不为空时循环
        int size = q.size(); // 获取当前队列的大小
        for (int i = 0; i < size; ++i) { // 遍历当前层的所有节点
            TreeNode* curr = q.front(); // 获取队列头部节点
            q.pop(); // 弹出队列头部节点
            cout << curr->val << " "; // 打印当前节点的值
            if (curr->left) q.push(curr->left); // 如果当前节点有左子节点,则将左子节点加入队列
            if (curr->right) q.push(curr->right); // 如果当前节点有右子节点,则将右子节点加入队列
        }
        cout << endl; // 打印换行
    }
}

这个是用了队列的结构,暂时不提。

int main() {
    TreeNode* root = createCompleteBinaryTree(); // 创建三层完全二叉树
    cout << "三层完全二叉树" << endl;
    levelOrderTraversal(root); // 层序遍历二叉树
    return 0;
}

主函数入口,程序运行时从主函数开始运行

  1. TreeNode* root = createCompleteBinaryTree(); 创建二叉树,也就是运行这个代码:
TreeNode* createCompleteBinaryTree() {
    TreeNode* root = new TreeNode(1); // 创建根节点
    root->left = new TreeNode(2); // 创建第二层左子节点
    root->right = new TreeNode(3); // 创建第二层右子节点
    root->left->left = new TreeNode(4); // 创建第三层左子节点
    root->left->right = new TreeNode(5); // 创建第三层右子节点
    root->right->left = new TreeNode(6); // 创建第三层左子节点
    root->right->right = new TreeNode(7); // 创建第三层右子节点
    return root;
}
  1. cout << "三层完全二叉树" << endl;输出 三层完全二叉树 字样。
  2. levelOrderTraversal(root); 调用这个代码,传参root,就是root节点开始
void levelOrderTraversal(TreeNode* root) {
    if (root == nullptr) return; // 如果根节点为空,则直接返回
    queue<TreeNode*> q; // 创建一个队列来存放节点
    q.push(root); // 将根节点加入队列
    while (!q.empty()) { // 当队列不为空时循环
        int size = q.size(); // 获取当前队列的大小
        for (int i = 0; i < size; ++i) { // 遍历当前层的所有节点
            TreeNode* curr = q.front(); // 获取队列头部节点
            q.pop(); // 弹出队列头部节点
            cout << curr->val << " "; // 打印当前节点的值
            if (curr->left) q.push(curr->left); // 如果当前节点有左子节点,则将左子节点加入队列
            if (curr->right) q.push(curr->right); // 如果当前节点有右子节点,则将右子节点加入队列
        }
        cout << endl; // 打印换行
    }
}

这个代码会打印出二叉树的数据
在这里插入图片描述

结尾

这就是C++创造一个二叉树的原理,它不是通过调用外部库来实现的,他的指针可以向左向右指是因为他用了结构体,这个结构体里面有三个成员,两个指针和一个数据。对于二叉树里面的left和right指针来说,还是那句话,显示在屏幕上的是给人看的,不是本身所具有的, 从计算机的角度来看,二叉树中的每个节点都有一个指向左子节点和一个指向右子节点的指针。这两个指针实际上表示了节点与其子节点之间的关系。在计算机内部,它们只是指向不同节点的地址。
对于计算机而言,并没有明确的左右之分,而是通过指针来表示节点之间的相对位置关系。左右只是为了人类理解方便而引入的概念,帮助我们更好地理解和处理二叉树的结构。

你要是愿意,可以再加一个叫做上节点的节点和一个叫做下节点的节点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hewei2723

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值