广度优先搜索
广度优先搜索算法(又称宽度优先搜索)是最简便的图的搜索算法之一,其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
一、基本概念
广搜是一种图形搜索演算法。简单的说,BFS是从根节点开始,沿着树的宽度遍历树的节点,如果发现目标,则演算终止。
所谓宽度优先算法,就是算法自始至终一直通过已找到和未找到顶点之间的边界向外扩展,就是说,算法首先搜索和s距离为k的所有顶点,然后再去搜索和S距离为k+l的其他顶点。
所谓广度,就是一层一层的,向下遍历,层层堵截,我们如果要是广度优先遍历的话,我们的结果是V1 V2 V3 V4 V5 V6 V7 V8。
1、访问顶点vi ;
2、访问vi 的所有未被访问的邻接点w1 ,w2 , …wk ;
3、依次从这些邻接点(在步骤②中访问的顶点)出发,访问它们的所有未被访问的邻接点; 依此类推,直到图中所有访问过的顶点的邻接点都被访问;
二、与深度优先搜索的对比
深度优先搜索用**栈(stack)**来实现,整个过程可以想象成一个倒立的树形:
1、把根节点压入栈中。
2、每次从栈中弹出一个元素,搜索所有在它下一级的元素,把这些元素压入栈中。并把这个元素记为它下一级元素的前驱。
3、找到所要找的元素时结束程序。
4、如果遍历整个树还没有找到,结束程序。
广度优先搜索使用**队列(queue)**来实现,整个过程也可以看做一个倒立的树形:
1、把根节点放到队列的末尾。
2、每次从队列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到队列的末尾。并把这个元素记为它下一级元素的前驱。
3、找到所要找的元素时结束程序。
4、如果遍历整个树还没有找到,结束程序。
三、关于广搜的更多
完全性
广度优先搜索算法具有完全性。这意指无论图形的种类如何,只要目标存在,则BFS一定会找到。然而,若目标不存在,且图为无限大,则BFS将不收敛(不会结束)。
最佳解
若所有边的长度相等,广度优先搜索算法是最佳解——亦即它找到的第一个解,距离根节点的边数目一定最少;但对一般的图来说,BFS并不一定回传最佳解。
四、算法的实现
算法过程:
1.将根节点放入队列中
2.从队列中取出第一个元素,将队列中的第一个元素弹出
3.将所取得元素的全部节点加入队列中
4.判断队列是否为空
a. 若是,则结束
b.若不是,则跳到第二步
五、例题
定义一个二维数组:
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。
Input
一个5 × 5的二维数组,表示一个迷宫。数据保证有唯一解。
Output
左上角到右下角的最短路径,格式如样例所示。
Sample Input
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
Sample Output
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)
#include<iostream>
using namespace std;
#include<stdio.h>
#include<string.h>
#include<queue> //这里使用c++的<queue>函数,这样可以快速进行队列的操作
struct mi{
int x;
int y;
}ca; //记录结点坐标,特别注意,这里的坐标也可能是一维的,不要死板,具体情况具体分析
int map[5][5]={
{0,1,0,0,0},
{0,1,0,1,0},
{0,0,0,0,0},
{0,1,1,1,0},
{0,0,0,1,0},
} ;
struct mi zhen[5][5]; //这里往往是初始化好的迷宫地图,或是什么其他条件的障碍物,在这里1就是障碍物
int visited[5][5]; //用来记录已访问节点,但是也可以不要这个数组,每次走到后直接改变图,把此节点的内容直接变成障碍物即可
int xx[4]={1,-1,0,0};
int yy[4]={0,0,-1,1};
void BFS()
{
memset(visited,0,sizeof(visited)); //背下,初始化已访问数组
queue<mi>q; //背下,初始化队列
struct mi A; //背下,把第一个结点压入队列里
A.x=0; //初始化第一个节点坐标 没啥好说的
A.y=0;
visited[0][0]=1; //初始化首已访问节点,说明自己已被访问
zhen[0][0].x=0; //初始化对应关系底层结点 下面将详细解释
zhen[0][0].y=0;
//特别注意:千万不要死板,此题是走迷宫,所以首节点是左上角,其他题可不一定,千万不要一堆0就上去了
q.push(A); //将首节点压入队列
while(!q.empty()) //背下,只要队列里还有东西就继续
{
struct mi te=q.front(); //这两句背下
q.pop();
if(te.x==4&&te.y==4) //找到答案后退出
return;
//特别注意:不一定都需要退出条件,如果题目要求我们对单节点进行移动(后面会提到)
for(int i=0;i<4;i++) //背下,把各个方向都试着走一遍
{
int zx=te.x+xx[i]; //zx,zy为移动后节点坐标
int zy=te.y+yy[i];
if(zx<0||zx>4||zy<0||zy>4||map[zx][zy]||visited[zx][zy]) //背下,判断是否为合法路径,比如墙和已走过节点都为非法路径,但让我前面提到过,可以把已走过节点直接在地图上变成墙也行,这样就不需要visited数组
continue;
visited[zx][zy]=1; //将已访问节点标志为1 下标对应当前节点
struct mi kao; //背下,将合法子节点压入队列
kao.x=zx;
kao.y=zy;
q.push(kao);//记录着谁走到了它
zhen[zx][zy].x=te.x; //现在来说明这个二维数组怎样记录最短路径,首先这个数组里存的是坐标结构体,数组下标代表着子节点坐标,而数组内容存着父节点坐标,这样皆可以通过循环,一级一级找上去,既可以知道最短路径长度,也可以打印所有经过的节点坐标
zhen[zx][zy].y=te.y;
}
}
}
int main(void)
{
for(int m=0;m<5;m++) //初始化迷宫地图
{
for(int n=0;n<5;n++)
{
scanf("%d",&map[m][n]);
}
}
BFS();
int num[30][2];
int x=4,y=4;
int i=0;
while(1)
{ //把父子节点关系倒着取出放入数组中以便打印
int k;
k=x;
x=zhen[x][y].x;
y=zhen[k][y].y;
num[i][0]=x;
num[i][1]=y;
i++;
if(x==0&&y==0)
break;
}
for(int j=i-1;j>=0;j--)
{
printf("(%d, %d)\n",num[j][0],num[j][1]); //打印路径节点部分
}
printf("(4, 4)");
return 0;
}