小猴子下落
- 描述
-
有一颗二叉树,最大深度为D,且所有叶子的深度都相同。所有结点从左到右从上到下的编号为1,2,3,·····,2的D次方减1。在结点1处放一个小猴子,它会往下跑。每个内结点上都有一个开关,初始全部关闭,当每次有小猴子跑到一个开关上时,它的状态都会改变,当到达一个内结点时,如果开关关闭,小猴子往左走,否则往右走,直到走到叶子结点。
一些小猴子从结点1处开始往下跑,最后一个小猴儿会跑到哪里呢?
- 输入
- 输入二叉树叶子的深度D,和小猴子数目I,假设I不超过整棵树的叶子个数,D<=20.最终以 0 0 结尾
- 输出
- 输出第I个小猴子所在的叶子编号。
- 样例输入
-
4 2 3 4 0 0
- 样例输出
-
12 7
第一感觉就是这题题目思路和清晰,一开始想到直接算出答案输出即可,考虑到正在学习数据结构,因此还是选择了用树进行暴力求解。
没想到运气好居然过了。供学习树的朋友一同学习。
下面还将讲解优化算法:
树写代码如下:
1 #include<iostream> 2 #include<queue> 3 #include<cmath> 4 using namespace std; 5 struct node 6 { 7 int data; 8 int flag; 9 node *lchild,*rchild; 10 node(); 11 }; 12 node::node() 13 { 14 flag=-1; 15 rchild=lchild=NULL; 16 } 17 void createTree(int d,node *&root) 18 { 19 queue<node *> q; 20 while(!q.empty()) 21 q.pop(); 22 root=new node; 23 static int count=0; 24 root->data=++count; 25 q.push(root); 26 node *t=root; 27 while(count!=pow(2,d)-1) 28 { 29 t=q.front(); 30 q.pop(); 31 t->lchild=new node; 32 t->lchild->data=++count; 33 q.push(t->lchild); 34 t->rchild=new node; 35 t->rchild->data=++count; 36 q.push(t->rchild); 37 } 38 t=NULL; 39 count=0; 40 } 41 /* 42 void LevelOrder(node *root) 43 { //队列实现 44 queue<node *> q; 45 node *t=root; 46 if(t!=NULL) q.push(t); //根非空,入队 47 while(!q.empty()) //队不空 48 { 49 t=q.front(); 50 q.pop(); //出队 51 cout<<t->data<<" "; 52 if(t->lchild) 53 q.push(t->lchild); //遍历左孩子 54 if(t->rchild) 55 q.push(t->rchild); //遍历右孩子 56 } 57 58 } 59 */ 60 void Go(int &t,node *&root) 61 { 62 if(root->lchild&&root->rchild){ 63 if(root->flag==-1) 64 { 65 Go(t,root->lchild); 66 root->flag=1; 67 } 68 else 69 { 70 Go(t,root->rchild); 71 root->flag=-1; 72 } 73 } 74 else 75 t=root->data; 76 } 77 78 int main() 79 { 80 int d,num; 81 while(cin>>d>>num,d&&num){ 82 node *root=NULL; 83 createTree(d,root); 84 int t; 85 for(int i=0;i<num;i++) 86 Go(t,root); 87 cout<<t<<endl; 88 } 89 return 0; 90 }
但是如果测试数据有N组,层数D有19层呢(D<=20),那么树将建立2^19-1个结点,时间和空间耗费都很大。那么怎么办?
下面讲一下优化算法:
1 | ||||||||||||||
2 | 3 | |||||||||||||
4 | 5 | 6 | 7 | |||||||||||
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
根据右图测试数据可知,一共有n行(3,4,5),x个猴子中每2^n出现一循环,理由就是它是满二叉树。
根据左图四层我们列出数据看看:
第1只猴子 | 1 | 2 | 4 | 8 |
第2只猴子 | 1 | 3 | 6 | 12 |
第3只猴子 | 1 | 2 | 5 | 10 |
第4只猴子 | 1 | 3 | 7 | 14 |
第5只猴子 | 1 | 2 | 4 | 9 |
第6只猴子 | 1 | 3 | 6 | 13 |
第7只猴子 | 1 | 2 | 5 | 11 |
第8只猴子 | 1 | 3 | 7 | 15 |
请读者看看四层二叉树(上左图)和上表中对比不难发现,进入第n个结点的次数i为奇数(即前面已有n-1过猴子访问过该结点),那么遍历其左子树根;
若为偶数,则遍历其右子树根。
因此,对照上表,得出规律:i为奇数,k=k*2;i=(i+1)/2;//第i个进入左子树
i为偶数,k=k*2+1;i=i/2; //第i个进入右子树
例如
第1个猴子:则对于第一个结点来说,i=1为奇数,那么下一个要走的结点k=1*2=2;然后i=(1+1)/2=1(第一个进入左子树),继续判断其左子树i的奇偶性……
第3个猴子:则对于第一个结点来说,i=3为奇数,那么下一个要走的结点k=1*2=2;然后i=(3+1)/2=2(第二个进入左子树)……
第5个猴子:则对于第一个结点来说,i=5为奇数,那么下一个要走的结点k=1*2=2;然后i=(5+1)/2=3(第三个进入左子树)……
……
1 for (int j=0;j<d-1;j++)
2 if(i%2) {k=k*2;i=(i+1)/2;}
3 else {k=k*2+1;i /=2;}
OK接着按照输入标准写出完整算法如下:
1
2 #include<iostream>
3 using namespace std;
4
5 int main()
6 {
7 int d,i,k;
8 while(cin>>d>>i && (d+i) !=0)
9 {
10 k=1;
11 for (int j=0;j<d-1;j++)
12 if(i%2) {k=k*2;i=(i+1)/2;}
13 else {k=k*2+1;i /=2;}
14 cout<<k<<endl;
15
16 }
17 }
当然,你可以将/2换成位运算左移一位,效率更高。