数据结构——详解栈应用之迷宫问题

一.算法概述

迷宫如下:

        0 1 2 3 4 5 6 7 8 9
    0	1 1 1 1 1 1 1 1 1 1
	1	1 0 1 1 1 0 1 1 1 1
	2	1 1 0 1 0 1 1 1 1 1
	3	1 0 1 0 0 0 0 0 1 1
	4	1 0 1 1 1 0 1 1 1 1
	5	1 1 0 0 1 1 0 0 0 1
	6	1 0 1 1 0 0 1 1 0 1
	7	1 1 1 1 1 1 1 1 1 1

迷宫为8*6方阵
方向由1-8分别为上,右上,右,右下,下,左下,左,左上

1.初始化迷宫并输出

初始化一个长度10,宽度8二维数组,之所以要把长度宽度都加2,是因为需要一圈围墙来判断迷宫是否出界,并输入迷宫(注:这里我定义横向为长,纵向为宽,注意,在定义二维数组一定要maze[宽+2][长+2],也就是先宽再长)

2.用二维数组记录是否到过迷宫中的点

定义一个二维数组flag[Width+2][Length+2]记录各个点是否到过,是为1,否为0

3.定义(1,1,3)为起始点坐标

将当前坐标,当前方向定义为(1,1,3),即坐标(1,1)方向向右将flag[1][1]标记为1初始化一个栈,用于保存路径。定义next_x,next_y保存下一待定坐标,为什么是待定呢?因为不一定能够走到,x,y为当前坐标

4.算法核心:

当下一个点的坐标不是终点坐标时,就用while一直循环遍历
即如果(next_x,next_y)不是(Length,Width),则一直循环
4.1.在进入方向循环之前,进行一次下一步待定坐标的求取
4.2.按照方向direction从1到8循环
4.2.1.求取下一步的待定坐标
4.2.2.判断这个待定坐标,如果下个点没来过(flag为0),并且下个点可以通行(maze为0)执行
{记录下来目前这个点的三个属性并将其压栈,沿着这个方向移动一次,
新到的点标记为来过(flag[y][x]=1)}
4.3.如果4.2.2中没有发生移动,证明走到了死角,保存走到死角之前的坐标,并将这个方向+1,再从这个新的坐标(走到死角之前的x,y坐标和这个+1后的方向)开始继续探索,然后把这个死角前坐标出栈
4.4.如果栈为空,那么迷宫无法出去

5.循环结束,栈中从底到顶即为一条路线

6.输出所有的路线,并求出最短路线

二.具体代码实现:

#include<iostream>
using namespace std;
void intend_move(int x,int y,int &next_x,int &next_y,int direction);//这个是修改下一步待定坐标的函数
void move(int &x,int &y,int &direction);//这个是修改当前坐标的函数
//链栈 注:由于不知道有多少个元素,所以用链栈可以省空间
template<class DataType>
struct Node
{
	DataType data;
	Node<DataType> *next;
};
template<class DataType>
class LinkStack
{
public:
    LinkStack(){top=NULL;num=0;}
    ~LinkStack(){};
    void Push(DataType x);
    DataType Pop();
    DataType GetTop(){if(top!=NULL)return top->data;}
    int Empty();
	int num;
private:
    Node<DataType> *top;
};
template<class DataType>//链栈入栈算法
void LinkStack<DataType>::Push(DataType x)
{
	Node<DataType> *s;
    s=new Node<DataType>;s->data=x;
    s->next=top;top=s;
	num++;
}
template<class DataType>//链栈出栈算法
DataType LinkStack<DataType>::Pop()
{
	Node<DataType> *p;
	p=new Node<DataType>;
    if(top==NULL)throw"下溢";
    DataType x=top->data;p=top;
    top=top->next;
	num--;
    delete p;
    return x;
}
template<class DataType>//判空操作
int LinkStack<DataType>::Empty()
{
	if(top==NULL)
		return 1;
	else
		return 0;
}

