第6章_递归

递归算法不是一种数据结构,而是一种有效的算法设计方法。

递归的概念

若一个算法直接地或间接地调用自己本身,则称这个算法是递归算法。
递归算法 用把问题分解为形式更加简单的子问题的方法来求解问题。递归算法既是一种有效的分析问题的方法,也是一种有效的算法设计方法。递归算法是解决许多复杂应用问题的重要方法。

递归算法的执行过程

例1:
计算阶乘函数的递归算法,并给出n = 3时递归算法的执行过程。

#include <stdio.h>

long int Fact(int n)
{
    long int y;
    if(n < 0)
    {
        printf("非法参数!");
        return -1;
    }

    if(n == 0) return 1;
    else
    {
        y = Fact(n - 1);        //递归调用
        return n * y;
    }
}


void main(void)
{
    long int fn;
    fn = Fact(3);
    printf("%d\n",fn);
}
//6

这里写图片描述
例2:
给出在有序数组a中查找数据元素x是否存在的递归算法
设计:算法的参数包括:有序数组a,要查找的数据元素x,数组下界下标low,数组上界下标high。当在数组a中查找到数据元素x时,函数返回数组a的下标;当在数组a中查找不到数据元素x时,函数返回-1。

#include <stdio.h>

int BSearch(int a[],int x,int low,int high);

void main(void)
{
    int a[] = {1,3,4,5,17,18,31,33};
    int x = 17;
    int bn;
    bn = BSearch(a,x,0,7);
    if(bn == -1) printf("x=%d,不在数组a中",x);
    else
        printf("x=%d,在数组a中下标为%d",x,bn);
    printf("\n");
}


int BSearch(int a[],int x,int low,int high)
{
    int mid;
    if(low > high) return -1;   //查找不成功

    mid = (low + high) / 2;
    if(x == a[mid]) return mid; //查找成功
    else if(x < a[mid])
    {
        return BSearch(a,x,low,mid - 1);
    }
    else
    {
        return BSearch(a,x,mid + 1,high);
    }
}

这里写图片描述

递归算法的设计方法

递归算法既是一种有效的算法设计方法,也是一种有效的分析问题的方法。递归算法求解问题的基本思想是:对于一个较为复杂的问题,可把原问题分解成若干个相对简单且类同的子问题,这样,较为复杂的原问题就变成了相对简单的子问题,而简单到一定程序的子问题可以直接求解。这样,原问题就可得到解了。
并不是每个问题都适宜于用递归算法求解。适宜于于用递归算法求解的问题的充分必要条件是:
1) 问题具有某种可借用的类同自身的子问题描述的性质
2) 某一有限步的子问题(也称作本原问题)有直接的解存在。
当一个问题存在上述两个基本要素时,设计该问题的递归算法的方法是:
1) 把对原问题的求解设计成包含有对子问题求解的形式;
2) 设计递归出口

例3:
设计模拟汉诺塔问题求解过程的算法。汉诺塔问题的描述是:设有三根标号为A,B,C的柱子,在A柱上放着n个盘子,每一个都比下面的略小一点,要求把A柱上的盘子全部移到C柱上,移动的规则是:a.一次只能 移动一个盘子;b.移动过程中大盘子不能放在小盘子上面;c.在移动过程中盘子可以放在A,B,C的任意一个柱子上。

问题分析:可以用递归方法求解n个盘子的汉诺塔问题。基本思想是:一个盘子的汉诺塔问题可直接移动。n个盘子的汉诺塔问题可递归表示为,首先把上边的n-1个盘子从A柱移动B柱,然后把最下边的一个盘子子A柱移到C柱,最后把移到B柱的n-1个盘子再移到C柱。

