如何编写递归程序(分治法)

一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

递归调用在语法上很简单,递归即为普通的函数调用。但是在算法上却比较困难,主要需要解决下面两个问题:

  1. 如何找到递归形式?
  2. 如何找到递归边界?

递归算法一般用于解决三类问题:

  1. 数据的定义是按递归定义的。(Fibonacci函数)
  2. 问题解法按递归算法实现。(回溯)
  3. 数据的结构形式是按递归定义的。(树的遍历,图的搜索)

优点:

结构清晰,可读性强,而且容易用数学归纳法来证明算法的正确性,因此它为设计算法、调试程序带来很大方便。

缺点:

递归算法解题的运行效率较低。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。

递归算法可以分为两种类型:基于分治策略的递归算法和基于回溯策略的递归算法。

基于分治策略的递归

分治的思想:把问题划分为若干个子问题,以同样的方式分别去处理各个子问题,把各个子问题的处理结果综合来,形成最终的处理结果。下面介绍一些经典的例子来分析基于分治法的递归问题。

例一、计算n的阶乘

n! 可以分解为 n*(n-1)!,同样(n-1)!可以分解为(n-1)*(n-2)!,依次类推,因此递归形式应该为 :n! = n * (n-1)!,同时当n = 1 时阶乘为1,所以递归边界为:1! = 1。实现代码为

	
void   main( )
{        
    int   n;       
    printf("请输入一个整数:");        
    scanf("%d",   &n);        
    printf("%d!  =  %d \n", n, fact(n));
 }
 
int   fact(int  n)
 {        
    if(n  ==  1)    // 递归边界
        return(1);        
    else    
        return(n * fact(n-1));// 递归形式
 }

例二、Fibonacci数列

兔子繁殖问题: 如果每对兔子从出生后第三个月起每个月繁殖一对子兔,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,试问一对兔子一年能繁殖多少对兔子?

这是一个很典型的数列问题,由于小兔子在第一个月和第二个月均不产生兔子,因此递归边界为 n = 1 或者 n = 2。兔子的规律为数列1,1,2,3,5,8,13,21…。可以看出从第三个数开始,每个数都是之前两个数之和,因此递归形式为

F(n) = F(n-1) + F(n-2)

实现代码:

int F(int n) // n 为月数
{
    if (n == 1 || n == 2)// 递归边界
    {
        return 1;
    }
    else 
    {
        return F(n - 1) + F(n - 2);// 递归形式
    }
}

例三、汉诺(Hanoi)塔问题

又称为河内塔问题。有位僧人整天把三根柱子上的金盘倒来倒去,原来他是想把64个一个比一个小的金盘从一根柱子上移到另一根柱子上去。移动过程中遵守以下规则:每次只允许移动一只盘,且大盘不得落在小盘上。如下图所示:

《如何编写递归程序(分治法)》《如何编写递归程序(分治法)》

可以先从最简单的情况分析,在A柱上只有一只盘子,假定盘号为1, 这时只需将该盘直接从A 搬至C,记为

move from A to C

在A柱上有二只盘子,1为小盘2为大盘。分三步进行:

move  from  A  to  B;

move  from  A  to  C;

move  form  B  to  C;

在A柱上有3只盘子,从小到大分别为1号,2号,3号。将过程分解成三步:

move   2   discs   from   A   to   B   using   C;// 利用两个盘子的方法,先把A的上面两层移到B      

move   from   A   to   C;// 把A的3号移到C

move   2   discs   from   B   to   C   using   A ;// 把B的两层移到C

因此得出n个盘子的递归形式为

 move   n-1   discs   from   A   to   B   using   C;     

 move   1       discs   from   A   to   C;

 move   n-1   discs   from   B   to   C   using   A ;

递归边界为 n = 1。

实现代码:

#include  <stdio.h>
void   move(int  n,   char  L,   char  M,   char  R);
void   main( )
{        
    int   n;        
    printf("请输入一个整数:");        
    scanf("%d",  &n);        
    move(n,  'A',  'B',  'C');
}

void   move(int  n,  char  L,  char  M,  char  R)// L上的n 个盘子移到 R上
{        
    if(n  ==  1)             // 递归边界
        printf("move #1 from %c to %c\n",  L,  R);        // 从 L移动到R上
    else        
    {            
        move(n-1,  L,  R,  M);    // 将 L 的(n-1)个盘子移动到 R上
        printf("move #%d from %c to %c\n",  n,  L,  R);// 将L 的一个盘子移动到 R上         
        move(n-1,  M,  L,  R);        // 将R 上的(n-1)个盘子移动到 R上
    }
}

一次实验的结果:

请输入一个整数:3

move  #1  from  A  to  C

move  #2  from  A  to  B

move  #1  from  C  to  B

move  #3  from  A  to  C

move  #1  from  B  to  A

move  #2  from  B  to  C

move  #1  from  A  to  C

上面代码仅仅是提供一种移动的方法,并没有在程序中真正地实现。

例四、爬楼梯问题

爬楼梯时可以1次走1个台阶,也可以1次走2个台阶。对于由n个台阶组成的楼梯,共有多少种不同的走法?

1个台阶:只有1种走法;

2个台阶:有两种走法;(1+1;2)

n个台阶(n>2),记走法为f(n),分解:

第1次走1个台阶,还剩(n-1)个台阶,走法为f(n-1);

第1次走2个台阶,还剩(n-2)个台阶,走法为f(n-2)。

所以,f(n)=f(n-1)+f(n-2)。

定义:

《如何编写递归程序(分治法)》

递归边界:n = 0 或者n = 1

递归形式:f(n) = f(n-1) + f(n-2)

因此,这个问题与第二个问题是同一种解法。

    原文作者:递归与分治算法
    原文地址: https://blog.csdn.net/xgf415/article/details/52026961
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