FBI 树(二叉树基础知识)


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,递归的构造方法如下:

  1. T T T的根结点为 R R R,其类型与串 S S S的类型相同;
  2. 若串 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(0N10)

第二行是一个长度为 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 2n1, 而第 n 层结点个数为 2 n − 1 2^{n - 1} 2n1(第一层为 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;
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值