目录
一、什么是递归?
程序调用自身的编程技巧称为递归( recursion)。递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
通俗来说,递归就是将大事化小的一种方法。
二、递归的两个条件
1.在递归时,必须加上限制条件,只有有了限制条件我们的函数才不会一直执行下去。
2.当我们的函数进行递归调用后,应该越来越接近限制条件。
三、练习讲解
接受一个整型值(无符号),按照顺序打印它的每一位。例如:输入:1234,输出 1 2 3 4.
#include <stdio.h>
void print(int n)
{
if (n > 9)
{
print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
int num = 1234;
print(num);
return 0;
}
当我们输入一个数字,我们需要按顺序打印出这个数字各个位上的数时,我们可以考虑进行函数的递归。
这个图中红色的线代表函数开始的调用,直到找到了函数的限制条件时返回,黑线表示在找到限制条件后然后一层一层的进行输出。
编写函数不允许创建临时变量,求字符串的长度。
#include <stdio.h>
int Strlen(const char* str)
{
if (*str == '\0')
return 0;
else
return 1 + Strlen(str + 1);
}
int main()
{
char* p = "abcdef";
int len = Strlen(p);
printf("%d\n", len);
return 0;
}
当我们需要求字符abcdef的长度时,如果不使用临时变量的话,我们就是可以采用递归的方式来编写程序。
我们可以来这么分析:
第一次a(bcdef),这样拆分开来,就可以求得a的字符长度,然后再来求bcdef的长度。
第二次 b(cdef),拆分开来,求得b的字符长度为1,再来求cdef的长度。
以此类推,我们就将一个复杂的问题拆解成一个一个的小块,当我们遇到我们的限制条件,即遇到\0时说明这个字符我们都计算到了。
在函数中,str是一个字符型指针,str+1,就可以遍历到下一个字符,以此类推,当我们遍历到\0时,为我们的限制条件,即可得到字符的个数。
三、递归与迭代
求 n 的阶乘。(不考虑溢出)
int factorial(int n)
{
if (n <= 1)
return 1;
else
return n * factorial(n - 1);
}
这是求阶乘的一个函数,他有自己的限制条件,当n>1时才开始递归调用。
在我们的数学中,例如5!=5*4*3*2*1,也等于5*4!,而我们就采取了第二种方法来计算阶乘。
求第n个斐波那契数。(不考虑溢出)
我们先要知道什么是斐波那契数列,他又被成为兔子数列,简单来说就是从第三个数起,等于前两个数字之和,1 1 2 3 5 8 13 21 34 ....
int fib(int n)
{
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
在这个函数递归中,限制条件为n>2,而每一次递归n都在减小,所以符合我们递归的要求。
但是我们发现 有问题 ;在使用 fib 这个函数的时候如果我们要计算第 50 个斐波那契数字的时候特别耗费时间。使用 factorial 函数求 10000 的阶乘(不考虑结果的正确性),程序会崩溃。为什么呢?我们发现 fib 函数在调用的过程中很多计算其实在一直重复。如果我们把代码修改一下:
int count = 0;//全局变量
int fib(int n)
{
if(n == 3)
count++;
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
我们发现程序当我们求第40个斐波那契数时,执行的很慢,而n等于3时被执行了很多次,这造成了严重的资源浪费,所以我们要对代码进行改进。
在调试 factorial 函数的时候,如果你的参数比较大,那就会报错: stack overflow (栈溢出)这样的信息。系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一 直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。
1. 将递归改写成非递归。2. 使用 static 对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
int fib(int n)
{
int result;
int pre_result;
int next_older_result;
result = pre_result = 1;
while (n > 2)
{
n -= 1;
next_older_result = pre_result;
pre_result = result;
result = pre_result + next_older_result;
}
return result;
}
在这里,我们定义三个变量,让它被加之和,就移向下一个数,最终得到第40个斐波那契数列。
1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。