一、递归的思想和定义
(一)、孙子兵法道:凡治众如治寡,分数是也。在使用计算机解决问题时我们经常遇到的问题是规模较大的问题,不能直接求解,最普遍的方法就是分解问题。递归就是一种特殊的分治思想,在解决一个规模为n的大问题时,先将这个大问题分解为规模为k的与原问题在形式上相同且相互独立的子问题,若子问题不够小继续分解直到子问题小到能直接解决为至,然后解决各个子问题,自底向上的合并各个子问题的解形成原问题的解。
(二)、直接或间接地调用自身的算法称为递归算法 ,用函数自身给出定义的函数称为递归函数 。
二、常见的递归问题
1、n的阶乘
#include
//计算n!
double factorial(double n)
{
//边界条件
if(n==1)
return 1;
//递归方程
else
return n*factorial(n-1);
}
int main()
{
int n;
printf("please input n\n");
scanf("%d",&n);
printf("%d! =%.2f\n",n,factorial(n));
return 0;
}
无穷数列1,1,2,3,5,8,13,21,34,55, … ,被称为Fibonacci数列。它可以递归地定义为:
#include
//计算斐波那契数列的第n项
double Fibonacci(int n){
//边界条件
if(n<=2)
return 1;
//递归方程
else
return Fibonacci(n-1)+Fibonacci(n-2);
}
int main(){
int n;
printf("please input n \n");
scanf("%d",&n);
printf("Fibbnicc(%d) = %.2f\n",n,Fibonacci(n));
return 0;
}
#include
#define Length 5
//输出R中所有元素
void printAllElement(int* R){
int i;
for(i = 0; i < Length; i++){
printf("%d ",R[i]);
}
printf("\n");
}
//交换R[i],R[j]
void swap(int R[],int i, int j){
int temp;
temp = R[i];
R[i] = R[j];
R[j] = temp;
}
//递归排列 R[k]--R[m]中的(m-k+1)个元素
void perm(int* R,int start,int finish){
//边界条件(R中只有一个元素)
if(start == finish){
printAllElement(R);
}
//递归方程
else{
//循环指定R中的每个元素ri
for(int i = start; i < finish; i++){
//通过交换R[i]/R[start]实现指定R[i]到首位置
swap(R,start,i);
//对R中剩下的元素R(start+1,finish)进行排列
perm(R,start+1,finish);
//换回原位置
swap(R,start,i);
}
}
}
int main(){
int R[]={1,2,3,4,5};
perm(R,0,5);
return 0;
}
4、Hanoi塔问题
设有A、B、C三个塔座,起始时A上有n个圆盘(编号:1,2,3,...n)自项向下,由小到大排列。现要求把A上的n个圆盘移到到B上,并按原次序排列,移到过程中遵守以下三个规则:
void Hanoi(int n, int A, int B, int C)
{
if(n>0)
{
//先把A上的前n-1个借助B移动到C上
Hanoi(n-1,A,C,B);
//把A上的最下面的第n个移动到B
move(A,B);
//最后把C上的n-1个借助A移动到B上
Hanoi(n-1,C,B,A);
}
}
5、正整数的划分
将正整数n表示成一系列正整数之和:n=n1 +n2+ … +nk ,其中n1≥n2≥ … ≥nk≥1,k≥1,这种表示称为正整数n的划分。求正整数n的不同划分个数。
#include
//递归求解正整数n的划分数p(n)=q(n,n)
int qIntegerDivision(int n,int m){
//边界条件 q(1,1)=1, (n=1,m=1)
if(n == 1 && m == 1){
return 1;
}
//递归方程 q(n,n), (nm>1)
if(n > m && m > 1){
return qIntegerDivision(n,m-1) + qIntegerDivision(n-m,m);
}
}
int main(){
int n,m;
printf("please input n,m\n");
scanf("%d,%d",&n,&m);
printf("q(%d,%d)=%d\n",n,m,qIntegerDivision(n,m));
return 0;
}
6、Ackerman函数
当一个函数及其它的变量是由函数自身定义时,称这个函数是双递归函数。
#include
//Ackerman双递归函数
int ackerman(int n,int m){
//边界条件 A(1,0)=2
if(n == 0 && m == 1){
return 0;
}
//边界条件 A(0,m)=1, m>=0
if(n == 0 && m >= 0){
return 1;
}
//边界条件 A(n,0)=n+2, n>=2
if(n >= 2 && m == 0){
return n + 2;
}
//递归方程 A(n,m)=A(A(n-1,m),m-1), n,m>=1
if(n >= 1 && m >= 1){
return ackerman(ackerman(n-1,m),m-1);
}
}
int main(){
int n,m;
printf("please input n,m\n");
scanf("%d,%d",&n,&m);
printf("A(%d,%d)=%d\n",n,m,ackerman(n,m));
return 0;
}
三、递归优缺点
1、优点:结构清晰、设计简单、容易实现,可用数学归纳法来证明它的正确性。
2、缺点:运行效率低,需要耗费大量的CPU时间和内存空间,尤其当递归变量n大到增大到一定程度时,这种低效率更为明显。