数据结构7————递归解决迷宫问题和马踏棋盘
文章目录
一.前言
1. 迷宫问题的描述
在高为H,宽为W的地图中,0代表可以走,1代表障碍物,不重复的走到终点。给定地图和终点求下列问题
- 子问题1 求出最少步数
- 子问题2 求出最多步数
- 子问题3 输出所有走法
2. 马踏棋盘问题的描述
将马放在国际象棋8×8棋盘某个方格中,马按走棋规则进行移动,要求每个方格只进入一次,走遍棋盘上全部64个方格。编制程序,求出马的行走路线,并按求出的行走路线将数字1,2,…,64依次填入一个8×8的方阵并输出。
3.递归
程序调用自身的编程技巧称为递归( recursion)
递归的本质是栈(函数在内存中是使用栈式存储的),所以可以用递归解决,就一定可以用栈解决,在下一篇中我使用非递归方法,解决上面两个问题
二. 迷宫问题
1.思路
- 将右下左上四个方向分别定义为k=0,1,2,3,定义一个book[H][W]标记数组,如果走过标1,未走过标0;
- 对于每个点,使其从k=0,1,2,3四个方向依次遍历
- 如果k方向的坐标1.未出界 2.未走过 3.不是障碍物 则标记已走,将该点k方向的新坐标传入函数中,重复2
- 反之 则跳过k方向,进行k+1方向的试探
- 如果四个方向都试探完毕,说明这个点是死路,结束这个函数,取消这个点的标记
- 如果该点是终点,根据需求进行操作,结束这个函数
- 个人建议,如果对这个过程不太清晰,可以根据源码,代入数据手工运行一遍
2.找出最少步数
- 当该点是终点时,将这种走法所需要的步数和当前最小步数比较,如果当前步数小,则更新最小步数
#include <stdio.h>
#include <string.h>
#define H 5
#define W 4
#define Accept_x 3
#define Accept_y 3
int min=999999,book[10][10];
int a[H][W]={{0,0,1,0},
{0,0,0,0},
{0,0,1,1},
{0,1,0,0},
{0,0,0,1}};
void dfs(int x,int y,int step)
{
int next[4][2]={{0,1},//右
{1,0},//下
{0,-1},//左
{-1,0}};//上
if(x==Accept_x&&y==Accept_y)
{
printf("到达 %d\n",step);
if(step<min)//如果步数比当前最小值小,更新
min=step;
return;//返回上一步
}
int tx,ty,k;
for(k=0;k<4;k++)//枚举所有走法
{
tx=x+next[k][0];//下一步
ty=y+next[k][1];
if(tx<0||ty<0||ty>=W||tx>=H)//判断是否越界
continue;
if(a[tx][ty]!=1&&book[tx][ty]==0)//判断下一步是否被走过和是否有障碍物
{
book[tx][ty]=1;//标记
//printf("入(%d,%d)%d %d\n",tx,ty,k,step);
dfs(tx,ty,step+1);//走下一个节点
book[tx][ty]=0;//当该节点退出(回溯)时,取消标记
//printf("出(%d,%d)%d %d\n",tx,ty,k,step);
}
}
return;
}
int main(void)
{
book[0][0]=1;//标记起始点
dfs(0,0,0);//第一个参数是起始点的x坐标,第二个参数是起始点的y坐标,第三个参数是步数
printf("%d\n",min);
}
3.找出最多步数
- 和前一个程序类似,只是将最小值改为最大值
4.输出所以走法
- 建立一个栈,当标记已走过时,入栈,取消标记是时,出栈
- 当到达终点时,输出栈内的元素
- 代码中没有关于栈的基本运算函数,文章末尾代码中的git有完整版
SeqStack *SS;
int n,m,p=4,q=3,min=9999999,book[10][10];
int a[5][4]={{0,0,1,0},
{0,0,0,0},
{0,0,1,1},
{0,1,0,0},
{0,0,0,1}};
void dfs(int x,int y,int step)
{
int next[4][2]={{0,1},//右
{1,0},//下
{0,-1},//左
{-1,0}};//上
if(x==3&&y==3)
{
int i;
printf("到达\n");
if(step<min)//如果步数比当前最小值小,更新
min=step;
for(i=0;i<SS->top+1;i++){
printf("过程(%d,%d)%d\n",SS->data[i].x,SS->data[i].y,SS->data[i].direction);
}
return;//返回上一步
}
int tx,ty,k;
for(k=0;k<4;k++)//枚举所有走法
{
tx=x+next[k][0];//下一步
ty=y+next[k][1];
if(tx<0||ty<0||ty>=4||tx>=5)//判断是否越界
continue;
if(a[tx][ty]!=1&&book[tx][ty]==0)//判断下一步是否被走过和是否有障碍物
{
Elemtype e;
e.x=tx;
e.y=ty;
e.direction=k;
book[tx][ty]=1;//标记
Push(SS,e);
//printf("入(%d,%d)%d %d\n",e.x,e.y,e.direction,step);
dfs(tx,ty,step+1);//走下一个节点
book[tx][ty]=0;//当该节点退出(回溯)时,取消标记
//printf("出(%d,%d)%d\n",e.x,e.y,e.direction);
Pop(SS,&e);
}
}
return;
}
int main(void)
{
Elemtype e;
SS=InitStack();
e.x=0;
e.y=0;
e.direction=0;
Push(SS,e);
book[0][0]=1;//标记起始点
dfs(0,0,0);//第一个参数是起始点的x坐标,第二个参数是起始点的y坐标,第三个参数是步数
printf("%d\n",min);
}
三.马踏棋盘
1.思路
和迷宫问题类似
- 将每个点可以走的8个方向分别定义为0到7,定义一个book[W][H]
如果走过,标记当前步数,如果没走过,标记0 - 对于每个点
- 如果k方向的坐标1.未出界2.未走过则标记已走(当前步数),将该点k方向的新坐标传入函数中,重复2
- 反之则跳过k方向,进行k+1方向的试探
- 如果四个方向都试探完毕,说明这个点是死路,结束这个函数,取消这个点的标记
- 如果此时步数= W *H,输出本次走法,结束这个函数,寻找下一种走法
2.代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#define H 8
#define W 8
int book[H][W] ={0};//标记
int t=0;
void dfs(int x,int y,int step){
int next[8][2]={{-2,1}, //下一步走法
{-1,2},
{1,2},
{2,1},
{2,-1},
{1,-2},
{-1,-2},
{-2,-1}};
int i,j;
if(step==W*H){//打印
for(i=0;i<H;i++){
for(j=0;j<W;j++)
printf("%4d",book[i][j]);
printf("\n");
}
t++;
printf("%d\n",t);
return ;
}
int k,tx,ty;
for(k=0;k<8;k++){
tx = x+next[k][0];
ty = y+next[k][1];
if(tx<0||ty<0||tx>=H||ty>=W){
continue;
}
if(book[tx][ty]==0){
book[tx][ty]=step+1;
//printf("入(%d %d) %d\n",tx,ty,step+1);
dfs(tx,ty,step+1);
//printf("出(%d %d)\n",tx,ty);
book[tx][ty]=0;
}
}
return;
}
int main(void)
{
book[0][0]=1;//标记起始点
int a=clock();
dfs(0,0,1);//第一个参数是起始点的x坐标,第二个参数是起始点的y坐标,第三个参数是步数
int b=clock();
printf("\n%lf秒\n",(b-a)/1000.0);
}