【数据结构与算法】利用栈和队列进行迷宫求解

实验名称

利用栈和队列进行迷宫求解

实验内容

运用栈和队列两种方法实现迷宫求解

实验目的

  1. 通过“穷举求解”的方法实现栈的迷宫求解,熟练掌握栈的相关应用
  2. 运用队列相关知识实现迷宫求解,熟练掌握队列的相关应用

实验步骤

栈实现迷宫求解:

  1. 首先构建迷宫,确定输入输出

输入为迷宫的总行数和列数,其中包括了迷宫的围墙;输入迷宫中不通的格的数量以及其坐标,这时输入的坐标是去除围墙后的坐标;输入入口和出口坐标(一般为左上角入口(1 ,1)和右下角)。

输出为初始迷宫的构建和在迷宫中的路径。初始迷宫中围墙以及不通的格用“0”表示,通路用“1”表示;可以走出迷宫则输出走出迷宫的每一步的步数,用1、2、3……表示第几步。若不能走出迷宫输出:没有从入口到出口的路。

  1. 定义相关结构体类型和变量

  迷宫坐标位置类型

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

  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;

}

  1. 迷宫求解核心

若当前位置“可通”,则纳入路径,继续前进;

若当前位置“不可通”,则后退,换方向继续探索;

若四周“均无通路”,则将当前位置从路径中删除出去。

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. 测试案例

队列方式迷宫求解

队列求解算法中,以队列存储可以探索的位置。利用队列先进先出的特点,实现在每个分支上同时进行搜索路径,直到找到出口。这是一种广度优先搜索的方法。

1.确定输入输出

本次输入直接为整个迷宫也就是一个二维数组先确定其长和宽的个数之后用0表示可以通过,1表示不可通输出为用坐标形式表示每一步的路径这样更加便捷

  1. 相关结构体的书写

struct Box     //地图中每个格

{

     int x;     //横坐标

     int y;     //纵坐标

     int pre;   //前一个格子再在队列里面存放的位置(下标)

};

//顺序队列存放路径的信息

struct Queue

{

     Box data[SIZE];

     int front;

     int rear;

};

  1. 队列相关函数的书写

包括初始化和进队列出队列判断是否为空的操作等

  1. 核心部分

根据设定的入口出口进行入队列和出队列的操作从入口开始依次探索路径放入队列中,并对每个元素设置好前驱标志,这样一直遍历到出口后,按照前驱进行探索,可以得到整个迷宫的倒序。其中遍历时要考虑四个方向,判断是否可通,可通则进入队列。

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. 测试案例

实验总结

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
 */

  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值