【数据结构】10分钟教你用栈求解迷宫老鼠问题超详细教程附C++源代码

简介: 【数据结构】10分钟教你用栈求解迷宫老鼠问题超详细教程附C++源代码

问题描述

给定一张迷宫地图和一个迷宫入口,然后进入迷宫探索找到一个出口。如下图所示:

微信图片_20220421152545.jpg

该图是一个矩形区域,有一个入口和出口。迷宫内部包含不能穿越的墙壁或者障碍物。这些障碍物沿着行和列放置,与迷宫的边界平行。迷宫的入口在左上角,出口在右下角。

问题分析

  1. 首先要有一张迷宫地图,地图由两部分组成:
    (1)一是迷宫中各处的位置坐标,
    (2)二是迷宫各位置处的状态信息,即该处是墙还是路

所以,该迷宫地图可由一个二维数组来表示。数组的横纵坐标表示迷宫各处的位置坐标,数组元素表示各位置处的状态信息。

2.在这里,假定:

(1)迷宫地图是m*n的,即二维数组是m行n列的。

(2)在迷宫中用1表示墙,用0表示路。当然,为了便于标识,我们后面还会用其他数字代表更多含义。

因此,迷宫的地图一个刻画如下:

微信图片_20220421152549.png

现在我们要找一条从入口到出口的路径。路径是一个由位置组成的序列,每一个位置都没有障碍,并且除入口外,路径上的每一个位置都是前一个位置在东西南北方向上相邻的一个位置。

不过,考虑到边界问题不太好处理。我们做这样的工作,在地图外围加一层围墙,给它全部填上1。这样,在处理各个坐标时,都没有差别了。这样一来便大大简化了我们的工作。

微信图片_20220421152552.png

下面我们要编写程序求解这个问题。

程序设计

这次还是采用一个简单的模块化来设计这个程序。那么主要有下面几个模块:

  1. 显示欢迎信息
  2. 初始化工作
  3. 生成地图
  4. 找路
  5. 打印地图和路径

下面我们分别完成这些功能。

显示欢迎信息

这个模块就很简单了,输出一些信息提醒使用者就行,主要是为了增加程序的友好性而设置的。大家根据自己的需要自行发挥。例如我的就很随便了:

1void welcome()
2{
3    cout << "welcome to RAT IN MAZE" << endl;
4    system("pause");
5    system("cls");
6}

微信图片_20220421152555.png

初始化工作

这个主要是设置一些全局变量的取值和完成内存的分配,地图的存储还是从堆上分配内存比较好。因为一般来说,考虑到地图可能会很大,这样需要的存储空间就很多了。在这里一并把相关的全局变量给讲解了吧。

1typedef struct  
 2{
 3    int row;
 4    int col;
 5}POSITION;
 6
 7
 8const POSITION maze_size = { 20 , 60 };
 9
10int ** const maze = new int*[maze_size.row + 2];
11
12stack<POSITION> path;
13POSITION offset[4];//direction
  • POSITION结构体
    坐标结构体变量类型,很容易理解,有两个成员变量:x坐标和y坐标。
  • maze_size
    定义地图的大小,实际分配内存的时候,我们还需要考虑地图边界也需要存储空间。总之,我们的地图坐标范围是1 to maze_size。
  • maze
    二位数组,存储地图,分配的时候+2是用来存储边界的。至于const则是约束指针不改变。不过我们的地图数组是根据maze_size大小动态分配的。
  • path
    用来存路径的。
  • offset
    用来设置位置偏移的。比如我们当前位置是(row = 1, col = 1),那么通过row + 1便可往下走,row - 1就是往上走。col + 1往右走,col - 1 往左走。等等。通过坐标加减offset偏移,便可以移动了。
1void init()
 2{
 3    //偏移
 4    offset[0].row = 0; offset[0].col = 1; //right
 5    offset[1].row = 1; offset[1].col = 0; //down
 6    offset[2].row = 0; offset[2].col = -1; //left
 7    offset[3].row = -1; offset[3].col = 0; //up
 8
 9    //maze = new int*[maze_size.row + 2];
10    for (int i = 0; i < maze_size.row + 2; i++)
11    {
12        maze[i] = new int[maze_size.col + 2];
13    }
14}

这个代码就是设置偏移的数值,以及动态分配地图数组了。

生成地图

生成地图还是根据地图尺寸,然后随机设置障碍。不过要注意障碍出现的概率设置得小一点,不然地图一般无解。可以用rand()随机数来做。这一步也要把围墙设置好。

1//地图范围1 - maze_size 有围墙
 2void randomMaze()
 3{
 4    int i, j, rate;
 5
 6    for (i = 0; i < maze_size.row + 2; i++)
 7    {
 8        for (j = 0; j < maze_size.col + 2; j++)
 9        {
10            //设置围墙
11            if ((i == 0) || (i == maze_size.row + 1) || (j == 0) || (j == maze_size.col + 1))
12            {
13                maze[i][j] = 1;
14            }
15            else
16            {
17                rate = rand() % 10+1;
18                if (rate <= 3)
19                {
20                    maze[i][j] = 1;//随机生成障碍
21                }
22                else
23                {
24                    maze[i][j] = 0;
25                }
26            }
27        }
28    }
29    //最后保证起点和终点能走
30    maze[1][1] = maze[maze_size.row][maze_size.col] = 0;
31}

