文章目录
一、二叉树的概念
二叉树是一种特殊的树型结构,它的特点是:
- 每个结点至多只有 2 棵子树(即二叉树中不存在度大于 2 的结点)
- 并且二叉树的子树有左右之分,其次序不能任意颠倒,因此是一颗有序树。
- 二叉树 = 根节点 + 左子树 + 右子树,其中左子树和右子树又是一颗二叉树,所以二叉树也是递归定义的。
满二叉树:(这里已知孩子结点求父节点不用减一除二而是直接除二,因为是从下标1开始存储的)

完全二叉树:

二、⼆叉树的存储
在上一节中,我们已经学过树的存储,⼆叉树也是树,也是可以⽤vector数组或者链式前向星来存储。仅需在存储的过程中标记谁是左孩⼦,谁是右孩⼦即可。
• ⽐如⽤ vector 数组存储时,可以先尾插左孩⼦,再尾插右孩⼦;
• ⽤链式前向星存储时,可以先头插左孩⼦,再头插右孩⼦。只不过这样存储下来,遍历孩⼦的时候先遇到的是右孩⼦,这点需要注意。
但是,由于⼆叉树结构的特殊性,我们除了⽤上述两种⽅式来存储,还可以⽤符合⼆叉树结构特性的⽅式:分别是顺序存储和链式存储。
顺序存储: 二叉树的顺序存储本质就是堆,小编这里就不过多讲解了。
链式存储: 链式存储类⽐链表的存储,都有静态实现和动态实现的⽅式,我们这⾥依旧只讲静态实现,也就是⽤数组模拟。 竞赛中给定的树结构⼀般都是有编号的,参考上⼀章的树结构。因此我们可以创建两个数组 l[N],r[N] ,其中 l[i] 表⽰结点号为的结点的左孩⼦编号, r[i] 表⽰结点号为 的结点的右孩⼦编号。这样就可以把⼆叉树存储起来。

题目叙述如下:

