算法入门经典第六章数据结构—树和二叉树-1.二叉树的编号

时间:2021-10-20 17:30:04

问题描述:一棵二叉树的深度为D。所有结点从上到下从左到右编号依次为1,2,3,…,2^D-1,在结点1处放一个小球。每个结点有个开关,初始全部关闭,当每次球落到一个结点上,此结点的开关状态放上改变。球如果遇到关闭的结点向左走,否则向右。输入小球个数I,输出第I个小球最后落到的叶子编号。

                1
2 3
4 5 6 7
8 9 10 11 12 13 14 15

我们如果用一个数组s来保存这个树中结点的状态,刚开始这个数组全部赋为0表示全部结点的开关为关闭状态。比如上图是一个深度D为4的二叉树,如果输入I为2,模拟一下下落过程。
1.第一个小球从1开始,因为刚开始所有结点的开关都关闭,所以一直向左,顺序为1->2->4->8,而小球落到一个结点开关状态改变,所以1,2,4,8都改变为开启状态(即s[1、2、4、8]都为1)。
2.第二个小球从1开始,因为1状态为开启,所以向右到达3,而从以3开始的子树的状态还依然是关闭,所以从3开始都向左走,所以第二个小球的下落顺序为1->3->6->12
3.输出12
这个过程还是很容易理解的,并且用数组来解决也是很自然的,但是当D很小时候这个没什么大运算量,但是当D很大时候,因为I可达2^D-1,所以这个数组要开的特别特别大,很显然,根本不现实。
所以,有了另外一个神奇的方法。

我们分析一下,每个小球刚开始都是从根结点1开始的,第一个小球向左,第二个向右,第三个向左,第四个….,所以就直接用I的奇偶性来判断小球落在那棵子树上。对于那些落入根结点左子树的小球来说,只需要知道该小球是第几个落在根的左子树里,就能知道向左还是向右了,如果是第一个落在根的左子树里,就是向左,第二个就向右(注意这个第二不是第二个小球,而是第二个落在左子树
当I为奇数时,则它是往左走的第(I+1)/2个小球;如果是偶数,则是往右走的第I/2个小球。
所以,减少了数组,直接模拟第I个小球的下落路线

#include<cstdio>
using namespace std;

int main()
{
int D,I;
while(scanf("%d%d",&D,&I)==2)
{
int k=1;
for(int i=0;i<D-1;i++)
if(I%2)
{
I = (I+1)/2;
k=k*2;
}
else{
I = I/2;
k=k*2+1;
}
printf("%d\n",k);
}
return 0;
}