小球下落--二叉树编号

题目:
小球下落:
一颗最大深度 D 的二叉树,所有叶子深度相同,
所有节点从上到下,从左到右的编号依次1,2,3,,,,2^D - 1,
在节点1处放一个球往下落。
每个内节点都有一个开关,初始全部关闭,
但每次有小球落到一个开关时状态改变。
若关闭,则左走,否则右走,直到叶子节点。
一共I个球下落,求第I个球最后所在叶子编号;

(I不可超过整棵树的叶子个数,D<=20)

初见可以写出模拟程序:

左走:子节点 = 2 * 父节点   右走: 子节点 = 2 * 父节点 + 1
#include <iostream>
using namespace std;
bool switch[10010];
int main()
{
	int D, I;
	while(cin>>D>>I){
		memset(switch,0,10010*sizeof(bool));
		int pos = 1;
		int limit = (1 << D) - 1;
		for(int i = 0;i< I;++i){
			pos = 1;
			for(;;){
				switch[pos] = !switch[pos];
				pos = switch[pos]? 2 * pos : 2 * pos + 1;
				//此时已经越界
				if(pos>limit) 	break;
			}
		}
		cout<< (pos>>1) <<'\n';
	}
	return 0;
}

但运算量量太大,I 可以高达 2 ^ D - 1 ,每组测试数据可能高达 2 ^ 19 * 19 , 而一共可能 1000组数据.

然而我们可以看到 每个小球都会落在根节点上,
因此前两个小球必然一个在 左子树,一个在右子树;
后面,我们可以根据小球编号的奇偶性判断最终叶子编号;

或许下面这个表可以借助理解: 以 0 表 左走, 1 表 右走
以 D = 4_I = 8 为例:

 0 0 0
 1 0 0
 0 1 0
 1 1 0
 0 0 1
 1 0 1
 0 1 1
 1 1 1

再结合代码想想:

#include <iostream>
using namespace std;
int main()
{
	int D , I;
	while(cin>>D>>I){
		//做  D - 1 次 选择
		int pos = 1;
		for(int i = 0;i < D - 1;++i){
			if(I % 2) { pos = 2 * pos ;  I = (I + 1) / 2;}
			else { pos = 2 * pos + 1; I /= 2; } 
		}
		cout<< pos <<'\n';
	}
	return 0;
}

需要说明的是,

	if(I % 2) { pos = 2 * pos ;  I = (I + 1) / 2;}

对于 i 为奇数 , 则左走 ,容易理解;
再看看下面这幅”第一步图示“:

	0	 0	 0	 0	 0	 0	 0	 0	 0
	   1   1   1   1   1   1   1   1   1

0	1	0	1	0	1	0	1	0	1	0 	1

观察第 i 个 和 向左走的 第 i 个
思考:为什么想左走 第 i 个球的下一步 与 第 i 个球第一步相同

即将 节点  2 * pos 作为根节点,向左走的第 (i + 1) / 2 个球,相当 在 2 * pos节点下落的 第 (i + 1) / 2 个球 ;
同理, 向右走也是如此;
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值