迷宫寻路:老鼠如何找到奶酪

【前言(免责声明)】

各位看官,这个标题找AI取的,因为想不到什么好标题。

而这篇文章大篇幅是我自言自语,逻辑可能不是很清晰,因为我思维跳跃,会突然想到自己前面有问题或者漏了什么,然后折回去删删改改。

同时也会有大篇幅伪代码。

【问题描述】

在迷宫中放着一大块奶酪,一只老鼠在迷宫中。迷宫中有墙,老鼠如何绕开墙,找到奶酪? 图中所示黑色部分为墙,已知老鼠、奶酪、墙的坐标的情况下,编程输出老鼠的行进路线。

【编程任务】

编程输出从老鼠到奶酪的行进路线。

【分析思路】

1. 如何在 input.txt 中表达地图?

通过坐标。

第一行表示地图大小,这里用10*10,第二行老鼠的起始位置,第三行奶酪位置,第四行黑格子数量,其余行黑色格子坐标。

 1 #include <fstream>
 2 using namespace std;
 3 // 开辟比原地图要大些的路,通过在四周建立“虚拟墙”缓冲区
 4 // 作为一个学者,我其实不太理解上述内容,只是记得老师说过这一点,说这个是“增强了算法的鲁棒性”
 5 int maze[12][12] = {0}// 初始化全为路
 6 int rows, cols, startX, startY, targetX, targetY,wallCount;
 7 
 8 ifstream infile("input.txt");
 9 
10 if(!infile){
11     cout << "文件打开失败" << endl;
12     return 0
13 }
14 
15 // 按照 input.txt 格式顺序读取
16 infile >> rows >> cols;
17 infile >> startX >> startY;
18 infile >> sargetX >> stargetY;
19 infile >> wallCount;
20 
21 for(int i = 0; i < wallCount; i++){
22     int r,c;
23     maze[r][c]=1;//标记为墙
24 }

2. 怎么判断哪些地方可走?

黑色方格为不可走路线,记作1,白色方格为可走路线,记作0.

同时,为了避免老鼠来回走,需要用2记作老鼠走过的位置。

if(老鼠撞墙){
    maze[1][3]=1;
}
if(老鼠没撞墙且未走过那条路){
//只有为0时老鼠才可以走过去
    maze[1][3]=0;
}
if(老鼠走过那条路){
    maze[1][3]=2;
}

3. 怎么确定“吃到了奶酪”?

