小论c语言递归与递推

来源:互联网 发布:python网页架构 编辑:程序博客网 时间:2024/04/20 01:41

递归和递推都是算法设计中的难点,算法又十分相近,很多和我一样学生误认为是一回事,非常容易混淆。其实它们之间既有相似点,又有明显的区别。

递推一般用循环来解决,从已知条件到未知逐渐接近结果;

(1)将复杂运算分解为若干重复的简单运算

(2)后一步骤建立在前一步骤之上

(3)计算每一步骤的方法相同

(4)从开始向后计算出结果

(5)使用循环结构,通过多次循环逐渐逼近结果

递归一般自己调用自己,从未知到已知,把规模大的、较难解决的问题变成规模较小的、易解决的同一问题。规模较小的问题又变成规模更小的问题,并且小到一定程度可以直接得出它的解,从而得到原来问题的解。

(1)每一次递归都缩小问题规模,直到问题足够小

(2)使用选择分支语句

(3)从后往开始逐步逼近

(4)达到最开始,再把初始值带入往后逐一求解


下面通过例子逐个介绍。

递推:

一。 解阶乘   n! = 1*2*3*4*....*(n-1)*n.

fun(int n) {      long s=1;   int i;   for(i=1;i<=n;i++)   s=s*i;   return s; }


二。捕鱼问题 

A,B,C,D,E五个渔夫夜间合伙捕鱼,,第二天清A先醒来,他把鱼均分五份,把多余的一条扔回湖中,便拿了自己的一份回家了,B醒来后,也把鱼均分五份,把多余的一条扔回湖中,便拿了自己的一份回家了,C,D,E也按同样方法分鱼。问5人至少捕到多少条鱼?

这也是一个递推问题,递推关系式为   F(n+1) = F(n)*5/4+1  (i = 1,2,3,4)  F(5)即捕鱼的总数

#include <stdio.h>int main(){    int i, n, f[5], flag;    flag = 1;    n = 1;    while (flag == 1)    {        f[0] = 5*n+1;          flag = 0;        for (i=1; i<5; i++)        {            if (f[i-1]%4!=0)            {                flag=1;                break;            }            f[i] = 5*f[i-1]/4+1;        }        n++;    }    printf("%d\n",f[4]);    return 0;}


三。平面分割

问题:在平面上画n条封闭的曲线,各曲线之间两两相交于两点,并且任意三条封闭的曲线都不相交于一点,求这样的n条曲线将平面分成多少个区域?
输入:输入多组测试数据,n=0表示输入介绍。

问题分析:设满足条件的n条封闭曲线可将平面分成an个区域。则
当n=1时,A1=2.
当n=2时,A2=4
当n=3时,A3=8
设当有n-1条曲线时可将平面分成A(n-1)个区域,此时,加入第n条曲线,因为各曲线之间两两相交于两点,所以,第n条封闭曲线与前n-1条曲线共有2*(n-1)个交点,这些交点将第n条曲线截为2*(n-1)段,而每一段将其所在区域一分为二,所以增加了2*(n-1)个区域。
所以有递推关系:    An = A(n-1) + 2*(n-1)   其中,a1 = 2, a2 =4
#include <stdio.h>int main(){    int i, n, f2, f1;    while (1)    {        scanf("%d",&n);        if (n==0)        {            break;        }        f1 = 2;        for (i=1; i<=n; i++)        {            f2 = f1+2*(i-1);            f1 = f2;        }        printf("%d\n",f2);    }    return 0;}




可见递推也是一种思想,是从已知条件出发,用一种具体的算法,一步一步接近未知,一般采用循环结构。递推算法在求解的过程中,每一个间量都是已知,而且没有重复计算,运算简洁,但是书写代码和l理解代码比较难。


递归:

一。计算组合数