这里写图片描述
汉诺塔在线小游戏^V^,同学先感受一下吧http://www.4399.com/flash/109504_1.htm
设计:
首先,盘子的个数n是必须的一个输入参数,对n个盘子,可从上至下依次编号为1,2,…,n;其次,输入参数还需有三个柱子的代号,令三个柱子的参数名分别为fromPeg,auxPeg和toPeg;最后,汉诺塔问题的求解是一个处理过程,因此算法的输出是n个盘子从柱子fromPeg借助柱子auxPeg移动到柱子toPeg的移动步骤。设计每一步的移动为屏幕显示如下形式的信息:
Move Disk I from Peg X to Peg Y

#include <stdio.h>


void towers(int n,char fromPeg,char toPeg,char auxPeg);

void main(void)
{
    towers(4,'A','C','B');
}

void towers(int n,char fromPeg,char toPeg,char auxPeg)
{
    if(n == 1)
    {
        printf("%s%c%s%c\n","Move Disk 1 From Peg ",fromPeg," To Peg ",toPeg);
        return ;
    }

    //把n-1个圆盘从fromPeg借助toPeg移至auxPeg
    towers(n -  1,fromPeg,auxPeg,toPeg);

    //把圆盘n由fromPeg直接移至toPeg
    printf("%s%d%s%c%s%c\n","Move Disk ",n," From Peg ",fromPeg," To Peg ",toPeg);

    //把n-1个圆盘从auxPeg借助fromPeg移至toPeg
    towers(n - 1,auxPeg,toPeg,fromPeg);
}

这里写图片描述

递归算法到非递归算法的转换

有些问题需要用低级程序设计语言来实现,而低级程序设语言(如汇编语言)一般不支持递归,此时需要采用问题的非递归结构算法。一般来说,存在如下两种情况的递归算法:
1) 存在不借助堆栈的循环结构的非递归算法,如阶乘计算问题、斐波那契数列的计算问题、折半查找问题等。
2) 存在借助堆栈的循环结构的非递归算法,所有递归算法都可以借助堆栈转换成循环结构的非递归算法

对于第一种情况可直接用循环结构的算法
对于第二种,可以把递归算法转换成相应的非递归算法,此时,有两种转换方法:一种方法是借助堆栈,用非递归算法形式化模拟递归算法的执行过程;二种方法是根据要求解问题的特点,设计借助堆栈的循环结构算法。这两种方法都需要使用堆栈,这是因为堆栈的后进先出特点正好与递归函数的运行特点相吻合。

例4:
计算斐波那契数列Fib(n)问题.

#include <stdio.h>

long Fib2(int n);

void main(void)
{
    int i = 0;
    for(i = 0;i < 10; i++)
        printf("Fib(%d)=%d\n",i,Fib2(i));
}

long Fib2(int n)
{
    long int oneBack,twoBack,current;
    int i;
    if(n == 0 || n == 1) return n;
    else
    {
        oneBack = 1;
        twoBack = 0;
        for(i = 2;i <= n; i++)
        {
            current = oneBack + twoBack;
            twoBack = oneBack;
            oneBack = current;
        }
        return current;
    }
}

这里写图片描述

例5:
借助堆栈模拟系统的运行时栈,把汉诺塔问题的递归算法 转换为非递归算法。
SeqStack.h

//顺序堆栈结构体定义
typedef struct 
{
    DataType stack[MaxStackSize];
    int top;
}SeqStack;

//1.初始化
void StackInitiate(SeqStack*S)
{
    S->top = 0;
}

//2.判断是否为空
int StackNotEmpty(SeqStack S)
{
    //判断顺序堆栈S非空否,非空返回1,否则返回0
    if(S.top <= 0) return 0;
    else return 1;
}

//3.入栈
int StackPush(SeqStack*S,DataType x)
{
    //把数据元素值x压入顺序堆栈S,入栈成功返回1,否则返回0
    if(S->top >= MaxStackSize)
    {
        printf("堆栈已满无法插入!\n");
        return 0;
    }
    else
    {
        S->stack[S->top] = x;
        S->top++;
        return 1;
    }
}

//4.出栈
int StackPop(SeqStack*S,DataType*d)
{
    //弹出顺序堆栈S的栈顶数据元素值到参数d,出栈成功返回1,否则返回0
    if(S->top <= 0)
    {
        printf("堆栈已空无数据元素出栈!\n");
        return 0;
    }
    else
    {
        S->top--;
        *d = S->stack[S->top];
        return 1;
    }
}

