1. 问题描述
围绕着山顶有10个洞,狐狸想要吃兔子,兔子说:“可以,但必须找到我,我就藏身于这10个洞中,你从10号洞出发,先到1号洞找,第二次隔1个洞找,第三次隔2个洞找,以后如此类推,次数不限。”但狐狸从早到晚进进出出了1000次,仍没有找到兔子。问兔子究竟藏在哪个洞里?
2. 问题分析
(1) 把10个洞编上序号:1,2,3,4,5,6,7,8,9,0
(2) 狐狸从0号洞出发,第1次在1号洞找;第2次隔1个洞,在3号洞找;第3次隔2个洞,在6号洞找;第4次隔3个洞,在0号洞找;第5次隔4个洞口,即在5号洞找;以此类推。
(3) 可见,其实上述问题其实是一个等差数列求和问题(首项为1,公差为1)。将求和结果对10取余(求和结果%10),即可得到狐狸每次进去的洞口号。
(4) 如下图所示,仔细观察可以发现,如果狐狸连续两次都进入出发洞口(0号洞),表明接下来进入洞口的顺序与之前的顺序相同!因此遇到连续两次都进入出发洞口的情况,即可停止寻找,因为接下来只是重复之前找过的洞口。
3. 算法描述
(1) 首项为1,公差为1的等差数列求和公式:n*(n+1)/2,n为寻找的次数
(2) 狐狸每次进入的洞口号为:(n*(n+1)/2)%10,对10取余是因为有10个洞口
(3) 设x为寻找的次数,连续两次进入都是出发时的洞口(0号洞),可停止寻找。设
HOLENUM为洞口的个数,本例是10
即第x-1次:((x-1)*(x-1+1)/2)%HOLENUM == 0
第x次:(x*(x+1)/2)%HOLENUM == 0
一般的,x = 2*HOLENUM。即如果洞口有10个,则需要找20次,就会在遇到连续两次进入出发的洞口(本例是第19次和第20次)
特殊的,如果HOLENUM是(2^k +1),则 x = HOLENUM,(k = 0,1,2,3......)。即如果洞口有15个, 则只需找15次,就会遇到连续两次进入出发时的洞口(第14次和第15次)
(4) 可见,该算法的时间复杂度与洞口的个数有关,具体的时间复杂度为Θ(n)
4. 代码实现
1 #include <stdio.h> 2 #define HOLENUM 10 3 4 void FindRibbit(int Hole[HOLENUM]); 5 6 int main() 7 { 8 int hole[HOLENUM] = {0}; // 10个山洞 9 FindRibbit(hole); 10 return 0; 11 } 12 13 void FindRibbit(int Hole[HOLENUM]) 14 { 15 int i = 0; 16 int currenthole = 0, nexthole = 0; 17 for(i = 1; i <= 2 * HOLENUM; ++i) 18 { 19 currenthole = (currenthole + i) % HOLENUM; // 当前进去的山洞 20 Hole[currenthole % HOLENUM] = 1; 21 nexthole = (currenthole + i + 1) % HOLENUM; // 下一次进去的山洞 22 23 if(currenthole == 0 && nexthole == 0) // 如果连续两次进去的山洞都是出发时的洞口,则接下来的进洞顺序与前面的顺序重复,故可以跳出循环 24 break; // 本例是从10号(即0号)洞口出发 25 } 26 28 for(i = 0; i < HOLENUM; ++i) // 输出兔子可能躲藏的洞口 29 { 30 if(Hole[i] == 0) 31 printf("the ribbit may be in %d hole\n", i); 32 } 33 }
5. 测试结果
the ribbit may be in 2 hole the ribbit may be in 4 hole the ribbit may be in 7 hole the ribbit may be in 9 hole