bfs常见题型总结

文章目录

    • (一)bfs模板题
    • (二)Flood Fill
    • (三)最短路模型
    • (四)多源bfs

(一)bfs模板题

题目描述
给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。
最初,有一个人位于左上角 (1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。
请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。
数据保证 (1,1) 处和 (n,m) 处的数字为 0,且一定至少存在一条通路。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含 m 个整数(0 或 1),表示完整的二维数组迷宫。
输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数。
数据范围
1≤n,m≤100
输入样例:
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
输出样例:
8

AC代码:

#include
#include
#include
#include
#include

#define x first 
#define y second 

using namespace std;

const int N = 110;

int n, m, cnt;
int g[N][N];

int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};

int bfs(){
    queue<pair<int, int>> q;
    q.push({0, 0});

    while(!q.empty()){
        int size = q.size();
        cnt ++ ;
        for(int j = 0; j < size; j ++ ){
            auto tem = q.front();
            q.pop();

            if(tem.x == n - 1 && tem.y == m - 1)   return cnt;

            for(int i = 0; i < 4; i ++ ){
                int nx = tem.x + dx[i], ny = tem.y + dy[i];
                if(nx < 0 || ny < 0 || nx >= n || ny >= m)    continue;
                if(g[nx][ny] == 1)  continue;
                q.push({nx, ny});
                g[nx][ny] = 1;
            }
        }
    }
}

int main(){
    cin >> n >> m;
    for(int i = 0; i < n; i ++ )
        for(int j = 0; j < m; j ++ )
            scanf("%d", &g[i][j]);

    int ans = bfs();

    cout << ans - 1 << endl;

    return 0;
}

(二)Flood Fill

  • 该类题型中,通常在给定的地图中包含若干个连通块,求解连通块的个数以及连通块的面积(通常为最大面积)。这类题型可以采用bfs解法,也可以使用dfs求解

例题1:

题目背景描述:
农夫约翰有一片 N∗M 的矩形土地。
最近,由于降雨的原因,部分土地被水淹没了。
现在用一个字符矩阵来表示他的土地。
每个单元格内,如果包含雨水,则用”W”表示,如果不含雨水,则用”.”表示。
现在,约翰想知道他的土地中形成了多少片池塘。
每组相连的积水单元格集合可以看作是一片池塘。
每个单元格视为与其上、下、左、右、左上、右上、左下、右下八个邻近单元格相连。
请你输出共有多少片池塘,即矩阵中共有多少片相连的”W”块。
输入格式
第一行包含两个整数 N 和 M。
接下来 N 行,每行包含 M 个字符,字符为”W”或”.”,用以表示矩形土地的积水状况,字符之间没有空格。
输出格式
输出一个整数,表示池塘数目。
数据范围
1≤N,M≤1000
输入样例:
10 12
W…WW.
.WWW…WWW
…WW…WW.
…WW.
…W…
…W…W…
.W.W…WW.
W.W.W…W.
.W.W…W.
…W…W.
输出样例:
3

dfs解法:
直接搜索每一个“ W ”周围是否还有“ W ”.如果有,就将其变为“ . ”.再统计答案即可.

#include
#include
#include

using namespace std;

const int N = 1010;

int n, m, cnt;
char s[N][N];

int dx[8] = {1, -1, 0, 0, 1, -1, 1, -1};
int dy[8] = {0, 0, 1, -1, 1, -1, -1, 1};

void dfs(int x, int y){
    s[x][y] = '.';
    for(int i = 0; i < 8; i ++ )
    {
        int a = x + dx[i], b = y + dy[i];
        if(s[a][b] == 'W')
            dfs(a, b);
    }
}

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

    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= m; j ++ )
            if(s[i][j] == 'W'){
                dfs(i, j);
                cnt ++ ;
            }

    cout << cnt << endl;

    return 0;
}

bfs解法:

#include

#define x first
#define y second

using namespace std;

const int N = 1010;
int n, m;
char s[N][N];
bool st[N][N];

int dx[8] = {0, 0, 1, -1, 1, -1, -1, 1};
int dy[8] = {1, -1, 0, 0, 1, -1, 1, -1};

void bfs(int a, int b){
    queue<pair<int, int>> q;
    q.push({a, b});
    st[a][b] = true;

    while(!q.empty()){
        auto t = q.front();
        q.pop();
        a = t.x, b = t.y;

        for(int i = 0; i < 8; i ++ )
        {
            int na = a + dx[i], nb = b + dy[i];
            if(na < 0 || na >= n || nb < 0 || nb >= m)    continue;
            if(s[na][nb] == '.' || st[na][nb])  continue;

            st[na][nb] = true;
            q.push({na, nb});
        }
    }
}

int main(){
    scanf("%d%d", &n, &m);

    for(int i = 0; i < n; i ++ )
        for(int j = 0; j < m; j ++ )
            cin >> s[i][j];

    int cnt = 0;
    for(int i = 0; i < n; i ++ )
        for(int j = 0; j < m; j ++ )
            if(s[i][j] == 'W' && st[i][j] == false)
            {
                bfs(i, j);
                cnt ++ ;
            }

    cout << cnt << endl;

    return 0;
}

