前提条件
- 熟悉C语言与指针
- 熟悉数据结构与算法
简介
- 程序调用自身的编程技巧称为递归( recursion)。递归做为一种算法在程序设计语言中广泛应用。
- 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
- 递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
- 从认知事物的角度看,递归是一种定义方式,它与一般定义方式的不同之处在于“使用被定义对象的自身来为其下定义”。
- 在定义中使用自身进行递归描述的数据结构称为递归数据结构。
- 例如,二叉树和树等是典型的递归数据结构。对递归数据结构的常用操作都可以采用递归算法实现。
- 从程序设计的角度看,递归是一种程序设计方法。函数直接或间接地调用自身,称为递归调用。
- 含有递归调用的函数称为递归函数。
递归调用是用相同的策略去解决规模更小的问题,直至问题规模小于或等于某个边界条件时,不再进行递归调用,而是直接处理。
递归基础
汉诺塔问题
-
汉诺塔问题是一个源于印度古老传说的游戏:
-
有3根柱子A、B和C,还有一组数量为n、大小不一的圆盘。
-
开始时,所有圆盘从大到小叠放在A柱上。游戏的任务是将所有圆盘从A移到C,其中B柱可以作为临时的中转柱使用。
-
游戏规则:
- (1)在任何时刻都不允许大盘子在小盘子之上。
- (2)每次只能移动一个盘子。
-
现从1开始对圆盘从小到大编号。圆盘数量决定了汉诺塔问题的复杂程度。当n=1时,可直接将盘子从A移到C。
-
当n=2时,由于规则限制,既不能将两个盘子立即从A移到C,也不能直接把2号盘抽出来移到C,所以为了移动2号盘,唯有先把1号盘从A移到B,然后将2号盘从A移到C,最后将1号盘从B移到C。
-
当n=3时,仍然可以沿用n=2时的思路,为了移动3号盘,先将1号盘和2号盘从A移到B,剩余步骤也类似。如图所示,步骤(1)和(3)虽然不能一次完成,但盘子数和处理方法却与n=2时相同,仅仅是要移到的目标柱不同。
-
由上述分析可以得到n>1时的一般求解步骤。
- (1)将1至n-1号盘从A柱移动到B柱。
- (2)将n号盘从A柱移动到C柱。
- (3)将1至n-1号盘从B柱移动到C柱。
-
其中步骤(1)和(3)是求解n-1规模的汉诺塔问题,可用递归函数调用实现。
完整代码
#include <stdio.h>
int cnt=0;//全局变量
void move(int id, char from, char to) // 打印移动方式:编号,从哪个盘子移动到哪个盘子
{
++cnt; // 记录走过的步数
printf ("step %d: move %d from %c->%c\n", cnt, id, from, to);
}
void hanoi(int n, char x, char y, char z)
{//以y为中转柱,将n个盘从源柱x移到目标柱z
if (1 == n)
move(1, x, z);//将1号盘从x柱移到z柱
else{
hanoi(n - 1, x, z, y);//将n-1号盘从x柱移到y柱,z为中转柱
move(n, x, z);将n号盘从x柱移到z柱
hanoi(n - 1, y, x, z);//将1号盘从y柱移到z柱,x为中转柱
}
}
int main() {
int n=3;
hanoi(n, 'A', 'B', 'C');
return 0;
}
输出结果
递归函数执行过程
- 在程序执行过程中,递归函数调用与一般函数调用的处理相同。但在递归函数的执行过程中,每次递归调用时,总会跳转至函数的入口处执行,造成重新开始执行的假象。实际上在递归调用中,虽然代码被重复执行,但每次执行时程序的运行空间(包括局部变量、传递的实参和返回结果)并不相同,所以两次递归调用的执行过程其实是完全不同的。
- 函数递归调用的嵌套层数称为递归层次。其他函数对递归函数的调用称为第0层调用。
- 例如,某函数调用了汉诺塔函数hanoi(3,A,B,C),下图给出了在第0层第4行发生的递归调用和返回过程,其中,①是向第1层递归调用传递实参(2,A,C,B),目的是将1~2号盘从A柱移到B柱,C柱为中转柱,⑥是第1层执行完毕后返回至第0层的位詈。②和④都是进入第2层的调用,但需要注意两次进入第2层所传递的实参不同,分别为(1,A,B,C)和(1,C,A,B)。③和⑤分别是②和④对应的调用返回步骤。
一些简单的递归应用
求n!
long fact(int n)
/*求n!*/
{
if(n==1) return 1;/*第一部分,始基*/
else
{
return n*fact(n-1);/*第二部分,递归前进*/
}
}
求整数数组最大值
int maxint(int A[],int k1,int k2)
/*求A[k1]~A[k2]中的最大值并返回*/
{
if(k1==k2) return A[k1];
else
{
int m=maxint(A,k1+1,k2);
return(A[k1]>m)?A[k1]:m;
}
}
完整代码
#include<stdio.h>
#include<stdlib.h>
long fact(int n)
/*求n!*/
{
if(n==1) return 1;/*第一部分,始基*/
else
{
return n*fact(n-1);/*第二部分,递归前进*/
}
}
int maxint(int A[],int k1,int k2)
/*求A[k1]~A[k2]中的最大值并返回*/
{
if(k1==k2) return A[k1];
else
{
int m=maxint(A,k1+1,k2);
return(A[k1]>m)?A[k1]:m;
}
}
int main(){
printf("%ld\n",fact(4));/*求4!*/
int A[6]={4,6,5,1,3,2};
printf("%d\n",maxint(A,0,5));/*输出数组A的最大值*/
system("pause");
return 0;
}
输出结果
参考文献
[1] 严蔚敏,吴伟民. 数据结构(C语言版). 北京: 清华大学出版社,2020
[2] 严蔚敏,李冬梅,吴伟民. 数据结构(C语言版)(第二版). 北京: 人民邮电出版社,2021
[3] 吴伟民,李小妹,刘添添,黄剑锋,苏庆,林志毅,李杨.数据结构. 北京:高等教育出版社,2017
[4] 张淑平、覃桂敏. 程序员教程(第5版). 北京:清华大学出版社,2018