首先先对递归进行入门。
递归是以自相似的方式重复项目的过程。在编程语言中,如果程序允许您在同一函数内调用函数,则称其为函数的递归调用。
简而言之,递归就是函数的自身调用。可以看看下面的递归使用:
void Recursive() { Recursive();//call itself } int main(void) { Recursive(); system("PAUSE"); return 0; }
借前辈一句话,递归定义就是:递归中的“递”就是入栈,递进;“归”就是出栈,回归。
因为递归在整个函数结束时才释放数据区,而每一次调用函数都会存储临时的变量,因此递归次数过多,会造成栈溢出,上面的例子就会出现这种状况。
如果你会将递归与return联系起来,但实际上return的作用只是将值返回给调用参数的函数。
N项求和
我们以前都计算过求1+2+3+4+...+n,n项求和。现在要求我们使用递归写出来。
1.我们设第n项的和为sum(n),而前n项之和,可以由前n-1项之和加第n项。用表达式就是:sum(n-1) + n。
可以得到关系式:sum(n) = sum(n -1) + n;
2.接下来我们可以想一下,sum(n-1)又等于前一项加n-1一直循环下去计算,直到sum(2) = sum(1) + 2;计算完毕,此时sum(2)是我们要求的值,sum(1)是未知的,因此我们还需要知道sum(1)的值,才能求前n项和。
由1, 2的叙述,我们列出:
sum(n) = sum(n-1) + n; sum(1) = 1;
我们将第一个式子称作为“关系”, 第二个式子称作“出口”(可以理解为结束递归的条件)。
由此我们可以写出程序:
#include <stdio.h> #include <stdlib.h> int sum(int n) { if (n == 1) { return 1; } else { return sum(n - 1) + n; } } int main(void) { int k = sum(100); printf("%d\n", k); system("PAUSE"); return 0; }
Question:
接着我们可以试着自己做一下n!的递归计算,同样是第n项等于 前n-1项相乘 * 第n项,出口为第1项,当然出口也可以为第m项(m>0&&m<=n),但我们这里算n!,就不管了。
奇/偶数求和
同样,对于奇数,偶数求和也就是前n项的变型,这里不再说,我们这里可以对奇/偶数求第n项的值,进行递归计算。这里举例奇数计算:1+3+5+7...,设num(n)为第n个奇数。
1.通过第一个例子我们首先可以列出关系,num(n) = num(n - 1) + 2;
2.写出出口,num(1) = 1;
写出主要程序:
int num(int n) { if (n == 1) { return 1; } else { return num(n - 1) + 2; } }
斐波那契数列(Fibonacci sequence)
接着我们看看 斐波那契数列:1, 1, 2, 3, 5, 8, 13...
得出规律,后一项等于前两项相加。写出关系式f(n) = f(n-1) + f(n-2);
随之我们对关系式的出口(结束条件)进行判断,我们需要求f(n),而f(n-1)和f(n-2)都是未知的,我们只写其中一项为出口都是不够的,因此我们需要两个出口。f(1) = 1; f(2) = 1;
通过关系和出口,我们写出:
f(n) = f(n-1) + f(n-2); f(1) = 1; f(2) = 1;
写出程序:
#include <stdio.h> #include <stdlib.h> int f(int n) { if (n == 1) return 1; if (n == 2) return 1; return f(n - 1) + f(n - 2); } int main(void) { int k = f(7); printf("%d\n", k); system("PAUSE"); return 0; }
可以发现越高层的函数调用,自身调用的次数越多。
数组求和
使用递归,对数组array[] = { 1, 2, 3, 4, 5, 6};求和。
和之前n项求和思想相似,不过这里多了将数组地址传入,同样我们可以将数组关系写出 sum(array, n) = sum(array, n-1) + array[n]; 注意:我们这里传入的n应当是数组的最大下标(数组从0~n-1,n个数)。
很显然作为递归出口的应当是当数组下标为0时,sum(array, 0) = array[0];
我们可以写出程序:
#include <stdio.h> #include <stdlib.h> int sum(int *arr, int n) { if (n == 0) return arr[0]; return sum(arr, n - 1) + arr[n]; } int main(void) { int array[] = { 1, 2, 3, 4, 5, 6 }; int k = sum(array, sizeof(array) / sizeof(int) - 1);//这里填数组最大下标 //int k = sum(array, 5); printf("数组元素之和:%d", k); system("PAUSE"); return 0; }
汉诺塔问题
有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆环,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至C杆:
- 每次只能移动一个圆盘;
- 大盘不能叠在小盘上面。
这道题的解题步骤就三个:
- 将A(source)杆中前n - 1个盘移到B(auxiliary)杆;
- 将A(source)杆最后一个盘移到C(destination)杆;
- 将B(auxiliary)杆n - 1个盘移到C(destination)杆;
动态图演示(借前辈图一用)
如果这样说你还是不能理解过程,那么我们就回想一下之前的n项求和,我们将前n-1项 + 第n项。那么在这里,我们将前n-1个盘看成一个整体(盘的位置不变),将最后一个大盘看成一个整体,先将那一大坨移到B杆,再把A杆剩下的那个大盘移到C杆,然后我们再把那一大坨移到C杆。
(此地插图)
整体过程:
a.同样的这道题我们通过解题步骤去找关系式:(整个函数的声明是void Hanoi(int n, char SourcePole, char AuxiliaryPole, char DestinationPole);)
- Hanoi(n - 1, SourcePole, DestinationPole, AuxiliaryPole);
- printf("将盘%d,从%c柱------>%c柱\n", n ,SourcePole, DestinationPole);
- Hanoi(n - 1, AuxiliaryPole, SourcePole, DestinationPole);
(因为输出对象是SourcePole到DestinationPole,因此我们要将A杆的盘转移到B杆上,就需要在递归调用函数,传入参数时,将参数换位。)
b.接着我们写出口,移动n - 1个盘,也就是1~(n -1),当n = 0时结束函数。
因此写出程序:
#include <stdio.h> #include <stdlib.h> void Hanoi(int n, char SourcePole, char AuxiliaryPole, char DestinationPole){ if(n == 0){ return; } Hanoi(n - 1, SourcePole, DestinationPole, AuxiliaryPole); printf("将盘%d,从%c柱------>%c柱\n", n ,SourcePole, DestinationPole); Hanoi(n - 1, AuxiliaryPole, SourcePole, DestinationPole); } int main(void) { Hanoi(3, 'A', 'B', 'C'); system("PAUSE"); return 0; }
当然,对于出口也有另一种,盘数是从1~(n-1)的,当n = 0时结束入栈,当n = 1时恰好是最后一个入栈的。因此,可以当n = 1时进行一次移盘操作之后结束入栈。
此时的代码为(将SourcePole...更换变量名,便于读者阅读):
#include <stdio.h> #include <stdlib.h> void Hanoi(int n, char A, char B, char C){ if(n == 1){ return printf("将盘%d,从%c柱------>%c柱\n", n ,A, C); } Hanoi(n - 1, A, C, B); printf("将盘%d,从%c柱------>%c柱\n", n ,A, C); Hanoi(n - 1, B, A, C); } int main(void) { Hanoi(3, 'A', 'B', 'C'); system("PAUSE"); return 0; }
还有一道从N个球中取M个球的递归问题也不错,有兴趣可以看:点击链接
可以看看这个,绕一下入栈出栈,理解递归的执行。(PS:等会起来解析)
#include <stdio.h> #include <stdlib.h> void func(int num) { while(num>1) { //func(num-1); printf("%c",65+num-1); printf("%c",65); func(--num); } } int main(void) { int num; scanf("%d",&num); func(num); return 0; } /* --num CABABA --num变成2进行递归,同时,进入循环进行判断,所以打印两次2的值,然后,都变为1,再次分别进入循环与递归,不满足 退出 num-- CACA... 首先num进入,分别进入循环和递归,在递归中,num自减1,等待本次递归结束,因为num=3时恒大于1,所以递归一直未结束,造成死循环。 num-1 CABABAB...首先,num-1,变成2,2进入,循环,递归,变成,1,递归不满足,退出本次递归,回到上次递归,变成2,2进入循环,递归,变成1... 前: --num AABAAA num-- 崩溃 num-1 BABA...*/
各位读者能够有收获便是我最大的快乐!写教程不易,熬夜伤身,有个赞什么的,我也是不介意滴!哈哈哈!