理解广度优先搜索 (BFS)

前言

参考视频教程洛谷 普及组试炼场 - 广度优先搜索 (BFS)

广度优先搜索 (BFS) - 类似队列的思想,先进先出。 搜过了的就不用再搜索了。

当题目要求最短步骤或路径时,就可考虑用BFS的思想。

以简单基础普及题型为例,可在洛谷上查找题目提交,代码仅供参考。

题目列表:

1.填涂颜色

2.马的遍历

3.奇怪的电梯

4.01迷宫

5.字串变换

6.机器人搬重物

1.填涂颜色

题目描述

由数字00组成的方阵中,有一任意形状闭合圈,闭合圈由数字11构成,围圈时只走上下左右44个方向。现要求把闭合圈内的所有空间都填写成2.例如:6×6的方阵(n=6),涂色前和涂色后的方阵如下:

0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1

0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1

输入格式

每组测试数据第一行一个整数n*(1≤*n≤30)

接下来n行,由0和1组成的n×n的方阵。

方阵内只有一个闭合圈,圈内至少有一个0。

输出格式

已经填好数字2的完整方阵。

输入输出样例

输入 #1复制

6
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1

输出 #1复制

0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1

说明/提示

1≤n≤30

代码

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

int n;
int vis[40][40];  //记录访问节点 
int mp[40][40];  //初始数据 
int xx[] = { 0,0,1,-1 };
int yy[] = { 1,-1,0,0 };

int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			cin >> mp[i][j];
		}
	}

	queue<int> x;
	queue<int> y;
	x.push(0);
	y.push(0);

	while (!x.empty())
	{
		for (int i = 0; i < 4; i++)
		{
			int dx = x.front() + xx[i];
			int dy = y.front() + yy[i];
			if (dx >= 0 && dx <= n + 1 && dy >= 0 && dy <= n + 1 && mp[dx][dy] != 1 && vis[dx][dy] == 0)
			{
				x.push(dx);
				y.push(dy);
				vis[dx][dy] = 1;
			}
		}
		x.pop();
		y.pop();
	}

	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			if (vis[i][j] == 0 && mp[i][j] != 1)
			{
				cout << "2";
			}
			else
			{
				cout << mp[i][j];
			}
			cout << " ";
		}
		cout << endl;
	}
	return 0;
}

2.马的遍历

题目描述

有一个n*m的棋盘(1<n,m<=400),在某个点上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步

输入格式

一行四个数据,棋盘的大小和马的坐标

输出格式

一个n*m的矩阵,代表马到达某个点最少要走几步(左对齐,宽5格,不能到达则输出-1)

输入输出样例

输入 #1复制

3 3 1 1

输出 #1复制

0    3    2    
3    -1   1    
2    1    4    

代码

#include <iostream>
#include <bits/stdc++.h>
#include <queue>
using namespace std;
int mp[420][420];
int xx[] = { 1,2,2,1,-1,-2,-2,-1 };   //马的移动需要知道
int yy[] = { 2,1,-1,-2,-2,-1,1,2 };

struct node
{
	int x, y, t;
};

int main()
{
	int n, m, sx, sy;
	cin >> n >> m >> sx >> sy;
	memset(mp, -1, sizeof(mp));
	queue<node> q;
	node p;
	p.x = sx;
	p.y = sy;
	p.t = 0;
	q.push(p);
	mp[sx][sy] = 0;

	while (!q.empty())
	{
		for (int i = 0; i < 8; i++)
		{
			int dx = q.front().x + xx[i];
			int dy = q.front().y + yy[i];
			if (dx >= 1 && dx <= n && dy >= 1 && dy <= m && mp[dx][dy] == -1)
			{
				mp[dx][dy] = q.front().t + 1;
				node temp;
				temp.x = dx;
				temp.y = dy;
				temp.t = mp[dx][dy];
				q.push(temp);   // 或者直接强制转换 q.push((node){dx,dy,mp[dx][dy]});
			}
		}
		q.pop();
	}

	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			cout << left << setw(5) << mp[i][j];  //左对宽
		}
		cout << endl;
	}
	return 0;
}

3.奇怪的电梯

题目描述

呵可,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第i层楼(1≤i≤N)上有一个数字K1(0≤压1≤N)。电梯只有四个按钮:开,关,上,下。上下的层数等于 当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如:3,3,1,2,5代表了K1(压1=3K2=3,),从1楼开始。在1楼,按“上"可以到4楼,按”下"是不起作用的,因为没有-2 楼。那么,从A楼到B楼至少要按几次按钮呢?

输入格式

共二行。

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

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

输出格式

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

输入输出样例

输入 #1复制

5 1 5
3 3 1 2 5

输出 #1复制

3

代码

#include <iostream>
#include <queue>

