简单易懂的迷宫走法--栈应用(c++)

版权声明:转载请注明出处,谢谢合作 https://blog.csdn.net/qq_40738840/article/details/85304635

哈喽~小伙伴们,你们帅气的阿俊又回来啦,他有好东西和你们分享哦,那就是困扰了他好久的迷宫问题。感觉自己走迷宫时简单得很,可让计算机走咋这么难哩,我明白了,一定是因为计算机太笨了,真是笨死了,啥都要人家一步步教他咋做,还想取代人?路漫漫其修远兮哦,嘿嘿嘿,那我们看看咋教他走迷宫吧!

如果学习过数据结构或图论的小伙伴应该听过深度/广度优先搜索算法,这两个算法可是图论中大多数算法的基础,非常重要哦。

图可以用线代利器–邻接矩阵存储,而迷宫地图也是可以用矩阵存储哟,1表示墙,0表示通路。这样迷宫求解问题就等价于指定深搜起点(迷宫入口),在深搜搜索过程中遇到指定终点(迷宫出口)就立即停止,因为它找到一条通路啦

深搜思想比较简单,就一句话,当前点只要有邻接点,就走下去;无邻接点,往回退。重复判断,直到遍历完所有点。

这是典型的递归,不过递归虽然写起来简洁明了,但是却不是太好写,而且出错时难以调试,效率也不是太高,于是我们想到了用栈来实现深搜,弥补了递归的三个缺点,但可能代码量大一些,不过没关系,黑猫,白猫,抓到老鼠就是好猫,那咱们看看如何用栈实现迷宫求解吧
先给你们看看局部效果图,馋哭你们,嘿嘿嘿
在这里插入图片描述


算法描述

多说无益,咱们直接上图吧
核心处理流程图
在这里插入图片描述

-1表示墙;0表示通路;-2表示已走过;-3表示此路不通;

1,假设最初以出发点为当前点p
2,判断p是否为出口,
2.1若p为出口,p入栈,并在地图相应位置赋-2,表示已经过,然后结束;
2.2若p不为出口,判断p是否有空白邻接点,
2.2.1若有,p入栈,在地图相应位置赋-2,同时p更新为下一个空白邻接点;
2.2.2若无,在p对应的地图位置上赋-3,同时弹出栈顶作为p
3,栈非空,重复步骤2

数据结构

栈的数据结构我就不贴了,想看可以翻我博客,有具体实现,这里就直接用STL的栈啦

typedef struct
{
	int x,y;
}Pos;//点x,y坐标 
typedef struct
{
	int n;//迷宫大小
	Pos start,end;//起止坐标 
	int maze[N][N]; //迷宫分布图。-1-->墙;0-->通路 
}Maze; 

1 建迷宫咯

打死我我也不用文件读取数据,嘿嘿嘿,真香

//从文件读取迷宫 
void CreateMaze(Maze &m)
{
	fstream inFile("迷宫.txt",ios::in);
	if(!inFile)cout<<"文件打开失败!"<<endl;
	
	inFile>>m.n;
	inFile>>m.start.x>>m.start.y;
	inFile>>m.end.x>>m.end.y;
	
	for(int i = 0; i < m.n; i++)
	{
		for(int j = 0; j < m.n; j++)
		{
			inFile>>m.maze[i][j];
		}
	} 
	inFile.close();
	/*测试是否读取成功
	cout<<m.n<<endl;
	cout<<m.start.x<<m.start.y<<endl;
	cout<<m.end.x<<m.end.y<<endl;
	
	for(int i = 0; i < m.n; i++)
	{
		for(int j = 0; j < m.n; j++)
		{
			cout<<m.maze[i][j]<<" ";
		}
		cout<<endl;
	}*/
}

文件迷宫.txt内容

