P1087 [NOIP2004 普及组] FBI 树(二叉树)
基础知识
二叉树 每个结点都最多可以分出两个子树来. 二叉树的第一个结点为 根结点; 如果一个结点没有任何子树, 这个结点就是 叶子结点。可以说二叉树要么为空, 要么由根结点、左子树、右子树构成, 而左右子树分别又是一颗二叉树.
二叉树的深度是从根结点到叶子结点所经过的最多的层数。
假设从根结点开始标号为 1, 然后按从左到右从上到下的顺序标号, 如下图。
这时可以看到结点 1 的左子树的根结点为 2, 右子树的根结点为 3; 观察每个结点的左右子结点会发现:
左子结点的标号 = 根结点的标号 * 2; 右子结点的标号 = 根结点的标号 * 2 + 1;
这样对于结点数目不太大时, 对于一些二叉树题型可以用直接用数组储存, 数组的下标即是结点的标号。例如建一个数组 a, 访问标号为 x 的结点的左子结点以写为 a[x * 2], 同理 x 的右结点为 a[x * 2 + 1]。
结合搜索既可以对二叉树进行遍历了, 这道题就是利用 深度优先搜索(dfs) 对二叉树进行遍历.
三种遍历方式: 根据对结点的遍历顺序不同可以分为 前序遍历, 中序遍历, 后序遍历
前序遍历: 先遍历根结点, 再遍历左子树, 最后遍历右子树;
void pre(int x) {
cout << a[x];
if (a[x * 2]) pre(x * 2);
if (a[x * 2 + 1]) pre(x * 2 + 1);
}
中序遍历: 先遍历左子树, 再遍历根结点, 最后遍历右子树;
void md(int x) {
if (a[x * 2]) md(x * 2);
cout << a[x];
if (a[x * 2 + 1]) md(x * 2 + 1);
}
后序遍历: 先遍历左子树, 再遍历右子树, 最后遍历根结点;
void post(int x) {
if (a[x * 2]) post(x * 2);
if (a[x * 2 + 1]) post(x * 2 + 1);
cout << a[x];
}
题面
我们可以把由“0”和“1”组成的字符串分为三类:全“0”串称为 B B B串,全“1”串称为 I I I串,既含“0”又含“1”的串则称为 F F F串。
F B I FBI FBI树是一种二叉树,它的结点类型也包括 F F F结点, B B B结点和 I I I结点三种。由一个长度为 2 N 2^{N} 2N的“01”串 S S S可以构造出一棵 F B I FBI FBI树 T T T,递归的构造方法如下:
- T T T的根结点为 R R R,其类型与串 S S S的类型相同;
- 若串 S S S 的长度大于1,将串 S S S 从中间分开,分为等长的左右子串 S 1 S_1 S1 和 S 2 S_2 S2, 由左子串 S 1 S_1 S1 构造 R R R 的左子树 T 1 T_1 T1, 由右子串 S 2 S_2 S2 构造 R R R 的右子树 T 2 T_2 T2。
现在给定一个长度为
2
N
2^N
2N 的“01”串,请用上述构造方法构造出一棵
F
B
I
FBI
FBI树,并输出它的后序遍历序列。
输入格式
第一行是一个整数
N
(
0
≤
N
≤
10
)
N(0 ≤ N ≤ 10)
N(0≤N≤10)
第二行是一个长度为 2 N 2^N 2N的“01”串。
输出格式
一个字符串,即
F
B
I
FBI
FBI树的后序遍历序列。
解析
根据题意可知输入的字符串 s 是这颗二叉树的最后一层的叶子结点的值(尚未转换为
F
B
I
FBI
FBI树), 因为叶子几点没有子树所以该节点对应的字符串就是
′
0
′
'0'
′0′或
′
1
′
'1'
′1′, 这样我们可以把
0
0
0 看作
B
B
B,
1
1
1 看作
I
I
I。由等比数列公式可知, 二叉树前 n 层的结点个数为
2
n
−
1
2 ^n - 1
2n−1, 而第 n 层结点个数为
2
n
−
1
2^{n - 1}
2n−1(第一层为 1 个结点, 为
2
0
2^0
20); 所以给出了长度为
2
n
2^n
2n的"01"串, 相等于给出了二叉树第 n+1层的每个结点的值,利用位运算,我们将值储存在 a[0 + (1<<n)], a[1 + (1<< n)]……a[(1<<n ) - 1 + (1<<n)] 里; 这样在遍历就可以了.
以下是我写的代码:
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int n;
char a[3000];//用来存储每个结点的值
string s, s2;//s 是输入的字符串, s2 是最终的输出结果;
void dfs(int x) {//深度优先搜索遍历树
if (x >= (1 << n)) {//满足该条件时, 代表遍历到二叉树的最后一层;
s2 += a[x];//结果字符串加上这个字符
return;
}
dfs(x * 2);//遍历左子树;
dfs(x * 2 + 1);//遍历右子树;
if (a[x * 2] == a[x * 2 + 1]) {//如果左右子树相同则进一步判断;
if (a[x * 2] == 'F') {//如果等于 F , a[x]也一定为 F;
a[x] = 'F';//将该结点记为 F, 一下两个判断同理;
}
else if (a[x * 2] == 'B') {
a[x] = 'B';
}
else {
a[x] = 'I';
}
}
else {//若干左右子树根结点不相同, 则a[x] 一定为 F;
a[x] = 'F';
}
s2 += a[x];
return;
}
int main() {
cin >> n;
cin >> s;
for (int i = 0; i < (1 << n); ++i) {//输入2的n次方个数;
if (s[i] == '1')//如果该点字符为 1, 则该叶子结点为 I 串;
a[i + (1 << n)] = 'I';
else if (s[i] == '0')//如果该点字符为 0, 则该叶子结点为 B 串;
a[i + (1 << n)] = 'B';
}
dfs(1);//遍历该树;
cout << s2 << endl;
return 0;
}