using namespace std;

int y[205];
int vis[205];   //防止返回
int main()
{
	int n, a, b;
	cin >> n >> a >> b;
	for (int i = 1; i <= n; i++)
	{
		cin >> y[i];
	}
	queue<int> s;
	queue<int> bs;
	s.push(a);
	bs.push(0);
	while (!s.empty())
	{
		if (s.front() == b)
		{
			cout << bs.front() << endl;
			return 0;
		}
		int ss = s.front() + y[s.front()];
		if (ss <= n && vis[ss] == 0)
		{
			s.push(ss);
			bs.push(bs.front() + 1);
			vis[ss] = 1;
		}
		int ss2 = s.front() - y[s.front()];
		if (ss2 >= 1 && vis[ss2] == 0)
		{
			s.push(ss2);
			bs.push(bs.front() + 1);
			vis[ss2] = 1;
		}
		s.pop();
		bs.pop();
	}
	cout << "-1" << endl;
	return 0;

}

4.01迷宫

题目描述

有一个仅由数字0与1组成的n×n格迷宫。若你位于一格0上,那么你可以移动到相邻4格中的某一格1上,同样若你位于一格1上,那么你可以移动到相邻4格中的某一格0上。

你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)。

输入格式

第11行为两个正整数n,m

下面n行,每行n个字符,字符只可能是0或者1,字符之间没有空格。

接下来m行,每行2个用空格分隔的正整数i,j,对应了迷宫中第i行第j列的一个格子,询问从这一格开始能移动到多少格。

输出格式

m行,对于每个询问输出相应答案。

输入输出样例

输入 #1复制

2 2
01
10
1 1
2 2

输出 #1复制

4
4

代码

#include <iostream>
#include <queue>
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int n, m;
int mp[maxn][maxn];
int vis[maxn][maxn];
int color; 
int cnt = 0;
int ans[1000010];   //记录每种颜色可移动的格子数

int xx[] = { 0,1,0,-1 };
int yy[] = { 1,0,-1,0 };

void bfs(int x, int y)
{
	queue<int> h;
	queue<int> l;
	h.push(x);
	l.push(y);
	vis[x][y] = color;       //同一块的区域涂颜色
	while (!h.empty())
	{
		for (int i = 0; i < 4; i++)
		{
			int dx = h.front() + xx[i];
			int dy = l.front() + yy[i];
			if (dx <= n && dx >= 1 && dy <= n && dy >= 1 && !vis[dx][dy] && mp[dx][dy] != mp[h.front()][l.front()])
			{
				vis[dx][dy] = color;
				h.push(dx);
				l.push(dy);
			}
		}
		h.pop();
		l.pop();
		cnt++;      //用来记录每次弹出的次数,即可移动的格子数
	}

}

int main()
{

	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{ 
			char k;   //注意是输入字符间无空格
			cin >> k;
			if (k == '1')
			{
				mp[i][j] = 1;
			}
		}
	}
	
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			if (!vis[i][j])
			{
				color++;
				bfs(i, j);
				ans[color] = cnt;
				cnt = 0;
			}
	
		}
	}
	
	for (int i = 1; i <= m; i++)
	{
		int h, l;
		cin >> h >> l;
		cout << ans[vis[h][l]] << endl;
	}
	
	return 0;

}

5.字串变换

题目描述

已知有两个字串 A,B及一组字串变换的规则(至多 6 个规则):

A1 ->B1

A2 -> B2

规则的含义为:在 A 中的子串 A1 可以变换为 B1,A2 可以变换为B2 …。

例如:A=abcd,B=xyz,

变换规则为:

abc→xu,ud→y,y→yz

则此时,A 可以经过一系列的变换变为 B*,其变换的过程为:

abcd→xud→xy→xyz。

共进行了 3次变换,使得 A变换为 B。

输入格式

输入格式如下:

AB
A1​ B1​
A2B2​ |-> 变换规则

… …/

所有字符串长度的上限为 2020。

输出格式

若在 10 步(包含 10 步)以内能将 A 变换为 B,则输出最少的变换步数;否则输出 NO ANSWER!

输入输出样例

输入 #1复制

abcd xyz
abc xu
ud y
y yz

输出 #1复制

3

代码

#include <iostream>
#include <bits/stdc++.h>
#include <queue>
#include <string>
using namespace std;

