实验要求和内容
迷宫只有两个门,一个叫做入口,另一个叫做出口。把一只老鼠从一个无顶盖的大盒子的入口处赶进迷宫。迷宫中设置很多隔壁,对前进方向形成了多处障碍,在迷宫的唯一出口处放置了一块奶酪,吸引老鼠在迷宫中寻找通路以到达出口。求解迷宫问题,即找出从入口到出口的路径。
实验思路
考虑到迷宫的性质以及我们走迷宫的步骤,我们选择回溯法来寻找通路,选择栈这种数据结构用来保存一条完整的从入口到出口的路线。即从入口出发,按某一方向向前探索,若能走通(未走过的),即某处可以到达,则到达新点,否则试探下一方向 ; 若所有的方向均没有通路,则沿原路返回前一点,换下一个方向再继续试探,直到所有可能的通路都探索到,或找到一条通路,或无路可走又返回到入口点。
实验内容和实验步骤
需求分析
首先我们将迷宫设定为 个空格组成的矩阵 **(也可以设置成n×m,但个人感觉意义不大)**在程序中,我用一个map[100][100]的数组来储存地图,接着我选择用数字来表示地图:0 代表一个空格,1 代表障碍,2 代表起点,3 代表终点。
其次,为了满足用户需求,我提供用户自行输入地图,和程序随机生成地图两种模式。其中随机生成地图障碍和空格的比例大致为1:10,用户也可以自行调整。
最后,我们将找到的那一条从起点到终点的通路连同地图打印出来。
概要设计
为了表示一个座标x,y,我们选择使用结构体来存储一个座标点。为了保存一条完整的路径,我们自定义一个栈来存储。
定义如下:
// 结构体表示座标
typedef struct {
int x;
int y;
}PointPos;
// 栈数据类型的定义
typedef struct
{
PointPos data[StackSize];
int top;
}SeqStack;
// 定义栈的主要功能
// 初始化一个栈
void InitStack(SeqStack &S);
// 判断栈是否为空
int StackEmpty(SeqStack S);
// 判断栈是否为满
int StackFull(SeqStack S);
// 压栈
void push(SeqStack &S, PointPos x);
// 弹出
PointPos pop(SeqStack &S);
// 返回栈顶元素
PointPos top(SeqStack S);
主程序的流程:请用户选择自行输入还是随机生成地图 -> 确定地图 -> 寻找起点和终点 -> 利用Stack实现深度优先搜索 -> 将结果打印在屏幕上
详细设计以及代码分析
用户选择
为了实现用户选择的功能,我编写了两个函数。
// 用户自行输入函数
void Choose(int n)
{
printf("0 代表空格子,1 代表墙壁,2 代表起点,3 代表终点\n");
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> map[i][j];
}
// 随机生成函数
void Random(int n)
{
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
{
// 设置空格和障碍的比例为10:1
if (rand() % 10 == 1)
map[i][j] = 1;
else
map[i][j] = 0;
}
// 随机设置起点和终点
int a, b, c, d;
a = rand() % n;
b = rand() % n;
c = rand() % n;
d = rand() % n;
map[a][b] = 2;
map[c][d] = 3;
printf("\n随机生成的地图是:\n");
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
printf("%d ", map[i][j]);
}
printf("\n");
}
}
时间复杂度
寻找起点和终点
遍历一遍地图,记录下起点和终点
void FindPos(PointPos& start, PointPos& end)
{
// 找到起点和终点座标
int f = 0;
SeqStack pointstack;
// 初始化栈
InitStack(pointstack);
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (2 == map[i][j])
{
// 找到起点
start.x = i;
start.y = j;
f = f + 1;
}
else if (map[i][j] == 3)
{
// 找到终点
end.x = i;
end.y = j;
f = f + 1;
}
if (2 == f) break;
}
if (2 == f) break;
}
}
最坏时间复杂度
利用栈实现深度优先遍历算法
深度优先搜索(DFS)(栈类实现),可以被形象的描述为“打破沙锅问到底”,具体一点就是访问一个顶点之后,我继续访问它的下一个邻接的顶点,如此往复,直到当前顶点被访问或者它不存在邻接的顶点。
具体思路如下:先从起点开始,依次寻找上下左右的座标,如果是障碍就跳过,如果不是障碍就把这个点入栈,并且标记下来,如此往复。与此同时,我给每个遍历过的点增加一定的权值,在相同条件下,优先走权值大的点。如果找不到任何通路,就做一个标记,打印”没有任何通路“。
在一定条件下,可以保证有曼哈顿距离的最优解。
具体代码实现
void dfs(PointPos start, PointPos end)
{
// 定义存储栈
SeqStack pointstack;
InitStack(pointstack);
// 标记路径权值,定义点座标和临时变量
int flag = 4;
PointPos pointpos, temp;
// 将起点座标入栈,并进入循环搜索
int cnt = 0;
push(pointstack, start);
while (!StackEmpty(pointstack))
{
cnt = 0;
pointpos = top(pointstack);
pop(pointstack);
// count记录每个座标点有路径的方向个数
int i, j, count = 0;
i = pointpos.x;
j = pointpos.y;
//如果为搜索过的路径,置为权值
if (0 == map[i][j]) map[i][j] = flag;
// 座标点上下是否可走
if (i >= 0 && i <= n - 1)
{
if (i != 0 && 0 == map[i - 1][j])
{
temp.x = i - 1;
temp.y = j;
push(pointstack, temp);
++count;
}
else if (3 == map[i - 1][j] && i != 0)
{
cnt++;
break;
}
if (0 == map[i + 1][j] && i != n - 1)
{
temp.x = i + 1;
temp.y = j;
push(pointstack, temp);
++count;
}
else if (3 == map[i + 1][j] && i != n - 1)
{
cnt++;
break;
}
}
// 座标点左右是否可走
if (j >= 0 && j <= n - 1)
{
if (0 == map[i][j - 1] && j != 0)
{
temp.x = i;
temp.y = j - 1;
push(pointstack, temp);
++count;
}
else if (3 == map[i][j - 1] && j != 0)
{
cnt++;
break;
}
if (0 == map[i][j + 1] && j != n - 1)
{
temp.x = i;
temp.y = j + 1;
push(pointstack, temp);
++count;
}
else if (3 == map[i][j + 1] && j != n - 1)
{
cnt++;
break;
}
}
// 关于有多条通路的处理
if (1 != count)
++flag;
}
// check=1表示没有通路
if (cnt == 0)
check = 1;
}
打印结果
通过栈已经将可能的路径标记下来了,现在只需要输出就可以。并且如果check=1的话,证明没有通路,可以作为特判条件。
具体代码实现
void Output(PointPos start)
{
// 特判
if (check == 1)
{
printf("没有一条合适的路径\n");
return;
}
// 搜索到达的路径
int i, j;
int ni = -1; int nj = -1;
int max;
i = start.x;
j = start.y;
max = map[i][j];
// 如果不是终点
while (3 != map[i][j])
{
// 上下判断
if (i >= 0 && i <= n - 1)
{
if (map[i - 1][j] > max && i != 0)
{
ni = i - 1;
nj = j;
max = map[i - 1][j];
}
if (map[i - 1][j] == 3 && i != 0)
break;
if (map[i + 1][j] > max&& i != n - 1)
{
ni = i + 1;
nj = j;
max = map[i + 1][j];
}
if (map[i + 1][j] == 3 && i != n - 1)
break;
}
// 左右判断
if (j >= 0 && j <= n - 1)
{
if (map[i][j - 1] > max&& j != 0)
{
ni = i;
nj = j - 1;
max = map[i][j - 1];
}
if (map[i][j - 1] == 3 && j != 0)
break;
if (map[i][j + 1] > max&& j != n - 1)
{
ni = i;
nj = j + 1;
max = map[i][j + 1];
}
if (3 == map[i][j + 1] && j != n - 1)
break;
}
// 标记为路径 即 -1
if (ni != -1 && nj != -1)
map[ni][nj] = -1;
else
{
// printf("没有一条合适的路径\n");
return;
}
// 设置初始最大值
max = 3;
// 进入迭代
i = ni;
j = nj;
}
// 输出结果
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
// 标记为路径
if (-1 == map[i][j])
cout << '*' << ' ';
// 恢复原来的值
else if (map[i][j] > 3)
cout << 0 << ' ';
else
cout << map[i][j] << ' ';
}
cout << endl;
}
}
调试分析
调试过程中遇到的问题
- 边界条件处理的不好。当i== 0 或者 i==n-1时 需要特殊判断
- 一开始无法解决没有通路该如何显示结果。后来想到,当栈为空时还没有访问到终点,证明没有任何一条通路到达终点。这样就能解决这个问题了。
- 地图和栈的初始化问题,本来地图初始化是零,但是因为我设置的空格也是0,所以这样就会出现问题。最后我把地图初始化成了-1.
- 结果不正确。主要是本来应该存在通路的情况却显示没有。通过我的调试,我发现是因为我在特判时出现了问题。
break
语句和cnt++
语句搞反,导致还没有自增1就退出了循环,debug了很久最终解决。
算法的时空分析
根据我的算法,打印地图的时间复杂度为。深度优先搜素算法时间复杂度为,也就是所有边的数量,,根据我的估算,时间复杂度大致为。深度优先搜索算法空间复杂度为也就是所有顶点的个数,为。因此本算法,时空复杂度均为
实验结果
初始界面
选择用户输入
需要输入n
输入地图
打印有解情况
若是输入无解
选择随机生成地图
保证曼哈顿距离最短
实验总结
- 记得break;要在语句之后,否则还没来得及执行就退出循环了。
- DFS算法也可以用栈实现,但是BFS 算法必须要用到队列这种数据结构。
- 在设置地图时要控制好障碍和空格的比例,不然一旦点多了就无法找到结果。
- 在无解时,可以通过检查栈为空时是否到达终点来判断。