首先要确保程序读取了input.txt里面第三行奶酪的坐标位置$(TargetX, TargetY)$。&amp;lt;/p&amp;gt;&amp;lt;div class="cnblogs_code"&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;span style="color: #008080;" data-mce-style="color: #008080;"&amp;gt;1 &lt;span style="color: #0000FF;" data-mce-style="color: #0000ff;"&gt;if(mouse.x == targetX &amp;&amp; mouse.y ==<span style="color: #000000;" data-mce-style="color: #000000;"> targetY){ 2 // 老鼠吃到奶酪,保存所有坐标输出 3 }else{ 4 // 继续执行代码 5 }

4. 用什么方式来代表“向上、向下、向左、向右”这四个动作的顺序?

WASD

W(上):行号+1,列号不变→(1,0);

A(左):行号不变,列号-1→(0,-1);

S(下):行号-1,列号不变→(-1,0);

D(右):行号不变,列号+1→(0,1);

因此,代码中我们需要有两个数组来表示坐标的偏移量。

为了确保老鼠撞墙后会掉头,因此需要一个可以记录WASD的量。

 1 struct Node{
 2     int x,y;//当前所在格子的坐标
 3     int dir;//记录走到了 WASD 哪一个,清楚下一步走哪边
 4 }
 5 
 6 while(!s.empty){
 7     Node &curr = s.top();//看看现在位置
 8 
 9     // 检查老鼠吃没吃到奶酪
10     if(curr.x == targetX && curr.y == targetY){
11         print("吃到奶酪");
12         break;
13     
14     if(dir==0){
15         // 移动位置,不论前面是否有路都执行
16         curr.x++17     }else if(curr.dir == 1){
18         // 向下走
19     }else if(curr.dir == 2){
20         // 向左走
21     } //以此类推
22 
23     if (curr.dir == 4) {
24         s.pop(); // 当前格子四个方向都试过了,全是墙或走过的,彻底放弃这个格子
25     }
26 }

 

其实到了这里,我觉得差不多了,只要仔细写一下  dir==0  部分就可以了。

但是我忽然意识到栈是“后进先出”的。

so,pass

同时,我忽略了如何不让老鼠跑外面这一点。

并且, curr.x++  这个逻辑也有问题。

Node &curr = s.stop(); 指的是老鼠当前站着的格子

假设老鼠站在 (1, 1),程序执行了  curr.x++ ,老鼠还没动,但它脚下的坐标变成了 (2, 1)。

为了避免该问题,应该创建一个新变量来存储它。

 1 // 假设当前老鼠位置(curr.x,curr.y)
 2 int nx = curr.x;
 3 int ny = curr.y;
 4 
 5 // 老鼠不跑出迷宫
 6 if (nx >= 1 && nx <= 10 && ny >= 1 && ny <= 10 && maze[nx][ny] == 0) {
 7     // 只有同时满足在界内,且目标点是“路(0)”,老鼠才迈步
 8 }
 9 
10 // 老鼠走到下一个位置, curr.x不变
11 if(curr.dir == 0){
12     nx = curr.x + 1;
13 }else if(curr.dir == 1){
14     ny = curr.y +115 }
16 // ......
17 
18 if(老鼠不跑出迷宫){
19     // 新坐标压进去,老鼠迈步
20 }

5. 固定数值代表某个反向

写到这里我觉得有必要先固定死一些数值,避免自己想一出是一出。

curr.dir(WASD):

  • W(上):行号+1,列号不变→(1,0);
  • A(左):行号不变,列号-1→(0,-1);
  • S(下):行号-1,列号不变→(-1,0);
  • D(右):行号不变,列号+1→(0,1);

因此,代码中我们需要有两个数组来表示坐标的偏移量。

为了确保老鼠撞墙后会掉头,因此需要一个可以记录WASD的量。

 1 struct Node{
 2     int x,y;//当前所在格子的坐标
 3     int dir;//记录走到了 WASD 哪一个,清楚下一步走哪边
 4 }
 5 
 6 while(!s.empty){
 7     Node &curr = s.top();//看看现在位置
 8 
 9     // 检查老鼠吃没吃到奶酪
10     if(curr.x == targetX && curr.y == targetY){
11         print("吃到奶酪");
12         break;
13     
14     if(dir==0){
15         // 移动位置,不论前面是否有路都执行
16         curr.x++17     }else if(curr.dir == 1){
18         // 向下走
19     }else if(curr.dir == 2){
20         // 向左走
21     } //以此类推
22 
23     if (curr.dir == 4) {
24         s.pop(); // 当前格子四个方向都试过了,全是墙或走过的,彻底放弃这个格子
25     }
26 }

这么一看,它们逻辑相当相似,一套完整的if-else语句下来,坐标变动情况:

  • (1, 0) —— 向下

  • (0, 1) —— 向右

  • (-1, 0) —— 向上

  • (0, -1) —— 向左

那么有没有什么好办法打包起来呢?

数组!绝对的数组!我想不到比数组还好的方法!

将x所有变量放入dx[ ],将y所有变量放入dy[ ]不就game over了?

1 int dx[] = {1,0,-1,0};
2 int dy[] = {0,1,0,-1};
3 
4 // 原本curr.dir == 0/1/2/3/4,如今可以作为数组的索引
5 // 在循环里只需要一句话就能算出新坐标:
6 int nx = curr.x + dx[curr.dir];
7 int ny = curr.y + dy[curr.dir];

当  curr.dir == 0  时, dx[0]==1,dy[0]==0 , 坐标为(1,0);

当  curr.dir == 1  时, dx[1]==0,dy[1]==1 , 坐标为(0,1);

......

有了它,我就可以省去if-else语句。

 1 while(!s.empty){
 2     Node &curr = s.stop();
 3     
 4     if(curr.dir < 4){
 5         int nx = curr.x + dx[curr.dir];
 6         int ny = curr.y + dy[curr.dir];
 7 
 8         curr.dir++;
 9         
10         if(nx >= 1 && nx <= 10 && ny >= 1 && ny <= 10 && maze[nx][ny] == 0){
11             // 标记走过
12             maze[nx][ny] = 2;
13             // 压入新格子,新格子的 dir 从 0 开始
14             s.push({nx,ny,0});
15 
16             // 检查是否到终点
17             if(nx == targetX && ny == targetY){
18                 print("吃到奶酪");
19                 break;
20             }
21             
22             // 没吃到,跳出循环,回到栈顶,处理新格子
23             continue;
24         }
25         // 如果撞墙、出界,dir进入新数字
26     }else{
27         // 0/1/2/3/4都试过了
28         s.top();
29     }
30 }

7. 处理“后进先出”问题

第一步:定义一个足够大的数组放栈

1 Node path[144];
2 int num = 0;
3 
4 while(!s.empty()){
5     path[num] = s.top();// 把栈顶的坐标复制给数组的第 count 个位置
6     num++;
7     s.pop();
8 }

第二步:倒着打印数组

这个时候, path[0] 是终点, path[count-1] 是起点。

1 for(int i = path[cout-1]; i >=0; i--){
2     cout << path[i].x << " " <<path[i].y << endl;
3 }
4 cout << endl;

【实验代码】

  1 #include <fstream>
  2 #include <iostream>
  3 #include <stack>
  4 #include <vector>
  5 using namespace std;
  6 
  7 struct Node
  8 {
  9     int x, y;
 10     int dir; // 0: 下, 1: 右, 2: 上, 3: 左
 11 };
 12 stack<Node> s;
 13 
 14 int maze[12][12] = {0};
 15 int rows, cols, startX, startY, stageX, stageY, wallCount;
 16 
 17 int main()
 18 {
 19     ifstream inFile("input.txt");
 20 
 21     if (!inFile)
 22     {
 23         cerr << "不能打开 input.txt" << endl;
 24         return 1;
 25     }
 26 
 27     // 1. 读地图大小
 28     inFile >> rows >> cols;
 29 
 30     // 2. 读起点坐标
 31     inFile >> startX >> startY;
 32 
 33     // 3. 读奶酪(终点)坐标
 34     inFile >> stageX >> stageY;
 35 
 36     // 4. 读墙的总数
 37     inFile >> wallCount;
 38 
 39     // 5. 接下来需要用循环读入所有的墙坐标
 40     for (int i = 0; i < wallCount; i++)
 41     {
 42         int r, c;
 43         inFile >> r >> c;
 44         maze[r][c] = 1; // 标记为墙
 45     }
 46 
 47     inFile.close();
 48 
 49     maze[startX][startY] = 2;    // 标记起点为已访问
 50     s.push({startX, startY, 0}); // 将起点入栈
 51 
 52     while (!s.empty())
 53     {
 54         Node &curr = s.top();
 55 
 56         if (curr.dir < 4)
 57         {
 58             int dx[] = {1, 0, -1, 0}; // 下, 右, 上, 左
 59             int dy[] = {0, 1, 0, -1};
 60 
 61             int nx = curr.x + dx[curr.dir];
 62             int ny = curr.y + dy[curr.dir];
 63 
 64             curr.dir++; // 尝试下一个方向
 65 
 66             if (nx >= 0 && nx <= rows && ny >= 0 && ny <= cols && maze[nx][ny] == 0)
 67             {
 68                 s.push({nx, ny, 0}); // 将新位置入栈
 69                 maze[nx][ny] = 2;    // 标记为已访问
 70 
 71                 if (nx == stageX && ny == stageY)
 72                 {
 73                     cout << "找到奶酪了!" << endl;
 74                     break; // 找到奶酪,结束程序
 75                 }
 76                 continue; // 没吃到,继续探索新位置
 77             }
 78         }
 79         else
 80         {
 81             s.pop(); // 回退到上一个位置
 82         }
 83     }
 84 
 85     // 打印路径
 86     Node path[144]; // 最多12*12个位置
 87     int pathLength = 0;
 88     while (!s.empty())
 89     {
 90         path[pathLength] = s.top();
 91         pathLength++;
 92         s.pop();
 93     }
 94 
 95     ofstream outFile("output.txt");
 96 
 97     if (!outFile)
 98     {
 99         cerr << "不能创建 output.txt" << endl;
100         return 1;
101     }
102 
103     for (int i = pathLength - 1; i >= 0; i--)
104     {
105         outFile << path[i].x << " " << path[i].y << endl;
106     }
107     outFile.close();
108 }

【数据测试1】

屏幕截图 2026-04-06 163756

【测试数据2】

屏幕截图 2026-04-06 164050

posted @ 2026-04-06 16:58  A-pigeon  阅读(1)  评论(0)    收藏  举报