递归算法不是一种数据结构,而是一种有效的算法设计方法。
递归的概念
若一个算法直接地或间接地调用自己本身,则称这个算法是递归算法。
递归算法 用把问题分解为形式更加简单的子问题的方法来求解问题。递归算法既是一种有效的分析问题的方法,也是一种有效的算法设计方法。递归算法是解决许多复杂应用问题的重要方法。
递归算法的执行过程
例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此迷宫无通路!");
}
}