问题:计算组合数C(10,3)(组合数:从n个不同元素中,任取m(m≤n)个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合;从n个不同元素中取出m(m≤n)个元素的所有组合的个数,叫做从n个不同元素中取出m个元素的组合数。

我们可以利用组合恒等式:

若表示在n个物品中选取m个物品,则如存在下述公式: C(n,m)= C(n,n-m)= C(n-1,m-1)+C(n-1,m)。典型的递归
#include <stdio.h>int Cmn(int m, int n ){    if (m<0 || m<n || n<0)    {        return 0;    }        if (m==n) {        return 1;    }    if (n==1)    {        return m;    }    return Cmn(m-1,n)+Cmn(m-1,n-1);}int main(){    printf("C(10,3) = %d \n",Cmn(10,3));    return 0;}


二。 解阶乘   n! = 1*2*3*4*....*(n-1)*n.

递归公式:
n! = 1 (n=0,1)
n!=n*(n-1)!  (n>1)
long ff(int n){    long f;    if(n<0) printf("n<0,input error");    else if(n==0||n==1) f=1;    else f=ff(n-1)*n;    return(f);}main(){    int n;    long y;    printf("\ninput a inteager number:\n");    scanf("%d",&n);    y=ff(n);    printf("%d!=%ld",n,y);}

三。Hanoi 汉诺塔问题

一块板上有三根针,A,B,C。A针上套有64个大小不等的圆盘,大的在下,小的在上。要把这64个圆盘从A针移动C针上,每次只能移动一个圆盘,移动可以借助B针进行。但在任何时候,任何针上的圆盘都必须保持大盘在下,小盘在上。求移动的步骤。

本题算法分析如下,设A上有3个盘子:

1将A上的2个圆盘移到B

2将A上的1个圆盘直接移到C

3将B上的2个圆盘移到C其中第2步可以直接实现

将第1步分解:(借助C)

1.1    将A上的一个圆盘从A移到C

1.2    将A上的一个圆盘从A移到B

1.3    将C上的一个圆盘从C移到B

将第3步分解:(借助A)

3.1  将B上的一个圆盘从B移到A

3.2  将B上的一个圆盘从B移到C

3.3  将A上的一个圆盘从A移到C

由此可知,将3个圆盘从A移到C,需要7步,将n个圆盘从A移到C需要2的n次方减1步,要进行第n步,需要先进行第n-1步,可分为3个步骤

1将A上n-1个圆盘借助C移到B

2将A上剩下的1个圆盘直接移到C

3将B上n-1个圆盘借助A移到C

#include <stdio.h>void Move(int n,char a,char b){    printf("%d:%c-->%c\n",n,a,b);}void Hanoi(int n,char a,char b,char c){    if(n==1)//将A上的1个圆盘直接移到C,递归退出条件        Move(n,a,c);    else    {        Hanoi(n-1,a,c,b);//将A上n-1个圆盘借助C移到B        Move(n,a,c);//将A上剩下的1个圆盘直接移到C        Hanoi(n-1,b,a,c);//将B上n-1个圆盘借助A移到C    }}int main(){    int n;    printf("\ninput   number:");    scanf("%d",&n);    Hanoi(n,'A','B','C');    return 0;}/* 打印结果  input   number:3 1:A-->C 2:A-->B 1:C-->B 3:A-->C 1:B-->A 2:B-->C 1:A-->C*/




递归函数的主要优点是可以把算法写的比使用非递归函数时更清晰更简洁,而且某些问题,特别是与人工智能有关的问题,更适宜用递归方法。递归的另一个优点是,递归函数不会受到怀疑,较非递归函数而言,某些人更相信递归函数。编写递归函数时,必须在函数的某些地方使用if语句,强迫函数在未执行递归调用前返回。如果不这样做,在调用函数后,它永远不会返回。在递归函数中不使用if语句,是一个很常见的错误。在开发过程中广泛使用printf()和getchar()可以看到执行过程,并且可以在发现错误后停止运行。

下面通过一个具体例子比较一下递归和递推。

斐波那契数列:1,1,2,3,5,8,13,21……公式F(n)=F(n-1)+F(n-2),F(1)=F(2)=1;这个公式本身是具有递归性的。
递归解:
long f(int n){    if(n<=2)        return 1;    else        return f(n-1)+f(n-2);}


递推解:
long f(int n){    long t;f1=1,f2=1;    if(n<=2)        reutrn 1;    for(int i=3;i<=n;i++)    {        t=f1+f2;        f1=f2;        f2=t;     } }

由此可见,递归重在“归”,你要求的是什么,就直接求什么,至于要怎么去调用、怎么去求,让函数自己一步一步去反复调用;递推重在“推”,是按照我们平时做数学的方法来算。编程时递归比较特殊,表现在函数自身调用自身,这样编程代码紧凑,程序的可读性好,但效率低,还可能导致堆栈溢出,特别是递归层次比较多时。一般来说,递归程序都可以用循环程序实现,用循环程序实现虽然较难理解,但安全可靠。 
从软件工程的角度来说,递归使用程序更加简单清晰
从效率角度来说,递推会比递归效率高很多
一般选择递推代替递归,以适当的增加程序复杂性的代价来换取效率