算法入门到进阶(三)——搜索技术(BFS和队列)


BFS和DFS

深度优先搜索(Depth-First Search,DFS)和广度优先搜索(Breadth-First Search,BFS 或称为宽度优先搜索)是基本的暴力技术,常用于解决图,树的遍历问题。

首先考虑算法思路。以老鼠走迷宫为例,这是DFS和BFS在现实中的模型。迷宫内部的路错综复杂,老鼠从入口进去以后怎么才能找到出口呢?有两种不同的方法:

  1. 一只老鼠走迷宫。它在每个路口都选择先走右边(当然,选择先走左边也可以),能走多远就走多远,直到碰壁无法继续往前走,然后回退一步,这次走左边,接着继续往下走。用这个办法能走遍所有的路,而且不会重复(这里的规定回退不能算重复走)。这个思路就是DFS。
  2. 一群老鼠走迷宫。假设老鼠是无限多的,这群老鼠进去以后,在每个路口派出部分老鼠探索所有没走过的路。走某条路的老鼠,如果碰壁无法前进,就停下;如果到达的路口已经有其他老鼠探索过了,也停下。很显然,所有的道路道路都会走到,而且不会重复。这个思路就是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}.
  2. 1出队,1的邻居2,3进队。当前队列是{2,3}(可以理解为从1扩散到了2,3)
  3. 2出队,2的邻居,4,5,6进队。当前队列是{3,4,5,6}(可以理解为从2扩散到了4,5,6)。
  4. 3出队,7,8进队。当前队列是{4,5,6,7,8}(可以理解为从3扩散到7,8)。
  5. 4出队,9进队。当前队列为{5,6,7,8,9}。
  6. 5出队,10进队,当前队列为{6,7,8,9,10}。
  7. 6出队,11进队,当前队列为{7,8,9,10,11} 。
  8. 7出队,12,13进队,当前队列为{8,9,10,11,12,13}。
  9. 8,9出队,10出队,14进队。当前队列为{11,12,13,14} 。
  10. 11出队,15进队。当前队列是{12,13,14,15}。
  11. 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;
}

运行结果

在这里插入图片描述

分析

在这里插入图片描述在这里插入图片描述

总结

大家自己总结哈,我已经在心里总结了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jacky~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值