代码实现:
cpp
using namespace std;
#include <iostream>
const int N = 1e6 + 10;
int n, l[N], r[N];
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> l[i] >> r[i];
}
return 0;
}
dfs遍历二叉树
思路和我们在数据结构初阶学习一样,分为前、中、后序遍历,小编不过多解释了,直接看代码。这里小编要补充一点,递归实现时我们采用先判断左右子树是否为空,不为空再执行递归,而不是像传统方法一样无论子节点是否为空,都会执行递归调用。
补充:二叉树存储时没有像上一节介绍树一样存结点的父节点,每个结点只存储了它的左右孩子结点,所以不用bool数组标记。
cpp
//传统方法
//void dfs1(int u)
//{
// if (u == 0)
// return;
// cout << u << " ";
// dfs1(l[u]);
// dfs1(r[u]);
//}
//前序遍历
void dfs1(int u)
{
cout << u << " ";
if (l[u]) //左子树存在
dfs1(l[u]);
if (r[u]) //右子树存在
dfs1(r[u]);
}
//中序遍历
void dfs2(int u)
{
if (l[u])
dfs2(l[u]);
cout << u << " ";
if (r[u])
dfs2(r[u]);
}
//后序遍历
void dfs3(int u)
{
if (l[u])
dfs3(l[u]);
if (r[u])
dfs3(r[u]);
cout << u << " ";
}
int main()
{
//建树
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> l[i] >> r[i];
}
//遍历
dfs1(1);
cout << endl;
dfs2(1);
cout << endl;
dfs3(1);
cout << endl;
return 0;
}
bfs遍历二叉树
cpp
void bfs()
{
queue <int> q;
q.push(1);
while (!q.empty())
{
int tmp = q.front();
q.pop();
cout << tmp << " ";
if(l[tmp])
q.push(l[tmp]);
if (r[tmp])
q.push(r[tmp]);
}
}
三、二叉树算法题
新二叉树
题目描述
题目解析
本题就是二叉树前序遍历,只不过之前数组存的是数字,结点本身存储的数据就是它的物理数组下标,比如l[1]和r[1]就是1号结点的左右孩子,所以可以拿到结点的数组下标就是拿到结点本身,就可以遍历它的左右孩子,而本题数组里存储的是字符,数组下标和结点本身解耦了,但是我们不能通过拿到字符反推出该字符代表的结点下标,所以我们需要把字符和数组下标强行耦合起来,思路就是用存储char类型数据的数组l[N], r[N],字符本质是ASCII码数值,所以可以把结点存储的字符转化成ASCII码数值作为l[N], r[N]的下标,然后把结点的左右孩子字符存储到l[N], r[N]对应位置中。
简化:利用字符的 ASCII 码作为数组下标,直接存储对应节点的左右孩子字符,实现节点与数组下标的关联
cpp
//若这里输入字符a
cin >> a;
//若这里输入字符y,y字符会存储到数组的97下标对应位置中
//因为这里编译器会自动将字符a转化为它的ASCLL码值
cin >> arr[a];
代码
cpp
using namespace std;
#include <iostream>
const int N = 300; //ASCII码范围
int n;
char e[N], l[N], r[N];
void bfs(char u)
{
cout << u;
if(l[u] != '*')
bfs(l[u]);
if(r[u] != '*')
bfs(r[u]);
}
int main()
{
//建树
cin >> n;
//后面bfs要传root,所以不能在for循环里面输入root
//在for循环外面特殊处理root
char root;
cin >> root;
cin >> l[root] >> r[root];
//根节点已经处理,所以i从2开始
for (int i = 2; i <= n; i++)
{
char t;
cin >> t;
cin >> l[t] >> r[t];
}
bfs(root);
return 0;
}
二叉树的遍历
题目描述
题目解析
本题和前面介绍的二叉树前、中、后序遍历几乎一样,小编就不过多解释了。
代码
cpp
using namespace std;
#include<iostream>
const int N = 1e6 + 10;
int n, l[N], r[N];
void dfs1(int u)
{
cout << u << " ";
if(l[u]) dfs1(l[u]);
if(r[u]) dfs1(r[u]);
}
void dfs2(int u)
{
if (l[u]) dfs2(l[u]);
cout << u << " ";
if (r[u]) dfs2(r[u]);
}
void dfs3(int u)
{
if (l[u]) dfs3(l[u]);
if (r[u]) dfs3(r[u]);
cout << u << " ";
}
int main()
{
//建树
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> l[i] >> r[i];
}
dfs1(1);
cout << endl;
dfs2(1);
cout << endl;
dfs3(1);
cout << endl;
return 0;
}
二叉树深度
题目描述
题目解析
这道题我们也讲过了,这里小编也不细讲了:详情点这里
代码
cpp
using namespace std;
#include <iostream>
const int N = 1e6 + 10;
int n, l[N], r[N];
int deep(int u)
{
if (u == 0)
return 0;
int ldeep = deep(l[u]);
int rdeep = deep(r[u]);
return 1 + (ldeep > rdeep ? ldeep : rdeep);
}
int main()
{
//建树
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> l[i] >> r[i];
}
cout << deep(1);
return 0;
}
求先序排列
题目描述
题目解析
这种题是二叉树的经典题目,核心思路是递归,分两步走: 1、找到根节点 2、根据根节点划分左右子树
思路很简单,落实在题目中就是通过下标划分序列,这里要用到序列的一个特点,不论是前、中、后序遍历的序列,在序列根结点的左侧都是左子树的结点,在序列根结点的右侧都是右子树的结点,用下标把序列划分为左子树,根节点,右子树,然后再分别递归处理左子树,右子树,由于本题是输出前序遍历序列,所以递归过程中拿到后序序列后直接打印后序序列最后一个元素。
代码
cpp
using namespace std;
#include <iostream>
#include <string>
string a, b; //代表输入的两个序列
void dfs(int l1, int r1, int l2, int r2)
{
//递归出口
if (l1 > r1)
return;
cout << b[r2];
//1、找中序序列中根节点位置
//根节点为后序遍历最后一个元素
int p = l1;
while (a[p] != b[r2])
++p;
//p为中序序列根节点下标
//2、划分左右子树
dfs(l1, p - 1, l2, l2 + (p - 1 - l1 + 1) - 1); //递归左子树
dfs(p + 1, r1, l2 + (p - 1 - l1 + 1), r2 - 1); //递归右子树(b[r2]是根节点,所以需要r2 - 1)
}
int main()
{
cin >> a >> b;
dfs(0, a.size() - 1, 0, b.size() - 1);
return 0;
}
美国血统
题目描述
题目解析
本题和前一题类似,上一题的知道中,后序序列求前序,本题的知道中、前序序列求后序序列,思路一样,无非是打印根节点时机的区别,上一题是先打印根节点再划分左右子树并递归,本题是划分左右子树并递归后再打印根节点。
代码
cpp
using namespace std;
#include <iostream>
#include <string>
string a, b;
void dfs(int l1, int r1, int l2, int r2)
{
//递归出口
if (l1 > r1)
return;
//1、找根节点
int p = l1;
while (a[p] != b[l2])
++p;
//2、划分左右子树并递归
dfs(l1, p - 1, l2 + 1, l2 + 1 + (p - 1 - l1 + 1) - 1);
dfs(p + 1, r1, l2 + 1 + (p - 1 - l1 + 1), r2);
cout << b[l2];
}
int main()
{
cin >> a >> b;
dfs(0, a.size() - 1, 0, b.size() - 1);
return 0;
}
二叉树问题
题目描述
题目解析
1、本题是一道综合题目,首先需要理解题意,宽度和深度很好理解,结点间距离比较抽象,我们来举个例子,比如说结点6和8之间的距离:

