BFS(广度优先遍历)

BFS(广度优先遍历)

1. BFS原理

原理

  • 使用BFS可以求解最短路径,前提是:所有边的权值均相同

  • 使用队列完成BFS,在遍历的过程中,每次将队首的元素弹出,然后再将该队首相邻的且未被遍历的数据插入队尾,直到队列为空停止。

  • BFS可以解决的问题:

    (1)Floodfill参考网址

    (2)最短路问题

    (3)多源BFS问题

    (4)最小步数问题

    (5)双端队列广搜

    (6)双向广搜

    (7)A*

    这里只讲解后六种类型。

  • BFS求最短路的正确性证明

    • 我们需要证明任意时刻队列中的数据具有:1. 两段性:最多有两段不同的数据;2. 单调性:队列后面的数据一定大于等于前面的数据

      可以使用数学归纳法进行证明:

      (1)刚开始队列中只有一个起点,到自己的距离为0,满足上面两条性质;

      (2)假设某一时刻满足上面两条性质,那么

      在这里插入图片描述

      因此任意时刻队列都具有两段性单调性

    • 证明完上述性质后,我们有两种方法证明BFS是正确的。

      方法1:这里的队列相当于dijstkra算法中的优先队列,因为dijstkra算法是正确的,所以BFS是正确的。

      方法2:使用数学归纳法+反证法证明,归纳假设为:所有已经出队的元素的最小值不会再改变了。

      (1)当起点出队后,起点到自己的距离为0,是最小值,归纳的起点成立;

      (2)假设某一时刻上述归纳假设成立,那么考虑下一时刻:

      在这里插入图片描述

      因此归纳假设成立,所以BFS是正确的。

2. 最短路问题

AcWing 844. 走迷宫

问题描述

分析

  • 在BFS的过程中使用二维数组记录可以到达的位置需要的步数即可

代码

  • C++
#include <iostream>
#include <cstring>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 110, M = N * N;

int n, m;
int g[N][N];
PII q[M];
int d[N][N];  // 记录步数的数组,同时还具有判重作用

int bfs() {
    
    memset(d, -1, sizeof d);
    
    int hh = 0, tt = 0;
    q[0] = {0, 0};
    d[0][0] = 0;
    
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    
    while (hh <= tt) {
        auto t = q[hh++];
        for (int i = 0; i < 4; i++) {
            int a = t.x + dx[i], b = t.y + dy[i];
            if (a >= 0 && a < n && b >= 0 && b < m && d[a][b] == -1 && g[a][b] == 0) {
                q[++tt] = {a, b};
                d[a][b] = d[t.x][t.y] + 1;
            }
        }
    }
    return d[n - 1][m - 1];
}

int main() {
    
    cin >> n >> m;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> g[i][j];
    
    cout << bfs() << endl;
    
    return 0;
}

AcWing 1076. 迷宫问题

问题描述

分析

  • 使用一个二维pre数组,记录到达某点的前一个是什么
  • 为了最终输出从(0, 0)->(n - 1, n - 1)的路径,我们反向遍历即可,即从(n - 1, n - 1)遍历到(0, 0)

代码

  • C++
#include <iostream>
#include <cstring>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1010, M = N * N;

int n;
int g[N][N];
PII q[M];
PII pre[N][N];  // 记录路径,同时还有判重的作用

void bfs(int sx, int sy) {
    
    memset(pre, -1, sizeof pre);
    
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    
    while (hh <= tt) {
        auto t = q[hh++];
        for (int i = 0; i < 4; i++) {
            int a = t.x + dx[i], b = t.y + dy[i];
            if (a < 0 || a >= n || b < 0 || b >= n) continue;  // 出界
            if (g[a][b]) continue;  // 障碍物
            if (pre[a][b].x != -1) continue;  // 已经被遍历过
            
            q[++tt] = {a, b};
            pre[a][b] = t;
        }
    }
}

int main() {
    
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            scanf("%d", &g[i][j]);
    
    bfs(n - 1, n - 1);
    
    PII end(0, 0);
    while (true) {
        printf("%d %d\n", end.x, end.y);
        if (end.x == n - 1 && end.y == n - 1) break;
        end = pre[end.x][end.y];
    }
    
    return 0;
}

AcWing 188. 武士风度的牛

问题描述

