文章目录
- [1. 新⼆叉树](#1. 新⼆叉树)
- [2. 二叉树的遍历](#2. 二叉树的遍历)
- [3. 二叉树的深度](#3. 二叉树的深度)
- [4. 求先序排列](#4. 求先序排列)
- [5. American Heritage](#5. American Heritage)
- [6. P3884 [JLOI2009] ⼆叉树问题(存疑)](#6. P3884 [JLOI2009] ⼆叉树问题(存疑))
1. 新⼆叉树
https://www.luogu.com.cn/problem/P1305
- 建树:和常规的链式存储方式一致。
因为结点是字符,所以可以直接用 ASCII 码值当做下标来使用。比如'a'直接映射成 97,l[97]里面就存着'a'的左儿子,r[97]里面就存着'a'的右儿子,以此类推,建立二叉树。- 先序遍历:根左右。
cpp
#include<bits/stdc++.h>
using namespace std;
int n;
const int N =300;
char l[N],r[N];
void dfs(char root)
{
if(root=='*')
{
return;
}
cout<<root;
dfs(l[root]);
dfs(r[root]);
}
int main()
{
cin>>n;
char a,root;
cin>>root;
cin>>l[root]>>r[root];
for(int i=2;i<=n;i++)
{
cin>>a;
cin>>l[a]>>r[a];
}
dfs(root);
}
2. 二叉树的遍历
https://www.luogu.com.cn/problem/B3642
建树+dfs遍历即可!
cpp
#include<bits/stdc++.h>
using namespace std;
int n;
const int N=1e6+10;
int l[N],r[N];
void dfs1(int root)
{
if(root==0)
{
return;
}
cout<<root<<" ";
dfs1(l[root]);
dfs1(r[root]);
}
void dfs2(int root)
{
if(root==0)
{
return;
}
dfs2(l[root]);
cout<<root<<" ";
dfs2(r[root]);
}
void dfs3(int root)
{
if(root==0)
{
return;
}
dfs3(l[root]);
dfs3(r[root]);
cout<<root<<" ";
}
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);
}
3. 二叉树的深度
https://www.luogu.com.cn/problem/P4913
⼆叉树的⾼度 = 1 + max(左⼦树的⾼度, 右⼦树的⾼度);因此,可以递归解决。
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n;
int l[N], r[N];
int dfs(int root)
{
if(root==0)
{
return 0;
}
return max(dfs(l[root]),dfs(r[root]))+1;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> l[i] >> r[i];
}
cout << dfs(1) << endl;
return 0;
}
4. 求先序排列
https://www.luogu.com.cn/problem/P1030
我们先⽤⼀个具体的例⼦模拟⼀下,在已知「中序」和「后序」的基础上,如何求「先序」:
s1:中序遍历字符串 (Inorder)s2:后序遍历字符串 (Postorder)- 参数含义:
l1, r1:当前子树在 中序 (s1) 中的左右边界下标。l2, r2:当前子树在 后序 (s2) 中的左右边界下标。实例解析:
输入数据:
- 中序 (
s1):BADC(下标 0~3)- 后序 (
s2):BDCA(下标 0~3)目标: 输出前序遍历。
预期结果:ABCD(根A -> 左B -> 右C -> 右D? 不对,让我们一步步推导)第一轮调用 (整棵树):
dfs(0, 3, 0, 3)
- 范围 :
- 中序
s1[0...3]= "BADC"- 后序
s2[0...3]= "BDCA"- 找根节点 :
- 后序遍历的最后一个元素一定是根。
s2[r2]即s2[3]= 'A'。- 代码执行
while(s1[p] != s2[r2]) p++;:
p从l1(0) 开始。s1[0]='B' != 'A' ->p++s1[1]='A' == 'A' -> 停止。- 此时
p = 1。- 输出根 :
cout << 'A'。 (当前输出:A)- 划分左右子树 :
- 在中序
s1中,p=1('A') 左边是左子树,右边是右子树。- 左子树 (中序):
s1[0...0]("B")。长度 = 1。- 右子树 (中序):
s1[2...3]("DC")。长度 = 2。- 递归调用 :
- 左子树递归 :
dfs(l1, p-1, l2, l2+p-1-l1)
- 参数:
dfs(0, 0, 0, 0+1-1-0)->dfs(0, 0, 0, 0)- 右子树递归 :
dfs(p+1, r1, l2+p-l1, r2-1)
- 参数:
dfs(2, 3, 0+1-0, 3-1)->dfs(2, 3, 1, 2)
第二轮调用 (处理左子树):
dfs(0, 0, 0, 0)
- 范围 :
- 中序
s1[0...0]= "B"- 后序
s2[0...0]= "B"- 找根节点 :
- 后序最后一个
s2[0]= 'B'。while循环:s1[0]('B') == 'B',p保持为 0。- 输出根 :
cout << 'B'。 (当前输出:AB)- 递归调用 :
- 左子树 :
dfs(0, -1, ...)->l1 > r1,直接return。- 右子树 :
dfs(1, 0, ...)->l1 > r1,直接return。- 返回 到第一轮。
第三轮调用 (处理右子树):
dfs(2, 3, 1, 2)
- 范围 :
- 中序
s1[2...3]= "DC" (注意:s1[2]='D',s1[3]='C')- 后序
s2[1...2]= "DC" (注意:s2[1]='D',s2[2]='C')- 解释:原后序是 BDCA,去掉根A和左子树B,剩下 DC 对应右子树。
- 找根节点 :
- 后序最后一个
s2[r2]即s2[2]= 'C'。while循环:
p从l1(2) 开始。s1[2]='D' != 'C' ->p++ (p=3)s1[3]='C' == 'C' -> 停止。- 此时
p = 3。- 输出根 :
cout << 'C'。 (当前输出:ABC)- 划分左右子树 :
- 在中序
s1中,p=3('C') 左边是左子树,右边无。- 左子树 (中序):
s1[2...2]("D")。- 右子树 (中序): 空。
- 递归调用 :
- 左子树递归 :
dfs(2, 2, 1, 1+3-1-2)->dfs(2, 2, 1, 1)
- 计算细节:
l2=1,p=3,l1=2. 新r2=1 + 3 - 1 - 2= 1.- 右子树递归 :
dfs(4, 3, ...)->l1 > r1,直接return。--- 第四轮调用 (处理右子树的左孩子):
dfs(2, 2, 1, 1)
- 范围 :
- 中序
s1[2...2]= "D"- 后序
s2[1...1]= "D"- 找根节点 :
- 后序最后一个
s2[1]= 'D'。while循环:s1[2]== 'D',p= 2。- 输出根 :
cout << 'D'。 (当前输出:ABCD)- 递归调用:左右均为空,返回。
- 中序验证 :左(B) -> 根(A) -> 右(D, C) => BADC (符合)
- 后序验证 :左(B) -> 右(D, C) -> 根(A) => BDCA (符合)
- 前序输出 :根(A) -> 左(B) -> 右(C, D) => ABCD (符合代码输出)
- 左子树的后序范围 :
- 左子树的长度 =
p - l1(在中序中,根下标p减去 左边界l1)。- 后序遍历中,左子树紧挨着起始位置
l2。- 所以左子树在后序中的结束位置 =
l2 + 长度 - 1=l2 + (p - l1) - 1。- 代码:
l2 + p - 1 - l1- 右子树的后序范围 :
- 右子树在后序中的起始位置 =
左子树结束位置 + 1=(l2 + p - l1 - 1) + 1=l2 + p - l1。- 右子树在后序中的结束位置 =
当前结束位置 - 1(因为最后一个是根) =r2 - 1。- 代码:
l2 + p - l1,r2 - 1
⚠️:请自己边画边结合上面描述理解,其实很简单的!重点是一定要画!不能光看!
cpp
#include<bits/stdc++.h>
using namespace std;
string s1,s2;
int n;
int l1,l2,r1,r2;
void dfs(int l1,int r1,int l2,int r2)
{
//退出调节
if(l1>r1)
{
return;
}
int p=l1;//用p记录根结点
while(s1[p]!=s2[r2]) p++;
cout<<s2[r2];
//以p为分界点,左右子树分别再次bfs
dfs(l1,p-1,l2,l2+p-1-l1);
dfs(p+1,r1,l2+p-l1,r2-1);
}
int main()
{
cin>>s1>>s2;
l1=0,l2=0;
r1=s1.size()-1,r2=s2.size()-1;
dfs(l1,r1,l2,r2);
return 0;
}
5. American Heritage
https://www.luogu.com.cn/problem/P1827
解法同第四题,如果第四题掌握了,那太简单了,不想多说,可以参考一下博主做这道题的时候画的分析图:
cpp
#include<bits/stdc++.h>
using namespace std;
string s1,s2;//s1 中 s2 后
void dfs(int l1,int r1,int l2,int r2)
{
if(l1>r1) return;
int p=l1;
while(s1[p]!=s2[l2]) p++;
dfs(l1,p-1,l2+1,l2+p-l1);
dfs(p+1,r1,l2+p-l1+1,r2);
cout<<s2[l2];
}
int main()
{
cin>>s1>>s2;
dfs(0,s1.size()-1,0,s2.size()-1);
return 0;
}
6. P3884 [JLOI2009] ⼆叉树问题(存疑)
https://www.luogu.com.cn/problem/P3884
【解法】
深度:递归。
宽度:宽搜。
两点之间的距离:通过向上不断找父结点。第一个重叠的位置,就是两者的最近公共祖先。可以一边寻找,一边计算结果。
首先,由于它没有明确说明谁是左右孩子,所以只能用vector数组来存储,里面dfs的递归思想也是很常见,bfs就是用队列模拟找哪一层最多也不解释,最难的两点距离,本质就是求公共的祖先,用一个fa[N]数组存储父亲结点,然后把其中一个结点一直往上遍历直到根结点,然后存储中间遍历的所有结点,然后让另一个结点往上爬,遇到第一个中间遍历过的结点就行了
cpp
#include<bits/stdc++.h>
using namespace std;
int n;
const int N=110;
int fa[N],dist[N];
vector<int> vc[N];
int dfs(int root)
{
int ret=0;
for(auto e:vc[root])
{
ret=max(ret,dfs(e));
}
return ret+1;
}
int bfs(int root)
{
queue<int> q;
int ret=0;
q.push(root);
while(q.size())
{
int num=q.size();
ret=max(ret,num);
while(num--)
{
for(auto e:vc[q.front()])
{
q.push(e);
}
q.pop();
}
}
return ret;
}
int main()
{
cin>>n;
int u,v;
for(int i=1;i<n;i++)
{
cin>>u>>v;
vc[u].push_back(v);
fa[v]=u;
}
cin>>u>>v;
cout<<dfs(1)<<endl;
cout<<bfs(1)<<endl;
int x=u;
while(x!=1)
{
dist[fa[x]]=dist[x]+1;
x=fa[x];
}
int y=v;
int len = 0;
//⚠️:我认为这个逻辑是有问题的 主播如果最后输入 3 7 明眼人看距离都是1 但是输出是4 奇怪的是最后居然特喵AC了!!主播不是很懂 有懂的希望留言交流谢谢!
while(y != 1 && dist[y] == 0)
{
len++;
y = fa[y];
}
cout << dist[y] * 2 + len << endl;
}










