洪水覆盖算法(Flood Fill)
算法介绍
洪水填充(Flood fill)算法:从一个起始节点开始把附近与其连通的节点提取出或填充成不同颜色颜色,直到封闭区域内的所有节点都被处理过为止,是从一个区域中提取若干个连通的点与其他相邻区域区分开(或分别染成不同颜色)的经典算法。
相关参数
洪水填充算法接受三个参数:
- 起始节点,目标节点特征和针对提取对象要执行的处理。
实现方式
- 目前有许多实现方式,基本上都显式的或隐式的使用了队列或者栈。
- 洪水填充算法实现最常见有四邻域填充法(不考虑对角线方向的节点),八邻域填充法(考虑对角线方向的节点),基于扫描线填充方法。
根据实现又可以分为递归与非递归(基于栈)。
- 最简单的实现方法是采用深度优先搜索的递归方法,也可以采用广度优先搜索的迭代来实现。基于递归实现的泛洪填充算法有个致命的缺点,就是对于大的区域填充时可能导致栈溢出错误,基于扫描线的算法实现了一种非递归的洪水填充算法。
- 除提出连通区域外,还可以应用于计算从某一节点开始,到可能到达其他所有节点的距离。比如解决像走迷宫这类的问题。
例题讲解
池塘计数
思路分析:模板题。这道题遍历所有的点如果是W就进行bfs找到与他相邻的W并把它标记成".",每次进行一次bfs即是一个连通块。
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e3+10;
char g[N][N];
int n,m,ans = 0;
//八连通
int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};
void bfs(int x,int y){
g[x][y] = '.';
queue<PII> q;
q.push({x,y});
while(!q.empty()){
auto t = q.front(); q.pop();
for(int i = 0; i < 8; i++){
int sx = t.first+dx[i],sy = t.second+dy[i];
if(sx >= 0 && sy >= 0 && sx < n && sy < m && g[sx][sy] == 'W'){
g[sx][sy] = '.'; //把找到的点标记为点,避免再次查找
q.push({sx,sy});
}
}
}
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i++) cin >> g[i];
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
if(g[i][j] == 'W'){
ans++;
bfs(i,j);
}
}
}
cout << ans << endl;
return 0;
}
城堡问题
二进制取位就可以知道是哪个墙挡住,比如0110,是有东墙和北墙。
- - 1 第零位,对应二进制数 0001 , 表示有西墙
- - 1 第一位,对应二进制数 0010 , 表示有北墙
- - 1 第二位,对应二进制数 0100 , 表示有东墙
- - 1 第三位,对应二进制数 1000 , 表示有南墙
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e2+10;
int n,m;
int g[N][N];
bool st[N][N];
int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};
int bfs(int sx,int sy){
st[sx][sy] = 1;
queue<PII> q;
q.push({sx,sy});
int area = 0;
while(!q.empty()){
auto t = q.front(); q.pop();
area++;
for(int i = 0; i < 4; i++){
int x = t.first+dx[i],y = t.second+dy[i];
if(x >= 0 && x < n && y >= 0 && y < m && !st[x][y] && !(g[t.first][t.second] >> i & 1)){ //如果是0就表示无墙
st[x][y] = 1;
q.push({x,y});
}
}
}
return area;
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++) cin >> g[i][j];
}
int cnt = 0,area = 0;
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
if(!st[i][j]){
area = max(area,bfs(i,j)); //统计最大值
cnt++;
}
}
}
cout << cnt << endl << area << endl;
return 0;
}
山峰和山谷
思路分析:有时候从模板题上多统计一下额外信息,这道题就是统计两个点(块)之间的关系。从题意中我们可以知道如果这个块比周围的点要低,就是山谷;如果这个块比其他点要高,就是山峰;如果一样高即使山谷又是山峰。那么这道题思路就是这样。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int dx[] = { -1, -1, 0, 1, 1, 1, 0, -1 };
int dy[] = { 0, 1, 1, 1, 0, -1, -1, -1 };
int w[N][N];
bool vis[N][N];
int n, ans1, ans2;
void bfs(int sx, int sy) {
queue<pair<int, int> > q;
q.push({ sx, sy });
int val = w[sx][sy];
vis[sx][sy] = 1;
bool flag1 = 0, flag2 = 0;
while (!q.empty()) {
auto t = q.front();
q.pop();
int x = t.first, y = t.second;
for (int i = 0; i < 8; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= n) {
if (w[nx][ny] != val) {
if (w[nx][ny] > val)
flag2 = 1;
if (w[nx][ny] < val)
flag1 = 1;
} else if (!vis[nx][ny]) {
vis[nx][ny] = 1;
q.push({ nx, ny });
}
}
}
}
//注意:这里不能只判断一个值,因为如果周围的山参差不齐那他什么都不是
if (!flag1 && !flag2) //如果一样高
ans1++, ans2++;
if (flag1 && !flag2) //高于其他山
ans1++;
if (!flag1 && flag2) //低于其他山
ans2++;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) cin >> w[i][j];
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (!vis[i][j])
bfs(i, j);
}
}
cout << ans1 << " " << ans2 << endl;
return 0;
}
最短路模型
算法介绍
BFS可以求边权相等的最短路,也被称为最短路模型。这种题往往用图论建图比较麻烦,但是BFS会实现简单一点。
例题讲解
迷宫问题
思路分析:这道题就是算法基础课里的走迷宫,只不过这次他让输出最短路径。这道题也算是模板题去统计一下额外信息。记录路径方式也简单,就是记录当前他的状态从哪个状态转换来的,换句话说就是他从哪个地方走过来的,很像链表的存储。
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
int n;
const int N = 1e3+10;
int g[N][N],st[N][N];
PII pre[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
void bfs(int x,int y){
st[x][y] = 1;
queue<PII> q;
q.push({x,y});
while(!q.empty()){
auto t = q.front(); q.pop();
for(int i = 0; i < 4; i++){
int sx = t.first+dx[i],sy = t.second+dy[i];
if(sx >= 0 && sy >= 0 && sx < n && sy < n && st[sx][sy] == 0 && g[sx][sy] == 0){
q.push({sx,sy});
pre[sx][sy] = t; //记录当前状态是从哪个状态来的
st[sx][sy] = 1;
}
}
}
}
int main()
{
cin >> n;
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++) cin >> g[i][j];
}
bfs(n-1,n-1); //应为路径是从后回溯到前的,所以倒置找最短路,刚好是正序输出
PII end = {0,0};
while(true){
cout << end.first << " " << end.second << endl;
if(end.first == n-1 && end.second == n-1) break;
end = pre[end.first][end.second];
}
return 0;
}
武士风度的牛
思路分析:模板题。算法基础课-迷宫问题,就是把方向数组变换一下,四连通改成日字八连通,其他地方不用改。
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e3+10;
int n,m;
char g[N][N];
int d[N][N];
bool st[N][N];
//日字八连通
int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};
int bfs(int x,int y,int ex,int ey){
memset(d, -1, sizeof d);
d[x][y] = 0;
queue<PII> q;
q.push({x,y});
while(!q.empty()){
auto t = q.front(); q.pop();
for(int i = 0; i < 8; i++){
int sx = t.first+dx[i],sy = t.second+dy[i];
if(sx >= 0 && sy >= 0 && sx < n && sy < m && g[sx][sy] == '.' && d[sx][sy] == -1){
d[sx][sy] = d[t.first][t.second]+1;
q.push({sx,sy});
}
}
}
return d[ex][ey];
}
int main()
{
cin >> m >> n;
int sx,sy,ex,ey;
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
char c; cin >> c;
if(c == 'K'){
sx = i,sy = j;
g[i][j] = '.';
}
else if(c == 'H'){
ex = i,ey = j;
g[i][j] = '.';
}
else g[i][j] = c;
}
}
cout << bfs(sx,sy,ex,ey) << endl;
return 0;
}
抓住那头牛
思路分析:跟模板题很想就是把八连通或四连通的方向状态转移换成他指定的x+1,x-1,x*2,就行了,这里给出两种写法,STL实现队列,和数组模拟队列
注意: 这里需要判断是否越界,一定要把判断越界条件放在前面,不然会数组越界!!!
//STL:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int d[N];
int n, k;
int bfs() {
memset(d, -1, sizeof d);
d[n] = 0;
queue<int> q;
q.push(n);
while (!q.empty()) {
int t = q.front();
q.pop();
if (t == k) return d[k];
if (t * 2 < N && d[t * 2] == -1) {
d[t * 2] = d[t] + 1;
q.push(t * 2);
}
if (t - 1 >= 0 && d[t - 1] == -1) {
d[t - 1] = d[t] + 1;
q.push(t - 1);
}
if (t + 1 < N && d[t + 1] == -1) {
d[t + 1] = d[t] + 1;
q.push(t + 1);
}
}
return -1;
}
int main() {
cin >> n >> k;
cout << bfs() << endl;
return 0;
}
//数组模拟
//因为不知名原因发现这里把范围判断放在后面也可以,但是最好还是放在前面,STL一定是前面
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int d[N];
int q[N];
int n,k;
int bfs(){
memset(d, -1, sizeof d);
d[n] = 0;
q[0] = n;
int hh = 0,tt = 0;
while(hh <= tt){
auto t = q[hh++];
if(t == k) return d[k];
if(d[t*2] == -1 && t*2 < N){
d[t*2] = d[t]+1;
q[++tt] = t*2;
}
if(d[t-1] == -1 && t-1 >= 0){
d[t-1] = d[t]+1;
q[++tt] = t-1;
}
if(d[t+1] == -1 && t+1 < N){
d[t+1] = d[t]+1;
q[++tt] = t+1;
}
}
return -1;
}
int main()
{
cin >> n >> k;
cout << bfs() << endl;
return 0;
}
多源BFS
算法介绍
多源 BFS 是指从多个源点同时进行广度优先搜索的算法。在传统的 BFS 中,我们通常从一个起始点开始,逐层遍历所有的相邻节点。而在多源 BFS 中,我们可以同时从多个源点开始,从这些源点出发,逐层向外扩展,直到达到目标或者遍历完整个图。
多源 BFS 可以用于解决一些问题,例如:
- 多个人同时逃生: 在一个迷宫中,有多个人同时被困在不同的位置,需要找到最短路径逃离迷宫。可以从这些人的位置同时开始 BFS,第一个相遇的点就是大家逃生的最短路径。
- 多点到达目标问题: 在一些网络传播或者路由问题中,多个点需要同时到达某个目标点,可以使用多源 BFS 找到最短路径。
- 并行计算: 在一些并行计算的场景中,多源 BFS 可以用于并行地计算多个点到其他点的最短路径。
多源 BFS 的实现方式与单源 BFS 类似,只是需要同时从多个源点开始扩展。通常使用队列来进行层次遍历,每一层表示到达目标的步数。
例题讲解
矩阵距离
思路分析:这道题多源BFS的模板题。算法实现就是把所以为1的点都入队,因为最近1-0的距离,也是最近0-1的距离,正常进行BFS就行了
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e3+10;
char g[N][N];
int d[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int n,m;
void bfs(){
memset(d,-1,sizeof d);
queue<PII> q;
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
if(g[i][j] == '1'){ //所有为1的点都入队
d[i][j] = 0;
q.push({i,j});
}
}
}
while(!q.empty()){
auto t = q.front(); q.pop();
for(int i = 0; i < 4; i++){
int sx = t.first+dx[i],sy = t.second+dy[i];
if(sx >= 0 && sy >= 0 && sx < n && sy < m && d[sx][sy] == -1){
d[sx][sy] = d[t.first][t.second]+1;
q.push({sx,sy});
}
}
}
}
int main(){
cin >> n >> m;
for(int i = 0; i < n; i++) cin >> g[i];
bfs();
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
cout << d[i][j] << " ";
}
cout << endl;
}
return 0;
}
最小步数模型
算法介绍
求题意给定的一种状态,到另一种状态的最短距离,最小变换次数。
例题讲解
魔板
思路分析:这道题是对BFS的应用和八数码那道题很像,都是需要将二维先映射成一维,再对他进行如上A,B,C三种操作,一步一步扩展变换,即可找到指定状态,要保证方案字典序最小,那每次按照A,B,C的顺序去扩展,第一次找到状态与指定状态相同时,那字典序一定最小。
注意:这里的变换代码不好实现,但是发现只有8个数,那么手动暴力也可以。
A = {s[7], s[6], s[5], s[4], s[3], s[2], s[1], s[0]};
B = {s[3], s[0], s[1], s[2], s[5], s[6], s[7], s[4]};C = {s[0], s[6], s[1], s[3], s[4], s[2], s[5], s[7]};
#include<bits/stdc++.h>
using namespace std;
typedef pair<string, string> PII;
map<string,bool> mp;
queue<PII> q;
int main()
{
string s;
for(int i = 1; i <= 8; i++){
char c; cin >> c;
s += c;
}
q.push({"12345678",""});
while(!q.empty()){
auto t = q.front(); q.pop();
int res = t.first,now = t.second;
//找到答案
if(s == res){
if(!now.size()) cout << 0 << endl;
else{
cout << now.size() << endl << now << endl;
break;
}
}
string A, B, C;
//状态变换
A = {s[7], s[6], s[5], s[4], s[3], s[2], s[1], s[0]};
B = {s[3], s[0], s[1], s[2], s[5], s[6], s[7], s[4]};
C = {s[0], s[6], s[1], s[3], s[4], s[2], s[5], s[7]};
//标记访问,now代表操作方案,s代表变换后的样子
if(!mp[A]) mp[A] = 1, q.push({A,now+'A'});
if(!mp[B]) mp[B] = 1, q.push({B,now+'B'});
if(!mp[C]) mp[C] = 1, q.push({C,now+'C'});
}
return 0;
}