分析

  • 使用偏移量技巧,可以很方便的遍历8个方向

在这里插入图片描述

代码

  • C++
#include <iostream>
#include <cstring>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 155, M = N * N;

int n, m;
char g[N][N];
PII q[M];
int dist[N][N];

int bfs() {
    
    int dx[] = {-2, -1, 1, 2, 2, 1, -1, -2};
    int dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
    
    // 寻找起点
    int sx, sy;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            if (g[i][j] == 'K') {
                sx = i, sy = j;
                break;
            }
            
    memset(dist, -1, sizeof dist);
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    dist[sx][sy] = 0;
    
    while (hh <= tt) {
        auto t = q[hh++];
        for (int i = 0; i < 8; i++) {
            int a = t.x + dx[i], b = t.y + dy[i];
            if (a >= 0 && a < n && b >= 0 && b < m && dist[a][b] == -1 && g[a][b] != '*') {
                if (g[a][b] == 'H') return dist[t.x][t.y] + 1;
                q[++tt] = {a, b};
                dist[a][b] = dist[t.x][t.y] + 1;
            }
        }
    }
    return -1;
}

int main() {
    
    cin >> m >> n;
    for (int i = 0; i < n; i++) cin >> g[i];
    
    cout << bfs() << endl;
    
    return 0;
}

AcWing 1100. 抓住那头牛

问题描述

分析

  • 我们可以发现N的变化的过程中不可能变为负数,如果变为负数的话,只有通过N=N+1才能变为正数,花费的时间变长。
  • 如果N>K,我们只能用过-1得到结果,否则我们可以通过三者的组合得到K,并且在过程中数据大小不会超过K+1。
  • 题目是一定有解的,因为最差我们可以挺过+1或者-1得到结果。

代码

  • C++
#include <iostream>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int n, k;
int q[N];
int dist[N];

int bfs() {
    
    memset(dist, -1, sizeof dist);
    dist[n] = 0;
    q[0] = n;
    
    int hh = 0, tt = 0;
    while (hh <= tt) {
        int t = q[hh++];
        
        if (t == k) return dist[k];
        
        if (t + 1 < N && dist[t + 1] == -1) {
            q[++tt] = t + 1;
            dist[t + 1] = dist[t] + 1;
        }
        if (t - 1 >= 0 && dist[t - 1] == -1) {
            q[++tt] = t - 1;
            dist[t - 1] = dist[t] + 1;
        }
        if (t * 2 < N && dist[t * 2] == -1) {
            q[++tt] = t * 2;
            dist[t * 2] = dist[t] + 1;
        }
    }
    return -1;
}

int main() {
    
    cin >> n >> k;
    
    cout << bfs() << endl;
    
    return 0;
}

3. 多源BFS

AcWing 173. 矩阵距离

问题描述

分析

  • 本题中的1相当于超市,0相当于住户,需要求解每个住户距离最近的一个超市的距离。

在这里插入图片描述

  • 1相当于超市,也是上图中的起点。代码实现的时候并不需要创建虚拟源点,只需要在最开始的时候将所有1的位置加入到队列中即可,然后执行BFS得到结果。

代码

  • C++
#include <iostream>
#include <cstring>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1010, M = N * N;

int n, m;
char g[N][N];
PII q[M];
int dist[N][N];

void bfs() {
    
    memset(dist, -1, sizeof dist);
    
    // 起点放入队列
    int hh = 0, tt = -1;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            if (g[i][j] == '1') {
                dist[i][j] = 0;
                q[++tt] = {i, j};
            }
    
    int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
    
    while (hh <= tt) {
        auto t = q[hh++];
        for (int i = 0; i < 4; i++) {
            int a = t.x + dx[i], b = t.y + dy[i];
            if (a < 1 || a > n || b < 1 || b > m) continue;
            if (dist[a][b] != -1) continue;
            
            dist[a][b] = dist[t.x][t.y] + 1;
            q[++tt] = {a, b};
        }
    }
}

int main() {
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%s", g[i] + 1);
    
    bfs();
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) printf("%d ", dist[i][j]);
        puts("");
    }
    
    return 0;
}

4. 最小步数模型

之前的问题都是在一个二维数组中操作,从二维数组中的某个点到达数组中的另外一个点。

下面的两个问题中每个二维数组可以看成一种状态,类似于棋盘,每操作一步转换成一种状态。