//5.取栈顶数据元素
int StackTop(SeqStack S,DataType*d)
{
    if(S.top <= 0)
    {
        printf("堆栈已空!\n");
        return 0;
    }
    else
    {
        *d = S.stack[S.top - 1];
        return 1;
    }
}

Main

#include <stdio.h>
typedef struct 
{
    int retAddr;            //代表返回地址的数值
    int nPara;              //参数1
    char fromPegPara;       //参数2
    char toPegPara;         //参数3
    char auxPegPara;        //参数4
} DataType;
#define MaxStackSize 1000
#include "SeqStack.h"


/*
把n个盘子从fromPeg柱借助auxPeg柱移到toPeg柱
*/
void SimTowers(int n,char fromPeg,char toPeg,char auxPeg);

void main(void)
{
    SimTowers(4,'A','C','B');
}

void SimTowers(int n,char fromPeg,char toPeg,char auxPeg)
{
    DataType currarea;
    SeqStack s;
    char temp;
    int i;
    StackInitiate(&s);  //堆栈初始化

    //当前工作区初始化
    currarea.nPara = n;
    currarea.fromPegPara = fromPeg;
    currarea.toPegPara = toPeg;
    currarea.auxPegPara = auxPeg;
    currarea.retAddr = 1;
    StackPush(&s,currarea);

    //递归出口
start:
    if(currarea.nPara == 1)
    {
        printf("%s%c%s%c\n","Move Disk 1 from  Peg ",currarea.fromPegPara," To Peg ",currarea.toPegPara);
        i = currarea.retAddr;
        StackPop(&s,&currarea);
        switch(i)
        {
        case 1:goto label1;
        case 2:goto label2;
        case 3:goto label3;
        }
    }

    //递归自调用过程
    StackPush(&s,currarea);
    currarea.nPara--;
    temp = currarea.auxPegPara;
    currarea.auxPegPara = currarea.toPegPara;
    currarea.toPegPara = temp;
    currarea.retAddr = 2;
    goto start;

    //以下模拟返回第一次递归调用
label2:
    printf("%s%d%s%c%s%c\n","Move Disk ",currarea.nPara," From Peg ",currarea.fromPegPara," To Peg ",currarea.toPegPara);
    StackPush(&s,currarea);
    currarea.nPara--;
    temp = currarea.fromPegPara;
    currarea.fromPegPara = currarea.auxPegPara;
    currarea.auxPegPara = temp;
    currarea.retAddr = 3;
    goto start;

    //以下模拟返回第二次归调用
label3:
    i = currarea.retAddr;
    StackPop(&s,&currarea);
    switch(i)
    {
    case 1:goto label1;
    case 2:goto label2;
    case 3:goto label3;
    }


    //以下模拟返回主调函数
label1:
    return ;
}

这里写图片描述
例6:
设计一个输出如下形式数值的递归算法。
这里写图片描述
设计:
问题分析:该问题可以看成由两部分组成:一部分是输出一行值为n的数值;另一部分是原问题的子问题,其参数为n-1。当参数减到0时不再输出任何数据值,因此,递归的出口是当参数n<=0时空语句返回。

#include <stdio.h>

void Display(int n)
{
    int i;
    for(i = 1;i <= n; i++)
        printf("%d ",n);
    printf("% \n");
    if(n > 0) Display(n-1);
}

void main(void)
{
    Display(5);
}

方法二:

#include <stdio.h>

void Display(int n)
{
    int i,j;
    for(i = 1;n >= i; n--)
    {
        for(j = 1;j <= n; j++)
            printf("%d ",n);
        printf("\n");
    }
}

void main(void)
{
    Display(5);
}

这里写图片描述
例7:
求两个正整数n和m最大公约数

#include <stdio.h>

