天天看点

从1开始的算法学习 1,快速理解宽度优先搜索并用c++实现快速理解宽度优先搜索并用c++实现

快速理解宽度优先搜索并用c++实现

1,广度优先搜索

q:什么是广度优先搜索?

a:我们以走迷宫为例:

迷宫问题

一个迷宫
00010
01000
00100
11010
01110

其中1为障碍,0为无障碍,入口为(1,1),出口为(m,n),每次移动只能从一个无障碍单元移动到其周围4个方向的另外一个无障碍单元

输入:第一行为两个整数,m,n,保证1<m,n<100
以下m行,每行n个整数
表示n行m列的0-1矩阵

输出:输出路径坐标,如果没有路,输出-1 

【输入样例】
1 2
0 0

【输出样例】
1 1
1 2 
           

利用队列的特点,一层一层向外寻找拓展可以走的点,直到找到出口为止 (当然,这里的队列也可以用其他东西代替)

q:具体实现方法?

a:假设当前位置是(x,y),为了避免回头重新搜索上一步,我们将上一步位置设为-1,这样就可以保证不会回头。

紧接着,当我们到达目的地的时候,理论上就会找到“最快”的路径了 。

另外,我们需要用两个一维数组来实现存储各个方向的取值 ,可以参考下面代码

q:我还是看不懂怎么办?

a:举个例子吧

比如地图

000

101

010

第一步:我们在左上角1,1位置

第二步:存在两种走法:1,2和2,2

第三步,存在3种走法(不走已经走过的地方):1,3、3,3和3,1

此时,由于3,3满足题设,所以得到了最短路径1,1。2,2。3,3。

事实上,当我们实现的时候,往往是倒着来的:3,3的上一步是2,2.而2,2的上一步是1,1

具体代码

注:该代码输出结果为倒序

注2:该代码所有的具体解释请下移

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>

using namespace std;
#define N 1000

int m,n;

struct Map
{
	int x;
	int y;
} 

pre[N][N];

bool vist[N][N];

int map[N][N];

int a[4][2]={{-1,0},{0,-1},{1,0},{0,1}};

bool in(int x,int y)
{
	if(x<0||x>m||y<0||y>n)return false;
	return true;
}

void bfs()
{
	queue<Map>q;
	
	while(!q.empty())q.pop();
	 
	vist[0][0]=true;
	
	Map cu;
	cu.x=0;
	cu.y=0;
	q.push(cu);
	
	while(!q.empty())
	{
		cu=q.front();
		q.pop();
		
		for(int i=0;i<4;i++)
		{
			int x=a[i][0]+cu.x;
			int y=a[i][1]+cu.y;
			
			if(in(x,y) && !vist[x][y] && !map[x][y])
			{
				Map temp;
				vist[x][y]=true;
				temp.x=x;
				temp.y=y;
				q.push(temp); 
				
				pre[x][y].x=cu.x;
				pre[x][y].y=cu.y;
			}
		}
	}
}

void PrintWay(int x,int y)
{
	
	if(x==0 && y==0)
	{
		printf("%d %d\n",x+1,y+1);
		return;
	}
	printf("%d %d\n",x+1,y+1);
	PrintWay(pre[x][y].x,pre[x][y].y);
	
}

int main()
{
	cin >> m >> n;
	for(int i=0;i<m;i++)
	{
		getchar();
		for(int j=0;j<n;j++)
		{
			char a;
			a=getchar();
			map[i][j]=(int)a -48;
		}
	}
	memset(vist,false,sizeof(vist));
	bfs();
	PrintWay(m-1,n-1);
	return 0;
}
           

对于代码的具体解释

1,前设

struct Map
{
	int x;
	int y;
} 

pre[N][N];

bool vist[N][N];

int map[N][N];

int a[4][2]={{-1,0},{0,-1},{1,0},{0,1}};
           

Map结构:用来记录x,y坐标

这里我们设置了三个表,第一个pre,代表的是记录,记录该位置之前应该在什么位置,比如1,2的上一步在1,1 ,那么

pre[1][2].x = 1;
pre[1][2].y = 1;
           