AcWing 845. 八数码

问题描述

分析

在这里插入图片描述

代码

  • C++
#include <iostream>
#include <queue>
#include <unordered_map>

using namespace std;

int bfs(string start) {
    
    queue<string> q;
    unordered_map<string, int> d;
    
    q.push(start);
    d[start] = 0;
    
    int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
    
    string end = "12345678x";
    while (q.size()) {
        string t = q.front(); q.pop();
        
        if (t == end) return d[t];
        
        int distance = d[t];
        int k = t.find('x');
        int x = k / 3, y = k % 3;  // 一维坐标转换成二维坐标
        for (int i = 0; i < 4; i++) {
            int a = x + dx[i], b = y + dy[i];
            if (a >= 0 && a < 3 && b >= 0 && b < 3) {
                swap(t[a * 3 + b], t[k]);  // 二维坐标转换成一维坐标
                if (!d.count(t)) {
                    d[t] = distance + 1;
                    q.push(t);
                }
                swap(t[a * 3 + b], t[k]);
            }
        }
    }
    return -1;
}

int main() {
    
    char s[2];
    
    string start;
    for (int i = 0; i < 9; i++) {
        cin >> s;
        start += *s;
    }
    
    cout << bfs(start) << endl;
    
    return 0;
}

AcWing 1107. 魔板

问题描述

分析

  • 思路:首先将"12345678"放到队列的队头,然后用宽搜搜索可以到达的状态,直到搜到终点为止。

  • 每一次扩展的时候分别做一下A、B、C三种操作,做完之后得到一个字符串,然后判断这个字符串是否被搜到过,如果没有搜到过的话,把它的距离更新一下,然后存到队列中。

  • 因为要输出方案,我们记录一下每个状态是从哪个状态更新过来的即可。

  • 如何保证存在多个方案时,输出字典序最小的方案呢?我们在扩展的时候按照A、B、C三种方案进行扩展即可。可以使用数学归纳法证明:任意时刻按照A、B、C三种方案进行扩展得到的序列字典序是最小的。

    (1)刚开始序列为空,归纳假设显然成立。

    (2)假设某一时刻上述归纳假设成立,那么考虑下一时刻:

    在这里插入图片描述

代码

  • C++
#include <iostream>
#include <queue>
#include <unordered_map>
#include <algorithm>

using namespace std;

char g[2][4];  // 盘面
// pre[s2] = {'A', s1}  表示:s1变到s2是通过A方案实现的
unordered_map<string, pair<char, string>> pre;
unordered_map<string, int> dist;

// 将string转化为盘面
void set(string state) {
    for (int i = 0; i < 4; i++) g[0][i] = state[i];
    for (int i = 7, j = 0; j < 4; i--, j++) g[1][j] = state[i];
}

// 将盘面转化为string
string get() {
    string res;
    for (int i = 0; i < 4; i++) res += g[0][i];
    for (int i = 3; i >= 0; i--) res += g[1][i];
    return res;
}

string move0(string state) {
    set(state);
    for (int i = 0; i < 4; i++) swap(g[0][i], g[1][i]);
    return get();
}

string move1(string state) {
    set(state);
    int v0 = g[0][3], v1 = g[1][3];
    for (int i = 3; i > 0; i--) {
        g[0][i] = g[0][i - 1];
        g[1][i] = g[1][i - 1];
    }
    g[0][0] = v0, g[1][0] = v1;
    return get();
}

string move2(string state) {
    set(state);
    int v = g[0][1];
    g[0][1] = g[1][1];
    g[1][1] = g[1][2];
    g[1][2] = g[0][2];
    g[0][2] = v;
    return get();
}

int bfs(string start, string end) {
    
    if (start == end) return 0;
    
    queue<string> q;
    q.push(start);
    dist[start] = 0;
    
    while (!q.empty()) {
        auto t = q.front(); q.pop();
        
        string m[3];
        m[0] = move0(t);  // 'A'
        m[1] = move1(t);  // 'B'
        m[2] = move2(t);  // 'C'
        
        for (int i = 0; i < 3; i++) 
            if (!dist.count(m[i])) {
                dist[m[i]] = dist[t] + 1;
                pre[m[i]] = {i + 'A', t};
                q.push(m[i]);
                if (m[i] == end) return dist[end];
            }
    }
    return -1;
}