map<string, int> mp;   //神奇工具~ ,记录每个字符串是否为访问过
int main()
{
	string a, b;
	cin >> a >> b;
	string aa[10], bb[10];
	int n = 1;
	while (cin >> aa[n] >> bb[n])
	{
		n++;
	}
	n--;
	queue<string> w;
	queue<int> bs;
	w.push(a);
	bs.push(0);
	while (!w.empty())
	{
		if (w.front() == b)
		{
			cout << bs.front() << endl;
			return 0;
		}
		if (bs.front() == 10)
		{
			bs.pop();
			w.pop();
		}
		string tt = w.front();
		if (mp.count(tt))   // tt 是否出现 ,1 为出现,0为否 ,出现过就没必要继续查它了
		{
			w.pop();
			bs.pop();
			continue;
		}
		mp[tt] = 1;
		for (int i = 1; i <= n; i++)
		{
			int p = 0;
			while (tt.find(aa[i], p) <= tt.length())    //从 tt 的下标 p 开始查找a[i],找到则返回找到的第一个字母位置,找不到则返回一个很大的值  
			{
				p = tt.find(aa[i], p);
				w.push(tt.substr(0, p) + bb[i] + tt.substr(p + aa[i].length()));
				bs.push(bs.front() + 1);
				p++;   // p++往下移一位继续搜寻 tt 中等于 aa[i] 的片段
			}
		}
		w.pop();
		bs.pop();
	}
	cout << "NO ANSWER!" << endl;
	return 0;
}

6.机器人搬重物

题目描述

机器人移动学会(RMI)现在正尝试用机器人搬运物品。机器人的形状是一个直径1.6米的球。在试验阶段,机器人被用于在一个储藏室中搬运货物。储藏室是一个 N×M 的网格,有些格子为不可移动的障碍。机器人的中心总是在格点上,当然,机器人必须在最短的时间内把物品搬运到指定的地方。机器人接受的指令有:向前移动1步(Creep);向前移动2步(Walk);向前移动33 步(Run);向左转(Left);向右转(Right)。每个指令所需要的时间为1 秒。请你计算一下机器人完成任务所需的最少时间。

输入格式

第一行为两个正整数N*,M(N,M≤50),下面N行是储藏室的构造,0表示无障碍,1表示有障碍,数字之间用一个空格隔开。接着一行有4个整数和1个大写字母,分别为起始点和目标点左上角网格的行与列,起始时的面对方向(东E,南S*,西W*,北N*),数与数,数与字母之间均用一个空格隔开。终点的面向方向是任意的。

输出格式

一个整数,表示机器人完成任务所需的最少时间。如果无法到达,输出-1。

img

输入输出样例

输入 #1复制

9 10
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 1 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 1 0 0 0 0
0 0 0 1 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 1 0
7 2 2 7 S

输出 #1复制

12

代码

#include <iostream>
#include <bits/stdc++.h>
#include <queue>
using namespace std;

bool mp[60][60];
bool vis[60][60][5];  //方向也要标记 

int xx[] = { 0,1,0,-1 };
int yy[] = { 1,0,-1,0 };


struct dian
{
	int x, y, t, fx;
};

int main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			int temp;
			cin >> temp;
			if (temp == 1)
			{
				mp[i][j] = mp[i - 1][j] = mp[i][j - 1] = mp[i - 1][j - 1] = 1;  //注意是一个方格子上的四个点,这想法妙哇
			}
		}
	}
	int sx, sy, fx, fy;
	char w;
	cin >> sx >> sy >> fx >> fy >> w;
	dian d;
	d.x = sx; d.y = sy; d.t = 0;
	if (w == 'E') d.fx = 0;    //将字母方向匹配上移动方向数组
	if (w == 'S') d.fx = 1;
	if (w == 'W') d.fx = 2;
	if (w == 'N') d.fx = 3;

	queue<dian> q;
	vis[d.x][d.y][d.fx] = 1;
	q.push(d);
	while (!q.empty())
	{
		dian temp = q.front();
	
		if (temp.x == fx && temp.y == fy)
		{
			cout << temp.t;
			return 0;
		}
	
		temp.t++;
	
		temp.fx = (q.front().fx + 1) % 4;    //记得取余
		if (!vis[temp.x][temp.y][temp.fx])
		{
			vis[temp.x][temp.y][temp.fx] = 1;
			q.push(temp);
		}
		temp.fx = (q.front().fx + 3) % 4;
		if (!vis[temp.x][temp.y][temp.fx])
		{
			vis[temp.x][temp.y][temp.fx] = 1;
			q.push(temp);
		}
		temp.fx = q.front().fx;
		int run = 3;
		while (run--)     //慢慢一步步走
		{
			temp.x += xx[temp.fx];
			temp.y += yy[temp.fx];
			if (!vis[temp.x][temp.y][temp.fx] && !mp[temp.x][temp.y] && temp.x >= 1 && temp.x < n && temp.y >= 1 && temp.y < m)
			{
				vis[temp.x][temp.y][temp.fx] = 1;
				q.push(temp);
			}
			else
			{
				break;
			}
		}
		q.pop();
	}
	cout << "-1";
	return 0;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值