广度优先搜索(BFS)

· · 算法·理论

引言:

BFS,全名为广度优先搜索(Breadth-First Search),是一种用于图或树的遍历或搜索的算法。它的主要思想是由节点自身开始向它的邻节点新进展开搜索,因此也常被形象地称为“层序遍历”。

BFS 的基本概念

BFS的工作原理是,从开始节点开始,在访问节点的邻居节点之前,我们先访问其它节点。换句话说,我们旧基于当前层次来遍历节点,然后移至下一层再遍历节点。

BFS是以一种队列(Queue)结构的方式运行的,首先我们有一个包含开始节点的队列,然后:

通过以上步骤将会发现,在访问节点的时候,首先访问的是距离开始节点最近的节点(层次最低的节点),然后层次逐渐提升,这就是 BFS 的特性。

BFS 伪代码模板

求最少步数

int bfs() {
    queue<nn> q;
    q.push({ax, ay, 0});
    vis[ax][ay] = 1;
    while (!q.empty()) {
        nn u = q.front();
        q.pop();
        for (int i = 0; i < 4; i++) {
            int nx = u.x + dx[i];
            int ny = u.y + dy[i];
            if (nx >= 1 && nx <= n && ny >= 1 && ny <= m && !vis[nx][ny]) {
                q.push({nx, ny, u.step + 1});
                vis[nx][ny] = 1;
                if (nx == bx && ny == by) {
                    return u.step + 1;
                }
            }
        }
    }
    return -1;
}

打印最短路径

void bfs(node s) {
    queue<node> q;
    q.push(s);
    vis[s.x][s.y] = true;
    while (!q.empty()) {
        node t = q.front();
        q.pop();
        for (int i = 0; i < 4; i++) {
            int newX = t.x + X[i];
            int newY = t.y + Y[i];
            if (judge(newX, newY)) {
                pre[newX][newY] = t;
                temp.x = newX;
                temp.y = newY;
                q.push(temp);
                vis[newX][newY] = true;
            }
        }
    }
}

传送门

有点长,不展示

BFS 经典题型

1. 求最少步数:D1227 仙岛求药

根据广搜的特点,从起点开始,第一次搜索到点a_{i,j}所用的步数 就是从起点开始到点a_{i,j}的最小步数

所以可以直接从起点搜索一遍。在搜索过程中,如果当前点是终点,则直接输出从起点到当前点所用的步数。

代码:

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

int n, m;
char c[50][50];
bool vis[50][50];
int ax, ay, bx, by;
struct nn {
    int x, y, sp;
};
int dx[] = {0, 0, -1, 1};
int dy[] = {1, -1, 0, 0};

int bfs() {
    queue<nn> q;
    q.push({ax, ay, 1});
    vis[ax][ay] = 1;
    while (!q.empty()) {
        nn u = q.front();
        q.pop();
        for (int i = 0; i < 4; i++) {
            int nx = dx[i] + u.x;
            int ny = u.y + dy[i];
            if (nx && ny && nx <= n && ny <= m && !vis[nx][ny]) {
                q.push({nx, ny, u.sp + 1});
                vis[nx][ny] = 1;
                if (nx == bx && ny == by) {
                    return u.sp;
                }
            }
        }
    }
    return -1;
}

int main() {
    while (1) {
        memset(vis, 0, sizeof vis);
        cin >> n >> m;
        if (n == 0 && m == 0) break;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                cin >> c[i][j];
                if (c[i][j] == '#') {
                    vis[i][j] = 1;
                }
                if (c[i][j] == '@') {
                    ax = i, ay = j;
                }
                if (c[i][j] == '*') {
                    bx = i, by = j;
                }
            }
        }
        cout << bfs() << endl;
    }
    return 0;
}

2. 打印最短路径:迷宫问题

对于简单的问最短路径长度的问题。我们可以通过在表示位置的结构体中加上一个step变量,在BFS的时候逐步更新step,当到达终点的时候返回最终的step即可。

