搜索之BFS

BFS

宽度优先搜索: 对每个搜索到的结点向外扩展,每次搜索一层的子节点.
特点: 对于边权相等的图,可以求最短路.

BFS基本模板:

void bfs()
{
    queue<int> q;
    st[1] = 1;      //从一号点开始
    q.push(1);

    while (q.size()){

        int u = q.front();
        q.pop();

        for (int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];

            if (!st[j]){
                st[j] = 1;
                q.push(j);
            }

        }
    }    
}

BFS基本应用:

  • Flood Fill(洪水灌溉模型求解联通问题)

  • 最短路模型

  • 最小步数模型

  • 多源BFS

  • 双端队列广搜

BFS的优化

BFS 相对于 DFS 优化较少
  • 双向广搜

  • A*

Flood Fill 模型

AcWing 1097. 池塘计数

算法思路:

Flood Fill 模板题,遍历一遍所有结点,每次没有询问的结点,进行BFS, 每次BFS会找到一个联通块

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

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

char g[N][N];
int n,m;
int st[N][N];
int res = 0;
int dx[8] = {1,0,-1,0,1,-1,1,-1};
int dy[8] = {0,-1,0,1,-1,1,1,-1};


void bfs(int x, int y)
{
    
    queue<PII> q;
    q.push({x,y});
    st[x][y] = 1;
    
    while (q.size()){
        auto it = q.front();
        q.pop();
        
        
        for (int i = 0; i < 8; i ++ ){
            int xx = it.first + dx[i];
            int yy = it.second + dy[i];
            if (xx >= 1 && xx <= n && yy >= 1 && yy <= m && !st[xx][yy] && g[xx][yy] == 'W'){
                q.push({xx,yy});
                st[xx][yy] = 1;
            }
        }
    }
    
}


int main()
{
    cin >> n >> m;
    char c = getchar();
    for (int i = 1; i <= n; i ++ )
        scanf("%s",g[i] + 1);
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            if (!st[i][j] && g[i][j] == 'W'){
                bfs(i, j);
                res ++;
            }
    
    cout << res << endl;
    
    return 0;
}

AcWing 1098. 城堡问题

算法思路:

同样是Flood Fill 模板题,要注意的是对每个点的处理.
使用二进制处理每个点, \(1 -> 2 ^ 0 -> 1\), \(2 -> 2 ^ 1 -> 10\), \(4 -> 2 ^ 2 -> 100\), \(8 -> 2 ^ 3 -> 1000\).
使用 1 << i & k 判断对应二进制位是否为0

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;
typedef pair<int,int> PII;
const int N = 60;
int mp[N][N];
int st[N][N];
int dx[4] = {0,-1,0,1};
int dy[4] = {-1,0,1,0};
int n,m;

int bfs(int x, int y)
{
    int res = 1;
    queue<PII> q;
    q.push({x,y});
    st[x][y] = 1;
    
    while (q.size()){
        auto it = q.front();
        q.pop();
        
        int t = mp[it.first][it.second];
        for (int i = 0; i < 4; i ++ ){
            int xx = it.first + dx[i];
            int yy = it.second + dy[i];
            if (!(1 << i & t) && xx >= 1 && xx <= n && yy >= 1 && yy <= m && !st[xx][yy]){
                q.push({xx,yy});
                st[xx][yy] = 1;
                res ++;
            }
        }
    }
    return res;
}

int main()
{
    int res = 0;
    int ans = 0;
    
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            cin >> mp[i][j];
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            if (!st[i][j]){
                ans = max(ans,bfs(i,j));
                res ++;
            }
            
    cout << res << endl << ans << endl;
    return 0;
}

AcWing 1106. 山峰和山谷

算法思路:

题目涉及Flood Fill 的同时处理联通块之间的关系(山峰与山谷), 在每次BFS向外扩展时, 当遇到联通块以外的结点,处理结点之间的关系.

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;
typedef pair<int,int> PII;
const int N = 1010;
int mp[N][N];
int st[N][N];
int n;
int higher;
int lower;
int dx[8] = {1,0,-1,0,1,-1,1,-1};
int dy[8] = {0,1,0,-1,1,-1,-1,1};

void bfs(int x, int y)
{
    queue<PII> q;
    q.push({x,y});
    st[x][y] = 1;
    
    while (q.size()){
        auto it = q.front();
        q.pop();
        
        for (int i = 0; i < 8; i ++ ){
            int xx = it.first + dx[i];
            int yy = it.second + dy[i];
            
            if (xx < 1 || xx > n || yy < 1 || yy > n)
                continue;
            if (mp[x][y] == mp[xx][yy] && !st[xx][yy]){
                q.push({xx,yy});
                st[xx][yy] = 1;
            }
            if (mp[xx][yy] != mp[x][y]){
                if (mp[xx][yy] > mp[x][y])
                    higher = 1;
                else
                    lower = 1;
            }
        }
    }
    
}


