实验名称 | 利用栈和队列进行迷宫求解 | ||||
实验内容 | 运用栈和队列两种方法实现迷宫求解 | ||||
实验目的 |
| ||||
实验步骤 | 栈实现迷宫求解:
输入为迷宫的总行数和列数,其中包括了迷宫的围墙;输入迷宫中不通的格的数量以及其坐标,这时输入的坐标是去除围墙后的坐标;输入入口和出口坐标(一般为左上角入口(1 ,1)和右下角)。 输出为初始迷宫的构建和在迷宫中的路径。初始迷宫中围墙以及不通的格用“0”表示,通路用“1”表示;可以走出迷宫则输出走出迷宫的每一步的步数,用1、2、3……表示第几步。若不能走出迷宫输出:没有从入口到出口的路。
迷宫坐标位置类型: typedef struct { int x; //行值 int y; //列值 }PosType; 栈元素类型: typedef struct { int ord; //序号 int di; //方向 PosType seat; //位置 | ||||
实验步骤 | }StackType; //栈元素类型 栈类型: typedef struct { StackType *base; //在构造之前和销毁之后,base的值为NULL StackType *top; //栈顶指针 int stacksize; //当前已分配的存储空间,以元素为单位 }SqStack; //顺序栈 定义全局变量: typedef int Status; typedef int MazeType[MAXLENGTH][MAXLENGTH]; //迷宫数组[行][列] PosType begin, end; //迷宫入口坐标,出口坐标 PosType direction[4] = { {0,1},{1,0},{0,-1},{-1,0} }; //{行增量,列增量},移动方向依次为东南西北 MazeType maze; //迷宫数组 int x, y;//迷宫的行,列 int curstep = 1; //当前足迹,初值在入口处为1
栈的初始化函数: int InitStack(SqStack *p) { p->base = (StackType*)malloc(STACK_INIT_SIZE * sizeof(StackType)); if (p->base == NULL) return ERROR; //内存分配失败 p->top = p->base; //栈顶与栈底相同表示一个空栈 p->stacksize = STACK_INIT_SIZE; return OK; } 判断栈是否为空: int EmptyStack(SqStack *p) { //若为空栈 则返回OK,否则返回ERROR if (p->top == p->base) return OK; else return ERROR; } 顺序栈的压入: int Push(SqStack *p, StackType e) { //插入元素e为新的栈顶元素 if ((p->top - p->base) >= p->stacksize) //栈满,追加储存空间 { p->base = (StackType*)realloc(p->base, (p->stacksize + STACKINCREMENT) * sizeof(StackType)); if (p->base == NULL) return ERROR;// 储存空间分配失败 p->top = p->base + p->stacksize; p->stacksize += STACKINCREMENT; } *(p->top) = e; (p->top)++; return OK; } 顺序栈的弹出: int Pop(SqStack *p, StackType *e) { //若栈不空,则删除p的栈顶元素,用e返回其值 if (p->top == p->base) return ERROR; --(p->top); *e = *(p->top); return OK; } //将顺序栈置空 栈还是存在的,栈中的元素也存在, //如果有栈中元素的地址任然能调用 int ClearStack(SqStack *p) { p->top = p->base; return OK; } 顺序栈的销毁: int DestroyStack(SqStack *p) { //释放栈底空间并置空 free(p->base); p->base = NULL; p->top = NULL; p->stacksize = 0; return OK; }
若当前位置“可通”,则纳入路径,继续前进; 若当前位置“不可通”,则后退,换方向继续探索; 若四周“均无通路”,则将当前位置从路径中删除出去。 Status MazePath(PosType start, PosType end) { //若迷宫m中存在从入口start到出口end的通道 //则求得一条存放在栈中,并返回TRUE;否则返回FAlSE SqStack s,*S; //顺序栈 S = &s; PosType curpos; StackType e,*pe; pe = &e; InitStack(S); curpos = start; do { if (Pass(curpos)) //当前可以通过,则是未曾走到的通道块 { FootPrint(curpos); //留下足迹 e.ord = curstep; //栈元素序号为当前序号 e.seat = curpos; //栈元素位置为当前位置 e.di = 0; //从当前位置出发,下一位置为东 Push(S, e); //入栈当前位置及其状态 curstep++; //足迹加1 if (curpos.x == end.x&&curpos.y == end.y) //到达出口 return TRUE; curpos = NextPos(curpos, e.di); //由当前位置及移动方向确定下一个当前位置 } else //当前位置不能通过 if (!EmptyStack(S)) //栈不为空 { Pop(S, pe); // 退栈到前一位置 curstep--; //足迹减1 while (e.di == 3 && !EmptyStack(S)) //前一位置处于最后一个方向(北) { MarkPrint(e.seat); //留下不能通过的标记(-1) Pop(S, pe); //退一步 curstep--; //足迹再减1 } if (e.di < 3) //没有到最后一个方向(北) { e.di++; //换下一个方向探索 Push(S, e); //入栈该位置的下一个方向 curstep++;// 足迹加1 curpos = NextPos(e.seat, e.di); } } } while (!EmptyStack(S)); return FALSE; }
队列方式迷宫求解: 队列求解算法中,以队列存储可以探索的位置。利用队列先进先出的特点,实现在每个分支上同时进行搜索路径,直到找到出口。这是一种广度优先搜索的方法。 1.确定输入输出: 本次输入直接为整个迷宫,也就是一个二维数组,先确定其长和宽的个数,之后用0表示可以通过,1表示不可通。输出为:用坐标形式表示每一步的路径,这样更加便捷。
struct Box //地图中每个格 { int x; //横坐标 int y; //纵坐标 int pre; //前一个格子再在队列里面存放的位置(下标) }; //顺序队列存放路径的信息 struct Queue { Box data[SIZE]; int front; int rear; };
包括初始化和进队列、出队列、判断是否为空的操作等
根据设定的入口出口进行入队列和出队列的操作。从入口开始依次探索路径放入队列中,并对每个元素设置好前驱标志,这样一直遍历到出口后,按照前驱进行探索,可以得到整个迷宫的倒序。其中遍历时要考虑四个方向,判断是否可通,可通则进入队列。 Box now; int i, j, ii, jj; now.x = x1; now.y = y1; now.pre = -1; push(q, now); //入口坐标入队 map[x1][y1] = -1; while (EmptyQueue(q) != 1) { pop(q, &now); i = now.x; j = now.y; if (i == x2 && j == y2) //出口 { ShowPath(q, q->front); return 1; } int dir; for (dir = 0; dir < 4; dir++) //循环四次,遍历四个方向上右下左 { switch(dir) { case 0: ii = i - 1;jj = j;break;//上 case 1: ii = i;jj = j + 1;break;//右 case 2: ii = i + 1;jj = j;break;//下 case 3: ii = i;jj = j - 1;break;//左 } //判断该点是否可走 if (ii>=0&&jj>=0&&ii<=a&&jj<=b&&map[ii][jj]==0)//格子可以走 { now.x = ii; now.y = jj; now.pre = q->front; push(q, now); map[ii][jj] = -1; //该点已经走过 } } } 5.打印路径 把经过的路径前驱设置为-1,之后前驱为-1的格按顺序输出得到每一步的路径。 void ShowPath(Queue *q, int front) { int p = front, tmp; while (p != 0) { tmp = q->data[p].pre; q->data[p].pre = -1; p = tmp; } int i; for (i = 0; i <= q->rear; i++) { if (q->data[i].pre == -1) { printf("(%d, %d) ", q->data[i].x, q->data[i].y); } } printf("\n"); }
| ||||
实验总结 | 1.栈解决迷宫问题占用内存相对较小,但用栈找到的出路只是所有出路中的其中一条,具体是哪一条取决于di列表中上下左右位置定义的顺序。 2.具体从入口出发,顺着某个方向向下探索,探索分为上下左右四个方位,哪个方向是通的就将向下走,如果每个方向都走不下去就进行原路“回退”。所以需要一个后进先出的结构来保存从入口到出口的路径。所以运用栈来实现是非常方便的,沿着某个方向走,将每个可通的位置进行入栈标记,再切换到下个位置;如果都不通,则栈顶出栈取消标记,再寻找。 3.队列解决迷宫问题是依次探索路径放入队列中,并对每个元素设置好前驱标志,这样一直遍历到终点时,按照前驱进行探索,输出整个迷宫的倒序,并对这些坐标进行编码,再一次遍历迷宫输出路径。与栈不同,栈得到的是任意一条路径,而队列求解时我们不能只考虑一条路径,而是能走的路径都要考虑,队列存的是当前所有能走的路的分叉(要考虑的结点)。由于所有路径是同时走的,所以先找到终点的那条路径一定是最短的。队列得到的时最短的路径。 4.通过迷宫求解的编程练习思考数据结构的使用,比如对栈、指针、链表,队列等的深入了解,让我们感受到了数据结构及其算法重要。此外还熟悉了各种函数的应用。此次编写栈和队列两种方式迷宫求解,对掌握数据结构的知识有很大的帮助。我们通过编程实践,拓展了思路,提高了编程能力。 |
栈迷宫代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define STACK_INIT_SIZE 100//储存空间初始分配量
#define STACKINCREMENT 10//存储空间分配增量
#define OK 1
#define ERROR 0
#define MAXLENGTH 25 //设迷宫最大行列为25
#define TRUE 1
#define FALSE 0
typedef struct {
int x; //行值
int y; //列值
}PosType; //迷宫坐标位置类型
typedef struct
{
int ord; //序号
int di; //方向
PosType seat; //位置
}StackType; //栈元素类型
typedef struct {
StackType *base; //在构造之前和销毁之后,base的值为NULL
StackType *top; //栈顶指针
int stacksize; //当前已分配的存储空间,以元素为单位
}SqStack; //顺序栈
//栈的初始化
int InitStack(SqStack *p) {
p->base = (StackType*)malloc(STACK_INIT_SIZE * sizeof(StackType));
if (p->base == NULL) return ERROR; //内存分配失败
p->top = p->base; //栈顶与栈底相同表示一个空栈
p->stacksize = STACK_INIT_SIZE;
return OK;
}
//判断栈是否为空
int EmptyStack(SqStack *p) {
//若为空栈 则返回OK,否则返回ERROR
if (p->top == p->base) return OK;
else return ERROR;
}
//顺序栈的压入
int Push(SqStack *p, StackType e) {
//插入元素e为新的栈顶元素
if ((p->top - p->base) >= p->stacksize) //栈满,追加储存空间
{
p->base = (StackType*)realloc(p->base, (p->stacksize + STACKINCREMENT) * sizeof(StackType));
if (p->base == NULL) return ERROR;// 储存空间分配失败
p->top = p->base + p->stacksize;
p->stacksize += STACKINCREMENT;
}
*(p->top) = e;
(p->top)++;
return OK;
}
// 顺序栈的弹出
int Pop(SqStack *p, StackType *e) {
//若栈不空,则删除p的栈顶元素,用e返回其值
if (p->top == p->base) return ERROR;
--(p->top);
*e = *(p->top);
return OK;
}
//将顺序栈置空 栈还是存在的,栈中的元素也存在,
//如果有栈中元素的地址任然能调用
int ClearStack(SqStack *p) {
p->top = p->base;
return OK;
}
//顺序栈的销毁
int DestroyStack(SqStack *p) {
//释放栈底空间并置空
free(p->base);
p->base = NULL;
p->top = NULL;
p->stacksize = 0;
return OK;
}
//全局变量
typedef int Status;
typedef int MazeType[MAXLENGTH][MAXLENGTH]; //迷宫数组[行][列]
PosType begin, end; //迷宫入口坐标,出口坐标
PosType direction[4] = { {0,1},{1,0},{0,-1},{-1,0} }; //{行增量,列增量},移动方向依次为东南西北
MazeType maze; //迷宫数组
int x, y;//迷宫的行,列
int curstep = 1; //当前足迹,初值在入口处为1
struct SElemType
{
int ord; //通道块在路径上的“序号”
PosType seat; //通道块在迷宫中的“坐标位置”
int di; //从此通道块走向下一通道块的“方向”(0-3表示东-北)
};
void Print()
{
//输出迷宫结构
int i, j;
for (i = 0; i < x; i++)
{
for (j = 0; j < y; j++)
printf("%3d", maze[i][j]);
printf("\n");
}
}
void Init()
{
//设定迷宫布局(墙值为0,通道值为1)
//全局变量maze 未初始化 所以均为值均为0
int i, j, x1, y1;
printf("构建迷宫如下,请按提示输入:\n");
printf("请输入总行数、列数(包括周围):\n");
scanf("%d%d", &x, &y);
for (i = 1; i < x - 1; i++)
for (j = 1; j < y - 1; j++)
maze[i][j] = 1; //定义通道为1
printf("请输入迷宫内不通格的数量:");
scanf("%d", &j);
printf("请输入迷宫不通格的坐标(除去周围):\n");
for(i=1;i<=j;i++)
{
scanf("%d%d", &x1, &y1);
maze[x1][y1] = 0; //定义不通为0
}
printf("迷宫结构如下:\n");
Print();
printf("请输入入口的坐标:");
scanf("%d%d", &begin.x, &begin.y);
printf("请输入出口的坐标:");
scanf("%d%d", &end.x, &end.y);
}
void MarkPrint(PosType seat)
{//标记迷宫块不可通过
maze[seat.x][seat.y] = -1;
}
Status Pass(PosType b)
{
//当迷宫m的b点的值为1,return OK;
//否则return ERROR;
if (maze[b.x][b.y] == 1)
return OK;
else
return ERROR;
}
void FootPrint(PosType b)
{
//使迷宫m的b点的值变为足迹(curstep),表示经过
maze[b.x][b.y] = curstep;
}
PosType NextPos(PosType b,int di)
{
//根据当前位置b及其移动方向di,修改b为下一位置
b.x += direction[di].x;
b.y += direction[di].y;
return b;
}
Status MazePath(PosType start, PosType end)
{
//若迷宫m中存在从入口start到出口end的通道
//则求得一条存放在栈中,并返回TRUE;否则返回FAlSE
SqStack s,*S; //顺序栈
S = &s;
PosType curpos;
StackType e,*pe;
pe = &e;
InitStack(S);
curpos = start;
do
{
if (Pass(curpos)) //当前可以通过,则是未曾走到的通道块
{
FootPrint(curpos); //留下足迹
e.ord = curstep; //栈元素序号为当前序号
e.seat = curpos; //栈元素位置为当前位置
e.di = 0; //从当前位置出发,下一位置为东
Push(S, e); //入栈当前位置及其状态
curstep++; //足迹加1
if (curpos.x == end.x&&curpos.y == end.y) //到达出口
return TRUE;
curpos = NextPos(curpos, e.di); //由当前位置及移动方向确定下一个当前位置
}
else //当前位置不能通过
if (!EmptyStack(S)) //栈不为空
{
Pop(S, pe); // 退栈到前一位置
curstep--; //足迹减1
while (e.di == 3 && !EmptyStack(S)) //前一位置处于最后一个方向(北)
{
MarkPrint(e.seat); //留下不能通过的标记(-1)
Pop(S, pe); //退一步
curstep--; //足迹再减1
}
if (e.di < 3) //没有到最后一个方向(北)
{
e.di++; //换下一个方向探索
Push(S, e); //入栈该位置的下一个方向
curstep++;// 足迹加1
curpos = NextPos(e.seat, e.di);
}
}
}
while (!EmptyStack(S));
return FALSE;
}
int main()
{
Init(); //初始化迷宫
if (MazePath(begin, end)) //有通路
{
printf("迷宫入口到出口的路如下:\n");
Print();
}
else
printf("迷宫没有从入口到出口的路\n");
return 0;
}
队列迷宫代码参考:
#include <stdio.h>
#include <stdlib.h>
#define SIZE 1024
struct Box //表示一个格子的位置信息
{
int x; //横坐标
int y; //纵坐标
int pre; //前一个格子再在队列里面存放的位置(下标)
};
typedef struct Box Box;
//顺序队列存放路径的信息
struct Queue
{
Box data[SIZE];
int front;
int rear;
};
typedef struct Queue Queue;
int map[100][100];
int a,b;
/*int map[6][6] = {
{0, 0, 0, 1, 1, 1},
{1, 1, 0, 0, 0, 0},
{1, 1, 0, 1, 1, 0},
{1, 1, 0, 0, 0, 0},
{1, 1, 1, 0, 1, 1},
{1, 1, 1, 0, 0, 1},
};
*/
//初始化顺序队列
int InitQueue(Queue *q)
{
q->front = q->rear = -1;
return 1;
}
//进队操作
int push(Queue *q, Box b)
{
if (q->rear == SIZE - 1)
{
return 0;
}
(q->rear)++;
q->data[q->rear] = b;
return 1;
}
//判断队列是否为空
int EmptyQueue(Queue *q)
{
return (q->front == q->rear) ? 1 : 0;
}
//出队操作(只是操作对头指针,元素实际还保留在队列中)
int pop(Queue *q, Box *b)
{
if (q->front == q->rear)
{
return 0;
}
(q->front)++;
*b = q->data[q->front];
return 1;
}
//打印路径
void ShowPath(Queue *q, int front)
{
int p = front, tmp;
while (p != 0)
{
tmp = q->data[p].pre;
q->data[p].pre = -1;
p = tmp;
}
int i;
for (i = 0; i <= q->rear; i++)
{
if (q->data[i].pre == -1)
{
printf("(%d, %d) ", q->data[i].x, q->data[i].y);
}
}
printf("\n");
}
int Walk(Queue *q, int x1, int y1, int x2, int y2)
{
Box now;
int i, j, ii, jj;
now.x = x1;
now.y = y1;
now.pre = -1;
push(q, now); //入口信息入队
map[x1][y1] = -1;
while (EmptyQueue(q) != 1)
{
pop(q, &now);
i = now.x;
j = now.y;
if (i == x2 && j == y2) //出口
{
ShowPath(q, q->front);
return 1;
}
int dir;
for (dir = 0; dir < 4; dir++) //循环四次,遍历四个方向 上 右 下 左
{
switch(dir)
{
case 0: ii = i - 1;jj = j;break;//上
case 1: ii = i;jj = j + 1;break;//右
case 2: ii = i + 1;jj = j;break;//下
case 3: ii = i;jj = j - 1;break;//左
}
//判断该点是否可走
if (ii>=0&&jj>=0&&ii<=a&&jj<=b&&map[ii][jj]==0)//格子可以走
{
now.x = ii;
now.y = jj;
now.pre = q->front;
push(q, now);
map[ii][jj] = -1; //该点已经走过
}
}
}
return 0;
}
int main()
{
int m,n;//起点
int ml,nl;//终点
//输入地图大小
printf("请输入地图规格:");
scanf("%d %d",&a,&b);
for(int i=0;i<a;i++)
for(int j=0;j<b;j++)
{
map[i][j]=0;
}
printf("请输入地图(0表示通,1表示不通):\n");
//输入地图
for(int i=0;i<a;i++)
for(int j=0;j<b;j++)
{
scanf("%d",&map[i][j]);
}
Queue queue;
InitQueue(&queue);
printf("请输入起点坐标:\n");
scanf("%d %d",&m,&n);
if(m>=a||n>=b)
printf("请重新输入!");
printf("请输入终点坐标:\n");
scanf("%d %d",&ml,&nl);
if(ml>=a||nl>=b)
printf("请重新输入!");
if(m<a&&n<b&&ml<a&&nl<=b)
{
if (Walk(&queue, m, n, ml, nl) == 0)
{
printf("路径不存在\n");
}
}
return 0;
}
/*
1 1 1 1 1 1
1 1 1 0 0 0
0 0 1 1 0 0
1 1 1 0 1 0
0 0 1 1 1 0
1 1 0 1 1 1
*/