10
1 4
1 8
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1
-1 0 0 -1 0 0 0 -1 0 -1
-1 0 0 -1 0 0 0 -1 0 -1
-1 0 0 0 0 -1 -1 0 0 -1
-1 0 -1 -1 -1 0 0 0 0 -1
-1 0 0 0 -1 0 0 0 0 -1
-1 0 -1 0 0 0 -1 0 0 -1
-1 0 -1 -1 -1 0 -1 -1 0 -1
-1 -1 0 0 0 0 0 0 0 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1

2 查通路呀

看看流程图是不是发现是否存在通路这个判断位于逻辑中心,没有他真滴啥都做不了了呢,于是我们专门为他建立了一个函数,以示尊敬,希望他保佑我别出Bug

  • 查找通路按照我的习惯是东南西北顺时针查找,一找到立刻返回;但由于矩阵是x朝下,y朝右,所以大家写的时候注意下x,y的处理;其实只要你囊括了四个方向,顺序无所谓,最后都能达到目标,这里只是个人编码习惯问题
    我希望它做两件事,一是有通路返回最先找到的邻接点;二是无通路返回(-1,-1)表示无通路
//-2->已走过;-3->不通 
//判断当前点是否存在通路,通,返回第一个可通的坐标;不通,返回(-1,-1) 
Pos Pass(Maze m,Pos curpos)
{
	Pos nextpos;
	//初始化表示无通路 
	nextpos.x = -1;
	nextpos.y = -1;
	
	int x,y;
	x = curpos.x;
	y = curpos.y;
	//每次都按顺序查找,走过的路被标记为 -2,所以不会再走 
	//按照我们人的东西南北为参照,二维数组x朝下,y朝右,所以应注意方向 
	if(m.maze[x][y+1] == 0)//东 
	{
		nextpos.x = x;
		nextpos.y = y+1;
	}
	else if(m.maze[x+1][y] == 0)//南 
	{
		nextpos.x = x+1;
		nextpos.y = y;
	}
	else if(m.maze[x][y-1] == 0)//西 
	{
		nextpos.x = x;
		nextpos.y = y-1;
	}else if(m.maze[x-1][y] == 0)//北 
	{
		nextpos.x = x-1;
		nextpos.y = y;
	}
	return nextpos; 
}

3 找活路啦

看着流程图敲一敲,机智的你肯定没问题啦

代码后半部分仅仅是为打印时的美观,不写也行,不过人家看脸啦,还是比较注重形式的呀,看着可舒服啦。处理也很简单,再开一个栈(反正是STL他们家的,又不要钱)把从路径栈中弹出的点压入新栈,这样顺序就负负得正啦。然后把编号赋给弹出的栈顶在地图中对应的位置。然后输出地图时令值大于0的输出相应数值,其余皆输出#。这也是我为啥让-2,-3表示走过的路,不同的路,因为他们大于0,阻挡了我最求美的道路,于是无情地将他们打入负数的冷宫

void MazePath(Maze m)
{
	Pos curpos,nextpos;//当前位置,下个位置 
	stack<Pos> path_stack;//路径栈 
	
	curpos = m.start;
	do{
		nextpos = Pass(m,curpos); 
	//超级大bug,当终点为当前点且其四周都被没空白时,当前的的下一个点自然返回空,表示不可通,也就找不到终点了
	//所以应该先判断当前点是否为终点,是的话立刻退出,否则判断是否存在通路,存在当前点就入栈;否则将当前置为-3,弹出栈顶作为当前点 
		if(curpos.x == m.end.x && curpos.y == m.end.y)//当前点为终点
		{
			path_stack.push(curpos);//当前点入路径栈
			m.maze[curpos.x][curpos.y] = -2;//走过标记为-2 
			break;
		}
		if(nextpos.x != -1)//表示可通 
		{

			path_stack.push(curpos);//当前点入路径栈
			m.maze[curpos.x][curpos.y] = -2;//更新迷宫地图,表示该点已经过
			curpos = nextpos;//当前点更新为下一个点 
		}
		else
		{
			m.maze[curpos.x][curpos.y] = -3;//表示当前点不通
			curpos = path_stack.top();//当前点更新为栈顶元素 
			path_stack.pop();//删除栈顶 
		} 
	}while(!path_stack.empty());
	
	//=================以下仅仅是为了展示效果(人家看脸嘛)======================= 
	if(path_stack.empty())cout<<"死路一条!"<<endl;
 	else
 	{
 		stack<Pos>tmp_stack;
		 int ord = 0; 
 		//被标记为1的不全是路径上的点,但在栈中一定是路径上的点 
 		while(!path_stack.empty())
 		{
 			curpos = path_stack.top();
 			path_stack.pop();
 			tmp_stack.push(curpos);	
		}
		
		while(!tmp_stack.empty())
 		{
 			ord++;
 			curpos = tmp_stack.top();
 			tmp_stack.pop();
 			m.maze[curpos.x][curpos.y] = ord;
		}
 		//打印输出
 		cout<<endl;
		for(int i = 0; i < m.n; i++)
		{
			for(int j = 0; j < m.n; j++)
			{
				if(m.maze[i][j] > 0)
				{
					cout<<setw(2)<<m.maze[i][j]<<" ";
				}
				else cout<<setw(2)<<" # ";
				
			}
			cout<<endl;
		}
	}
	
} 