而对于要打印路径问题,就需要额外的开一个数组来存放当前结点的前驱结点的信息

用一个二维数组表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。数据保证有唯一解
输入:(一个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
输出:
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)

开一个结构体数组来保存当前结点的前驱结点nn pre[_][_]。这样在每次BFS操作的时候,对于满足条件的结点,将其前驱结点保存在pre[nowX][nowY]中。最后通过递归就可以得到答案。

代码:

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

const int n = 6;
int Maze[n][n];
bool vis[n][n];
struct nn {
    int x, y;
} temp;
nn pre[n][n];
int X[] = {0, 0, -1, 1};
int Y[] = {-1, 1, 0, 0};

bool judge(int x, int y) {
    if (x < 0 || x > 4 || y < 0 || y > 4)return false;
    if (Maze[x][y] == 1) return false;
    if (vis[x][y] == true) return false;
    return true;
}

void BFS(nn s) {
    queue<nn>q;
    q.push(s);
    vis[s.x][s.y] = true;
    while (!q.empty()) {
        nn t = q.front();
        q.pop();
        for (int i = 0; i < 4; i++) {
            int newX = t.x + X[i];
            int newY = t.y + Y[i];
            if (judge(newX, newY)) {
                pre[newX][newY] = t;
                temp.x = newX;
                temp.y = newY;
                q.push(temp);
                vis[newX][newY] = true;
            }
        }

    }
}

void jj(int x, int y) {
    if (x == 0 && y == 0) {
        printf("(%d, %d)\n", x, y);
        return;
    }
    temp = pre[x][y];
    jj(temp.x, temp.y);
    printf("(%d, %d)\n", x, y);
}

int main() {
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            scanf("%d", &Maze[i][j]);
        }
    }
    temp.x = temp.y = 0;
    BFS(temp);
    jj(4, 4);
    return 0;
}

3. 传送门类型:[USACO11OPEN] Corn Maze S

思路

如果将要被加入队列的位置是传送门,则将不加入传送门的起点拉入队列,而是将传送门的终点拉入队列

因为传送门可能再次使用,所以不标记传送门

代码:

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

const int _ = 305;
int n, m;
int ax, ay, bx, by;
char c[_][_];
bool vis[_][_];
int dx[] = {0, 0, -1, 1};
int dy[] = {1, -1, 0, 0};
struct nn {
    int x, y;
    int sp;
};

bool check(int x, int y) {
    return x >= 1 && x <= n&&y >= 1 && y <= m&&!vis[x][y];
}

int bfs() {
    queue<nn> q;
    q.push(nn{ax, ay, 0});
    vis[ax][ay] = 1;
    while (!q.empty()) {
        nn u = q.front();
        q.pop();
        for (int i = 0; i < 4; i++) {
            int nx = u.x + dx[i];
            int ny = u.y + dy[i];
            if (check(nx, ny)) {
                int xx = 0, yy = 0;
                if (c[nx][ny] >= 'A' && c[nx][ny] <= 'Z') {
                    for (int i = 1; i <= n; i++) {
                        for (int j = 1; j <= m; j++) {
                            if (c[i][j] == c[nx][ny] && (i != nx || j != ny)) {
                                xx = i, yy = j;
                            }
                        }
                    }
                    q.push(nn{xx, yy, u.sp + 1});
                } else {
                    q.push(nn{nx, ny, u.sp + 1});
                    vis[nx][ny] = 1;
                }
                if (nx == bx && ny == by) {
                    return u.sp + 1;
                }
            }
        }
    }
    return -1;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> c[i][j];
            if (c[i][j] == '#') {
                vis[i][j] = 1;
            }
            if (c[i][j] == '@') {
                ax = i, ay = j;
            }
            if (c[i][j] == '=') {
                bx = i, by = j;
            }
        }
    }
    cout << bfs();
    return 0;
}