(三)最短路模型

  • 这类问题通常要求输出最短的路径经过的各个点。通常设置一个path[ i ] 数组,记录点 i 的上一个点是哪一个点。需要注意的是,若从起点开始bfs遍历,在输出时路径为逆序,故需要额外花费时间去reverse(char类型可以直接使用reverse,int类型需要手动reverse)。在实际题目中,我们也可以从终点bfs遍历来避免上述操作,但是需要根据题目实际选取。

例题:

题目背景描述:
给定一个 n×n 的二维数组,如下所示:
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。
数据保证至少存在一条从左上角走到右下角的路径。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含 n 个整数 0 或 1,表示迷宫。
输出格式
输出从左上角到右下角的最短路线,如果答案不唯一,输出任意一条路径均可。
按顺序,每行输出一个路径中经过的单元格的坐标,左上角坐标为 (0,0),右下角坐标为 (n−1,n−1)。
数据范围
0≤n≤1000
输入样例:
5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
输出样例:
0 0
1 0
2 0
2 1
2 2
2 3
2 4
3 4
4 4

AC代码:

#include
#include
#include
#include

#define x first
#define y second

using namespace std;

typedef pair<int,int> PII;
const int N = 1010;

int n;
int s[N][N];
PII path[N][N];

void bfs(int sx, int sy){
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};

    queue<PII> q;
    q.push({sx, sy});
    memset(path, -1, sizeof path);
    path[n - 1][n - 1] = {0, 0};

    while(q.size()){
        auto t = q.front();
        q.pop();

        for(int i = 0; i < 4; i ++ )
        {
            int nx = t.x + dx[i], ny = t.y + dy[i];
            if(nx < 0 || ny < 0 || nx >= n || ny >= n)  continue;
            if(path[nx][ny].x != -1)    continue;
            if(s[nx][ny] == 1)  continue;

            q.push({nx, ny});
            path[nx][ny] = t;
        }
    }
}

int main(){
    cin >> n;

    for(int i = 0; i < n; i ++ )
        for(int j = 0; j < n; j ++ )
            cin >> s[i][j];

    bfs(n - 1, n - 1);

    PII end = {0, 0};
    while(true){
        cout << end.x << " " << end.y << endl;
        if(end.x == n - 1 && end.y == n - 1)    break;
        end = path[end.x][end.y];
    }

    return 0;
}

(四)多源bfs

  • 在某些题目中,给出多个起点以及多个终点,要求输出从某一起点出发到达终点的最短距离
  • 利用常规bfs去从每个终点开始遍历,保留最短距离是可行的,但是时间复杂度往往太大,出现超时的错误。
  • 多源bfs:我们假设一个虚拟起点距离题目中给出的起点距离都为0,从该起点出发bfs遍历,当第一次遍历到目标终点时,这个距离一定是最小的。在进一步,实际代码中可以不设置虚拟起点,而是将所有起点直接加入队列中(理论上等价)。

例题:

题目背景描述
给定一个 N 行 M 列的 01 矩阵 A,A[i][j] 与 A[k][l] 之间的曼哈顿距离定义为:
dist ( A[ i ] [ j ] ,A [ k ] [ l ]) = | i − k | + | j − l |
输出一个 N 行 M 列的整数矩阵 B,其中:
B [ i ] [ j ] = m i n 1 ≤ x ≤ N , 1 ≤ y ≤ M , min_{1≤x≤N,1≤y≤M,} min1xN,1yM,A[x][y]=1dist(A[i][j],A[x][y])
输入格式
第一行两个整数 N,M。
接下来一个 N 行 M 列的 01 矩阵,数字之间没有空格。
输出格式
一个 N 行 M 列的矩阵 B,相邻两个整数之间用一个空格隔开。
数据范围
1≤N,M≤1000
输入样例:
3 4
0001
0011
0110
输出样例:
3 2 1 0
2 1 0 0
1 0 0 1

AC代码:

#include
#include
#include
#include

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;
const int N = 1010;

int n, m;
char s[N][N];
int res[N][N];

void bfs(){
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};

    memset(res, -1, sizeof res);
    queue<PII> q;
    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= m; j ++ )
            if(s[i][j] == '1')
            {
                q.push({i, j});
                res[i][j] = 0;
            }

    int cnt = 0;
    while(q.size()){
        int size = q.size();
        cnt ++ ;
        for(int i = 0; i < size; i ++ ){
            auto t = q.front();
            q.pop();
            for(int j = 0; j < 4; j ++ ){
                int nx = t.x + dx[j], ny = t.y + dy[j];
                if(nx < 0 || ny < 0|| nx > n || ny > m)   continue;
                if(res[nx][ny] != -1)   continue;
                
                res[nx][ny] = cnt;
                q.push({nx, ny});
            }
        }
    }
}

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i ++ )    cin >> s[i] + 1;
    
    bfs();

    for(int i = 1; i <= n; i ++ )
    {
        for(int j = 1; j <= m; j ++ )
            cout << res[i][j] << " ";
        cout << endl;
    }

    return 0;
}

你可能感兴趣的:(搜索,图论,广度搜索,队列)