前言:今天刷到这道题实在太痛苦了,本来树就学得不好,/(ㄒoㄒ)/~~,但是!但是!困难是需要面对的,现在就是直面困难的时候!
文章目录
一、二叉树的三种遍历
最为简单,易懂的遍历方法,直接上链接:
https://blog.csdn.net/chinesekobe/article/details/110874773
理解三种遍历:
来,让我们先把所有空结点都补上。
还记得我们先序和后序遍历时候跑的顺序么?按照这个顺序再跑一次,就是围着树的外围跑一整圈。
让我们来理解一下绕着外围跑一整圈的真正含义是:遍历所有结点时,都先往左孩子走,再往右孩子走。
观察一下,你有什么发现?
有没有发现,除了根结点和空结点,其他所有结点都有三个箭头指向它。
一个是从它的父节点指向它,一个是从它的左孩子指向它,一个是从它的右孩子指向它。
一个结点有三个箭头指向它,说明每个结点都被经过了三遍。一遍是从它的父节点来的时候,一遍是从它的左孩子返回时,一遍是从它的右孩子返回时。
其实我们在用递归算法实现二叉树的遍历的时候,不管是先序中序还是后序,程序都是按照上面那个顺序跑遍所有结点的。
先序中序和后序唯一的不同就是,在经过结点的三次中,哪次访问(输出或者打印或者做其他操作)了这个结点。有点像大禹治水三过家门,他会选择一次进去。
先序遍历顾名思义,就是在第一次经过这个结点的时候访问了它。就是从父节点来的这个箭头的时候,访问了它。
中序遍历也和名字一样,就是在第二次经过这个结点的时候访问了它。就是从左孩子返回的这个箭头的时候,访问了它。
后序遍历,就是在第三次经过这个结点的时候访问了它。就是从右孩子返回的这个箭头的时候,访问了它。
怎么样,这样有没有很好的理解?其实不管是前序中序还是后序,在程序里跑的时候都是按照同样的顺序跑的,每个结点经过三遍,第几遍访问这个结点了,就叫什么序遍历。
理解原文链接:https://blog.csdn.net/qq_36332184/article/details/102775099
两、二叉树的重构(递归)
前序遍历+中序遍历重构二叉树
前序遍历的顺序是:根->左子树->右子树
中序遍历的顺序是:左子树->根->右子树;
因此前序遍历的第一个元素1就是整个二叉树的根节点;
在中序遍历中找到该元素,则该元素把中序遍历数组分成左右两部分,分别为左子树(4 2 8 5 9)和右子树(6 3 7 10);
前序遍历的第二个元素2为左子树的根节点,同理在中序遍历中找到该元素,左子树又被一分为二(4)和(8 5 9);
……
如此递归下去直到最后一个元素;
//已知后续中序遍历求前序遍历结果
#include<cstdio>
using namespace std;
const int MAX=10086;
int pre[MAX],in[MAX];//post记录后序遍历,in记录中序遍历
//root为当前根节点
//左子树在中序中的起始点start为start,末尾为end
void post(int root,int start,int end){
if(start>end) return ;
int i=start;
if(i<end&&pre[root]!=in[i]) i++;
pre(root+1,start,i-1);
pre(root+i-start+1,i+1,end);
cout<<pre[root]<<" ";
}
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++) cin>>pre[i];
for(int i=0;i<n;i++) cin>>in[i];
post(0,0,n-1);
return 0;
}
后序遍历+中序遍历重构二叉树
后序遍历的顺序是:左子树->右子树->根;
中序遍历的顺序是:左子树->根->右子树;
和前面相似,不同的是对于后序遍历我们从最后一个元素下手。
后续遍历数组的最后一个元素 1 就是整个数组的根节点;
在中序遍历数组中查找到该元素同样把整个数组分成两部分,左子树(4 2 8 5 9)和右子树(6 3 7 10);
后续遍历数组的倒数第二个元素 3 就是右子树的根节点,同样又把上述右子树分成左右两部分(6)和(7 10);
…….
如此地递归至最后一个元素。
注意,我们在后序遍历数组是从根节点到右子树的顺序取值的,所以在递归的时候也要先从右节点开始。
已知后序与中序输出前序(先序):
后序:3, 4, 2, 6, 5, 1(左右根)
中序:3, 2, 4, 1, 6, 5(左根右)
分析:
因为后序的最后一个总是根结点,令i在中序中找到该根结点,则i把中序分为两部分,左边是左子树,右边是右子树。因为是输出先序(根左右),所以先打印出当前根结点,然后打印左子树,再打印右子树。左子树在后序中的根结点为root – (end – i + 1),即为当前根结点-(右子树的个数+1)。左子树在中序中的起始点start为start,末尾end点为i – 1.右子树的根结点为当前根结点的前一个结点root – 1,右子树的起始点start为i+1,末尾end点为end。
输出的前序应该为:
1, 2, 3, 4, 5, 6(根左右)
//已知后续中序遍历求前序遍历结果
#include<cstdio>
using namespace std;
const int MAX=10086;
int post[MAX],in[MAX];//post记录后序遍历,in记录中序遍历
//root为当前根节点
//左子树在中序中的起始点start为start,末尾为end
void pre(int root,int start,int end){
if(start>end) return ;
int i=start;
if(i<end&&post[root]!=in[i]) i++;
cout<<post[root]<<" ";
pre(root-end+i-1,start,i-1);
pre(root-1,i+1,end);
}
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++) cin>>post[i];
for(int i=0;i<n;i++) cin>>in[i];
pre(n-1,0,n-1);
return 0;
}
叁、1020 Tree Traversals (输出层遍历)
题目链接:
https://pintia.cn/problem-sets/994805342720868352/problems/994805485033603072
分析:与后序中序转换为前序的代码相仿(无须构造二叉树再进行广度优先搜索~),只不过加一个变量index,表示当前的根结点在二叉树中所对应的下标(从0开始),所以进行一次输出先序的递归过程中,就可以把根结点下标index及所对应的值存储在map<int, int> level中,map是有序的会根据index从小到大自动排序,这样递归完成后level中的值就是层序遍历的顺序~~
代码
#include <cstdio>
#include <vector>
#include <map>
using namespace std;
vector<int> post, in;
map<int, int> level;
void pre(int root, int start, int end, int index) {
if(start > end) return ;
int i = start;
while(i < end && in[i] != post[root]) i++;
level[index] = post[root];
pre(root - 1 - end + i, start, i - 1, 2 * index + 1);
pre(root - 1, i + 1, end, 2 * index + 2);
}
int main() {
int n;
scanf("%d", &n);
post.resize(n);
in.resize(n);
for(int i = 0; i < n; i++) scanf("%d", &post[i]);
for(int i = 0; i < n; i++) scanf("%d", &in[i]);
pre(n-1, 0, n-1, 0);
auto it = level.begin();
printf("%d", it->second);
while(++it != level.end()) printf(" %d", it->second);
return 0;
}
二刷代码:
#include<bits/stdc++.h>
using namespace std;
int n;
int in[50],post[50];
map<int,int> mp;
void solve(int x,int l,int r,int kid){
if(l>r) return;
int root=post[x];
int k=l;
while(k<=r&&in[k]!=root) k++;
mp[kid]=root;
solve(x-1+k-r,l,k-1,kid*2);
solve(x-1,k+1,r,kid*2+1);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>post[i];
for(int i=1;i<=n;i++) cin>>in[i];
solve(n,1,n,1);
map<int,int>::iterator v=mp.begin();
cout<<v->second;
while(++v!=mp.end()) cout<<" "<<v->second;
return 0;
}
四、已知中序层序求前序
利用层序和中序的性质
层序记录的一定是当前二叉树的根节点,中序将二叉树分为左中右
那么,层序中第一个在中序中出现的结点就是根节点。
代码
#include<bits/stdc++.h>
using namespace std;
string c,z;
int n;
queue<char> q;
void solve(int l,int r){
if(l>r) return;
int idx=-1,i,j;
bool A=0;
for(i=0;i<n;i++){//在层序中找根节点
for(j=l;j<=r;j++){//出现第一个存在于中序数组里的就是根节点
if(c[i]==z[j]){
A=1;break;
}
}
if(A) break;
}
if(!A) return;
q.push(c[i]);
solve(l,j-1);
solve(j+1,r);
}
int main(){
cin>>z>>c;
n=c.size();
solve(0,n-1);
while(!q.empty()){
cout<<q.front();q.pop();
}
}
五、 一些应用
传送门
参考原文链接:https://blog.csdn.net/liuchuo/article/details/52137796
浪漫侧影(层序应用)
题目描述
“侧影”就是从左侧或者右侧去观察物体所看到的内容。例如上图中男生的侧影是从他右侧看过去的样子,叫“右视图”;女生的侧影是从她左侧看过去的样子,叫“左视图”。520 这个日子还在打比赛的你,也就抱着一棵二叉树左看看右看看了……我们将二叉树的“侧影”定义为从一侧能看到的所有结点从上到下形成的序列。例如下图这棵二叉树,其右视图就是 { 1, 2, 3, 4, 5 },左视图就是 { 1, 6, 7, 8, 5 }。
于是让我们首先通过一棵二叉树的中序遍历序列和后序遍历序列构建出一棵树,然后你要输出这棵树的左视图和右视图。
输入格式:
输入第一行给出一个正整数 N (≤20),为树中的结点个数。随后在两行中先后给出树的中序遍历和后序遍历序列。树中所有键值都不相同,其数值大小无关紧要,都不超过 int 的范围。
输出格式:
第一行输出右视图,第二行输出左视图,格式如样例所示。
输入样例:
8
6 8 7 4 5 1 3 2
8 5 4 7 6 3 2 1
1
2
3
输出样例:
R: 1 2 3 4 5
L: 1 6 7 8 5
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 35;
const int INF = 0x3f3f3f3f;
int n, maxlevel;
int in[MAXN], post[MAXN]; //存储中序遍历、后续遍历
deque<int> level[MAXN]; //存储层序遍历
void travel(int root, int left, int right, int deep)
{
//root 当前根节点
if(left > right){
return ;
}
int i = left;
while(i <= right && in[i] != post[root]){
//找到中序遍历中的根节点
i ++;
}
maxlevel = max(deep, maxlevel);
level[deep].push_back(post[root]);
travel(root - right + i - 1, left, i - 1, deep + 1); //遍历左子树
travel(root - 1, i + 1, right, deep + 1); //遍历右子树
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
for(int i = 0;i < n;i ++){
cin >> in[i];
}
for(int i = 0;i < n;i ++){
cin >> post[i];
}
maxlevel = 0; //记录最大深度
travel(n - 1, 0, n - 1, 1);
cout << "R:";
for(int i = 1;i <= maxlevel;i ++){
cout << " " << level[i].back();
}
cout << endl << "L:";
for(int i = 1;i <= maxlevel;i ++){
cout << " " << level[i].front();
}
return 0;
}