int Gcd(int n,int m)
{
    if(n < 0 || m < 0) exit(0);

    if(m == 0) return n;
    else if(m > n) return Gcd(m,n);
    else return Gcd(m,n%m);
}

void main(void)
{
    int n = 30,m = 4;
    printf("%d%d最大公约为%d\n",n,m,Gcd(n,m));
}

方法二:

#include <stdio.h>

int Gcd(int n,int m)
{
    int tn,tm,temp;
    if(n < 0 || m < 0) exit(0);

    if(m > n)
    {
        tn = m;
        tm = n;
    }
    else
    {
        tn = n;
        tm = m;
    }
    while(tm != 0)
    {
        temp = tn;
        tn = tm;
        tm = temp % tm;
    }
    return tn;  //返回最大公约数
}

void main(void)
{
    int n = 30,m = 4;
    printf("%d与%d最大公约为%d\n",n,m,Gcd(n,m));
}

这里写图片描述

回溯法-设计举例

例:
设计求解迷宫问题的算法并用实际例子测试。迷宫是一些互相连通的交叉路口的集合,给定一个迷宫入口,一个迷宫出口,当从入口到出口存在通路时输出其中的一条通路,当从入口到出口不存在通路时输出无通路存在。每个交叉路口除进来的路外还有三个路口,分别是向左、向前和向右。为简化设计,假设迷宫中不存在环路。如图
这里写图片描述

Maz1.dat

6
0 2 0
3 5 6
0 0 4
0 0 0
0 0 0
7 0 0
7

Maze.h

typedef struct      //路口结构体定义
{
    int left;       //向左方向
    int forward;    //向前方向
    int right;      //向右方向
} InterSection;

typedef struct 
{
    int mazeSize;           //路口个数
    InterSection * intSec;  //路口集合
    int Exit;               //出口
} Maze;

/*
回法搜索迷宫m的所有分支,输入参数currSetValue为当前所处的路口
  */
int TravMaze(Maze* m,int currSetValue)
{
    //currSetValue>0为有路径存在,可以继续控索发现一条路径
    if(currSetValue > 0)
    {
        if(currSetValue == m->Exit)             //递归出口
        {
            printf("%d <==",currSetValue);      //输出路口号
            return 1;
        }
        //向左探索
        else if(TravMaze(m,m->intSec[currSetValue].left) == 1)
        {
            printf("%d <==",currSetValue);
            return 1;
        }
        //向前探索
        else if(TravMaze(m,m->intSec[currSetValue].forward) == 1)
        {
            printf("%d <==",currSetValue);
            return 1;
        }
        //向右探索
        else if(TravMaze(m,m->intSec[currSetValue].right) == 1)
        {
            printf("%d <==",currSetValue);
            return 1;
        }
    }
    return 0;
}

//按文件Filename中存放的数据创建迷宫
void CreatMaze(char* filename,Maze* m)
{
    FILE* fp;
    int i;
    fp = fopen(filename,"r");       //打开路口数据文件
    if(!fp)
    {
        printf("数据文件无法打开!");
        return ;
    }
    fscanf(fp,"%d",&m->mazeSize);   //读入路口个数
    //建立mazeSize+1个元素的数组
    m->intSec = (InterSection*)malloc(sizeof(InterSection)*(m->mazeSize+1));

    for(i = 1;i <= m->mazeSize; i++)//读入全部路口的结构体数值
    {
        fscanf(fp,"%d%d%d",&m->intSec[i].left,&m->intSec[i].forward,&m->intSec[i].right);
    }
    fscanf(fp,"%d%d%d",&m->Exit);   //读入出口数据
    close(fp);                      //关闭文件资源
}

Main.c

#include <stdlib.h>
#include <stdio.h>
//数据放到资源文件Maze1.dat中
#include "Maze.h"

void main(void)
{
    Maze m;
    int start = 1;
    CreatMaze("Maze1.dat",&m);
    if(TravMaze(&m,start))
        printf("\n此迷宫的一条通路如上所示!");
    else
    {
        printf("\n此迷宫无通路!");
    }
}

这里写图片描述

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值