int main() {
    
    int x;
    string start, end;
    for (int i = 0; i < 8; i++) {
        cin >> x;
        end += char(x + '0');
    }
    for (int i = 1; i <= 8; i++) start += char('0' + i);
    
    int step = bfs(start, end);
    
    cout << step << endl;
    
    string res;
    while (end != start) {
        res += pre[end].first;
        end = pre[end].second;
    }
    
    reverse(res.begin(), res.end());
    
    if (step > 0) cout << res << endl;
    
    return 0;
}

5. 双端队列广搜

AcWing 175. 电路维修

问题描述

分析

在这里插入图片描述

代码

  • C++
// 非常类似于dijskra
#include <iostream>
#include <cstring>
#include <deque>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 550;

int n, m;  // 方格的数量为n * m
char g[N][N];  // 存储每个方格内的数据
int dist[N][N];  // 到达每个格点需要的步数
bool st[N][N];  // 判重数组,判断该点是否已求出结果

int bfs() {
    
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);
    dist[0][0] = 0;
    
    deque<PII> q;
    q.push_back({0, 0});
    
    char cs[] = "\\/\\/";  // 与格点导通的四种情况
    int dx[] = {-1, -1, 1, 1}, dy[] = {-1, 1, 1, -1};  // 相邻的四个格点
    int ix[] = {-1, -1, 0, 0}, iy[] = {-1, 0, 0, -1};  // 与当前格点相邻的四个方格
    
    while (q.size()) {
        
        PII t = q.front(); q.pop_front();
        
        if (st[t.x][t.y]) continue;  // 当前点已经求出最短距离
        st[t.x][t.y] = true;
        
        for (int i = 0; i < 4; i++) {
            int a = t.x + dx[i], b = t.y + dy[i];
            if (a < 0 || a > n || b < 0 || b > m) continue;
            
            int ca = t.x + ix[i], cb = t.y + iy[i];  // 考察与当前格点相邻的第i个方格
            int w = (g[ca][cb] != cs[i]);  // 不能导通,需要旋转一次
            int d = dist[t.x][t.y] + w;
            
            // 因为某个格点可能入队多次,需要更新dist[a][b],更新后的数据需要入队
            if (d < dist[a][b]) {
                dist[a][b] = d;
                if (w) q.push_back({a, b});
                else q.push_front({a, b});
            }
        }
    }
    
    return dist[n][m];
}

int main() {
    
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        for (int i = 0; i < n; i++) scanf("%s", g[i]);
        
        if ((n + m) & 1) puts("NO SOLUTION");
        else printf("%d\n", bfs());
    }
    
    return 0;
}

6. 双向广搜

AcWing 190. 字串变换

问题描述

分析

  • 使用两个队列从起点和终点同时扩展,每次选择状态比较少的一端进行扩展,这样可以大大降低需要搜索的空间。
  • 注意:每次必须将一层的点全部扩展,否则会出现问题

在这里插入图片描述

上图来自:题解

代码

  • C++
#include <iostream>
#include <queue>
#include <unordered_map>

using namespace std;

const int N = 6;

int n;  // 规则的数目
string a[N], b[N];  // 变换规则,从a变为b

// q: 需要扩展的队列; da: 扩展的新状态距离存储的位置
// db: 检查新状态另一侧是否已经扩展到了
int extend(queue<string> &q, unordered_map<string, int> &da, 
           unordered_map<string, int> &db, string a[], string b[]) {
    for (int k = 0, sk = q.size(); k < sk; k++) {
        string t = q.front();
        q.pop();
        
        // 针对每一个字符串进行扩展
        for (int i = 0; i < t.size(); i++)
            for (int j = 0; j < n; j++)
                if (t.substr(i, a[j].size()) == a[j]) {
                    string state = t.substr(0, i) + b[j] + t.substr(i + a[j].size());
                    if (da.count(state)) continue;
                    if (db.count(state)) return da[t] + 1 + db[state];
                    da[state] = da[t] + 1;
                    q.push(state);
                }
    }
    return 11;
}

