迷宫算法(DFS)

1.如果采用堆栈进行迷宫探测,则称之为深度优先搜索(DFS),它和递归的探测思路是基本一致的,可以看成是递归方式的非递归版本;

2.采用队列进行迷宫探测,则是广度优先搜索(BFS),广度优先搜索法利用队列的特点,一层层向外扩展查找可走的方块,直到找到出口为止,最先找到的这个答案就必然是最短的。

如果打比喻来说,DFS更适合模拟机器人走迷宫的方式,看到一个方向是通的,就一直走下去,遇到死胡同就退回;BFS则好比一个人站在迷宫入口处,拿出一堆小探测器,每个小探测器帮他搜索一个可能的路径去寻找,第一个找到出口的探测器发出了反馈,那么这个人就按照这个小探测器找到的路径走迷宫就行了。

迷宫问题

在这里插入图片描述

迷宫问题的数据结构

在这里插入图片描述

方向试探表示:用来记录走迷宫的顺序:右下左上

注意:这里走迷宫遵循右下左上的原则
在这里插入图片描述
在这里插入图片描述

栈中数据元素的组织:用来保存当前位置和方向,然后把Box结构体压入栈中

Box结构体中的di的取值在1到3之间,表示的是dircet结构体数组的第几个元素,而direct结构体数组里面每个元素表示的是移动的方向
在这里插入图片描述

防止重复到达某一点

在这里插入图片描述

走的路径都会被压入堆栈

当遇到死胡同的时候,会进行回退,每次回退的过程中都会检查是否有路可以走,会退的过程中会把错误的路径弹出栈,最后栈中保留的是正确的路径

将堆栈的信息反序输出,就可以打印出正确的路径

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

内存循环相当于判断当前位置能否往右下左上四个方向移动,如果能就把temp保存的当前位置元素元素压入栈,再将当前位置更新到移动后的位置,然后再进入内存循环判断能否往上下左右四个方向移动。

注意:temp每次循环内层循环结束后保存的应该是当前位置点的前面一个点

内层循环结束条件:1.走出迷宫 2.遇到死路

外层循环作用:1.当第一次进入外层循环的时候,会把当前位置坐标更新为temp保存的位置,然后把方向更新为temp.di+1因为按顺序移动,应该是右下左上,所以+1后di=0,方向是向右移动

2.当内存循环遇到死路时,会退出内层循环,进入外层循环,此时外层循环会进行出栈操作,把栈顶元素弹出,被弹出的栈顶的元素是在内层循环中发现该位置往上下左右都走不通的点。

下面是弹出栈顶进入外层循环后的操作说明

要注意temp保留的元素的点是当前位置前一个点

在这里插入图片描述
main.cpp

#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
#include"stack.hpp"
#define M 4
#define N 4
//迷宫   maze[M+2][N+2]
int maze[M + 2][N + 2] =
{
    
    
	{
    
    1, 1, 1, 1, 1, 1},
	{
    
     1,0,0,1,1,1 },
	{
    
     1,0,0,0,0,1 },
	{
    
     1,0,1,1,1,1 },
	{
    
     1,0,0,0,0,1 },
	{
    
     1,1,1,1,1,1 },
};

//用来记录走迷宫顺序的结构体
typedef struct {
    
    
	//int cx,int cy表示x和y方向的增量
	//cy+1:表示向右走  cy-1:向左走
	//cx+1:表示向下走  cx-1:向上走
	int cx, cy;
}Direction;
//数组中方向村粗顺序:右下左上
Direction direct[4] = {
    
     {
    
    0,1},{
    
    1,0},{
    
    0,-1},{
    
    -1,0} };

//用来记录当前位置,然后压入栈中
typedef struct {
    
    
	int x, y;//记录当前位置的坐标
	int di;//记录当前位置下一次移动的方向
}Box;