我们要先画出两个结点的最短路径,如图所示,向根结点边数指的是从8到1三条边,向叶结点指的是从1到6两条边,根据题目描述距离就是8。
2、本题的输入只给了树上的边信息,并没有给左右结点信息,所以本题不能以二叉树的存储方式存树结构,而应该用上一节的存树的方式存储。本题还规定了u是v的父节点,所以输入的一对信息只用存u->v一条边,不用像以前存两条边。
3、解题思路:
第一步 首先创建二叉树,这里我们就用vector数组存储。 第二步是求二叉树的深度,逻辑是树高 = max(左子树树高,右子树树高) + 1,而求左右子树树高又可以套用这个公式,所以树高可以通过递归求得。
这里补充一下max函数的用法,它是一个函数模板,可以用来比较两个元素大小:

第二步 求二叉树宽度,需要借助队列和bfs,思路是以bfs的思路借助队列按层遍历整棵二叉树,当队列不为空时一直执行结点入队列、把该结点的孩子结点带入队列、把队列头节点出队列,只不过现在不是一个一个结点的执行,而是按层执行,每次按层执行前需要统计当前队列中的元素个数,也就是当前层的结点个数size,如果当前层的结点个数比历史层结点个数ret更多,则更新ret,否则维持ret不变。
第三步求两个结点的距离,题目要求需要找到最短路径,由于我们站在两个结点视角无法知道在哪个结点拐弯是最短路径,所以需要两个结点都从原位置出发走到根结点,它们路径第一个相交的结点就是最短路径的拐弯点。
- 要算距离就需要把结点x到相交点和结点y到相交点的距离记录下来,思路是创建一个dist数组,dist[i]即为结点i到结点x的距离,先让x结点出发向根节点走,走的过程中把从结点x到根节点途径的所有结点都用dist数组记录起来。
- 但是这里会出现一个问题,要想让结点x走到根节点必须拿到对应结点的父节点,可是本题存储树时并没有存结点的父节点,所以我们需要创建一个fa数组,fa[i]表示结点i的父节点,当输入u、v时就用数组fa把对应结点父子关系记录起来。
- 然后让结点y出发向根节点走,用一个变量len记录结点y走的距离,当走到根节点或者走到相交点就停止,判断依据是相交点一定被dist数组记录过,根据题意最后距离就是2 * dist[y] + len。

代码
cpp
using namespace std;
#include <iostream>
#include <vector>
#include <queue>
const int N = 110;
vector<int> edges[N];
int n, u, v, x, y;
int fa[N]; //fa[i]表示i结点的父亲
int dist[N];//dist[i]表示i结点到X结点的最短距离
int deep(int u)
{
int ret = 0;
for (auto e : edges[u])
{
ret = max(ret, deep(e));
}
return 1 + ret;
}
int bfs()
{
queue<int> q;
int sz, ret = 0;
q.push(1);
while (!q.empty())
{
//循环更新每层宽度sz和历史最宽ret
sz = q.size();
ret = max(ret, sz);
while (sz--)
{
//按层执行
//把当前层结点全部出队列
//并把下一层结点全部入队列
int u = q.front();
q.pop();
for (auto e : edges[u])
{
q.push(e);
}
}
}
return ret;
}
int main()
{
// 1、建树
cin >> n;
for(int i = 1; i < n; i++)
{
cin >> u >> v;
edges[u].push_back(v);
fa[v] = u; //u是v的父结点
}
// 2、求深度
cout << deep(1) << endl;;
// 3、求宽度
cout << bfs() << endl;
// 4、求距离
cin >> x >> y;
int len = 0;
//把从结点x到根节点所有结点都用dist数组标记
while (x != 1)
{
dist[fa[x]] = dist[x] + 1;
x = fa[x];
}
//y也从原位置走到根节点
// 当y走到根结点或者
//当y和走到x走过的结点时停止
//也就是当dist[y]不为0时停止
while (y != 1 && !dist[y])
{
++len;
y = fa[y];
}
cout << 2 * dist[y] + len << endl;
return 0;
}
以上就是小编分享的全部内容了,如果觉得不错还请留下免费的关注和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~