int bfs(string A, string B) {
    
    queue<string> qa, qb;  // qa从起点开始扩展,qb从终点开始扩展
    unordered_map<string, int> da, db;  // 记录扩展步数,同时还有判重的作用
    qa.push(A), da[A] = 0;
    qb.push(B), db[B] = 0;
    
    while (qa.size() && qb.size()) {
        int t;
        if (qa.size() <= qb.size()) t = extend(qa, da, db, a, b);
        else t = extend(qb, db, da, b, a);
        // 如果t>10,存在两种情况: (1) 还没扩展完毕; (2) 实际步数确实大于10
        if (t <= 10) return t;
    }
    return 11;
}

int main() {
    
    string A, B;
    cin >> A >> B;
    while (cin >> a[n] >> b[n]) n++;
    
    int step = bfs(A, B);
    
    if (step > 10) puts("NO ANSWER!");
    else cout << step << endl;
    
    return 0;
}

7. A*

A*算法原理

在这里插入图片描述

AcWing 178. 第K短路

问题描述

分析

  • 这一题的估价函数:每个点到终点的最短距离。这个可以反向求解,只需要在反向图上跑一边dijkstra算法即可。

  • 那么如何求解第K短路呢?

    当终点第一次从队列中弹出来的时候,一定是最短路径(上面A*算法中已经证明了)。因此可以猜想,当终点从队列中第K次弹出来的时候,就是第K短路径,这个猜想的证明思路是类似的,只需要证明第二次弹出的是第二小的即可,也可以使用反证法,将上面A*算法证明中的最小值换成第二小值即可。

代码

  • C++
#include <iostream>
#include <queue>
#include <cstring>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;
typedef pair<int, PII> PIII;

const int N = 1010, M = 200010;

int n, m, S, T, K;
// 正向邻接表表头,反向邻接表表头,边,边权,next指针
int h[N], rh[N], e[M], w[M], ne[M], idx;
int dist[N];  // 记录反向表中终点到其他各个点的距离,估价函数的作用
int cnt[N];  // 记录每个点在队列中出现的次数
bool st[N];  // dijkstra过程中的判重数组

void add(int h[], int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

// 在反向图中运行dijkstra,求出终点到其余各点的最短路径
void dijkstra() {
    
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, T});  // (距离T的距离,当前点)
    
    memset(dist, 0x3f, sizeof dist);
    dist[T] = 0;
    
    while (heap.size()) {
        auto t = heap.top(); heap.pop();
        
        int ver = t.y;
        // BFS入队的时候就可以确定该点的最短路径
        // dijkstra在出队的时候可以确定该点的最短路径
        // astar只有终点在出队的时候可以确定该点最短路径
        if (st[ver]) continue;
        st[ver] = true;
        
        for (int i = rh[ver]; ~i; i = ne[i]) {
            int j = e[i];  // ver->j, 权重为w[i]
            if (dist[j] > dist[ver] + w[i]) {
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j], j});
            }
        }
    }
}

int astar() {
    
    priority_queue<PIII, vector<PIII>, greater<PIII>> heap;
    // (当前点v到起点的真实值+v到终点的估计值, (v到起点的真实值, v))
    heap.push({dist[S], {0, S}});
    
    while (heap.size()) {
        auto t = heap.top(); heap.pop();
        
        int ver = t.y.y, distance = t.y.x;
        cnt[ver]++;
        if (cnt[T] == K) return distance;
        
        // 扩展ver的相邻顶点,只要点在队列中的次数小于K,就要加入队列
        for (int i = h[ver]; ~i; i = ne[i]) {
            int j = e[i];
            if (cnt[j] < K) heap.push({distance + w[i] + dist[j], {distance + w[i], j}});
        }
    }
    
    return -1;
}

int main() {
    
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    memset(rh, -1, sizeof rh);
    
    for (int i = 0; i < m; i++) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(h, a, b, c);
        add(rh, b, a, c);
    }
    scanf("%d%d%d", &S, &T, &K);
    if (S == T) K++;  // 每条最短路中至少要包含一条边
    
    dijkstra();
    printf("%d\n", astar());
    
    return 0;
}

AcWing 179. 八数码

问题描述

分析

  • 八数码问题有解 等价于 按行展开后逆序对的数量为偶数
  • 这里的估价函数取为所有点到其最终位置的曼哈顿距离之和,这个值一定是小于最优解的。

代码

  • C++
#include <iostream>
#include <queue>
#include <unordered_map>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<int, string> PIS;  // (到终点的估计值, 当前状态)