int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            scanf("%d",&mp[i][j]);
    
    int res1 = 0;
    int res2 = 0;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (!st[i][j]){
                higher = 0;
                lower = 0;
                bfs(i, j);
                if (!higher)
                    res1 ++;
                if (!lower)
                    res2 ++;
            }
            
    cout << res1 << ' ' << res2 << endl;
    
    return 0;
}

最短路模型

AcWing 1076. 迷宫问题

算法思路 :

BFS求最短路,注意点是使用pre数组记录路径:
pre[i][j] = {x,y} 表示从\((x,y)\)走到\((i,j)\),反向记录路径.输出时向前导

#include <iostream>
#include <cstring>
#include <queue>

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

int mp[N][N];
int st[N][N];
int n;
PII pre[N][N];
int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0, -1, 0};

void bfs(int x, int y)
{
    memset(pre, -1 ,sizeof pre);
    queue<PII> q;
    q.push({x,y});
    pre[x][y] = {0,0};
    
    while (q.size()){
        auto it = q.front();
        q.pop();
        
        for (int i = 0; i < 4; i ++ ){
            int xx = it.first + dx[i];
            int yy = it.second + dy[i];
            if (xx < 0 || xx >= n || yy < 0 || yy >= n)
                continue;
            if (mp[xx][yy])
                continue;
            if (pre[xx][yy].first != -1)
                continue;
            q.push({xx,yy});
            pre[xx][yy] = {it.first,it.second};           //pre[xx][yy] = {x,y} 表示从(x,y) -> (xx,yy)
        }
    }
}


int main()
{
    cin >> n;
    
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            cin >> mp[i][j];
        
        
    bfs(n - 1, n - 1);              //倒着向前走
        
        
    PII end(0,0);                   //最后一个点是(0,0)
    
    while (1){
        cout << end.first << ' ' << end.second << endl;
        if (end.first == n - 1 && end.second == n - 1)
            break;
        end = pre[end.first][end.second];           //向前导
    }
        
    return 0;
}

AcWing 188. 武士风度的牛

算法思路:

求最短距离,注意8个方向如何表示

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int N = 160;
typedef pair<int,int> PII;
char mp[N][N];
int st[N][N];
int n,m;
int dist[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 ex,ey;

int bfs(int sx, int sy)
{
    queue<PII> q;
    q.push({sx, sy});
    st[sx][sy] = 1;
    dist[sx][sy] = 0;
    
    while (q.size()){
        auto it = q.front();
        q.pop();
        
        for (int i = 0; i < 8; i ++ ){
            int xx = it.first + dx[i];
            int yy = it.second + dy[i];
            if (xx < 1 || xx > n || yy < 1 || yy > m)
                continue;
            if (st[xx][yy] || mp[xx][yy] == '*')
                continue;
            q.push({xx,yy});
            dist[xx][yy] = dist[it.first][it.second] + 1;
            st[xx][yy] = 1;
        }
    }
    return dist[ex][ey];
}

int main()
{
    cin >> m >> n;
    int sx,sy;
    for (int i = 1; i <= n ; i ++ )
        scanf("%s",mp[i] + 1);
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            if (mp[i][j] == 'K'){
                sx = i;
                sy = j;
            }else if (mp[i][j] == 'H'){
                ex = i;
                ey = j;
            }
            
    int res = bfs(sx, sy);
    
    cout << res << endl;
    
    return 0;
}

AcWing 1100. 抓住那头牛

算法思路 :

一维求最短路,由于题目没有给出数轴的边界范围,需要判断边界范围(BFS不能无限扩展)
每次向外扩展时,是对下一个节点的合法性进行判断

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 10;
int n, k;
int dist[N];


int main()
{
    int res = INF;
    cin >> n >> k;
    memset(dist, -1, sizeof dist);
    queue<int> q;
    q.push(n);
    dist[n] = 0;
    
    while (q.size()){
        int t = q.front();
        q.pop();
                                                //需要求出下一个状态,注意是对下一个状态是否合法进行判断
        if (t + 1 <= N && dist[t + 1] == -1){
            q.push(t + 1);
            dist[t + 1] = dist[t] + 1;
        }
        if (t - 1 >= 0 && dist[t - 1] == -1){
            q.push(t - 1);
            dist[t - 1] = dist[t] + 1;
        }
        if (t * 2 <= N && dist[t * 2] == -1){
            q.push(2 * t);
            dist[2 * t] = dist[t] + 1;
        }
    }
    cout << dist[k] << endl;
    return 0;
}

多源BFS :

AcWing 173. 矩阵距离

算法思路 :

从多个点到一个确定点的最短距离, 与一般的多点到多点的最短距离不同,我们可以假设一个虚拟原点,该虚拟原点到各个起点的距离为0, 问题转化为单源最短路.
针对该题,可以将所有起始点直接入队.

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;
typedef pair<int,int> PII;

const int N = 1010;

char g[N][N];
int dist[N][N];
int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0,-1, 0};