说说心里话

  • 到这里我的分享快到尾声啦,我知道自己讲得很糟糕,但是我会努力的,总有一天我也会变得像你一样强

  • 主体逻辑一定要清晰,这次一开始将当前点是否存在通路作为第一个判断条件,在某些点作为出口时是得不出答案的,因为存在一种情况,当前点确实为终点,但是他举目四望,发现走投无路啦,谁知,路就在他脚下,可他没机会低头就被丢向无路可走的深渊,只要他肯低头,立马就踏破铁鞋无觅处,这告诉我们要脚踏实地,过好当下呀。好在我知错就改,立刻将是否为终点作为第一判断条件,避免了惨剧发生,不过还是付出些许代价的

  • 今天收获不小,关键是贼有成就感,对栈的理解有深了一步,有句话说得好,纸上得来终觉浅,觉知此事要躬行呐。

  • 书看再多,不实践,难以内化;实践再多,不总结,难以深化;总结再多,不写下来,难以升华;写得再多,你不点赞,难以开心


好啦,好啦,别依依不舍啦(我知道不会有人不舍),咳咳,我要回去画电路图啦,小伙伴们,回见,不要太想我哦


完整Code

#include<iostream>
using namespace std;
#include<stdlib.h>
#include<stack>
#include<fstream>
#include<iomanip> 
#define N 20
typedef struct
{
	int x,y;
}Pos;//x,y坐标 
typedef struct
{
	int n;//迷宫大小
	Pos start,end;//起止坐标 
	int maze[N][N]; //迷宫分布图。-1-->墙;0-->通路 
}Maze; 
//从文件读取迷宫 
void CreateMaze(Maze &m)
{
	fstream inFile("迷宫.txt",ios::in);
	if(!inFile)cout<<"文件打开失败!"<<endl;
	
	inFile>>m.n;
	inFile>>m.start.x>>m.start.y;
	inFile>>m.end.x>>m.end.y;
	
	for(int i = 0; i < m.n; i++)
	{
		for(int j = 0; j < m.n; j++)
		{
			inFile>>m.maze[i][j];
		}
	} 
	inFile.close();
	cout<<m.n<<endl;
	cout<<m.start.x<<m.start.y<<endl;
	cout<<m.end.x<<m.end.y<<endl;
	
	for(int i = 0; i < m.n; i++)
	{
		for(int j = 0; j < m.n; j++)
		{
			cout<<m.maze[i][j]<<" ";
		}
		cout<<endl;
	}
}
//-2->已走过;-3->不通 
//判断当前点是否存在通路,通,返回第一个可通的坐标;不通,返回(-1,-1) 
Pos Pass(Maze m,Pos curpos)
{
	Pos nextpos;
	//初始化表示无通路 
	nextpos.x = -1;
	nextpos.y = -1;
	
	int x,y;
	x = curpos.x;
	y = curpos.y;
	//每次都按顺序查找,走过的路被标记为 1,所以不会再走 
	//按照我们人的东西南北为参照,二维数组x朝下,y朝右,所以应注意方向 
	if(m.maze[x][y+1] == 0)//东 
	{
		nextpos.x = x;
		nextpos.y = y+1;
	}
	else if(m.maze[x+1][y] == 0)//南 
	{
		nextpos.x = x+1;
		nextpos.y = y;
	}
	else if(m.maze[x][y-1] == 0)//西 
	{
		nextpos.x = x;
		nextpos.y = y-1;
	}else if(m.maze[x-1][y] == 0)//北 
	{
		nextpos.x = x-1;
		nextpos.y = y;
	}
	return nextpos; 
}
void MazePath(Maze m)
{
	Pos curpos,nextpos;//当前位置,下个位置 
	stack<Pos> path_stack;//路径栈 
	
	curpos = m.start;//cout<<m.end.x<<m.end.y;
	do{//cout<<"curpos: "<<curpos.x<<" "<<curpos.y<<endl;
		nextpos = Pass(m,curpos); //cout<<"nextpos: "<<nextpos.x<<" "<<nextpos.y<<endl;
	
	//超级大bug,当终点为当前点且其四周都被没空白时,当前的的下一个点自然返回空,表示不可通,也就找不到终点了
	//所以应该先判断当前点是否为终点,是的话立刻退出,否则判断是否存在通路,存在当前点就入栈;否则将当前置为-3,弹出栈顶作为当前点 
		if(curpos.x == m.end.x && curpos.y == m.end.y)//当前点为终点
		{
			path_stack.push(curpos);//当前点入路径栈
			m.maze[curpos.x][curpos.y] = -2;//走过标记为-2 
			break;
		}
		if(nextpos.x != -1)//表示可通 
		{

			path_stack.push(curpos);//当前点入路径栈
			m.maze[curpos.x][curpos.y] = -2;//更新迷宫地图,表示该点已经过
			curpos = nextpos;//当前点更新为下一个点 
		}
		else
		{
			m.maze[curpos.x][curpos.y] = -3;//表示当前点不通
			curpos = path_stack.top();//当前点更新为栈顶元素 
			path_stack.pop();//删除栈顶 
		} 
	}while(!path_stack.empty());
	
	//以下仅仅是为了展示而处理效果 
	if(path_stack.empty())cout<<"死路一条!"<<endl;
 	else
 	{
 		stack<Pos>tmp_stack;
		 int ord = 0; 
 		//被标记为1的不全是路径上的点,但在栈中一定是路径上的点 
 		while(!path_stack.empty())
 		{
 			curpos = path_stack.top();
 			path_stack.pop();
 			tmp_stack.push(curpos);	
 		//	m.maze[curpos.x][curpos.y] = 5;
		}
		
		while(!tmp_stack.empty())
 		{
 			ord++;
 			curpos = tmp_stack.top();
 			tmp_stack.pop();
 		//	tmp_stack.push(curpos);
 			m.maze[curpos.x][curpos.y] = ord;
		}
 		
 		cout<<endl;
		for(int i = 0; i < m.n; i++)
		{
			for(int j = 0; j < m.n; j++)
			{
				if(m.maze[i][j] > 0)
				{
					cout<<setw(2)<<m.maze[i][j]<<" ";
				}
				else cout<<setw(2)<<" # ";
				
			}
			cout<<endl;
		}
	}
} 
int main()
{
	Maze m;
	CreateMaze(m);
	for(int i = 0; i < 10; i++)//测试:以所有点为终点
	{
		for(int j = 0; j < 10; j++)
		{
			m.end.x = i;
			m.end.y = j;
			cout<<"i: "<<i<<" j: "<<j<<endl;
			MazePath(m);
		}
	}
//	MazePath(m);
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/qq_40738840/article/details/85304635