struct route
{
	int x;
	int y;
	int direction;
};
const int Length=8;
const int Width=6;
int main()
{
/*		0 1 2 3 4 5 6 7 8 9

	0	1 1 1 1 1 1 1 1 1 1
	1	1 0 1 1 1 0 1 1 1 1
	2	1 1 0 1 0 1 1 1 1 1
	3	1 0 1 0 0 0 0 0 1 1
	4	1 0 1 1 1 0 1 1 1 1
	5	1 1 0 0 1 1 0 0 0 1
	6	1 0 1 1 0 0 1 1 0 1
	7	1 1 1 1 1 1 1 1 1 1
	//检验迷宫如图所示,0代表能通行,1代表不能通行,起点为maze[1][1],终点为maze[Width][Length]*/
	int i,j;
	bool maze[Width+2][Length+2]=
	{
		{1,1,1,1,1,1,1,1,1,1},
		{1,0,0,1,1,0,0,1,1,1},
		{1,1,0,1,0,1,1,1,1,1},
		{1,0,1,0,0,0,0,0,1,1},
		{1,0,1,1,1,0,1,1,1,1},
		{1,1,0,0,1,1,0,0,0,1},
		{1,0,1,1,0,0,1,1,0,1},
		{1,1,1,1,1,1,1,1,1,1}
	};//初始化一个长度10,宽度8的二维数组,之所以要把长度宽度都加2,是因为需要一圈围墙来判断迷宫是否出界,并输入迷宫
	//注:这里我定义横向为长,纵向为宽,注意,在定义二维数组一定要maze[宽+2][长+2],也就是先宽再长
	cout<<"迷宫为:"<<endl;
	for(i=0;i<Width+2;i++)
	{
		for(j=0;j<Length+2;j++)
			cout<<maze[i][j]<<" ";
		cout<<endl;
	}//输出迷宫
	bool flag[Width+2][Length+2]={0};//这个二维数组记录各个点是否到过,是为1,否为0

	//以下是求取路径算法
	LinkStack<route> Route;

	int x=1,y=1;int direction=1;//当前的坐标,当前的方向
	int next_x,next_y;//定义了下一步所走的点,如果x是横向的坐标,y是纵向的坐标,那么对应的点应该是maze[y][x]!!!这一点尤其注意,下面的同理,也需要这样表示
	int out_able=1;//设置标志,如果栈为空了,就把其置零,表明无法走出迷宫,否则说明走出了while循环且到达了目的地
	
	route NowNode;
	NowNode.x=1;NowNode.y=1;NowNode.direction=3;//起点,对应的是(1,1,右)

	flag[1][1]=1;//起点标记为来过

	cout<<"老鼠探索迷宫的路线为:"<<endl;
	while(next_x!=Length||next_y!=Width)//如果下一个坐标不是(8,6),这里要用到逻辑推理,A:x=8,B:y=6,出去的条件是AB,
	                                    //那么 ′(AB)=′A U ′B,即next_x!=Length||next_y!=Width
	{
		int move_flag=0;//定义是否移动了,0为否,1为是
		for(direction=1;direction<=8;direction++)//按照方向direction从1到8循环
		{
			intend_move(x,y,next_x,next_y,direction);
			if(!flag[next_y][next_x]&&!maze[next_y][next_x])//如果下个点没来过(flag为0),并且下个点可以通行(maze为0)			
			{	
				NowNode.x=x;NowNode.y=y;NowNode.direction=direction;//记录下来目前这个点的三个属性
				Route.Push(NowNode);//把这个点压栈
				cout<<"("<<x<<","<<y<<","<<direction<<")\t";
				move(x,y,direction);//沿着这个方向移动一次
				flag[y][x]=1;//新到的点标记为来过
				move_flag=1;
				break;			
			}
		}
		if(!move_flag)//如果没有移动过,即move_flag=0,说明此路不通,把路线倒回去一个,即出栈一个元素,再走
		{
			cout<<"("<<x<<","<<y<<","<<0<<")\t";//输出死角坐标(死角坐标是当前的x,y值,并未压入栈)
			cout<<"("<<Route.GetTop().x<<","<<Route.GetTop().y<<","<<0<<")\t";//输出回退一个的坐标
			x=Route.GetTop().x;y=Route.GetTop().y;direction=Route.GetTop().direction+1;//
			Route.Pop();//要尤其注意这里的理解,走到死角之前的一个坐标方向是错的,所以这个点一定要出栈,但是在上一步中,我们已经保存了走到死角之前的那个坐标,所以就从死角前坐标的下一个方向再开始探索
		}
		if(Route.Empty())
		{
			cout<<"迷宫无法出去"<<endl;
			out_able=0;
			break;
		}
	}
	if(out_able)
		cout<<"终点(8,6,0)"<<endl;

	int t=0,num=Route.num;
	route *FinalRoute=new route[Route.num];
	for(;t<num;t++)
		FinalRoute[t]=Route.Pop();
	cout<<"一条路径为:";
	for(t=num-1;t>=0;t--)
		cout<<"("<<FinalRoute[t].x<<","<<FinalRoute[t].y<<","<<FinalRoute[t].direction<<")"<<" ";
		cout<<"终点(8,6,0)"<<endl;
	cout<<endl;
	return 0;
}

//这个函数是用来计算下一个的坐标
void intend_move(int x,int y,int &next_x,int &next_y,int direction)//1,2,3,4,5,6,7,8所对应的是8个方向,从上、右上、右……左上分别对应1,2,3,4,5,6,7,8
{
	switch(direction)
	{
	case 1:
		next_x=x;
		next_y=y-1;
		break;
	case 2:
		next_x=x+1;
		next_y=y-1;
		break;	
	case 3:
		next_x=x+1;
		next_y=y;
		break;
	case 4:
		next_x=x+1;
		next_y=y+1;
		break;
	case 5:
		next_x=x;
		next_y=y+1;
		break;
	case 6:
		next_x=x-1;
		next_y=y+1;
		break;
	case 7:
		next_x=x-1;
		next_y=y;
		break;
	case 8:
		next_x=x-1;
		next_y=y-1;
		break;
	}
}
void move(int &x,int &y,int &direction)
{
	switch(direction)
	{
	case 1:
		x=x;
		y=y-1;
		break;
	case 2:
		x=x+1;
		y=y-1;
		break;	
	case 3:
		x=x+1;
		y=y;
		break;
	case 4:
		x=x+1;
		y=y+1;
		break;
	case 5:
		x=x;
		y=y+1;
		break;
	case 6:
		x=x-1;
		y=y+1;
		break;
	case 7:
		x=x-1;
		y=y;
		break;
	case 8:
		x=x-1;
		y=y-1;
		break;
	}
	direction=1;//移动后恢复坐标
}

三.实验截图:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值