递归详解以及汉诺塔的实现
1.递归的定义
递归指的是在函数的定义中使用函数自身的方法。(函数自己直接或间接调用自己)
简单来说,就是函数自己调用自己。
例如:
void function()
{
int i,j;
.......
function();//函数调用自己
}
函数的调用
在学习递归之前我们需要了解,函数是如何被调用的,一个函数为什么可以调用自己。
-
当在一个函数的运行期间调用另一个函数时,在运行被调函数之前,系统需要完成三件事:
- 将所有的实际参数,返回地址等信息传递给被调函数保存
- 为被调函数的局部变量(也包括形参)分配存储空间
- 将控制转移到被调函数的入口
-
从被调函数返回主调函数之前,系统也要完成三件事:
- 保存被调函数的返回结果
- 释放被调函数所占的存储空间
- 依照被调函数保存的返回地址将控制转移到调用函数
-
当有多个函数相互调用时,按照“后调用先返回”的原则,上述函数之间信息传递和控制转移必须借助”栈”来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就在栈顶分配一个存储区,进行压栈操作,每当一个函数退出时,就释放它的存储区,就进行出栈操作,当前运行的函数永远都在栈顶位置
-
A函数调用A函数和A函数调用B函数在计算机看来是没有任何区别的,只不过用我们日常的思维方式理解比较怪异而已
递归需要满足的三个条件
- 递归必须得有一个明确的中止条件
- 该函数所处理的数据规模必须在递减
- 这个转化必须是可解的
循环和递归的比较
-
递归
- 易于理解
- 运行速度慢
- 所需存储空间大
-
循环
- 不易于理解
- 运行速度快
- 所需存储空间小
2.递归的实现
接下来通过几个例子来实现递归:
- 求n的阶乘
- 求 1+2+3+…+100 的值
- 汉诺塔问题
1.求n的阶乘
n的阶乘为 1 × 2 × . . . × n 1\times2\times...\times n 1×2×...×n
-
循环实现
#include <stdio.h> int main() { int sum=1; int n=10; for(int i=1;i<=n;i++) { sum=sum*i; } printf("sum=%d",sum); }
-
递归实现
假设我们需要求5的阶乘,可以写成 5 × 4 ! 5\times 4! 5×4! ,现在我们只需要知道4的阶乘,而4的阶乘又可以写成 $4\times 3! $ ,以此类推, 3 ! = 3 × 2 ! 3!=3\times 2! 3!=3×2! , 2 ! = 2 × 1 ! 2!=2\times 1! 2!=2×1! ,而1的阶乘我们很容易知道就是它本身,所以我们可以求出2的阶乘,求出2的阶乘,我们又可以求出3的阶乘,…,所以我们最后就可以求出5的阶乘。
这就是递归的思想,将一个大的问题,往下拆分成较小的问题,再将较小的问题分为更小的问题。
我们现在需要求n的阶乘,而 n ! = n × ( n − 1 ) ! n!=n\times(n-1)! n!=n×(n−1)! ,所以我们只需要知道(n-1)的阶乘。
代码如下:
#include <stdio.h> int f(int n) { if(n == 1) return 1; else return n*f(n-1); } int main() { int n,sum; printf("请输入n:"); scanf("%d",&n); sum = f(n); printf("%d的阶乘为:%d",n,sum); return 0; }
2.求 1+2+3+…+100 的和
同样的思想,我们需要求1加到100的和,而1到100的和等于,1到99的和加上100,1到99的和等于,1到98的和加上99,…
代码如下:
#include <stdio.h>
int sum(int n)
{
if(n == 1)
return 1;
else
return n + sum(n-1);
}
int main()
{
int n=100;
int sums=0;
sums=sum(n);
printf("1+2+3+...+100=%d",sums);
return 0;
}
3.汉诺塔问题
汉诺塔的由来
- 汉诺塔是源自印度神话里的玩具。
- 上帝创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上安大小顺序摞着64片黄金圆盘。上帝命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另根柱子上。并且规定,在小圆盘的上面不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
- 有预言说,这件事完成时宇宙会在一瞬间闪电式毁灭。也有人相信婆罗门至今还在一刻不停地搬动着圆盘。
伪算法:
if(n > 1)
{
先把A柱子上的前n-1个盘子从A借助C移动到B
将A柱子上的第n个盘子直接移动到C
再将B柱子上的n-1个盘子借助A移动到C
}
代码实现:
#include <stdio.h>
int count = 1;//全局变量,用来记录移动的总步数
void hannuota(int n, char A, char B, char C)
{
/* 如果是1个盘子
直接将A柱子上的盘子从A移到C
否则
先将A柱子上的n-1个盘子借助C移到B
直接将A柱子上的盘子从A移到C
最后将B柱子上的n-1个盘子借助A移到C */
if (n == 1)
{
printf("%-2.2d.移动第%d个盘子 %c -> %c\n",count++, n, A, C);
}
else
{
hannuota(n - 1, A, C, B);
printf("%-2.2d.移动第%d个盘子 %c -> %c\n",count++, n, A, C);
hannuota(n - 1, B, A, C);
//需要注意的是,在传值的过程中,
//char A,char B,char C 的值是在不断变化的
}
}
int main()
{
int n;
printf("请输入盘子的个数n: ");
scanf("%d", &n);
hannuota(n, 'A', 'B', 'C');
return 0;
}