数据结构实验二——迷宫的求解

实验要求和内容

迷宫只有两个门,一个叫做入口,另一个叫做出口。把一只老鼠从一个无顶盖的大盒子的入口处赶进迷宫。迷宫中设置很多隔壁,对前进方向形成了多处障碍,在迷宫的唯一出口处放置了一块奶酪,吸引老鼠在迷宫中寻找通路以到达出口。求解迷宫问题,即找出从入口到出口的路径。

实验思路

考虑到迷宫的性质以及我们走迷宫的步骤,我们选择回溯法来寻找通路,选择栈这种数据结构用来保存一条完整的从入口到出口的路线。即从入口出发,按某一方向向前探索,若能走通(未走过的),即某处可以到达,则到达新点,否则试探下一方向 ; 若所有的方向均没有通路,则沿原路返回前一点,换下一个方向再继续试探,直到所有可能的通路都探索到,或找到一条通路,或无路可走又返回到入口点。

实验内容和实验步骤

需求分析

首先我们将迷宫设定为 n×n(0<n<100)n×n \quad(0<n<100)个空格组成的矩阵 **(也可以设置成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");
	}
}

时间复杂度O(n2)O(n^2)

寻找起点和终点

遍历一遍地图,记录下起点和终点

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;
	}
}

最坏时间复杂度O(n2)O(n^2)

利用栈实现深度优先遍历算法

深度优先搜索(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;
	}
}

调试分析

调试过程中遇到的问题

  1. 边界条件处理的不好。当i== 0 或者 i==n-1时 需要特殊判断
  2. 一开始无法解决没有通路该如何显示结果。后来想到,当栈为空时还没有访问到终点,证明没有任何一条通路到达终点。这样就能解决这个问题了。
  3. 地图和栈的初始化问题,本来地图初始化是零,但是因为我设置的空格也是0,所以这样就会出现问题。最后我把地图初始化成了-1.
  4. 结果不正确。主要是本来应该存在通路的情况却显示没有。通过我的调试,我发现是因为我在特判时出现了问题。break语句和cnt++语句搞反,导致还没有自增1就退出了循环,debug了很久最终解决。

算法的时空分析

根据我的算法,打印地图的时间复杂度为O(n2)O(n^2)。深度优先搜素算法时间复杂度为O(E)O(E),也就是所有边的数量,(n1)×(2n1)+(n1)=2n(n1)(n-1)\times(2n-1)+(n-1)=2n(n-1),根据我的估算,时间复杂度大致为O(n2)O(n^2)。深度优先搜索算法空间复杂度为O(V)O(V)也就是所有顶点的个数,为O(n2)O(n^2)。因此本算法,时空复杂度均为O(n2)O(n^2)

实验结果

初始界面

在这里插入图片描述
选择用户输入

需要输入n
在这里插入图片描述
输入地图

在这里插入图片描述

打印有解情况

在这里插入图片描述
若是输入无解

在这里插入图片描述

选择随机生成地图

保证曼哈顿距离最短
在这里插入图片描述

实验总结

  1. 记得break;要在语句之后,否则还没来得及执行就退出循环了。
  2. DFS算法也可以用栈实现,但是BFS 算法必须要用到队列这种数据结构。
  3. 在设置地图时要控制好障碍和空格的比例,不然一旦点多了就无法找到结果。
  4. 在无解时,可以通过检查栈为空时是否到达终点来判断。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章