浅谈递归函数—C语言
递归函数的概念
- 递归函数的定义:函数在其函数体内直接或间接调用它本身,则称该函数为递归函数。
- 递归的两个必要条件:(1)存在限制条件,当满足这个限制条件的时候,递归便不再继续;(2)每次递归调用之后越来越接近这个限制条件。
- 递归的主要思想:通过少量程序来描述解决问题过程所需的多次重复计算,即所谓的大事化小。
- 拓展:每一次调用递归函数时,计算机都会为该函数重新分配新的内存空间,并且将原递归函数暂停(可以理解为开副本),然后现场保存一下,即利用堆栈保存暂定位置,以及参数值保存。
例题
编程离不开示例,我们现在以例题帮我们加深对递归函数理解。
1.计算n!(1×2×3×·······×n)
先来看看整个的代码:
#include<stdio.h>
int Fac(int n)
{
if (n <= 1)//0!=1 1!=1
return 1;
else
return n*Fac(n - 1);//递归函数,自己调用自己
}
int main()
{
int n = 0;
scanf("%d", &n);//存储用户输入的值
int ret = Fac(n);
printf("%d\n", ret);
return 0;
}
我们以n=3为例子,来看看下面这张图的思路
要用递归我要先找到需要重复计算的部分:n*Fac(n - 1),以及递归函数的出口n <= 1,有了这两个我们才可以设计程序。
拓展:每次调用递归函数,都会利用堆栈保存现场,以刚刚计算阶乘为例,其在堆栈保存如图所示,先入的放在最底下,这样返回的时候,程序就可以清晰的直到自己要去到什么位置,从什么位置继续执行程序,后面进入先出来,当然这是比较抽象理解,在计算机内部都是以二进制形式表示。
2. 计算斐波那契数
的确斐波那契数考虑到效率,较优解并不是以递归的形式,而是以迭代循环的形式计算,这里举例斐波那契数列只是为了了解递归。
首先理解什么是斐波那契数:
1 1 2 3 5 8 13 21 34.........
从数学定义角度来看:
Fib(0)=1
F(1)=1
F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)
可以直观看出斐波那契数列在n>=2后其值为前两项之和
同样先看看代码:
int Fib(int n)
{
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
return 0;
}
我们以n=3为例子,来看看下面这张图的思路:
要用递归我要先找到需要重复计算的部分:Fib(n - 1) + Fib(n - 2),以及递归函数的出口n <= 2,有了这两个我们才可以设计程序。
3.汉诺塔问题
汉诺塔游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置n个金盘,游戏目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。
为了方便理解,用动态图来表示:(图片来源知乎)
用递归的思想,我们要找到递归设计的两个重要部分:(1)重复部分,即自相似部分;(2)递归的出口。
(1)重复部分:不难发现,圆盘移动分三步:
第一步 把n-1个圆盘从A杆经C杆移动到B杆;
第二步 将A杆上第n个圆盘移动到C杆;
第三步 然后再将n-1个圆盘从B杆经A杆移动到C杆。
同理移动n-1个圆盘与移动n个圆盘是相识的,但是又有细微的不同,就是圆盘的插入杆变化了。
所以我就先制定函数的参数:
int hanoi(int n, char A, char B, char C)
//hanoi 汉诺塔
//n表示圆盘数
//上式子可以表示为将n个圆盘从A经B移动到C
提取重复部分:
hanoi(n - 1, A, C, B)
//将n-1个圆盘从A经C移动到B
move(A, C);
//将第n个圆盘从A移动到C
hanoi(n - 1, B, A, C)
//将n-1个圆盘从B经A移动到C
(2)递归函数的出口:
但N=1 时 ,只剩下一个圆盘直接移动到C杆就可以
move(A, C)
//本质是打印:A=====> C
#include <stdio.h>
int count = 0;//计算移动次数
void move(char x, char y)
{
printf("%c =====> %c\n", x, y);
count++;
}
int hanoi(int n, char A, char B, char C)// n表示圆盘个数,A,B,C为三个杆子,表示从A经由B移动到C
{
if (n == 1)
move(A, C);
else
{
hanoi(n - 1, A, C, B);
move(A, C);
hanoi(n - 1, B, A, C);
}
}
int main()
{
int n;
printf("请输入圆盘数:\n");
scanf("%d", &n);
count=hanoi(n,'A','B','C');
printf("总共移动%d次",count);
return 0;
}
总结
- 递归程序设计的实质:把一个复杂的问题分解成若干子问题来处理时,其中某些子问题与原问题有相同的特征属性,则可以利用和原问题相同的分析处理方法(注意原问题与子问题能很好衔接)。
- 在设计递归函数时候,要将每个递归函数看成简单的操作, 不要想得过于复杂。