此次重点在于一个二叉树的引例题,其实和二叉树没什么太大的关系,但是很经典。
小球下落 刘汝佳白书P99
比较傻瓜化的方式是,对每个小球进行路线分析,在路线分析的过程中不断动态的改变节点的状态从而实现累积效果使最后一个小球的路线得以确定。
int s[1<<20]={0};//左移运算 二进制 1<<20= 2^20
//该数组保存每个节点的开关情况 0是关闭 1是开启
//D是层次数目 I是小球数目 n是节点总数
int D=4,I=5,n=(1<<D)-1,k;
for(int i=1;i<=I;i++)
{
k=1;//表示从1开始下落
for(;;)
{
s[k]=! s[k];//变换经过的节点的开关状态
k=s[k]?2*k:2*k+1;// 进入下一节点 若s[k]==1则走到2*k的位置 否则是2*k+1的位置,这是标准二叉树的一个在节点数序上的关系
if(k>n) break;//如果将要进入的节点已经超过了节点总数,那么就说明它已经到了根部,由于int的范式,则k/2就是它所在的最终节点
}
printf("%d ",k/2);
}
此种方法看起来很简洁也很清晰,但是有一个问题就是他是以小球的数目和路线进行了双重循环的 算法复杂度其实很大,当I很大时会很慢。
为了解决这个问题,不妨从D入手,试试能不能直接根据数学归纳法推导出最后一个小球的路线
通过观察和总结,可以发现
小球的移动方向取决于它是在当前层次第几个(I)来的小球,再由奇偶即可判断它的走向。(数学归纳)
当I是奇数时,它就是往下一层的左走的第(I+1)/2个小球 如果是偶数则是向下一层的右节点走的第I/2个小球
int D=4,I=2,n=(1<<D)-1,k=1;//此时的I还有一个身份就是它表示了最后一个小球是第多少个来到根节点的小球
for(int i=1;i<=D-1;i++)//分层研究最后一个球的去向
{
<span style="white-space:pre"> </span>//这部分代码其实是利用t的1和0简化过的,不够清晰
int t=I%2;//用来取消奇偶的判断
k=k*2+!t;//根据I的奇偶判断左右
I=(I+t)/2;//更新在下一层的序数
}
printf("%d",k);