// 估计函数
int f(string state) {
    int res = 0;
    for (int i = 0; i <= 8; i++)
        if (state[i] != 'x') {
            // 左上角的点对应应该防止1,这里方便计算从0开始计算
            // 放置在位置i上的数字目前为v
            int v = state[i] - '1';
            res += abs(i / 3 - v / 3) + abs(i % 3 - v % 3);
        }
    return res;
}

string bfs(string start) {
    
    string end = "12345678x";
    unordered_map<string, int> dist;
    // pre[s2] = {'u', s1}表示: s1通过操作u得到s2
    unordered_map<string, pair<char, string>> pre;
    priority_queue<PIS, vector<PIS>, greater<PIS>> heap;
    
    heap.push({f(start), start});
    dist[start] = 0;
    
    int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
    char op[5] = "urdl";  // 上右下左
    
    while (heap.size()) {
        auto t = heap.top(); heap.pop();
        
        string state = t.y;
        
        if (state == end) break;
        
        // 找到'x'所在位置
        int x, y;
        for (int i = 0; i < 9; i++)
            if (state[i] == 'x') {
                x = i / 3, y = i % 3;
                break;
            }
        
        string source = state;
        for (int i = 0; i < 4; i++) {
            int a = x + dx[i], b = y + dy[i];
            if (a < 0 || a >= 3 || b < 0 || b >= 3) continue;
            state = source;
            swap(state[x * 3 + y], state[a * 3 + b]);
            if (dist.count(state) == 0 || dist[state] > dist[source] + 1) {
                dist[state] = dist[source] + 1;
                pre[state] = {op[i], source};
                heap.push({dist[state] + f(state), state});
            }
        }
    }
    
    string res;
    while (end != start) {
        res += pre[end].x;
        end = pre[end].y;
    }
    reverse(res.begin(), res.end());
    
    return res;
}

int main() {
    
    string start, seq;
    char c;
    while (cin >> c) {
        start += c;
        if (c != 'x') seq += c;
    }
    
    int cnt = 0;
    for (int i = 0; i < 8; i++)
        for (int j = i + 1; j < 8; j++)
            if (seq[i] > seq[j])
                cnt++;
    
    if (cnt % 2) puts("unsolvable");
    else cout << bfs(start) << endl;
    
    return 0;
}

3. 力扣上的BFS题目

Leetcode 0127 单词接龙

问题描述

分析

  • 对于每个单词可以变成哪些单词,直接枚举即可。

代码

  • C++
/**
 * 执行用时:160 ms, 在所有 C++ 提交中击败了59.48%的用户
 * 内存消耗:15.7 MB, 在所有 C++ 提交中击败了34.35%的用户
 */
class Solution {
public:
    unordered_set<string> S;
    unordered_map<string, int> dist;  // 距离数组,同时具有判重的作用
    queue<string> q;

    int ladderLength(string beginWord, string endWord, vector<string> &wordList) {

        for (auto word : wordList) S.insert(word);
        dist[beginWord] = 0;
        q.push(beginWord);
        while (q.size()) {
            auto t = q.front();
            q.pop();

            string r = t;
            for (int i = 0; i < t.size(); i++) {
                t = r;
                for (char j = 'a'; j <= 'z'; j++) {
                    t[i] = j;
                    if (S.count(t) && dist.count(t) == 0) {
                        dist[t] = dist[r] + 1;
                        if (t == endWord) break;
                        q.push(t);
                    }
                }
            }
        }
        return dist[endWord] != 0 ? dist[endWord] + 1 : 0;
    }
};
  • Java
/**
 * Date: 2021/3/18 10:01
 * 执行用时:283 ms, 在所有 Java 提交中击败了35.26%的用户
 * 内存消耗:42.1 MB, 在所有 Java 提交中击败了18.79%的用户
 */
class Solution {

    HashSet<String> S = new HashSet<>();
    HashMap<String, Integer> dist = new HashMap<>();
    Queue<String> q = new LinkedList<>();

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {

        S.addAll(wordList);
        q.add(beginWord);
        dist.put(beginWord, 0);
        while (!q.isEmpty()) {
            String t = q.remove();

            for (int i = 0; i < t.length(); i++)
                for (char j = 'a'; j <= 'z'; j++) {
                    String r = t.substring(0, i) + j + t.substring(i + 1);
                    if (S.contains(r) && !dist.containsKey(r)) {
                        dist.put(r, dist.get(t) + 1);
                        if (endWord.equals(r)) break;
                        q.add(r);
                    }
                }
        }
        return dist.getOrDefault(endWord, -1) + 1;
    }
}

