递归 recursion
- 程序调用自身的编程技巧,称为递归;
- 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解;
- 递归的策略只需少量的程序就可描述出解题过程所需的多次重复计算,大大减少程序的代码量;
- 注意思想方式在于:大事化小;
必要条件
- 存在限制条件,当满足这个限制条件的时候,递归将不在继续,否则无限调用;
- 每次递归调用之后越来越接近这个限制条件;
//如n=1234,则打印1 2 3 4
void print(unsigned int n)
{
if (n > 9)
print(n / 10);
printf("%d ",n % 10);
}
//编写函数不允许创建临时变量,求字符串的长度
int myStrlen(char* str)
{
if ('\0' == *str) //限制条件
return 0;
else
return 1 + myStrlen(str + 1); //越来越接近‘\0’
}
int main()
{
char str[] = "abcd";
printf("%d", myStrlen(str));
}
注:递归层次不要太深,否则也会出现栈溢出情况;因为每调用一次即开辟一块内存,如果调用次数过多,即会有可能造成栈溢出;
//递归方式
//n的阶乘
int factorial (int n)
{
if(n <= 1)
return 1;
else
return n * factorial(n-1);
}
//注:求10000的阶乘时程序会崩溃;
//递归方式
//斐波那契
int fib(int n)
{
if(n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
//注:求50以上的斐波那契时,非常耗时,有可能栈溢出;
注:
- 栈区,局部变量、函数形参;
- 堆区,动态内存(malloc/calloc/realloc);
- 静态区,全局变量、静态变量;
程序员的知乎:Stack Overflow - Where Developers Learn, Share, & Build Careers
解决办法
- 将递归改写成非递归,如循环迭代;
- 使用static对象替代nonstatic局部对象。在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销, 而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问;
提示:
- 许多问题是以递归形式进行解释的,这只是因为它比非递归形式更为清晰;
- 迭代实现往往比递归实现效率更高,但代码的可读性稍微差些;
- 当一个问题相当复杂,难以迭代实现时,递归实现的简洁性可补偿运行时的开销;
//循环迭代方式
//n的阶乘
int factorial(int n)
{
int result = 1;
while (n > 1)
{
result *= n;
n -= 1;
}
return result;
}
//循环迭代方式
//斐波那契
int fib(int n)
{
int result;
int prepre;
int pre;
result = pre = 1;
while (n > 2)
{
n -= 1;
prepre = pre;
pre = result;
result = prepre + pre;
}
return result;
}
案例
- 汉诺塔问题
- 青蛙跳台阶问题