问题 D: 上帝视角
题目描述
给一棵二叉树的层序遍历序列和中序遍历序列,求这棵二叉树的先序遍历序列和后序遍历序列,并给出从右往左、从右上往左下、从上往下分别能看到的结点个数。注意,此处均把二叉树的每条边都设置为等长,角度为45度,因此结点可能在视觉上重叠。所谓从右往左看是指,对同一层的结点,右边的结点会挡住左边的结点,这样同一层结点就只能看到最右边的那一个;同样的,从右上往左下看是指,右上角的结点会挡住左下角45度的结点;从上往下看是指,对同一竖直位置来说,只能看到最上方的结点。
例如对下图来说,从右往左能看到3个结点,从右上往左下能看到3个结点,从上往下能看到5个结点。
输入
每个输入文件中一组数据。
第一行一个正整数N(1<=N<=30),代表二叉树的结点个数(结点编号为1~N)。
接下来两行,每行N个正整数,分别代表二叉树的层序遍历序列和中序遍历序列。数据保证序列中1~N的每个数出现且只出现一次。
输出
先输出两行,每行N个正整数,分别代表二叉树的先序遍历序列和后序遍历序列。
接下来分三行输出从右往左、从右上往左下、从上往下分别能看到的结点个数。
每行末尾均不允许输出多余的空格。
样例输入
样例输出
提示
Part One:
就是模拟赛(1)中的 C 题。
在先序+中序中,先序的第一个结点是根结点,然后用中序区分出左右子树。到这里为止,
层序+中序也是一样的,层序的第一个结点是根结点,然后用中序区分出左右子树。
不同在于后面的步骤。
由于先序中是按照“根结点-左子树所有结点-右子树所有结点”的顺序的,因此可以直接
对左子树跟右子树进行递归,但是层序不行,层序的左右子树结点在层序序列中可能是分散的。
因此必须想办法把左子树所有结点跟右子树所有结点都找出来。
于是可以开两个 vector,一个存左子树所有结点的层序,一个存右子树所有结点的层序。
然后遍历当前层序序列的所有结点,根据中序序列判断其属于左子树还是右子树,如果是左子
树就 push_back 到左子树的 vector,否则就 push_back 到右子树的 vector。
这样就可以继续递归了,递归参数中用 vector 表示层序,而中序可以仍然用数组下标。
Part Two:
从右往左看,其实就是求树的高度。
从右上往左下看,我们会发现,左孩子总是被根结点挡住。所以可以开一个计数变量,从
根节点开始遍历这棵树,当往左子树前进时,计数变量不变;当往右子树前进时,计数变量加
1。过程中计数变量能够达到的最大值就是我们要求的解。
从上往下看,可以发现,根结点左侧能看到的结点个数等于从根结点开始进入左子树之后,
从左前进的次数减去从右前进的次数;根结点右侧能看到的结点个数等于从根结点开始进入右
子树之后,从右前进的次数减去从左前进的次数。因此不妨开一个计数变量,从根结点开始遍
历这棵树,当往左子树前进时,计数变量减 1;当往右子树前进时,计数变量加 1。过程中计
数变量能够到达的最小负值就是根结点左侧能看到的结点个数;能达到的最大正值就是根结点
右侧能看到的结点个数。将以上两个绝对值相加后加 1 就是从上往下能看到的结点。
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int maxn = 50;
const int INF = 0x3fffffff;
struct node {
int data;
int lchild;
int rchild;
}Node[maxn];
int in[maxn]; //中序
vector<int> pre, post;
int num = 0; // 已分配的结点个数
int newNode(int x) { // 分配结点
Node[num].data = x;
Node[num].lchild = Node[num].rchild = -1; // -1表示NULL
return num++;
}
//当前二叉树的层序序列为layer,中序序列区间为[inL, inR]
//create函数返回构建出的二叉树的根结点地址
int create(vector<int> layer, int inL, int inR) {
if(layer.size() == 0) {
return -1;
}
int root = newNode(layer[0]); //新建一个新的结点,用来存放当前二叉树的根结点
int k;
for(k = inL; k <= inR; k++) { // 在中序中找根结点
if(in[k] == layer[0]) {
break;
}
}
vector<int> layerLeft; // 左子树层序
vector<int> layerRight; // 右右子树层序
for(int i = 1; i < layer.size(); i++) { // 剩余所有元素
bool isLeft = false; // 是否在左子树
for(int j = inL; j < k; j++) { // 在中序中找layer[i]
if(layer[i] == in[j]) { // 找到layer[i]
isLeft = true; // layer[i]在左子树
break;
}
}
if(isLeft) layerLeft.push_back(layer[i]); // 如果layer[i]在左子树,那么加入左子树层序
else layerRight.push_back(layer[i]); // 如果layer[i]在右子树,那么加入右子树层序
}
Node[root].lchild = create(layerLeft, inL, k - 1); // 处理左子树
Node[root].rchild = create(layerRight, k + 1, inR); // 处理右子树
return root; //返回根结点地址
}
void preOrder(int root) { // 先序
if(root == -1) {
return;
}
pre.push_back(Node[root].data);
preOrder(Node[root].lchild);
preOrder(Node[root].rchild);
}
void postOrder(int root) { // 后序
if(root == -1) {
return;
}
postOrder(Node[root].lchild);
postOrder(Node[root].rchild);
post.push_back(Node[root].data);
}
int ans1 = 0; // 从右往左:深度
void rightToLeftDFS(int root, int depth) {
if(root == -1) {
return;
}
if(depth > ans1) ans1 = depth; // 更新ans1
rightToLeftDFS(Node[root].lchild, depth + 1); // 左孩子跟右孩子的深度都加1
rightToLeftDFS(Node[root].rchild, depth + 1);
}
int ans2 = 0; // 从右上往左下:往右的次数
void rightupToLeftdownDFS(int root, int right) {
if(root == -1) {
return;
}
if(right > ans2) ans2 = right; // 更新ans2
rightupToLeftdownDFS(Node[root].lchild, right); // 往左时计数变量right不变
rightupToLeftdownDFS(Node[root].rchild, right + 1); // 往右时计数变量right加1
}
int minLeft = 0, maxRight = 0; // 从上往下:最小负值,最大正值
void upToDownDFS(int root, int balance) {
if(root == -1) {
return;
}
if(balance > maxRight) maxRight = balance; // 更新maxRight
if(balance < minLeft) minLeft = balance; // 更新minLeft
upToDownDFS(Node[root].lchild, balance - 1); // 往左时计数变量balance减1
upToDownDFS(Node[root].rchild, balance + 1); // 往右时计数变量balance加1
}
int main() {
vector<int> layer;
int n, temp;
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%d", &temp);
layer.push_back(temp); // 程序
}
for(int i = 0; i < n; i++) {
scanf("%d", &in[i]); // 中序
}
int root = create(layer, 0, n - 1); //建树
preOrder(root); // 先序
postOrder(root); // 后序
for(int i = 0; i < n; i++) {
printf("%d", pre[i]);
if(i < n - 1) printf(" ");
else printf("\n");
}
for(int i = 0; i < n; i++) {
printf("%d", post[i]);
if(i < n - 1) printf(" ");
else printf("\n");
}
rightToLeftDFS(root, 1); // 从右往左,根结点深度为1
rightupToLeftdownDFS(root, 1); // 从右上往左下,根节点为第一个结点
upToDownDFS(root, 0); // 从上往下,根结点先不算在内
printf("%d\n%d\n%d\n", ans1, ans2, maxRight - minLeft + 1); // 三个解
return 0;
}