目录
函数的递归
递归的定义
程序调用自身的编程技巧称为递归( recursion)。 递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小,复用自身的代码。
递归的使用条件
- 递归必须有明确的限制条件,当遇到限制条件时不再继续进行递归,避免出现死递归。
- 每次递归会越来越接近递归的限制条件。
递归的应用
- 数据的定义是按照递归定义的。例如Fibonacci函数。
- 数据的结构形式是按照递归定义的。例如二叉树,广义表等。
- 问题解法按递归算法实现。如Hanoi问题。
递归的实现
递归实现Strlen
int Strlen(const char* str)
{
if(*str == '\0')
{
return 0;
}
return 1 + Strlen(str + 1);
}
int main()
{
char str = "abcdefg";
int len = Strlen(&str);
printf("%d", len);
return 0;
}
递归实现Fibonacci函数
int fib(int n)
{
if(n <= 2)
{
return 1;
}
return fib(n - 1) + fib(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
fib(n)
return 0;
}
递归实现阶乘
int factorial(int n)
{
if(n <= 1)
return 1;
else
return n * factorial(n - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = factorial(n);
printf("%d", ret);
return 0;
}
递归的缺陷
-
当我们计算第50个 Fibonacci 数时,发现代码的运行时间远远超出预期。
Fibonacci函数递归过程如下:
我们通过图示可以发现,Fibonacci 函数在递归的时候进行了太多重复操作。
当 n 等于50的时候,函数进行了250 -1次调用,232 是32亿9千万左右的数,250 更要远远超出这个数据。这无疑是一个很大的问题。 -
在调试 factorial 函数的时候,如果你的参数比较大,那就会报错:
stack overflow(栈溢出)
这样的信息。 系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。 -
在调试的时候,因为递归是一种自身调用,所以调试过程比较麻烦,所以在代码实现的时候,一定要考虑递归结束时的限制条件以及如何达到限制条件,尽量避免调试的麻烦。
改进递归的方法
- 递归改为非递归。
- 使用static全局对象替代局部对象。
在递归函数设计中,可以使用全局对象替代局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放局部对象的开销,而且全局对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
迭代
迭代的定义
迭代法也称辗转法,是一种不断用变量的旧值递推新值的过程,跟迭代法相对应的是直接法(或者称为一次解法),即一次性解决问题。迭代算法是用计算机解决问题的一种基本方法,它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值,迭代法又分为精确迭代和近似迭代。比较典型的迭代法如“二分法”和"牛顿迭代法”属于近似迭代法。
迭代的考虑因素
- 确定迭代变量
在可以用迭代算法解决的问题中,至少存在一个直接或间接地不断由旧值递推出新值的变量,这个变量就是迭代变量 - 建立迭代关系式
所谓迭代关系式,指如何从变量的前一个值推出其下一个值的公式(或关系)。迭代关系式的建立是解决迭代问题的关键,通常可以顺推或倒推的方法来完成。 - 对迭代过程进行控制
在什么时候结束迭代过程?这是编写迭代程序必须考虑的问题。不能让迭代过程无休止地重复执行下去。
迭代过程的控制通常可分为两种情况:一种是所需的迭代次数是个确定的值,可以计算出来;另一种是所需的迭代次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实现对迭代过程的控制;对于后一种情况,需要进一步分析出用来结束迭代过程的条件。
迭代的实现
迭代实现阶乘
int factorial(int n)
{
int result = 1;
while(n > 1)
{
result *= n;
n--;
}
return result;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = factorial(n);
printf("%d", ret);
return 0;
}
迭代实现Fibonacci函数
int fib(int n)
{
int result = 1;
int prev_result = 1;
int prev_prev_result;
while(n > 2)
{
prev_prev_result = prev_result;
prev_result = result;
result = prev_prev_result + prev_result;
--n;
}
return result
}
int main()
{
int n = 0;
scanf("%d", &n);
fib(n)
return 0;
}
递归和迭代的总结
- 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
- 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
- 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。