迷宫问题

问题描述:

输入一个二维数组作为一个迷宫,0表示可以走,1表示墙,左上角为入口,右下角为出口。如果有解则输出一条路径,无解则输出“无解”。输入、输出格式如下:

input:

5 8

0 0 1 0 0 0 1 0

1 0 0 0 1 0 1 0

0 0 1 1 1 1 0 1

0 1 0 0 0 0 0 1

0 0 0 1 0 1 0 0

output:

(1,1) (1,2) (2,2) (3,2) (3,1) (4,1) (5,1) (5,2) (5,3) (4,3) (4,4) (4,5) (4,6) (4,7) (5,7) (5,8)



解决方案:

1、第一种方法就是用深度优先搜寻。在学数据结构的堆栈章节的时候有遇到过迷宫问题,里面用到的就是深度优先搜索,但在此我不用堆栈,直接用数组,实现的功能和堆栈一样(其实堆栈也就是用数组实现的)。

主要思路就是:从入口(即a[0])开始,判断它的上下左右四个元素是否满足条件(1、没有越界,2、元素值为0)。如果某个邻边元素满足则把邻边元素入栈,接着判断此邻边元素的上下左右元素。都不满足的话则回溯到上一个元素。

具体代码如下:

int Test(int** a,int s[],int i,int j,int n,int m,int *f)
{//此函数功能:判别邻边元素a[i][j]是否满足条件,满足的话则把该邻边元素入栈
	if(i>=0 && i<n && j>=0 && j<m && a[i][j]==0)//条件:元素没有越界,并且值为0
	{
		s[(*f)++]=i*m+j;///用一维数组s记录二维数组元素的位置,并让"指针功能"的变量*f指向下一元素
		a[i][j]=1;/如果某元素已经搜索过了,则将它的值置为1,防止重复搜索
		return 1;
	}
	return 0;
}

void DFS_kind(int** a,int n,int m)
{
	int s[1024],i=0,j=0,f=0;
	s[f++]=0;
	a[i][j]=1;
	while(f && !(i==n-1&&j==m-1))
	{//结束循环的条件就两个,栈空了,或者找到了“出口”
		if(Test(a,s,i-1,j,n,m,&f))	i--;
		else if(Test(a,s,i+1,j,n,m,&f))	i++;
		else if(Test(a,s,i,j-1,n,m,&f))	j--;
		else if(Test(a,s,i,j+1,n,m,&f))	j++;
		else///如果某元素的上下左右四个元素都不是0,即是“死路”,则出栈,回溯。
		{
			f--;
			i=s[f-1]/m;
			j=s[f-1]%m;
		}
	}
	if(f==0)///如果栈空了,即没有找到通路。
		printf("无解!");
	else for(i=0;i<f;i++)
		printf("(%d,%d) ",s[i]/m+1,s[i]%m+1);输出路径,因为遍历过的元素都以栈的形式存储在s中,所以应该逆序输出。
}
代码注释:

a、数组s和变量f一起组合实现堆栈功能。用来存储搜索过的元素。当堆栈为空时表示不存在通路,当搜索到了出口元素时堆栈里的元素即使所求通路路径。

b、当某元素被搜索过了,应当把它的值置为1,为了防止重复搜索。

c、当四个邻边元素都不符合条件时,进行出栈,回溯。但不需要将元素置回0,因为已经确认该元素不能通向出口,是一条死路,无需再进行搜索了。



2、下面介绍广度优先搜索。广度优先搜索的优点是可以用来搜索最短路径,思路和图的广度优先遍历一样,借助队列,我同样直接用数组实现。

主要思路:从入口元素开始,判断它上下左右的邻边元素是否满足条件,如果满足条件就入队列。完了以后将队列里的元素(即满足条件的邻边元素)出列,判断邻边元素的上下左右是否满足条件,满足的话就入队列......如此下去。

区别:广度优先看起来和深度优先很相似,但是深度优先搜索是遇到一个满足条件的邻边元素就将其入栈,接着去判断该邻边元素的上下左右。而广度优先搜索是判断完某元素的上下左右四个元素并入栈以后才进行下一轮判断的。广度优先搜索就像波纹一样,以入口元素为中心,逐层搜索路径长度为1、为2、为3...的所有元素,从而最先出来的路径就是最短路径。

具体代码如下:

void Test(int** a,int s[],int b[],int i,int j,int n,int m,int *f,int t)
{//此函数功能:判别邻边元素a[i][j]是否满足条件,满足的话则把该邻边元素压入队列s
	if(i>=0 && i<n && j>=0 && j<m && a[i][j]==0)/条件:元素没有越界,并且值为0
	{
		s[*f]=i*m+j;///用一维数组s记录二维数组元素的位置,并让作为"头指针"的变量*f指向下一元素
		(*f)=((*f)+1)%1024;
		a[i][j]=1;/如果某元素已经搜索过了,则将它的值置为1,防止重复搜索
		b[i*m+j]=t;b数组用来记录第(i*m+j)个元素的前驱元素
	}
}

