BFS和DFS
深度优先搜索(Depth-First Search,DFS)和广度优先搜索(Breadth-First Search,BFS 或称为宽度优先搜索)是基本的暴力技术,常用于解决图,树的遍历问题。
首先考虑算法思路。以老鼠走迷宫为例,这是DFS和BFS在现实中的模型。迷宫内部的路错综复杂,老鼠从入口进去以后怎么才能找到出口呢?有两种不同的方法:
- 一只老鼠走迷宫。它在每个路口都选择先走右边(当然,选择先走左边也可以),能走多远就走多远,直到碰壁无法继续往前走,然后回退一步,这次走左边,接着继续往下走。用这个办法能走遍所有的路,而且不会重复(这里的规定回退不能算重复走)。这个思路就是DFS。
- 一群老鼠走迷宫。假设老鼠是无限多的,这群老鼠进去以后,在每个路口派出部分老鼠探索所有没走过的路。走某条路的老鼠,如果碰壁无法前进,就停下;如果到达的路口已经有其他老鼠探索过了,也停下。很显然,所有的道路道路都会走到,而且不会重复。这个思路就是BFS。BFS看起来像“并行计算”,不过,由于程序是单机运行的,所以可以把BFS看成是并行运算的模拟。
在具体编程时,一般用队列这种数据结构来具体实现BFS,甚至可以说“BFS=队列”;对于DFS,也可以说“DFS=递归”,因为递归实现DFS是最普遍的。DFS也可以用“栈”这种数据结构来直接实现,栈和递归在算法思想上是一致的。
下面用一个图遍历的题目来介绍BFS和队列
案例:“Rad and Black”
题目
有一个长方形的房间,铺着方形瓷砖,瓷砖为红色或黑色。一个人站在黑色瓷砖上,他可以按
上,下,左,右方向移动到相邻的瓷砖。但是他不能在红色瓷砖上移动,只能在黑色瓷砖上移
动。编程计算他可以达到的黑色瓷砖的数量。
输入:第一行包含两个正整数W和H,W和H分别表示x方向和y方向上的瓷砖数量。W和H均不超
过20.下面有H行,每行包含W个字符。每个字符表示一片瓷砖的颜色。用符号表示如下;“●”表示
黑瓷钻;“#”表示红色瓷砖;“@”代表黑色瓷钻上的人,在数据集中只出现一次。
输出:一个数字,这个人从初始瓷钻能到达的瓷钻总数量(包括起点)。
思路
这个题目跟老鼠走迷宫差不多:“#”相当于不能走的陷阱或墙壁,“●”是可以走的路。下面按“一群老鼠走迷宫”的思路进行编程
要遍历所有可能的点,可以这样走:从起点1出发,走到它所有的邻居2,3;逐一处理
每个邻居,例如在邻居2上,在走它的所有邻居4,5,6;继续以上过程,直到所有点都被走到,如下图
这是一个“扩散”的过程,如果把搜索空间看成一个池塘,丢一颗石头到起点位置,激起的波浪会一层层扩散到整个空间。需要注意的是,扩散按从近到远的顺序进行,因此,从每个被扩散到的点到起点的路径都是最短的。这个特征对解决迷宫这样的最短路径问题很有用。
用队列来处理这个扩撒过程非常清晰,易懂,对照上面的图:
- 1进队,当前队列{1}.
- 1出队,1的邻居2,3进队。当前队列是{2,3}(可以理解为从1扩散到了2,3)
- 2出队,2的邻居,4,5,6进队。当前队列是{3,4,5,6}(可以理解为从2扩散到了4,5,6)。
- 3出队,7,8进队。当前队列是{4,5,6,7,8}(可以理解为从3扩散到7,8)。
- 4出队,9进队。当前队列为{5,6,7,8,9}。
- 5出队,10进队,当前队列为{6,7,8,9,10}。
- 6出队,11进队,当前队列为{7,8,9,10,11} 。
- 7出队,12,13进队,当前队列为{8,9,10,11,12,13}。
- 8,9出队,10出队,14进队。当前队列为{11,12,13,14} 。
- 11出队,15进队。当前队列是{12,13,14,15}。
- 12,13,14,15出队,当前队列是空{},结束。
源码
#include<iostream>
#include<queue>
using namespace std;
char room[23][23];
int dir[4][2] = {
{-1,0}, //表示向左方向移动,左上角的坐标为(0,0),二维数组表示,所以向下列数加1
{0,-1}, //表示向上方向移动
{1,0}, //向右方向移动
{0,1} //向下方向移动
};
int Wx, Hy, num; //Wx行,Hy列,用num统计可走的位置是多少
#define CHECK(x,y) (x<Wx && x>=0 && y<Hy && y>=0) //检查是否在room中
struct node { int x, y; };//定义坐标点
void BFS (int wx,int hy ){
num = 1; //起点包含在瓷钻内
queue<node>q; //创建struct node类型的队列
node start,next;
start.x = wx;
start.y = hy;
q.push(start);
while (!q.empty()) {//如果队列q不为空就执行while中的代码
start = q.front();//将队首元素赋值给start
q.pop();//删除队首元素
cout << "out queue:" << start.x << "," << start.y << endl;//打印出队元素
for (int i = 0; i < 4; i++) {//只有4个方向
next.x = start.x + dir[i][0];//左右两个方向的邻居
next.y = start.y + dir[i][1];//上下两个方向的邻居
if (CHECK(next.x, next.y) && room[next.x][next.y] == '.') {
room[next.x][next.y] = '#';//进队以后,标记为“#”
num++;
q.push(next); //加入到队列中
}
}
}
}
int main() {
int x, y, wx, hy;
cout << "Wx Hy:";
while (cin >> Wx >> Hy) {
if (Wx == 0 && Hy == 0) {
break;
}
else {
cout << "请输入您设计的地板:" << endl;
for (y = 0; y < Hy; y++) {//一次读一行
for (x = 0; x < Wx; x++) {
cin >> room[x][y];
if (room[x][y] == '@') {//读入起点
wx = x; //进行标记
hy = y;
}
}
}
}
num = 0;
BFS(wx, hy);//输入完成后就可以调用BFS函数
cout << "num:" << num << endl;
}
system("pause");
return 0;
}
运行结果
分析
总结
大家自己总结哈,我已经在心里总结了。