函数的递归与迭代(青蛙跳台阶与汉诺塔问题)
递归函数
定义
程序调用自身的编程技巧称为递归( recursion)。 递归作为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
注意
递归的两个必要条件:
-
存在限制条件,当满足这个限制条件时,递归不再继续 每次递归调用之后越来越接近这个限制条件
C通过运行时堆栈支持递归函数的实现,但堆栈并非递归的标准,由于堆栈的方式适合实现递归,所以我们现在接触到的大部分编译器都是采用堆栈实现递归
示例
接受一个整型值(无符号),按照顺序打印它的每一位。 例如: 输入:1234,输出 1 2 3 4
void print(int n) { if(n>9) { print(n/10); } printf("%d ", n%10); } int main() { int num = 1234; print(num); return 0; }
递归与迭代
求n的阶乘(不考虑溢出) 使用递归,函数写法为:
int factorial(int n) { if(n <= 1) return 1; else return n* factorial(n-1); }
递归函数的写法易懂且代码简单,但并不不是适用于所有满足递归条件的问题,上述函数调用时,参数进入堆栈,为局部变量分配空间。递归的调用返回值时,之前分配的空间均需还原,使得该函数许多步骤重复,效率低。当参数过大时,可能还会报错,因为系统分配给程序的栈空间有限,当程序出现死循环时,栈空间耗尽,栈溢出,程序报错 (`stack overflow) 如下图程序,设置全局变量count,最后输出可得一个巨大的count值,充分说明程序的效率低
int count=0; int factorial(int n) { if(n <= 1) return 1; else count++; return n* factorial(n-1); }
同样的,常见的求第n个斐波那契数也不适合使用递归的方法解题,这个时候我们可以考虑迭代法,虽然函数不如递归易懂,效率却高得多 迭代法是一类利用递推公式或循环算法通过构造序列来求问题近似解的方法,是一种不断用变量的旧值递推新值的过程。将求阶乘的函数改为迭代法:
int factorial(int n) { int result = 1; while (n > 1) { result *= n ; n -= 1; } return result; }
递归的典型运用
青蛙跳台问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级的台阶总共有多少种跳法?可得: n<=0; F (n) = 0 n=1; F (n) = 1 n=2; F (n) = 2 n>2; F (n) = F (n-1) + F (n-2) 代码表示为:
int jumpFloor(int n) { if(n<=0) { return 0; } else if((n==1)||(n==2)) { return n; } else { return jumpFloor(n-1)+jumpFloor(n-2); } }
汉诺塔问题
有三根相邻的柱子,标号为A,B,C,A柱子上从下到上按金字塔状叠放着n个不同大小的圆盘,要把所有盘子一个一个移动到柱子B上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方,请问至少需要多少次移动?可得代码:
char A,B,C; void print(char A,char C) { printf("%c--%c ",A,C); } void hanoi(int n,char A,char B,char C) { if(n==1) print(A,C); else { hanoi(n-1,A,C,B); print(A,C); hanoi(n-1,B,A,C); } } int main() { int n; void hano(int,char,char,char); scanf("%d",&n); hanoi(n,A,B,C); return 0; }