码迷,mamicode.com
首页 > 其他好文 > 详细

宽度优先搜索

时间:2020-09-15 20:53:10      阅读:27      评论:0      收藏:0      [点我收藏+]

标签:核心   单源最短路   cin   初始   最简   技术   适合   优先   names   

宽度优先搜索

概念

宽度优先搜索算法(又称广度优先搜索算法)是最简单的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijksta单源最短路径算法和Prim最小生成树算法都采用了与宽度优先搜索类似的思想。

宽度优先搜索的核心思想是:从初始结点开始,应用算符生成第一层结点,检查目标结点是否在这些后继结点中,若没有,再用产生式规则将所有第一层的结点逐一扩展,得到第二层结点,并逐一检查第二层结点中是否包含目标结点。若没有,再用算符逐一扩展第二层所有结点……,如此依次扩展,直到发现目标结点为止 。

举例说明

1、奇怪的电梯

【问题描述】

大楼的每一层楼都可以停电梯,而且第i层楼(1≤i≤N)上有一个数字Ki(0≤KiN)。电梯只有两个按钮:上,下。上下的层数等于当前楼层上的那个数字。求从A楼到B楼至少要按几次按钮呢?

例如有5层楼,从1到5层每层楼的数字为3 3 1 2 5。那么要从1楼到5楼,可以先从1到4楼,然后4楼减2到2楼,从2楼加3到5楼,总共要3次。

【输入格式】

共两行

第一行为3个用空格隔开的正整数,表示N,A,B(1≤N≤200, 1≤A,B≤N)。

第二行为N个用空格隔开的非负整数,表示Ki。

【输出格式】

一行,即最少按键次数,若无法到达,则输出-1。

【输入样例】

5 1 5
3 3 1 2 5

【输出样例】

3

问题分析:可以应用宽度优先搜索,从初始状态1层楼开始,在这一层的基础上进行扩展,所有一步能到的楼,都是第二层的节点,同时记录移动步数,在把所有第二层的节点全部取出,按照扩展规则扩展到第3层楼,同理记录下移动步数。重复这个过程,直到找到最终目标。注意在这个过程中需要判断重复。可以靠队列先进先出的特点,来实现。

参考程序

#include <iostream>
#include <queue>
#define INF 0x7fffffff
#define N 205
#define P pair<int, int >
using namespace std;
int n, A, B, k[N], dis[N];

int bfs() {
	queue <P> q;
	dis[A] = 0;
	q.push(P(A, dis[A]));
	while (!q.empty()) {
		int t = q.front().first;
		int d = q.front().second;
		q.pop();
		if (t + k[t] <= n) {
			if (dis[t+k[t]] > d+1) {
				dis[t+k[t]] = d+1;
				q.push(P(t+k[t], d+1));	
			}
		}
		if (t - k[t] >= 1) {
			if (dis[t-k[t]] > d+1) { //更新距离
				dis[t-k[t]] = d+1;
				q.push(P(t-t[k], d+1));
			}
		}
	}
	if (dis[B] == INF)
		dis[B] = -1;
	return dis[B];
}

int main () {
	cin >> n >> A >> B;
	for (int i = 1; i <= n; i++) {
		cin >> k[i];
		dis[i] = INF;
	}
	cout << bfs() << endl;
	return 0;
}

小结

宽度优先搜索适合于找最短路径,因为沿着深度逐层扩展的,只要能找到那么一定是最短的路径。在这个过程中需要注意节点的状态表示(数据结构)、扩展规则、路径记录、重复判断。

题目练习

1、走迷宫

【问题描述】

给定一个N?M的迷宫,每一步可以向上下左右四个方向走动,求出从起点到终点所需的最小步数
(’#’,’.’,‘S’,‘G’分别表示墙壁,通道,起点和终点)

【输入格式】

第一行n m

接下来n行每行m个字符

【输出格式】

输出一行,最小的步数

【样例输入】

10 10
#S######.#
......#..#
.#.##.##.#
.#........
##.##.####
....#....#
.#######.#
....#.....
.####.###.
....#...G#

【样例输出】

22

思路:

有起点和终点,只需要按层搜索扩展下去,扩展的过程中,需要判定是否撞墙或者超出界限。同时做好判重,和记录。

参考程序

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int INF = 100000; 
const int MAX = 101;
typedef pair<int, int> P;
char map[MAX][MAX];
int d[MAX][MAX];//表示起点到各个位置的最短距离 
int sx, sy, gx, gy;//表示起点和终点坐标 
int n, m;
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, 1, 0, -1};

