递归 ( 快速排序算法 ,台阶问题 ,阶乘问题 )


递归(recursion)就是子程序(或函数)直接调用自己或通过一系列调用语句间接调用自己,是一种描述问题和解决问题的基本方法递归通常用来解决结构自相似的问题。所谓结构自相似,是指构成原问题的子问题与原问题在结构上相似,可以用类似的方法解决。具体地,整个问题的解决,可以分为两部分:第一部分是一些特殊情况,有直接的解法;第二部分与原问题相似,但比原问题的规模小。实际上,递归是把一个不能或不好解决的大问题转化为一个或几个小问题,再把这些小问题进一步分解成更小的问题,直至每个小问题都可以直接解决。因此,递归有两个基本要素:

 1)边界条件:确定递归到何时终止,也称为递归出口。(2)递归模式:大问题是如何分解为小问题的,也称为递归体。递归函数只有具备了这两个要素,才能在有限次计算后得出结果

在递归函数中,调用函数和被调用函数是同一个函数,需要注意的是递归函数的调用层次,如果把调用递归函数的主函数称为第0层,进入函数后,首次递归调用自身称为第1层调用;从第i层递归调用自身称为第i+1层。反之,退出第i+1层调用应该返回第i层。

递归函数的内部执行过程

  一个递归函数的调用过程类似于多个函数的嵌套的调用,只不过调用函数和被调用函数是同一个函数。为了保证递归函数的正确执行,系统需设立一个工作栈。具体地说,递归调用的内部执行过程如下:

 1)运动开始时,首先为递归调用建立一个工作栈,其结构包括值参、局部变量和返回地址;

(2)每次执行递归调用之前,把递归函数的值参和局部变量的当前值以及调用后的返回地址压栈;

3)每次递归调用结束后,将栈顶元素出栈,使相应的值参和局部变量恢复为调用前的值,然后转向返回地址指定的位置继续执行。

以阶乘为例说明递归的工作原理:

long ff(int n){   long f; if(n<0)z; printf("n<0,input error");else if(n==0||n==1)   f=1; //为什么f=1,就不再继续递归调用?else  f=ff(n-1)*n;//这一步到底是怎么工作的return(f);

}

 

首先要清楚,递归就是某个函数直接或间接地调用了自身,这种调用方式叫做递归调用。说白了,还是函数调用。既然是函数调用,那么就有一个雷打不动的原则:所有被调用的函数都将创建一个副本,各自为调用者服务,而不受其他函数的影响。

你的ff函数,递归多少次,就有多少个副本,再利用内存的栈式管理,反向退出。这个最好找一下这方面的东西看看,挺容易的,就像子弹匣一样,先进后出。

你不理解,很有可能是因为误以为该这几行代码被反复使用了。从某种意义上说,这是不对的,因为就像刚才说的,一旦被调用,他将在内存中复制出一份代码,再被调用就再复制一份,换句话说,你可以吧同一个函数的多次调用理解称谓多个不同函数的一次调用,这样也会会简单些。

