实验题目:栈和队列的综合应用
1.迷宫问题。假设迷宫由m行n列构成,有一个入口和一个出口,入口坐标为(1,1),出口坐标为(m,n),试设计并验证以下算法:找出一条从入口通往出口的路径,或报告一个“无法通过”的信息。
要求
(1) 用C语言实现顺序存储结构上队列的基本操作,然后利用该队列的基本操作找出迷宫的一条最短路径。
(2) 设计一个二维数组MAZE[m+2][n+2]表示迷宫,数组元素为0表示该位置可以通过,数组元素为1表示该位置不可以通行。MAZE[1][1]、MAZE[m][n]分别为迷宫的入口和出口。
(3) 输入迷宫的大小m行和n列,动态生成二维数组;由随机数产生0或1,建立迷宫,注意m*n的迷宫需要进行扩展,扩展部分的元素设置为1,相当于在迷宫周围布上一圈不准通过的墙。
(4) 要求输出模拟迷宫的二维数组;若存在最短路经,则由出口回溯到入口(出队列并利用栈实现),再打印从入口到出口的这条路径,例如(1,1),……,(i,j),……,(m,n);若没有路径,则打印“No path!”。
(5) 为避免出现原地踏步的情况为了标志已经通过的位置,采用一个标志数组MARK[m+2][n+2],初值均为0,在寻找路径的过程中,若通过了位置(i,j),则将MARK[i][j]置为1。
(6) 为了记录查找过程中到达位置(i,j)及首次到达(i,j)的前一位置(i_pre,j_pre),需要记住前一位置(i_pre,j_pre)在队列中的序号pre,即队列中数据元素应该是一个三元组(i,j,pre)。
(7) 搜索过程简单描述如下:将入口MAZE[1][1]作为第一个出发点,依次在八个方向上搜索可通行的位置,将可通行位置(i,j,pre)入队,形成第一层新的出发点,然后依次出队,即对第一层中各个位置分别搜索它所在八个方向上的可通行位置,形成第二层新的出发点,…,如此进行下去,直至达到出口MAZE[m][n]或者迷宫所有位置都搜索完毕为止。
审题
- 用队列解迷宫问题,迷宫要求动态生成,队列中数据元素应该是一个三元组
- 在寻找路径的过程中,若通过了位置(i,j),则将MARK[i][j]置为1,二维数组MAZE[m+2][n+2]表示迷宫,数组元素为0表示该位置可以通过,数组元素为1表示该位置不可以通行
- 由出口回溯到入口(出队列并利用栈实现),再打印从入口到出口的这条路径
抽象数据定义
typedef struct {
int x;//点的坐标
int y;
int pre; //记录该结点的父结点(来源)
}position;
typedef struct my_queue {//队列
position data[1000];
int front;
int rear;
}Queue;
typedef struct{//栈
position data[1000];
int top;
}Stack;
算法思路
如何随机生成迷宫,搜索路径和如何打印路径
- 随机生成迷宫的关键是如何随机生成范围内的数字:
大概就是用一个根据系统时间生成的种子随机生成数,具体的细节,比如为什么需要一个种子,和获得时间为什么这样写,笔者也不知道。
//随机函数,生成minnum到maxnum的随机整数
int random_index(int minnum,int maxnum) {
struct timespec time1 = { 0, 0 };
clock_gettime(CLOCK_REALTIME, &time1);//获得毫秒级的时间
srand(time1.tv_nsec);//根据系统时间生成种子
int choice = maxnum - minnum + 1;
int a = rand() % choice + minnum;//生成minnum到maxnum的随机整数
return a;
}
- 搜索路径,建议食用视频,b站麦克老师讲算法,我这里也大概讲一下吧。
搜索算法:起点进队列,然后是一个大的队列非空循环,队首元素出队,如果队首元素是出口,那么找到了,循环结束,不然就看看这个元素周围可不可以走,可以走的全部进队(小循环),队列非空就继续队首元素出队,直到队列为空或是找到出口。
push(qu,e);//起点进队列
while(!EmptyQueue(qu)){//大的队列非空循环
pop(qu,e);//队首元素出队
i=e.x,j=e.y,pre=e.pre;//如果队首元素是出口,那么找到了,循环结束
if(i==m&&j==n){
print(qu,qu->front);
return 1;
}
int d=-1;
int in,jn;
while(d<8){//上,上右,右,下右,下,下左,左,上左
d++;
switch(d){
case 0:{in=i-1,jn=j;break;}
case 1:{in=i-1,jn=j+1;break;}
case 2:{in=i,jn=j+1;break;}
case 3:{in=i+1,jn=j+1;break;}
case 4:{in=i+1,jn=j;break;}
case 5:{in=i+1,jn=j-1;break;}
case 6:{in=i,jn=j-1;break;}
case 7:{in=i-1,jn=j-1;break;}
}
if(MAZE[in][jn]==0){//看看这个元素周围可不可以走,可以走的全部进队(小循环)
e.x=in,e.y=jn,e.pre=qu->front;
push(qu,e);
MAZE[in][jn]=-1;
}
}
}
- 打印路径,也建议食用视频,b站麦克老师讲算法。
打印算法:
搜索时,pre记录了上个点在队列的位置。
void print(Queue *&qu,int k)//k为q->front
{
int j;//因为进队出队没有空间操作,只是改变了头尾指针的值
Stack s;
s.top=-1;
while(k!=-1){//所以可以通过当前的qu->front回溯
j=k;//因为pre记录了该节点是从那个节点过来的
k=qu->data[k].pre;//所以可以不断通过pre索引到起点
s.data[++s.top]=qu->data[j]; //进栈
}
int ns=0;//打印路径节点坐标,ns用来换行
while(s.top!=-1){//出栈
ns++;
printf("(%d,%d)",s.data[s.top].x,s.data[s.top].y);
s.top--;
if(ns%5==0)
printf("\n");
else
printf("\t");
}
printf("\n");
}
详细代码
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#define N 100
int MAZE[100][100];
//int Direction[8][2]={{0,1},{1,1},{0,-1},{-1,-1},{1,1},{0,-1},{-1,-1},{0,1}};
typedef struct {
int x;
int y;
int pre; //记录该结点的父结点(来源)
}position;
typedef struct my_queue {
position data[1000];
int front;
int rear;
}Queue;
typedef struct{
position data[1000];
int top;
}Stack;
//顺序存储结构上队列的基本操作
void InitQue(Queue *&q)//初始化队列
{
q=(Queue *)malloc(sizeof(Queue));
q->front=q->rear=-1;
}
int EmptyQueue(Queue *&q){//判空
return (q->front==q->rear);
}
int push(Queue *&q,position &e){//进队
if(q->rear>999)
return 0;
e.pre=q->front;
q->rear++;
q->data[q->rear]=e;
return 1;
}
int pop(Queue *&q,position &e){//出队
if(EmptyQueue(q))
return 0;
q->front++;
e=q->data[q->front];
return 1;
}
//打印路径
void print(Queue *&qu,int k)
{
int j;//因为进队出队没有空间操作,只是改变了头尾指针的值
Stack s;
s.top=-1;
while(k!=-1){//所以可以通过当前的qu->front回溯
j=k;//因为pre记录了该节点是从那个节点过来的
k=qu->data[k].pre;//所以可以不断通过pre索引到起点
s.data[++s.top]=qu->data[j];
}
int ns=0;//打印路径节点坐标,ns用来换行
while(s.top!=-1){
ns++;
printf("(%d,%d)",s.data[s.top].x,s.data[s.top].y);
s.top--;
if(ns%5==0)
printf("\n");
else
printf("\t");
}
printf("\n");
}
//搜索路径
int mgpath1(int x,int y,int m,int n)
{
position e;
int i,j,pre;
Queue *qu;
InitQue(qu);
e.x=x,e.y=y,e.pre=-1;
MAZE[x][y]=-1;
push(qu,e);
while(!EmptyQueue(qu)){
pop(qu,e);
i=e.x,j=e.y,pre=e.pre;
if(i==m&&j==n){
print(qu,qu->front);
return 1;
}
int d=-1;
int in,jn;
while(d<8){//上,上右,右,下右,下,下左,左,上左
d++;
switch(d){
case 0:{in=i-1,jn=j;break;}
case 1:{in=i-1,jn=j+1;break;}
case 2:{in=i,jn=j+1;break;}
case 3:{in=i+1,jn=j+1;break;}
case 4:{in=i+1,jn=j;break;}
case 5:{in=i+1,jn=j-1;break;}
case 6:{in=i,jn=j-1;break;}
case 7:{in=i-1,jn=j-1;break;}
}
if(MAZE[in][jn]==0){
e.x=in,e.y=jn,e.pre=qu->front;
push(qu,e);
MAZE[in][jn]=-1;
}
}
}
return 0;
}
//随机函数,生成minnum到maxnum的随机整数
int random_index(int minnum,int maxnum) {
struct timespec time1 = { 0, 0 };
clock_gettime(CLOCK_REALTIME, &time1);
srand(time1.tv_nsec);
int choice = maxnum - minnum + 1;
int a = rand() % choice + minnum;
return a;
}
int main()
{
int m,n;
printf("请输入迷宫行数");
scanf("%d",&m);
printf("请输入迷宫列数");
scanf("%d",&n);
for(int i=0;i<m+2;i++){
for(int j=0;j<n+2;j++){
if(i==0||i==m+1)
MAZE[i][j]=1;
else if(j==0||j==n+1)
MAZE[i][j]=1;
else
MAZE[i][j]=random_index(0,100)%2;
MAZE[1][1]=0;
MAZE[m][n]=0;
printf("%d ",MAZE[i][j]);
Sleep(m+n);
}
printf("\n");
}
if(!mgpath1(1,1,m,n))
printf("无解");
return 0;
}