Leetcode 0126 单词接龙 II

问题描述

分析

  • 分为两步:(1) 正向bfs,求出起点到其他点的最短距离,用于剪枝 (2) 反向暴搜

代码

  • C++
/**
 * 执行用时:144 ms, 在所有 C++ 提交中击败了76.80%的用户
 * 内存消耗:15.5 MB, 在所有 C++ 提交中击败了75.43%的用户
 */
class Solution {
public:
    unordered_set<string> S;
    unordered_map<string, int> dist;  // 距离数组,同时具有判重的作用
    queue<string> q;
    vector<vector<string>> ans;
    vector<string> path;
    string beginWord;

    vector<vector<string>> findLadders(string _beginWord, string endWord, vector<string> &wordList) {

        for (auto word : wordList) S.insert(word);
        beginWord = _beginWord;
        dist[beginWord] = 0;
        q.push(beginWord);
        while (q.size()) {
            auto t = q.front();
            q.pop();

            string r = t;
            for (int i = 0; i < t.size(); i++) {
                t = r;
                for (char j = 'a'; j <= 'z'; j++) {
                    t[i] = j;
                    if (S.count(t) && dist.count(t) == 0) {
                        dist[t] = dist[r] + 1;
                        if (t == endWord) break;
                        q.push(t);
                    }
                }
            }
        }

        if (!dist.count(endWord)) return ans;
        path.push_back(endWord);
        dfs(endWord);
        return ans;
    }

    void dfs(string t) {
        if (t == beginWord) {
            reverse(path.begin(), path.end());
            ans.push_back(path);
            reverse(path.begin(), path.end());
        } else {
            string r = t;
            for (int i = 0; i < t.size(); i++) {
                t = r;
                for (char j = 'a'; j <= 'z'; j++) {
                    t[i] = j;
                    // 不能使用S.count(t), 因为起点不一定在S中
                    // dist[t] 表示t到起点的距离, 这里进行了剪枝
                    if (dist.count(t) && dist[t] + 1 == dist[r]) {
                        path.push_back(t);
                        dfs(t);
                        path.pop_back();
                    }
                }
            }
        }
    }
};
  • Java
/**
 * Created by WXX on 2021/3/18 9:29
 * 执行用时:254 ms, 在所有 Java 提交中击败了58.92%的用户
 * 内存消耗:42.3 MB, 在所有 Java 提交中击败了78.18%的用户
 */
public class Solution {
    HashSet<String> S = new HashSet<>();
    HashMap<String, Integer> dist = new HashMap<>();
    Queue<String> q = new LinkedList<>();
    List<List<String>> ans = new ArrayList<>();
    ArrayList<String> path = new ArrayList<>();
    String beginWord;

    public List<List<String>> findLadders(String _beginWord, String endWord, List<String> wordList) {

        S.addAll(wordList);
        beginWord = _beginWord;
        q.add(beginWord);
        dist.put(beginWord, 0);
        while (!q.isEmpty()) {
            String t = q.remove();

            for (int i = 0; i < t.length(); i++)
                for (char j = 'a'; j <= 'z'; j++) {
                    String r = t.substring(0, i) + j + t.substring(i + 1);
                    if (S.contains(r) && !dist.containsKey(r)) {
                        dist.put(r, dist.get(t) + 1);
                        if (endWord.equals(r)) break;
                        q.add(r);
                    }
                }
        }

        if (!dist.containsKey(endWord)) return ans;
        path.add(endWord);
        dfs(endWord);
        return ans;
    }

    private void dfs(String t) {
        if (beginWord.equals(t)) {
            Collections.reverse(path);
            ans.add((List<String>) path.clone());
            Collections.reverse(path);
        } else {
            for (int i = 0; i < t.length(); i++)
                for (char j = 'a'; j <= 'z'; j++) {
                    String r = t.substring(0, i) + j + t.substring(i + 1);
                    if (dist.containsKey(r) && dist.get(r) + 1 == dist.get(t)) {
                        path.add(r);
                        dfs(r);
                        path.remove(path.size() - 1);
                    }
                }
        }
    }
}