找路

这个是整个程序设计的核心功能,没有之一。在写代码之前,我们需要弄明白,到底怎么找路呢?

  1. 首先,把迷宫入口作为当前位置。
  2. 如果当前位置是迷宫出口,那么已经找到一条路径了,程序结束。
  3. 如果当前位置不是出口,则在当前位置放置障碍物,表示这里已经来过,防止下次又重复绕回来。然后检查相邻位置是否能走。
  4. 如果一个相邻位置能走,就移动到这个位置上。然后在新的位置上重新开始寻找出口。如果不能走,就尝试下一个相邻位置。
  5. 如果所有的相邻位置都不能走了,则回退到上一个位置,重新选择上一个位置的其他相邻位置,继续探索。
  6. 如果所有的相邻位置都被探索过了,仍然找不到路径,那说明这个迷宫不存在这样的路径。

例如,下面的地图:

微信图片_20220421152558.jpg

图8-9

微信图片_20220421152601.jpg

好了,说了这么多,相信大家已经了解得差不多,并且跃跃欲试了。

1bool findPath()
 2{
 3    POSITION here; //当前位置
 4    here.row = here.col = 1;
 5    maze[1][1] = 3; //放置障碍,防止回来
 6    int option = 0; //next step
 7    const int lastOption = 3; 
 8
 9    //find a path
10    while ( here.row != maze_size.row || here.col != maze_size.col)
11    {
12        //not reach the end
13        int r, c;
14        while (option <= lastOption)
15        {
16            r = here.row + offset[option].row;
17            c = here.col + offset[option].col;
18            if (maze[r][c] == 0)
19            {
20                break;
21            }
22            option++;//next choice
23        }
24
25        //相邻的位置能走?
26        if (option <= lastOption)
27        {
28            path.push(here);
29            here.row = r;
30            here.col = c;
31            maze[r][c] = 3; //走过了
32            option = 0;
33        }
34        else
35        {
36            if (path.empty())
37            {
38                return false;
39            }
40            //go back
41            maze[here.row][here.col] = 3; //此路不可通
42            here = path.top();
43            path.pop();
44            option = 0;
45        }
46    }
47
48    maze[maze_size.row][maze_size.col] = 2;
49
50    return true;
51}

相关代码如上面所示,结合前面的讲解,相信大家也能看懂。

打印地图和路径

这个功能就比较简单了,主要是根据maze的信息,生成相应的地图显示出来给大家直观的看到。对于maze里面存的数值,我们也可以作一个小小的规定:

  • 0
    表示位置可通行。
  • 1
    表示位置有障碍。
  • 2
    表示该位置处在找到的路径上面。
  • 3
    探索过程中放置的障碍物。这个障碍物和1表示的障碍物不同的是,这个障碍我们放置的,和生成地图时的固定障碍物不同。因此还是要区分开来的。

然后打印的时候,遍历maze数组,遇到:

  • 0
    打印空格。
  • 1
    打印*号。
  • 2
    打印#号,并且设置下颜色。
  • 3
    还是打印空格。(或者根据自己的喜好打印另外的符号,这样就可以把探索过的所有位置显示出来。)

最后在打印最终的地图和路径之前,如果找到一条路径。我们还要根据path中的路径,在maze里面设置好相应的值,才做最终的输出:

1void setPathOnMaze()
 2{
 3    POSITION pos;
 4    while (!path.empty())
 5    {
 6        pos = path.top();
 7        path.pop();
 8        maze[pos.row][pos.col] = 2;//路径
 9    }
10}
11
12然后,可以开始输出我们的地图了。

具体代码:

1void outputMaze()
 2{
 3    int i, j;
 4    for (i = 0; i < maze_size.row + 2; i++)
 5    {
 6        for (j = 0; j < maze_size.col + 2; j++)
 7        {
 8            if (maze[i][j] == 1)
 9            {
10                cout << "*";
11            }
12            else if ((maze[i][j] == 0) || (maze[i][j] == 3))
13            {
14                cout << " ";
15            }
16            else
17            {
18                HANDLE hOut;
19                hOut = GetStdHandle(STD_OUTPUT_HANDLE);
20                SetConsoleTextAttribute(hOut,FOREGROUND_GREEN | FOREGROUND_INTENSITY);
21                cout << "#";
22                SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
23            }
24        }
25        cout << endl;
26    }
27}

最终效果

  1. 没有找到路的情况:
    微信图片_20220421152604.jpg
  2. 找到了路径:
    微信图片_20220421152606.jpg


相关文章
|
4天前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
116 75
|
4天前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
27 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
4天前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
30 9
|
4天前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
25 7
|
2月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
265 9
|
2月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
42 1
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
79 5
|
2月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
2月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
2月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
57 4