前言:本人为C语言初学者,学识尚浅,研究程度存在很大的局限性,眼界很窄。以下所有观点仅代表个人见解和思路,各位游刃有余的前辈可以给予批评和指正!各位与鄙人同路的学子可相互探讨、发表看法,交换观点!
递归释义(个人理解):在函数中调用函数自身
递归的必要条件:1.递归必须存在限制条件,杜绝死递归
2.每次递归后要保证逼近限制条件,递归存在结束点
递归的特点:用少量代码完成多次类似的复杂运算
貌似递归听起来是很高大上的东西,给人一种很厉害的感觉。但众所周知,每次函数的调用就会占用栈区空间,当递归到很深的程度时,迎来了递归的致命伤:栈溢出。
#includevoid fun(int i) { if(i > 0) fun(i - 1); } int main() { fun(10000); return 0; }
首先,这串代码已经具备了两个必要的递归条件:
1.if(i > 0) 存在限制条件
2.fun(i - 1); 每次递归都在像限制条件靠近
但如果让代码跑起来,则得到了下面的结果:
代码直接挂掉了,如果我们F10调试一下,会发现问题:
非常明显的看到:Stack overflow -- 栈溢出
所以仅仅满足两个必要条件是不够的,这里通过一个简单的例子说明了递归不能太深,不然会导致栈溢出的问题。
当然,我们说递归用少量代码来完成复杂的计算,那么,递归一定可以使代码简化而实现同样的运算效果吗?在斐波那契数列中,我们得到了答案。
#define _CRT_SECURE_NO_WARNINGS 1 #includeint Fib(int n) { if (n <= 2) { return 1; } else { return Fib(n - 1) + Fib(n - 2); } } int main() { int num = 0; int ret = 0; scanf("%d",&num); ret = Fib(num); printf("ret = %dn", ret); return 0; }
以上代码即通过递归来实现斐波那契数求解,但是当我们需要求的位数足够大时,往往程序需要执行几分钟,CPU也处于满负荷状态,说明CPU并没有偷懒,那么罪魁祸首究竟是?
于是我们来模拟一些它的计算流程:
可以看到对于一个斐波那契数的求解,需要求的数呈树状图形式,为2的指数次增长,不知道是否大家还记得“J”型增长曲线。用红圈框出来的部分即是重复求解的部分,越往下,重复求解的越多,如果我们对代码进行一些小修改,看看他究竟算了多少次第三个斐波那契数,那么是这样的:
#define _CRT_SECURE_NO_WARNINGS 1 #includeint count = 0; //声明全局变量计算一下重复个数 int Fib(int n) { if (n == 3) //检索第三个斐波那契数被计算了多少次 { count++; } if (n <= 2) { return 1; } else { return Fib(n - 1) + Fib(n - 2); } } } int main() { int num = 0; int ret = 0; scanf("%d",&num); ret = Fib(num); - 这是递归计算法 printf("ret = %dn", ret); printf("第三个斐波那契数被计算了:%d次n", count); return 0; }
假设计算第30个斐波那契数,运行结果如下:
不言则明,递归在计算斐波那契数时完全不占优势,尽管它可以根据公式把代码简化到一定程度,但效率比迭代低得多。
故,如果我们用迭代的思路,可以轻松求解:
#define _CRT_SECURE_NO_WARNINGS 1 #includeint Fib_loop(int n) { int a = 1, b = 1,c = 0, i = 0; if (n <= 2) { return 1; } else { for (i = 3; i <= n; i++) { c = a + b; a = b; b = c; } return c; } } int main() { int num = 0; int ret = 0; scanf("%d",&num); ret = Fib_loop(num); //这是迭代计算法 printf("ret = %dn", ret); return 0; }
尽管我算第10000个斐波那契数,也可以瞬间得到答案(答案是错误的,但是验证了迭代的效率之高)
这是一些递归需要避雷的地方,当然,它的优点显而易见,只是在这里我不赘述,例如整数的拆分打印等等,我仅列出此代码供参考:
#define _CRT_SECURE_NO_WARNINGS 1 #includevoid print(unsigned int n) { if (n > 9) //1.递归必须存在限制条件 { print(n / 10); //2.每次递归后要保证向限制条件接近 } printf("%d ", n % 10); } int main() { unsigned int num = 0; printf("请输入一个正整数:"); scanf("%u", &num); print(num); return 0; }
接下来,我们讨论了这么多,是时候说一说递归的普遍思路和方法了。
以上代码我们可以知道,如果在知道公式的情况下,比如斐波那契数列:F(n) = F(n -1) + F(n - 2)
n >=2 , F(n) = 1 n< 2;完完全全就可以套公式到代码中,究其原因,还是因为我们知道了其实现原理,因为斐波那契数总是等于它前两项的和,我们发现了普遍规律。
例如如果我们想用递归求解n的k次方,如何找到它的普遍规律呢?n的k次方可以写成许多种形式,比如n*n^k-1, n*n^k-2,于此,我们也发现了它的普遍规律,每一次我们都可以剥离一个n出来,直到n的0次方。n的k次方,说白了也就是n个k相乘,我们要学会拆分整体为一个个相同的个体,n的k次方就完美的展示了这一点!
故递归的终极思路就是:化大为小,拆整为分
在整数的拆分打印中也是如此,这很直白的告诉了我们如果一个数字一个数字的剥离。
再比如字符串的逆序,如果我们想用递归实现“abcde”的逆序,我们就应该拆分成“a”和“bcde”逆序,在“bcde”中是“b”和“cde”逆序……如此下去,我们化大为小,逐步解决了问题。
以上就是我对于递归的一些简单思考和见解,学识尚浅,往指正!!!