void BFS_kind(int** a,int n,int m)
{
	int s[1024],b[10000],i=0,j=0,t=0,f=0;
	s[f++]=0;
	a[i][j]=1;
	while(t-1!=f && !(i==n-1&&j==m-1))
	{结束循环的条件就两个,队列空了,或者找到了“出口”
		Test(a,s,b,i-1,j,n,m,&f,3);
		Test(a,s,b,i+1,j,n,m,&f,1);
		Test(a,s,b,i,j-1,n,m,&f,2);
		Test(a,s,b,i,j+1,n,m,&f,4);
		i=s[t]/m;
		j=s[t]%m;
		t=(t+1)%1024;
	}
	if(t>f)/如果队列空了,即没有找到通路。
		printf("无解!\n");
	else 
		Output(b,n,m);//调用Output函数进行输出,数组b中记录了每个元素的前驱元素,从最后一个元素逆推便可找到通路路径
}
代码注释:

a、数组s和变量t、f实现队列功能,t为头结点,f为尾结点。虽然已经为s分配了1024个元素空间,但为了保险需要使用循环队列,即在t、f自增的时候对1024求余即可。

b、广度优先搜索和深度优先搜索不同,当搜索完以后路径无法在队列中记录。可以用一个数组b记录元素的前驱结点,当搜索完了以后只要从出口逆推便可找到出口。

c、记录前驱的方法可以存前驱结点的i*m+j,也可以用1、2、3、4代替上、右、下、左四个方向。当要找前驱结点的时候只需要用switch语句对i或j进行操作就可以了,Output的代码如下。

d、因为输出的时候是根据“前驱”找到路径的,所以会从出口到入口逆序输出。我们可以用结构体数组把它逆过来后再输出。或者可以把出口当做入口,逆过来去找路径,找到的就是正序的路径了。

typedef struct Data
{
	int c;
	int d;
}data;

void Output(int b[],int n,int m)
{
	int i=n-1,j=m-1,t=0;
	data k[256];将路径存在k里面,再逆序输出。
	while(!(i==0&&j==0))
	{
		k[t].c=i;
		k[t++].d=j;
		switch(b[i*m+j])
		{
		case 1:i--;break;
		case 2:j++;break;
		case 3:i++;break;
		case 4:j--;break;
		default:break;
		}
	}
	k[t].c=i;
	k[t++].d=j;
	while(t--)
		printf("(%d,%d) ",k[t].c+1,k[t].d+1);
}



3、 方法三:递归!学过数据结构的都知道,在图的遍历那一章节,是有三种方法的:用堆栈实现深度优先遍历,用队列实现广度优先遍历,用递归实现深度优先遍历。个人觉得,大部分深度优先遍历都可以用递归实现,因为两者的思想很相近,都是“深挖”、回溯。而广度优先遍历只能借助队列,因为它是一层一层解决的,而递归是自身调用,单点深入、回溯的。然后我就用递归实现了一下迷宫问题,具体代码如下:

int Test(int** a,int i,int j,int n,int m,int t)
{//此函数功能:判别邻边元素a[i][j]是否满足条件,满足的话则把该邻边元素的前驱存入b
	if(i>=0 && i<n && j>=0 && j<m && a[i][j]==0)//条件:元素没有越界,并且值为0
	{
		b[i*m+j]=t;
		a[i][j]=1;/如果某元素已经搜索过了,则将它的值置为1,防止重复搜索
		return 1;
	}
	return 0;
}

int Recursion_kind(int **a,int n,int m,int i,int j)
{
	if(i==n-1 && j==m-1)
		return 1;
	if(Test(a,i-1,j,n,m,3) && Recursion_kind(a,n,m,i-1,j))
		return 1;
	if(Test(a,i+1,j,n,m,1) && Recursion_kind(a,n,m,i+1,j))
		return 1;
	if(Test(a,i,j-1,n,m,2) && Recursion_kind(a,n,m,i,j-1))
		return 1;
	if(Test(a,i,j+1,n,m,4) && Recursion_kind(a,n,m,i,j+1))
		return 1;
	return 0;
}
代码注释:

a、递归实现和深度优先搜索相似,大约上是把while循环改成递归调用。

b、由于是递归调用,没有堆栈记录路径,所以采取广度优先搜索里面的方法:加一个全局变量b,用来存储元素的前驱结点。当搜索到路径的时候调用Output函数输出即可(Output函数和方法2的Output函数相同)。

c、记录前驱的方法和方法2的一样,1、2、3、4分别代表上、右、下、左。


转载请注明出处,谢谢!(原文链接:http://blog.csdn.net/bone_ace/article/details/41283585

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值