int main()
{
    memset(dist, -1, sizeof dist);
    int n,m;
    cin >> n >> m;
    queue<PII> q;
    char c = getchar();
    for (int i = 1; i <= n; i ++ )
        scanf("%s",g[i] + 1);
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            if (g[i][j] == '1'){
                q.push({i,j});
                dist[i][j] = 0;
            }
                    
    while (q.size()){
        
        auto it = q.front();
        q.pop();
        
        for (int i = 0; i < 4; i ++ ){
            int xx = it.first + dx[i];
            int yy = it.second + dy[i];
            
            if (xx < 1 || xx > n || yy < 1 || yy > m)
                continue;
            if (dist[xx][yy] != -1)
                continue;
            q.push({xx,yy});
            dist[xx][yy] = dist[it.first][it.second] + 1;
        }
        
    }
    
    for (int i = 1; i <= n; i ++ ){
        for (int j = 1; j <= m; j ++ )
            cout << dist[i][j] << ' ';
        cout << endl;
    }
    
    return 0;
}

最小步数模型

AcWing 1107. 魔板

算法思路:

区别最短路与最小步数 : 最短路一般指图内一点到另一点的最短距离,最小步数一般指状态之间的转移的最小次数.

模拟状态与状态之间的最短步数, 对于每个状态,使用Hash(map)存储每个状态.

这道题练代码实现能力

#include <iostream>
#include <cstring>
#include <queue>
#include <map>
#include <string>
#include <algorithm>

using namespace std;
char g[2][5];
map<string,int> dist;
map<string, pair<char,string>> pre;


void set(string state)
{
    for (int i = 0; i < 4; i ++ )
        g[0][i] = state[i];
    for (int i = 7, j = 0; j < 4; j ++, i -- )
        g[1][j] = state[i];
    return ;
}

string get()
{
    string state;
    for (int i = 0; i < 4; i ++ )
        state += g[0][i];
    for (int i = 3; i >= 0; i -- )
        state += g[1][i];
    //cout << state << "->" <<  endl;
    return state;
}


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);
    char s1 = g[0][3], s2 = 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] = s1;
    g[1][0] = s2;
    return get();
}

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


int bfs(string start, string eend)
{

    if (start == eend)
        return 0;

    queue<string> q;
    q.push(start);
    dist[start] = 0;
    while (q.size()){
        string t = q.front();
        q.pop();
        
        string m[4];
        m[0] = move0(t);
        m[1] = move1(t);
        m[2] = move2(t);
        /*
        cout << t << endl;
        for (int i = 0; i < 3; i ++ )
            cout << "->" << m[i] << endl;
        return 0;
        */
        for (int i = 0; i < 3; i ++ ){
            string state = m[i];
            if (!dist.count(state)){
                dist[state] = dist[t] + 1;
                pre[state] = {i + 'A', t};
                q.push(state);
                if (state == eend)
                    return dist[eend];
            }
        }
    }
}


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

双向广搜(BFS 优化):

AcWing 190. 字串变换

算法思路 :

使用两个队列同时向最终状态进行搜索,可以减少搜索空间.题目另一个难点在代码实现,使用Hash存储每个状态.

(具体见代码)

#include <iostream>
#include <queue>
#include <cstring>
#include <map>
#include <algorithm>
#include <string>

using namespace std;

int n;
string opa[7];
string opb[7];
queue<string> qa;
queue<string> qb;
map<string,int> dista;
map<string,int> distb;

int extend(queue<string>& q, map<string,int>& dista, map<string,int>& distb, string opa[], string opb[])
{
    string state = q.front();
    q.pop();
    
    for (int i = 0; i < n; i ++ )           //枚举所有变化方式
        for (int j = 0; j < state.size(); j ++ ){   //枚举当前字符串是否可以转换
            if (state.substr(j, opa[i].size()) == opa[i]){  //substr(i,len): 从i起取长为len的字符串
                string next = state.substr(0,j) + opb[i] + state.substr(j + opa[i].size());
                if (distb.count(next)) return distb[next] + dista[state] + 1;       //在反向中找到next,会合成功
                if (!dista.count(next)){
                    dista[next] = dista[state] + 1;
                    q.push(next);
                }
            }
        }
    return 99;
}


int bfs(string A, string B)
{
    dista[A] = 0;
    distb[B] = 0;
    qa.push(A);         //正向
    qb.push(B);         //反向
    int t;
    while (qa.size() && qb.size()){         //二者同时不空,若空一个,则二者不连通
        if (qa.size() <= qb.size())           //正向扩展(正向空间更小)
            t = extend(qa, dista, distb, opa, opb);
        else
            t = extend(qb, distb, dista, opb, opa);
        
        if (t <= 10)                        //找到一个使正反方向会合且操作数小于10的结果
            return t;
    }
    
    return 99;
}


int main()
{
    string A, B;
    cin >> A >> B;
    while (cin >> opa[n] >> opb[n])     //所有操作
        n ++;
    
    int res = bfs(A, B);
    
    if (res > 10)
        puts("NO ANSWER!");
    else
        cout << res << endl;
    
    return 0;
}
posted @ 2021-03-16 20:43  lhqwd  阅读(68)  评论(0)    收藏  举报