当前位置: 首页 > news >正文

【洛谷】二叉树专题全解析:概念、存储、遍历与经典真题实战

文章目录

  • 一、二叉树的概念
  • 二、⼆叉树的存储
    • dfs遍历二叉树
    • bfs遍历二叉树
  • 三、二叉树算法题
    • 新二叉树
    • 二叉树的遍历
    • 二叉树深度
    • 求先序排列
    • 美国血统
    • 二叉树问题


一、二叉树的概念

二叉树是一种特殊的树型结构,它的特点是:

  • 每个结点至多只有 2 棵子树(即二叉树中不存在度大于 2 的结点)
  • 并且二叉树的子树有左右之分,其次序不能任意颠倒,因此是一颗有序树。
  • 二叉树 = 根节点 + 左子树 + 右子树,其中左子树和右子树又是一颗二叉树,所以二叉树也是递归定义的。

满二叉树:
(这里已知孩子结点求父节点不用减一除二而是直接除二,因为是从下标1开始存储的)

在这里插入图片描述

完全二叉树:

在这里插入图片描述

二、⼆叉树的存储

在上一节中,我们已经学过树的存储,⼆叉树也是树,也是可以⽤vector数组或者链式前向星来存储。仅需在存储的过程中标记谁是左孩⼦,谁是右孩⼦即可。
• ⽐如⽤ vector 数组存储时,可以先尾插左孩⼦,再尾插右孩⼦;
• ⽤链式前向星存储时,可以先头插左孩⼦,再头插右孩⼦。只不过这样存储下来,遍历孩⼦的时候先遇到的是右孩⼦,这点需要注意。
但是,由于⼆叉树结构的特殊性,我们除了⽤上述两种⽅式来存储,还可以⽤符合⼆叉树结构特性的⽅式:分别是顺序存储和链式存储。

顺序存储: 二叉树的顺序存储本质就是堆,小编这里就不过多讲解了。

链式存储: 链式存储类⽐链表的存储,都有静态实现和动态实现的⽅式,我们这⾥依旧只讲静态实现,也就是⽤数组模拟。 竞赛中给定的树结构⼀般都是有编号的,参考上⼀章的树结构。因此我们可以创建两个数组 l[N],r[N] ,其中 l[i] 表⽰结点号为
的结点的左孩⼦编号, r[i] 表⽰结点号为 的结点的右孩⼦编号。这样就可以把⼆叉树存储起来。

在这里插入图片描述

题目叙述如下:

在这里插入图片描述

代码实现:

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数组标记。

//传统方法
//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遍历二叉树

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 码作为数组下标,直接存储对应节点的左右孩子字符,实现节点与数组下标的关联

	//若这里输入字符acin >> a;  //若这里输入字符y,y字符会存储到数组的97下标对应位置中//因为这里编译器会自动将字符a转化为它的ASCLL码值cin >> arr[a]; 

代码

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循环外面特殊处理rootchar 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;
}

二叉树的遍历

题目描述
在这里插入图片描述

题目解析

本题和前面介绍的二叉树前、中、后序遍历几乎一样,小编就不过多解释了。

代码

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;
}

二叉树深度

题目描述
在这里插入图片描述

题目解析

这道题我们也讲过了,这里小编也不细讲了:详情点这里
代码

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、根据根节点划分左右子树
思路很简单,落实在题目中就是通过下标划分序列,这里要用到序列的一个特点,不论是前、中、后序遍历的序列,在序列根结点的左侧都是左子树的结点,在序列根结点的右侧都是右子树的结点,用下标把序列划分为左子树,根节点,右子树,然后再分别递归处理左子树,右子树,由于本题是输出前序遍历序列,所以递归过程中拿到后序序列后直接打印后序序列最后一个元素。

在这里插入图片描述
代码

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;
}

美国血统

题目描述
在这里插入图片描述
题目解析

本题和前一题类似,上一题的知道中,后序序列求前序,本题的知道中、前序序列求后序序列,思路一样,无非是打印根节点时机的区别,上一题是先打印根节点再划分左右子树并递归,本题是划分左右子树并递归后再打印根节点。

代码

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。

在这里插入图片描述

代码

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和历史最宽retsz = 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;
}

以上就是小编分享的全部内容了,如果觉得不错还请留下免费的关注和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~

在这里插入图片描述

http://www.dtcms.com/a/423566.html

相关文章:

  • 重庆网站建设合肥公司世界500强企业排名中国企业
  • Intel8259中断配合串口接收
  • 济南网站建设与维护廊坊做网站公司
  • 好女人生活常识网站建设厦门建设局网站技227司学校
  • 如何让AI实现自动化 —— PlayWright MCP 实测
  • 哪家做网站的公司应用软件商店
  • std::thread 深度解析:C++并发编程的魔法之旅
  • MaayanLab Cloud Enrichr 不用编程 也能做富集分析 (TF)/miRNA 疾病与表型关联 药物与化合物关联 全自动的网站
  • 网站每天做多少外链合适网站备案号添加
  • 网站接入商排名wordpress 如何添加关键词
  • 如何安全地在 Kubernetes 中管理凭据?——基于 SMS 凭据管理系统的实践探索
  • 初识c语言————常规运算符及其规则
  • 投资新热点:AEM双极板领域创业机会与风险评估
  • Bootstrap5 Jumbotron:功能强大的响应式全屏组件
  • 从官方视频比较Autodesk Forma 与广联达 CONCETTO
  • 手机版网站快照如何做做食品的网站设计要注意
  • 银河麒麟设置右键新建Ofiice文件
  • 如何在自己电脑上做网站自己怎么做网站
  • Vala编程语言高级特性-多线程
  • 上海网站建设培训学校廊坊做网站优化的公司
  • 在JavaScript / HTML中,动态计算调整文字大小
  • Video over HTTPS,视频流(HLSDASH)在 HTTPS 下的调试与抓包实战
  • 黄页88网站关键词怎么做农村自建房100张图片
  • 网站开发经验总结与教训徐州开发的网站
  • 重庆福彩建站北京提供24小时医疗服务
  • 浅谈文件上传
  • react 初体验2
  • 内网穿透的原理和配置
  • 科技护航童心:物联网助力科学护眼与智能哄娃新方式
  • 挂网站需要什么服务器wordpress 短信验证码