Leetcode 0675 为高尔夫比赛砍树

问题描述

分析

  • 将树从小到大排序。然后求两点之间的最短路径,有多少棵树求多少次bfs。

代码

  • C++
/**
 * 执行用时:892 ms, 在所有 C++ 提交中击败了33.96%的用户
 * 内存消耗:174.7 MB, 在所有 C++ 提交中击败了10.38%的用户
 */
class Solution {
public:
    struct Tree {
        int x, y, h;  // 位于(x, y)的树的高度为h
        bool operator<(const Tree &t) const {
            return h < t.h;
        }
    };

    int n, m;
    vector<vector<int>> g;

    int bfs(Tree st, Tree ed) {
        if (st.x == ed.x && st.y == ed.y) return 0;  // 起点终点都是出发点
        queue<Tree> q;
        vector<vector<int>> dist(n, vector<int>(m, -1));
        dist[st.x][st.y] = 0;
        q.push(st);
        int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
        while (q.size()) {
            auto t = q.front();
            q.pop();
            for (int i = 0; i < 4; i++) {
                int x = t.x + dx[i], y = t.y + dy[i];
                if (x >= 0 && x < n && y >= 0 && y < m && g[x][y]) {
                    if (dist[x][y] == -1) {  // 没有判重数组,必须有这句话
                        dist[x][y] = dist[t.x][t.y] + 1;
                        if (x == ed.x && y == ed.y) return dist[x][y];
                        q.push({x, y});
                    }
                }
            }
        }
        return -1;
    }

    int cutOffTree(vector<vector<int>> &forest) {
        g = forest;
        n = g.size(), m = g[0].size();
        vector<Tree> trs;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                if (g[i][j] > 1)
                    trs.push_back({i, j, g[i][j]});
        sort(trs.begin(), trs.end());
        Tree last = {0, 0};
        int res = 0;
        for (auto &tr : trs) {
            int t = bfs(last, tr);  // last -> tr
            if (t == -1) return -1;
            res += t;
            last = tr;  // 更新last
        }
        return res;
    }
};
  • Java
/**
 * Created by WXX on 2021/3/7 18:51
 * 执行用时:242 ms, 在所有 Java 提交中击败了96.69%的用户
 * 内存消耗:38.7 MB, 在所有 Java 提交中击败了87.60%的用户
 */
public class Solution {
    static class Tree implements Comparable<Tree> {
        int x, y, h;
        
        public Tree(int x, int y, int h) {
            this.x = x;
            this.y = y;
            this.h = h;
        }

        @Override
        public int compareTo(Tree o) {
            return this.h - o.h;
        }
    }

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

    private int bfs(Tree st, Tree ed) {
        if (st.x == ed.x && st.y == ed.y) return 0;
        Queue<Tree> q = new LinkedList<>();
        int[][] dist = new int[n][m];
        for (int i = 0; i < n; i++) Arrays.fill(dist[i], 0x3f3f3f3f);
        dist[st.x][st.y] = 0;
        q.add(st);
        int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
        while (!q.isEmpty()) {
            Tree t = q.remove();
            for (int i = 0; i < 4; i++) {
                int x = t.x + dx[i], y = t.y + dy[i];
                if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] != 0) {
                    if (dist[x][y] > dist[t.x][t.y] + 1) {
                        dist[x][y] = dist[t.x][t.y] + 1;
                        if (x == ed.x && y == ed.y) return dist[x][y];
                        q.add(new Tree(x, y, -1));
                    }
                }
            }
        }
        return -1;
    }

    public int cutOffTree(List<List<Integer>> forest) {
        n = forest.size();
        m = forest.get(0).size();
        g = new int[n][m];
        List<Tree> trs = new ArrayList<>();
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++) {
                g[i][j] = forest.get(i).get(j);
                if (g[i][j] > 1) trs.add(new Tree(i, j, g[i][j]));
            }
        Collections.sort(trs);
        Tree last = new Tree(0, 0, -1);
        int res = 0;
        for (Tree tr : trs) {
            int t = bfs(last, tr);
            if (t == -1) return -1;
            res += t;
            last = tr;
        }
        return res;
    }
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值