//判断是否走出迷宫的代码
//参数1:迷宫图 参数2:存放方向的数组 参数3:保存通路的栈
bool findPath(int maze[M + 2][N + 2], Direction direct[], LinkStack<Box>& s)
{
    
    
	//创建Box temp结构体来保存当前位置
	Box temp;
	//记录当前位置和方向
	int x=0, y=0, di=0;
	//记录下次移动的位置坐标
	int line=0, col=0;
	//一开始点位于迷宫maze[1][1]的位置,所以将该位置的值变为-1
	maze[1][1] = -1;
	//temp值记录当前位置,将di设置为-1
	temp = {
    
     1,1,-1 };
	//将temp记录的当前位置压入栈中
	s.push(temp);
	//进入外层循环
	while (!s.isEmpty())//栈不为空
	{
    
    
		//弹出栈顶元素
		temp = s.pop();
		//更新当前位置
		x = temp.x;
		y = temp.y;
		di = temp.di+1;
		//内层循环
		while (di < 4)//尝试四个方向
		{
    
    
			//更新下一次移动的位置坐标
			line = x+direct[di].cx;
			col = y+direct[di].cy;
			//判断下一个位置能否走
			if (maze[line][col] == 0)
			{
    
    
				//如果下一个位置能够移动,就更新temp存储当前位置的值
				temp = {
    
     x,y,di };
				//将temp结构体存储当前位置的信息压入栈中
				s.push(temp);
				//更新当前位置
				x = line;
				y = col;
				//当前位置的值改为-1,表示走过了,下次不能走
				maze[line][col] = -1;
				//判断当前位置是否为迷宫出口
				if (x == M && y == N)
				{
    
    
					//把迷宫出口也压入栈中
					temp = {
    
     x, y, di };
					s.push(temp);
					//找到出口,退出函数
					return true;
				}
				else {
    
    
					//没有找到出口,但是可以移动到下一个点,那么下一个点起始移动方向更新为右,因为移动方向顺序:右下左上
					di = 0;
				}
			}
			else 
			{
    
    
				//比如一开始要往右走:如果右方向不能走,就要改变方向移动
				di++;
			}
		}

	}
	return false;
}
//测试打印迷宫通路
void test()
{
    
    
	LinkStack<Box> s;
	findPath(maze, direct, s);
	//逆序遍历栈
	int i = 0;
	int num = s.size();
	Box* data = new Box[num];
	while (!s.isEmpty())
	{
    
    
		//头删
		data[i++] = s.getTop();
		s.pop();
	}
	//对数组进行逆序遍历
	for(int j = num-1; j >=0; j--)
	{
    
    
		cout << "(" << data[j].x << "," << data[j].y << ")" << endl;
     }

}
int main()
{
    
    
	test();
	system("pause");
	return 0;
}

stack.hpp

#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
//节点结构体
template<class Data>
struct node {
    
    
	Data data;
	node<Data>* next;
};
//链表类
template<class Data>
class LinkStack 
{
    
    
private:
	node<Data>* top;
	int num;
public:
	//不需要有参构造函数
	LinkStack();
	~LinkStack();
	void push(Data val);
	Data pop();
	Data getTop();
	bool isEmpty();
	int size();
	class Empty{
    
    };
};
template<class Data>
LinkStack<Data>::LinkStack()
{
    
    
	//这里用的是无头链表
	num = 0;
	top = NULL;
};
template<class Data>
void LinkStack<Data> :: push(Data val)
{
    
    
	//在堆区开辟一块空间,来存放第一个节点
	node<Data>* newNode = new node<Data>;
	newNode->data = val;
	//无头链表的头插法
	newNode->next = top;
	//链表指向第一个节点
	top = newNode;
	num++;
}
template<class Data>
Data LinkStack<Data>::pop()
{
    
    
	//如果链表为空,就抛出异常
	if (top == NULL)
	{
    
    
		throw Empty();
	}
	//不为空,进行头删的操作
	//先释放再头删
	//注意要保存住被删除1的节点
	node<Data>* temp = top;
	Data tempData = top->data;
	//注意:要先把top移到下一个节点,再进行释放操作
	top = top->next;
	delete temp;
	num--;
	return  tempData;
}
template<class Data>
Data LinkStack<Data>::getTop()
{
    
    
	return top->data;
}
template<class Data>
bool LinkStack<Data>::isEmpty()
{
    
    
	if (top == NULL)
		return true;
	return false;
}
template<class Data>
LinkStack<Data>::~LinkStack()
{
    
    
	while (top)
	{
    
    
		node<Data>* temp = top;
		top = top->next;
		free(temp);
     }
	node<Data>* top = NULL;
}
template<class Data>
int LinkStack<Data>::size()
{
    
    
	//计算链表的长度
	return num;
}

测试结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_53157173/article/details/114486303