数据结构——树的构建及遍历的代码实现(完整代码+视频讲解)
本篇主要以二叉树的认识,构建,遍历为主。树由节点(node)和边(edge)组成,形成一个层级结构。每棵树有一个根节点(root),而其他节点通过边与根节点相连。
文章目录
前言
本篇主要以二叉树的认识,构建,遍历为主。
一、树的认识
1.定义
树由节点(node)和边(edge)组成,形成一个层级结构。每棵树有一个根节点(root),而其他节点通过边与根节点相连。
2. 树的基本特性
节点(Node): 树的基本组成单位,包含值和指向子节点的链接。
边(Edge): 连接节点的线,表示它们之间的关系。
根节点(Root): 树的顶层节点,没有父节点。
叶子节点(Leaf Node): 没有任何子节点的节点。
内部节点(Internal Node): 至少有一个子节点的节点。
深度(Depth): 一个节点到根节点的路径长度。
高度(Height): 从某一节点到其最远叶子节点的最长路径长度;树的高度是其根节点的高度。
子树(Subtree): 每个节点及其所有后代组成的树也是一棵树。
满二叉树(Perfect Binary Tree): 一个深度为k(>=-1)且有2^(k+1) - 1个结点的二叉树称为满二叉树。 (又称“完美二叉树”)。
完全二叉树(Complete Binary Tree) :完全二叉树从根结点到倒数第二层满足完美二叉树,最后一层可以不完全填充,其叶子结点都靠左对齐。
3.树的相关性质及计算
1.二叉树在第i层最多有2(i-1)个结点。当最后一层结点达到2(i-1) 时,该树为满二叉树。
第一层:1
第二层:2
第三层:4
……
第i层:2(i-1)
2.深度为k的二叉树最多有2k -1个结点。
因为第k层结点最多为2k-1 个
所以根据等差数列前k项之和为:(1-qn )/(1-q) = (1-2n )/(1-2) = 2k-1
3.对任意一棵二叉树T,如果度数为0的结点个数为n0,度数为2的结点个数为n1,度数为2 的结点个数为n2,则n0=n2+1。
证明如下:
令该树的总结点数为 n
n=n0+n1+n2
n=1+ n1 +n2 * 2
联立以上等式:n0+n1+n2=1+n1+n2*2
得证:n0=n2+1
4.具有n个结点的完全二叉树的深度为:depth=⌊log2(n)⌋+1
证明如下:
将二叉树分为两部分,最后一行看做一部分(part2),以上的看做一部分(part1)。
⌊log2(n)⌋计算的为part1的深度,再加上取下界舍去的一层
所以得证
5.性质5 如果将一棵有 n个结点的完全二叉树的结点按层序(自顶向下, 同一层自左向右)连续编号1,2, …,n, 并简称编号为 i 的结点为结点i ( 1 ≤ i ≤ n ),则有以下关系成立:
(1) 若 i = 1 ,则结点i 是二叉树的根,无双亲; 若 i > 1 ,则i的双亲为结点 ⌊i/2⌋ 。
证明如下:
已知该结点的层从左往右数,该结点为第k1个,编号为n1,深度depth1=⌊log2(n1)⌋+1
n1=2depth1-1 -1+k1
如果存在孩子n2.
因为一个结点有两个孩子,所以父类同层的前面节点(k-1)会产生(k-1)*2个 结点,最后加上自己?(左孩子1,右孩子2)
k2=(k1-1)*2+?,
由于是孩子,所以深度为父亲结点深度+1
depth2=depth1 + 1
n2=2depth2-1 -1+k2
=2depth1+1-1 -1+(k1-1)*2+?
=2depth1 +2 * k1-3+?
=2 * 2depth1-1 +2 * k1-3+?
由于n1= ⌊n2/2⌋
所以得证
(2) 若 2i ≤ n ,则i的左孩子为 2i , 否则无左孩子。
由(1)可知
n1=2depth1-1 +k1-1
因为是左孩子,所以?=1
所以n2=2 * 2depth1-1 +2 * k1-2
n2=2 * n1
所以得证
(3) 若 2i + 1 ≤ n 则 i 的右孩子为 2i+1 ,否则无右孩子。
由(2)可知:n2(左)=2*i
所以右孩子为 2i+1
二、树的遍历(前,中,后)
1.前序遍历(Pre-order Traversal):
访问顺序:根节点 → 左子树 → 右子树
过程:先访问当前节点,然后递归遍历左子树,最后递归遍历右子树。
void preOrder(TreeNode* root) {
if (root) {
cout << root->value << " "; // 访问当前节点
preOrder(root->left); // 遍历左子树
preOrder(root->right); // 遍历右子树
}
}
2.中序遍历(In-order Traversal):
访问顺序:左子树 → 根节点 → 右子树
过程:先递归遍历左子树,然后访问当前节点,最后递归遍历右子树。
void inOrder(TreeNode* root) {
if (root) {
inOrder(root->left); // 遍历左子树
cout << root->value << " "; // 访问当前节点
inOrder(root->right); // 遍历右子树
}
}
3.后序遍历(Post-order Traversal):
访问顺序:左子树 → 右子树 → 根节点
过程:先递归遍历左子树,然后递归遍历右子树,最后访问当前节点。
void postOrder(TreeNode* root) {
if (root) {
postOrder(root->left); // 遍历左子树
postOrder(root->right); // 遍历右子树
cout << root->value << " "; // 访问当前节点
}
}
4.暴力秒解法(做题)
三、树的构建
存储方式
1.顺序存储(简单提及)
顺序存储时,首先必须对树结构的结点进行某种方式 的线性化,使之成为一个线性序列,然后存储。完全 二叉树和满二叉树采用顺序存储结构比较合适,因为 根据二叉树中的结点序号可以唯一地反映出结点之间 的逻辑关系。
顺序存储是指按照完全二叉树结点的编号顺序,用一 组连续的存储单元存放结点内容。在一棵完全二叉树 中,按照从根结点起、自上而下、从左至右的方式对 结点进行顺序编号,便可得到一个反映结点之间关系 的线性序列。这种存储结构可以反映完全二叉树结点 的层次和顺序,既简单又节省存储空间。
c语言代码如下(参考课本):
#define maxsize 1024;
typedef char datatype;
typedef struct{
datdtype data[maxsize];
int last;
}sequenlist;
由于该方式是用数组存储,所以数组下标即为结点编号。如果存入结点为空,则可用其他字符表示(例如:@)。
父类语子类的关系则通过上面提到的关系计算其位置。
2.链式存储(重点讲解)
相比于顺序存储,二叉树一般采用链式存储结构来表示,因为链式存储有动态内存分配,灵活的结构,简化的插入和删除操作,支持多种数据类型等顺序存储所不具有的优点。
1.结点构造代码如下(示例):
struct Birtree { //结点的建立
int data;
Birtree* left; // 没有节点则为NULL
Birtree* right; // 没有节点则为NULL
};
结点主要包括存储的数据及左右结点地址
2.树的构造代码如下(示例):
void creatBiTree(Birtree* &T) { // 先序遍历
int n;
cout << "请输入当前结点的data的值,0代表当前节点为NULL:" ;
cin >> n;
if (n == 0) { //n为0表示叶子结点,没有子类,所以设为NULL
T = NULL;
return;
} else {
T = new Birtree;
T->data = n; //根
creatBiTree(T->left);//左
creatBiTree(T->right); //右
}
}
3.树的遍历代码如下(示例):
// 先序遍历
void preTree(Birtree* T) {
if (T == NULL) {
return;
} else {
cout << T->data << " "; //根
preTree(T->left);//左
preTree(T->right); //右
}
}
// 中序遍历
void midTree(Birtree* T) {
if (T == NULL) {
return;
} else {
midTree(T->left); //左
cout << T->data << " "; //根
midTree(T->right); //右
}
}
// 后序遍历
void postTree(Birtree* T) {
if (T == NULL) {
return;
} else {
postTree(T->left); //左
postTree(T->right); //右
cout << T->data << " "; //根
}
}
通过代码我们不难发现,三种遍历的代码几乎一样,唯一的不同就是三行代码的顺序
4.计算二叉树的深度代码如下(示例):
int depthtree(Birtree* T){
int m,n;
if(T==NULL) return 0;
else{
m=depthtree(T->left);//统计最长左子树深度
n=depthtree(T->right);//统计最长右子树深度
if(m>n) return (m+1);//每次遍历计算深度,最后找出最大深度
else return (n+1);
}
}
5.计算二叉树的叶子结点总数代码如下(示例):
int yezinumbertree(Birtree* T){
if(T==NULL) return 0;
if(T->left==NULL&&T->right==NULL){
cout << "先序排列的叶子结点的值为:" << T->data << endl;
return 1;
}
else{
return yezinumbertree(T->left)+yezinumbertree(T->right);
}//对叶子结点返回的1进行累加,最后返回
}
5.计算二叉树的结点总数代码如下(示例):
int nodenumbertree(Birtree* T){
if(T==NULL) return 0;//判断树是否为空
else{
return nodenumbertree(T->left)+nodenumbertree(T->right)+1;
}//对非空的树,每一次遍历加一
}
6.复制二叉树 代码如下(示例):
void copyTree(Birtree* T, Birtree* &NewT) { // 使用引用以操控指针
if (T == NULL) {
NewT = NULL;
return;
} else {
NewT = new Birtree; //创建结点
NewT->data = T->data; //传值
copyTree(T->left, NewT->left);
copyTree(T->right, NewT->right);
}
}
7.总体代码展示
#include <bits/stdc++.h>
using namespace std;
struct Birtree {
int data;
Birtree* left; // 没有节点则为NULL
Birtree* right; // 没有节点则为NULL
};
// 先序构建二叉树
void creatBiTree(Birtree* &T) { // 使用引用以操控指针
int n;
cout << "请输入当前结点的data的值,0代表当前节点为NULL:" ;
cin >> n;
if (n == 0) {
T = NULL;
return;
} else {
T = new Birtree;
T->data = n;
creatBiTree(T->left);
creatBiTree(T->right);
}
}
// 先序遍历
void preTree(Birtree* T) {
if (T == NULL) {
return;
} else {
cout << T->data << " ";
preTree(T->left);
preTree(T->right);
}
}
// 中序遍历
void midTree(Birtree* T) {
if (T == NULL) {
return;
} else {
midTree(T->left);
cout << T->data << " ";
midTree(T->right);
}
}
// 后序遍历
void postTree(Birtree* T) {
if (T == NULL) {
return;
} else {
postTree(T->left);
postTree(T->right);
cout << T->data << " ";
}
}
// 复制二叉树
void copyTree(Birtree* T, Birtree* &NewT) { // 使用引用以操控指针
if (T == NULL) {
NewT = NULL;
return;
} else {
NewT = new Birtree;
NewT->data = T->data;
copyTree(T->left, NewT->left);
copyTree(T->right, NewT->right);
}
}
//计算二叉树的深度
int depthtree(Birtree* T){
int m,n;
if(T==NULL) return 0;
else{
m=depthtree(T->left);
n=depthtree(T->right);
if(m>n) return (m+1);
else return (n+1);
}
}
//计算二叉树的结点总数
int nodenumbertree(Birtree* T){
if(T==NULL) return 0;
else{
return nodenumbertree(T->left)+nodenumbertree(T->right)+1;
}
}
//计算二叉树的叶子结点总数
int yezinumbertree(Birtree* T){
if(T==NULL) return 0;
if(T->left==NULL&&T->right==NULL){
cout << "先序排列的叶子结点的值为:" << T->data << endl;
return 1;
}
else{
return yezinumbertree(T->left)+yezinumbertree(T->right);
}
}
// 释放二叉树内存
void deleteTree(Birtree* T) {
if (T == NULL) {
return;
} else {
deleteTree(T->left);
deleteTree(T->right);
delete T;
}
}
int main() {
Birtree* T = NULL;
Birtree* newT = NULL;
creatBiTree(T);
cout << "先序遍历:";
preTree(T);
cout << endl;
cout << "中序遍历:";
midTree(T);
cout << endl;
cout << "后序遍历:";
postTree(T);
cout << endl;
copyTree(T, newT);
cout << "原树的先序遍历:";
preTree(T);
cout << endl;
cout << "复制树的先序遍历:";
preTree(newT);
cout << endl;
cout << "二叉树的深度:";
cout << depthtree (T);
cout << endl;
cout << "二叉树的总结点数:";
cout << nodenumbertree (T);
cout << endl;
// cout << "二叉树的叶子结点总数:";
cout << yezinumbertree (T);
cout << endl;
// 释放内存
deleteTree(T);
deleteTree(newT);
return 0;
}
//举例;1 3 5 0 0 8 0 0 2 0 0
四、通过遍历方式反推树的结构(做题无脑秒解)
1.已知先序遍历与中序遍历,求树的形状。
## 2.已知后序遍历与中序遍历,求树的形状。
3.二叉树的前序和后序可推什么结果?
五、平衡二叉树调整(做题秒解)
1.基本特征
二叉树: 平衡二叉树是二叉树的一种,每个节点最多有两个子节点(左子节点和右子节点)。
平衡条件: 平衡二叉树要求每个节点的左右子树的高度差(平衡因子)不超过 1。这意味着:
左子树的高度与右子树的高度之差的绝对值≤1。即,平衡因子的值为 -1、0 或 1。
自动调整: 在进行插入和删除操作后,平衡二叉树能够自动调整自身的结构,保持高度平衡。这通常是通过旋转操作来实现。
2.主要优点
快速查找、插入和删除: 由于保持了平衡,操作的时间复杂度为
O(logn),使得其在处理动态数据集时表现良好。
动态集合的表现: 适用于动态数据集合,如需要频繁插入和删除元素的场景。
3.视频讲解

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)