算法——广度优先搜索

本文介绍了广度优先搜索(BFS)的基本概念和用途,如寻找路径和最短距离。通过两个例题,分别讲解了如何用BFS解决抓牛问题和迷宫问题,阐述了BFS的解题思路和实现代码,帮助读者深入理解BFS算法。


一、简介

广度优先搜索(Breadth First Search),又称为"宽度优先搜索"或"横向优先搜索",简称BFS。

该算法可用来解决两类问题:
①从A出发是否存在到达B的路径;
②从A出发到达B的最短路径(这个应该叫最少步骤合理);

当然第二种问题我们遇到的最多,使用BFS让你能够找出两样东西之间的最短距离,不过最短距离的含义可以很广泛,例如:
①编写国际跳棋AI,计算最少走多少步就可获胜;
②根据你的人际关系网络找到关系最近的医生。

其思路为从图上一个节点出发,访问先访问其直接相连的子节点,若子节点不符合,再问其子节点的子节点,按级别顺序依次访问,直到访问到目标节点。

二、例题

1.抓住那头牛

问题描述
农夫知道一头牛的位置,想要抓住它。农夫和牛都位于数轴上,农夫起始位于点N(0<=N<=100000),牛位于点K(0<=K<=100000)。农夫有两种移动方式:

1、从X移动到X-1或X+1,每次移动花费一分钟
2、从X移动到2*X,每次移动花费一分钟

假设牛没有意识到农夫的行动,站在原地不动。农夫最少要花多少时间才能抓住牛?

输入
两个整数,N和K

输出
一个整数,农夫抓到牛所要花费的最小分钟数

样例输入
5 17

样例输出
4

解题思路
把农夫的位置结点化,农夫所处的每一个位置都可以向下再推出三个结点,假设农夫起始位置在5,那么4,6,10就是下一步可能的位置,把这些位置结点有层次地保存在一个队列中(利用队列先进先出的特点),从第一层的结点(起始节点)开始遍历,如果这个结点不是目标结点,就把它出队,并把它的所有子节点入队,再对这些子节点依次遍历,重复上面的操作,直到找到目标结点(也就是牛的位置)。

示例代码

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;

int N, K;	//农夫起始位置,牛的位置
const int MAXN = 100000;
int visited[MAXN + 10];	//判重标记
struct Step
{
	int x;		//位置
	int s;		//到初始位置的距离
	Step(int _x, int _s) : x(_x), s(_s) { }		//构造函数
};
queue<Step> q;	//创建一个队列用来存放位置结点

int main()
{
	cin >> N >> K;	
	memset(visited, 0, sizeof(visited));	//把判重数组都初始化为未访问
	q.push(Step(N, 0));	//把农夫的初始位置压入队列
	visited[N] = 1;	//把初始位置标记为已访问
	while (!q.empty()) {	//当队列不为空时
		Step s = q.front();	//s用于临时存放队头元素
		if (s.x == K) { //找到目标
			cout << s.s << endl;
			return 0;
		}
		else { //没找到目标,继续遍历子结点
			/*如果结点满足条件且没被遍历过,则压入队列*/
			if (s.x - 1 >= 0 && !visited[s.x - 1]) {
				q.push(Step(s.x - 1, s.s + 1));
				visited[s.x - 1] = 1;
			}
			if (s.x + 1 <= MAXN && !visited[s.x + 1]) {
				q.push(Step(s.x + 1, s.s + 1));
				visited[s.x + 1] = 1;
			}
			if (s.x * 2 <= MAXN && !visited[s.x * 2]) {
				q.push(Step(s.x * 2, s.s + 1));
				visited[s.x * 2] = 1;
			}
		}
		q.pop();	//弹出上述子结点的父结点
	}
	return 0;
}

2.迷宫问题

问题描述
在一个n*n(n行, n列)的迷宫中,存在着一个入口、一些墙壁以及一个宝藏。由于迷宫是四连通的,即在迷宫中的一个位置,只能走到与它直接相邻的其他四个位置(上、下、左、右)。从迷宫的入口处开始,最少需要走几步才能拿到宝藏?若永远无法拿到宝藏,则输出-1

输入
多组测试数据。
每组数据输入第一行为正整数n,表示迷宫大小。
接下来n行,每行包括n个字符,其中字符’.‘表示该位置为空地,字符’#'表示该位置为墙壁,字符’S’表示该位置为入口,字符’E’表示该位置为宝藏,输入数据中只有这四种字符,并且’S’和’E’仅出现一次。
n≤1000

输出
输出拿到宝藏最少需要走的步数,若永远无法拿到宝藏,则输出-1

样例输入
5
S.#…
#.#.#
#.#.#
#…E
#…

样例输出
7

解题思路
典型到不能再典型的BFS算法,从代码中总结思路吧

示例代码

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;

char map[1010][1010];	//用来存放地图
int vis[1010][1010];	//用来标记是否走过
int n;	//迷宫大小
int sx, sy;	//入口的横纵坐标
int ex, ey;	//宝藏的横纵坐标
struct Point
{
	int x;	//位置的横坐标
	int y;	//位置的纵坐标
	int time;	//距离起始位置,已经走过的次数
	Point(int _x, int _y, int _time) : x(_x), y(_y), time(_time) { }	//方便下面结点入队
};
queue<Point> q;	//用来存放结点的队列

int main()
{
	memset(vis, 0, sizeof(vis));	//把判重数组都初始化为未访问
	while (cin >> n)
	{
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				cin >> map[i][j];	//创建迷宫地图
			}
		}
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				if (map[i][j] == 'S') {	//找到入口位置
					sx = i;
					sy = j;
				}
				if (map[i][j] == 'E') {	//找到宝藏位置
					ex = i;
					ey = j;
				}
			}
		}
		q.push(Point(sx, sy, 0));	//将入口位置压入队列
		vis[sx][sy] = 1;
		while (!q.empty()) {
			Point now = q.front();	//now用来临时存放当前的队头元素
			if (now.x == ex && now.y == ey) {
				cout << now.time << endl;
				break;
			}
			else {
				if (now.x + 1 < n && map[now.x + 1][now.y] != '#' && !vis[now.x + 1][now.y]) {
					q.push(Point(now.x + 1, now.y, now.time + 1));
					vis[now.x + 1][now.y] = 1;
				}
				if (now.x - 1 >= 0 && map[now.x - 1][now.y] != '#' && !vis[now.x - 1][now.y]) {
					q.push(Point(now.x - 1, now.y, now.time + 1));
					vis[now.x - 1][now.y] = 1;
				}
				if (now.y - 1 >= 0 && map[now.x][now.y - 1] != '#' && !vis[now.x][now.y - 1]) {
					q.push(Point(now.x, now.y - 1, now.time + 1));
					vis[now.x][now.y - 1] = 1;
				}
				if (now.y + 1 < n && map[now.x][now.y + 1] != '#' && !vis[now.x][now.y + 1]) {
					q.push(Point(now.x, now.y + 1, now.time + 1));
					vis[now.x][now.y + 1] = 1;
				}
			}
			q.pop();
		}
	}
	return 0;
}


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值