bool Check(int x, int y) {
	if(x>=0 && x<n && y>=0 && y<m && d[x][y]==INF && map[x][y]!=‘#‘)
		return true;
	else return false;
} 

int bfs() {
	queue<P> que;
	for(int i = 0; i < n; i++)
		for(int j = 0; j < m; j++)
			d[i][j] = INF;
	
	que.push(P(sx, sy));
	d[sx][sy] = 0;
	
	while(!que.empty()) {
		P p = que.front(); 
		que.pop();
		if(p.first == gx && p.second == gy)
			break;
		for(int i = 0; i < 4; i++) {
			int nx = p.first + dx[i];
			int ny = p.second + dy[i];
			if(Check(nx, ny)) {
				que.push(P(nx, ny));
				d[nx][ny] = d[p.first][p.second] + 1;
			}
		}
	}
	return d[gx][gy]; 
}

int main() {
	cin >> n >> m;
	for(int i = 0; i < n; i++) {
		for(int j = 0; j < m; j++) {
			cin >> map[i][j];
			if(map[i][j] == ‘S‘) {
				sx = i;
				sy = j;
			}
			if(map[i][j] == ‘G‘) {
				gx = i;
				gy = j;
			}
		}
	}
	int res = bfs(); 
	cout<< res <<endl;
    return 0;
}

2、8数码问题

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

【输入格式】

输入初始状态,一行九个数字,空格用0表示

【输出格式】

只有一行,该行只有一个数字,表示从初始状态到目标状态最少需要的移动次数

【输入样例】

283104765

【输出样例】

4

只思考简单的宽度优先搜索,那么8数码问题,也是从一个初始状态出发,找到一个最终状态,求最短的路径。

技术图片

难点就变成了怎么样去表示每一个层数的状态,如何在按层数搜索扩展的时候,进行判重

由于是一个3×3的矩阵,可以每一行的数字连接成一个9位数,进行状态的存储。常规的判重操作是通过布尔或者整型数组,用下标来记录状态,但是9位数的数组太大不合适。因此可以使用map来判重,状态用关键值(key)表示,走到该状态花费的步数用键值(value)表示,判重可以通过map的内置方法count()来查找键值。

还有注意的是数组和状态值之间的转换。

参考程序

#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
using namespace std;
#define N 15

int mat[N][N]; 
int st, ed = 123804765;

queue <int> q;
map<int, int> m;
int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0, -1, 0};
int zx, zy;

void to_mat (int x) {
	int nu = 100000000;
	for (int i = 1; i <= 3; i ++)
		for (int j = 1; j <= 3; j++) {
			mat[i][j] = x / nu % 10;
			nu /= 10; 
			if (mat[i][j] == 0) {
				zx = i, zy = j;
			}
		}
}
int to_num () {
	int res = 0;
	for (int i = 1; i <= 3; i++)
		for (int j = 1; j <= 3; j++) 
			res = res * 10 + mat[i][j];
	return res;
}


int bfs () {
	int res;
	m[st] = 0;
	q.push (st);
	while (!q.empty()) {
		int now = q.front();
		q.pop();
		if(now == ed) {
			res = m[ed];
			break ;
		}
		to_mat(now);
		for (int i = 0; i < 4; i++) {
			int tx = dx[i] + zx;
			int ty = dy[i] + zy;
			if (tx > 0 && tx < 4 && ty > 0 && ty < 4) {
				swap(mat[zx][zy], mat[tx][ty]);
				int to = to_num();
				if (!m.count(to)) {
					m[to] = m[now] + 1;
					q.push(to);
				}
				swap(mat[zx][zy], mat[tx][ty]);
			}

		}

	}

	return res;
}

int main () {
	cin >> st;
	cout << bfs ();	
	return 0;
}

框架

void bfs(起始点) {
	将起始点放入队列中;
	标记起点访问;
	while(如果队列不为空){
		访问队列队首元素x;
		删除队首元素;
		if(出现最终状态) {
			做相应处理
		}
		for(遍历所有x下一层节点){
			if(该点未被访问过且合法){
				将该点加入队列末尾;
				做相应操作:记录距离,更新前置结点等
			}
		}
	}
	队列为空,广搜结束;
}

宽度优先搜索

标签:核心   单源最短路   cin   初始   最简   技术   适合   优先   names   

原文地址:https://www.cnblogs.com/s-k-p/p/13598668.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!