这样的话,我们就可以根据“终点”得到它“上一步”所在的位置,然后不停上一步,最终得到一条反向的路了。(你只要把终点和起点变化一下,就可以获得正向的路了)

紧接着,vist用来储存的是“这条路是否被走过”,如果被走过,则为true,否则为false。目的是为了防止我们在一个地方绕一辈子圈圈。

最后,map用来储存地图情况,1是障碍,0是路。

2,确认是否脱离边界

bool in(int x,int y)
{
	if(x<0||x>m||y<0||y>n)return false;
	return true;
}
           

我们每走一步,都要首先确定一件事情:我是不是跑出地图外面了,因此在这里要设置一个判断脱离边界的函数

3,队列

队列是一种特殊的线性表,只允许在前端删除,后端插入。

进行插入操作的叫做队尾,删除的叫做队头。队列没有元素的时候,称为空队列。往队列中加入元素叫做入队,删除元素叫做出队

声明方式 queue<数据类型>名称; 见上,这里的Map是我们之前设置的用来记录坐标的xy

基本方法:

q.push(x);入队,将x接到队列末端
q.pop();出队,将队列第一个元素删除。

q.front();访问队头元素
q.back(); 访问队尾元素

q.empty();判断队列是否为空,是的话返回true

q.size();获得队列中元素个数 
           

4,bfs函数(广度搜索函数)

第一部分:

首先清零我们刚刚设置的队列,并且在队列内加入第一个坐标:(0,0),并且将判断已走过的vist的0,0坐标改为true(来表示它已经被走过了)

while(!q.empty())q.pop();
	 
	vist[0][0]=true;
	
	Map cu;
	cu.x=0;
	cu.y=0;
	q.push(cu);
           

第二部分:

将队列内的数据提取出来进行运算

while(!q.empty())
	{
		cu=q.front();
		q.pop();
           

接上一步,我们将队列里的第一个数的数据提取出来到cu,并且删除这个数据(现在队列中就为空了)。这样的话,我们才可以对这个数据进行检测和运算

第三部分

for(int i=0;i<4;i++)
		{
			int x=a[i][0]+cu.x;
			int y=a[i][1]+cu.y;
			
			if(in(x,y) && !vist[x][y] && !map[x][y])
			{
				Map temp;
				vist[x][y]=true;
				temp.x=x;
				temp.y=y;
				q.push(temp); 
				
				pre[x][y].x=cu.x;
				pre[x][y].y=cu.y;
			}
		}
           

我们判断向每一个方向前进,所以循环四次,分别尝试向四个方向前进。

如果出现:

1,不超出边界( in(x,y) )
2,路没走过( !vist[x][y] )
3,路可以走( !map[x][y] )
           

那么我们就执行我们的内容:将“下一步的上一步”设置为“这一步”的坐标

简单来说,就是我们从(1,1)走到(1,2),就将(1,2)的上一步设为(1,1)

这样,我们的pre组中,每一步的上一步就都有了。

4,递归输出

在已经知道每一步的上一步后,我们就可以通过递归来从终点找到回到起点的路了。

我们只要创建一个简单的递归,就可以完成这个目的:

void PrintWay(int x,int y)
{
	
	if(x==0 && y==0)//递归出口
	{
		printf("%d %d\n",x+1,y+1);
		return;
	}
	printf("%d %d\n",x+1,y+1);
	PrintWay(pre[x][y].x,pre[x][y].y);
	
}
           

不懂递归请自行百度。

5,主函数,输入内容,输出结果

最简单的一步,将你的所做的一切穿起来

int main()
{
	cin >> m >> n;
	for(int i=0;i<m;i++)
	{
		getchar();//用来储存回车键防止影响正常输入
		for(int j=0;j<n;j++)//创建一个循环,将整个地图录入map中 
		{
			char a;
			a=getchar();//这里使用getchar的原因是我们每次只录入一个字符
			map[i][j]=(int)a -48;
		}
	}
	memset(vist,false,sizeof(vist));//把vist全部化为false,意思是开始的时候哪里都没走过
	bfs();//搜索!
	PrintWay(m-1,n-1);//打印结果
	return 0;
}
           

继续阅读