再说=1=0是为什么退出。递归,很需要注意的就是死递归,也就是说,某一个函数进入了无限调用自身的情况,永无止境地消耗内存等资源,这在编程方面是一大忌。但凡是递归的函数,一定会在某一个地方存在能够返回上一层函数的代码,否则必定死递归。ff函数中,那个else就是返回的出口,你可以这样想,如果没有那个if来进行判断,你递归到什么时候算完?ff是不是会一直调用自己呢?别指望被调用的函数会自动结束,因为一旦某个函数A中调用了函数B(或者自己),那么A中的代码会停在调用的位置,而转向B中去执行,同理,如果B又调用函数C,那么B又停在调用的位置,去执行C,如果无限调用,那么程序是永远不会结束的。当然,也有这种情况,A调用B,然后继续自己的代码,不管B的死活,这种不在我们的讨论范围内,因为那牵扯到另一种编程方式:多线程。(我们现在说的是单线程

给你拆极不看看吧:

3!=?

一层执行到f=ff(3-1)*3;停止,执行二层ff(3-1),也就是ff(2)

二层执行到f=ff(2-1)*2;停止,执行三层ff(2-1),也就是f(1)

三层执行到else if(n==0||n==1) f=1;然后return(f)到二层的ff(2-1)的位置,二层继续执行

二层执行f=1*2; 然后就return(f)到一层ff(3-1)的位置,一层继续执行

一层执行f=2*3; 然后就return(f)到了最初调用ff(3)main函数里,所以就得到y=6

大体过程就是这样的

这里每次一层都相当于一个不同的函数,你可以给他们起名ff1,ff2,ff3.....这样就不混了。只要注意一点,调用一次,不是在代码本身上执行,而是会复制出一份在执行,虽然不太恰当,但足以说明问题。

递归的存储以及执行顺序

在函数的执行过程中无可避免的会涉及到参数存储的问题,而函数的参数是存储栈中,栈最大的特点是“先进后出”,之前创建的参数在当前已经被其他变量覆盖,无法访问,但是是存在的,不能认为已经被销毁了。函数在被递归调用时让人疑惑的主要原因也因为参数的存储引起的。

下面的程序可以很好的解释递归的过程中参数的存储以及使用过程。

#include<stdio.h>
void binary_to_assic(unsignde int value ){
       unsigned int  a=value/10;
       if(a!=0)
          binaray_to_assic(a);//递归调用函数的过程就是将参数入栈的过程
       putchar(value%10+'0');   //使用参数的过程就是讲参数出栈的过程
}
void main(){
       binaray_to_assic(4267);
}

binary_to_assic()函数在执行过程中不断的对自身进行递归调用,将参数“value”压栈,压栈完成后从上到下的顺序是:4、42、426、4267。

接下来putchar语句会对变量进行引用,按照变量存储在栈中的顺序依次处理。所以最终会打印出:4267。

具体的函数流程能帮助我们进一步理解递归:

binaray_to_assic(4267)----binaray_to_assic(426)----binaray_to_assic(42)----binaray_to_assic(4)
binaray_to_assic(4267){
  binaray_to_assic(426){
    binaray_to_assic(42){
             binaray_to_assic(4){
         binaray_to_assic(0)//结束递归
       putchar(4%10)
      }
      putchar(42%10)
    }
    putchar(426%10)
  }
  putchar(4267%10)
}

当开始执行第4级调用时,a的值为0,if判断不再满足,这时候不在继续调用binary_to_assic()函数,第4级调用则继续执行其后面的语句:putchar();此时第4级调用结束,把控制权返回给该函数的调用函数,也就是第3级调用函数。依次类推。

递归的基本原理:

1  每一次函数调用都会有一次返回.当程序流执行到某一级递归的结尾处时,它会转移到前一级递归继续执行.

2  递归函数中,位于递归调用前的语句和各级被调函数具有相同的顺序.如打印语句 #1 位于递归调用语句前,它按照递归调用的顺序被执行了 4 次.

3 每一级的函数调用都有自己的私有变量.

4 递归函数中,位于递归调用语句后的语句的执行顺序和各个被调用函数的顺序相反.

5 虽然每一级递归有自己的变量,但是函数代码并不会得到复制.

6 递归函数中必须包含可以终止递归调用的语句

 

关于递归的讲解《C和指针》讲解的很细致,有兴趣的可以看看。


 

 

 

  A1057. FJ的字符串
问题描述  FJ在沙盘上写了这样一些字符串:
A1 = “A”
A2 = “ABA”
A3 = “ABACABA”
A4 = “ABACABADABACABA”
… …
你能找出其中的规律并写所有的数列AN吗?
输入格式  仅有一个数:N ≤ 26
输出格式  请输出相应的字符串AN,以一个换行符结束。输出中不得含有多余的空格或换行、回车符。
样例输入3

样例输出ABACABA   

思路:

观察规律,对于A2的结果来说,可以看到,A2的结果由三部分组成,A1+B+A1,可以接着往下猜测,A3也可能满足此规律,A3=A2+C+A2,的确也满足这种规律,由此可以推测出如下公式f(A)=A;f(B)=f(A)+B+f(A);f(C)=f(B)+C+f(B);f(D)=f(C)+D+f(C);就是一个递归调用的过程。

 

 

 

例2 楼梯有n阶台阶,上楼可以一步上1阶,也可以一步上2阶,编一程序计算共有多少种不同的走法.

示:设n阶台阶的走法数为f(n)。如果只有1个台阶,走法有1种(一步上1个台阶),即f(1)=1;如果有2个台阶,走法有2种(一种是上1阶,再上1阶,另一种是一步上2阶),即f(2)=2;如果有3个台阶,走法有4种(一种每次1阶,共一种;另一种是2+1,共两种;第三种是3,共1种),即f(3)=4

当有n个台阶(n>3)时,我们缩小问题规模,可以这样想:最后是一步上1个台阶的话,之前上了n-1个台阶,走法为f(n-1)种,而最后是一步上2个台阶的话,之前上了n-2个台阶,走法为f(n-2)种,故而f(n)=f(n-1)+f(n-2)。列出的递归方程为:
f(1)=1;f(2)=2;f(3)=4;if(n==1)return 1;else if(n==2)return 2;else if(n==3)return 4;else return  f(n-1)+f(n-2)+f(n-3),n>3

 

   快速排序算法   从大到小

#include"stdio.h"

int a[100],n;

void  quik_sort(int left,int right);

int main()

{

  int i,j;

for (i=0;i<10;i++)

scanf("%d",&a[i]);

    quik_sort(0,9);  

     for (i=0;i<10;i++)

   printf("%d ",a[i]);

  return 0;

 }

 //快速排序,从小到大

void quik_sort(int left,int right)

{

 int i,j,t,temp;

 if (left>right)

  {

   return ;

  }   

  temp=a[left];

  i=left;

  j=right;

  while (i!=j)

   {

    while (a[j]<temp&&i<j)

     j--;

    while  (a[i]>temp&&i<j)

     i++;

     if (i<j)

       {

          t=a[j];

          a[j]=a[i];

          a[i]=t;

}

   }

    a[j]=temp;   

  quik_sort(left,i-1);

  quik_sort(i+1,right);

}

//从小到大

#include"stdio.h"

int a[100],n;

void  quik_sort(int left,int right);

int main()

{

  int i,j;

for (i=0;i<10;i++)

scanf("%d",&a[i]);

    quik_sort(0,9);  

     for (i=0;i<10;i++)

   printf("%d ",a[i]);

  return 0;

 }

 //快速排序,从小到大

void quik_sort(int left,int right)

{

 int i,j,t,temp;

 if (left>right)

  {

   return ;

  }   

  temp=a[left];

  i=left;

  j=right;

  while (i!=j)

   {

    while (a[j]>temp&&i<j)

     j--;

    while  (a[i]<temp&&i<j)

     i++;

     if (i<j)

       {

          t=a[j];

          a[j]=a[i];

          a[i]=t;

}

   }

    a[j]=temp;   

  quik_sort(left,i-1);

  quik_sort(i+1,right);

}

//从大到小