汉诺塔
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
不管这个传说的可信度有多大,如果考虑一下把64片金片,由一根针上移到另一根针上,并且始终保持上小下大的顺序。这需要多少次移动呢?这里需要递归的方法。假设有n片,移动次数是f(n).显然f(0)=0,f(1)=1,f(2)=3,f(3)=7,且f(n)=2*f(n-1)+1(n>=1)。此后不难证明f(n)=2^n-1。假设每秒钟移动一次,那么当n=64时,f(n)=18446744073709551615,将其转换成年,大约需要5849.42年。
了解这个背景之后,我们来学习一下怎么用C语言来求解汉诺塔问题。
分析:
其实我们可以发现其中圆盘的移动规律:
第一步:把n-1个圆盘由A移到B
第二步:把第n个圆盘由A移到C
第三步:把n-1个圆盘由B移到C
整体的思路就是将n-1个盘子移到B柱上,然后再将最大的圆盘移到C柱上,最后将n-1个盘子移动到C柱上。
代码实现:
void Move(char Pos1, char Pos2) { printf("%c->%c ", Pos1, Pos2); } //n:盘子的个数 Pos1:A起始位置 Pos2:B过渡位置 Pos3:C目的位置 void Hanoi(int n, char Pos1, char Pos2, char Pos3) { if (n == 1) { Move(Pos1, Pos3); } else { Hanoi(n - 1, Pos1, Pos3, Pos2);//借助Pos3将盘子移动到Pos2 Move(Pos1, Pos3);//将最大的盘子移动到Pos3 Hanoi(n - 1, Pos2, Pos1, Pos3); } } int main() { Hanoi(1, 'A', 'B', 'C'); printf("\n"); Hanoi(2, 'A', 'B', 'C'); printf("\n"); Hanoi(3, 'A', 'B', 'C'); printf("\n"); Hanoi(4, 'A', 'B', 'C'); printf("\n"); return 0; }
#include <stdio.h> void Hanoi_move(int n, char Pos1, char Pos2, char Pos3) { if (n == 1) { printf("%c->%c ", Pos1, Pos3); } else { Hanoi_move(n - 1, Pos1, Pos3, Pos2);//借助Pos3将n-1个圆盘从Pos1移动到Pos2 printf("%c->%c ", Pos1, Pos3);//将最大的圆盘从Pos1移动到Pos3 Hanoi_move(n - 1, Pos2, Pos1, Pos3);//借助Pos1将n-1个圆盘从Pos2移动到Pos3 } } int main() { Hanoi_move(1, 'A', 'B', 'C'); printf("\n"); Hanoi_move(2, 'A', 'B', 'C'); printf("\n"); Hanoi_move(3, 'A', 'B', 'C'); printf("\n"); Hanoi_move(4, 'A', 'B', 'C'); printf("\n"); return 0; }
输出结果:
汉诺塔移动次数的代码示例:
因为我们知道汉诺塔移动次数的递推公式为f(n)=2*f(n-1)-1,所以递归的代码就变得很简单了。
#include <stdio.h> long long Hanoi_sum(int n) { //n为圆盘的个数 if (n == 1) return 1; else return 2 * Hanoi_sum(n - 1) + 1; } int main() { int n = 0; printf("请输入圆盘数:>"); scanf("%d", &n); long long sum = Hanoi_sum(n); printf("sum = %lld\n", sum); return 0; }
青蛙跳台阶
一只青蛙一次可以跳上 1 级台阶,也可以跳上2 级。求该青蛙跳上一个n 级的台阶总共有多少种跳法。其实青蛙跳台阶也是典型的斐波那契递归问题,现在我们就来分析一下。
题目分析:
第一步,递推:目标是想求n级台阶有多少种走法,现在先假设已经走完了n级台阶同时假设存在f(n)种走法可以走完n级台阶,现在退回到走完这n级台阶的上一步,即走完这n级台阶的最后一步,最后一步有两种可能的情况,第一种情况是这一步只走了1级台阶,即完成最后一步之前已经走了n-1级台阶,那么走完n-1级台阶存在f(n-1)种走法;第二种情况是最后一步走了两级台阶,即完成最后一步之前已经走了n-2级台阶,那么走完n-2级台阶存在f(n-2)种走法。那么理一下思路:完成n级台阶最后一步之前需要走完n-1级台阶或者n-2级台阶,因为n级存在台阶f(n)种走法,n-1级台阶存在f(n-1)种走法,n-2级台阶存在f(n-2)种走法,所以f(n)=f(n-1)+f(n-2);继续递推,完成n-1级台阶的最后一步时之前肯定走了n-2或n-3级台阶,再继续递推,走完n-2级台阶的最后一步时之前肯定走了n-3或n-4级台阶,一直递推下去,则有:f(n) = f(n-1)+f(n-2) , f(n-1) = f(n-2)+f(n-3) , f(n-2) = f(n-3)+f(n-4) , f(n-3) = f(n-4)+f(n-5) , ...... , f(4) = f(3)+f(2) , f(3) = f(2)+f(1) 至此,递推结束。
第二步,回归:分析上面递推中的表达式可知,f(n)的结果依赖于f(n-1)和f(n-2),f(n-1)的结果依赖于f(n-2)和f(n-3),每一项都是未知数,无法求解,只能一直依赖下去,直到最后依赖到f(1)和f(2)。也就是说只要f(1)和f(2)不是未知数,就可以逆着递推的顺序回归到f(n),解出f(n)。现在就要求f(1)和f(2),根据递推中的假设规律f(1)是指走1级台阶的走法,因为一级台阶只有一种走法,因此f(1)=1;f(2)是走2级台阶的走法,二级台阶的走法可以是先走一级再走一级或者直接走两级,因此f(2)=2。
如果看懂了上面的分析,现在我们写代码就很简单了,就相当于求第n个斐波那契数是多少。
代码示例:
int Fib(int n) { if (n <= 2) return 1; else return Fib(n - 1) + Fib(n - 2); } int main() { int n = 0; printf("请输入台阶数:>"); scanf("%d", &n); int ret = 0; ret = Fib(n); printf("跳完%d个台阶共有%d种跳法\n", n, ret); return 0; }
以上的两道题目就是非常经典的递归题目啦,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家啦!
结语
每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根。