BFS的使用(acwing提高课之搜索)

BFS

bfs是搜索算法里面最基础的算法,对于队首的点,每次搜索其周围所有的点,然后将其入队。队列里面的点具有两个特性:
(1)单调性,即队列的元素是递增的(可以相等)
(2)不重复,不会有相同的点

1. 多源bfs

一般的问题是求单源bfs,单源的意思就是一个起点,那么多源就是多个起点。

在这里插入图片描述

题意就是:遍历每个点,找到距离当前点最近的值为‘1’的点,记录他们之间的距离。

很明显,这是一个最短路问题,而且边权为1,满足这两个条件,可以考虑bfs了。但是有一个问题,通常使用bfs的时候,只有一个起点,这题每个点都是起点,那么怎么做?
实际上,将所有值为‘1’的点作为一个整体,都当成起点即可。因为值为1的点到最近的值为1的点就是其本身。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;

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

char g[N][N];
int dist[N][N];

int n,m;

void bfs()
{
    int dx[4]={-1,0,1,0};
    int dy[4]={0,1,0,-1};
    queue<PII>q;
    memset(dist,-1,sizeof dist);
    //首先将所有值为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.push({i,j});
            }
        }
    while(!q.empty())
    {
       PII t=q.front();
       q.pop();
       int a=t.first;
       int b=t.second;
       for(int i=0;i<4;i++)
       {
            int x=a+dx[i];
            int y=b+dy[i];
            if(x<1||y<1||x>n||y>m)continue;
            if(dist[x][y]!=-1)continue;
            else
            {
                dist[x][y]=dist[a][b]+1;  //不需要去min,因为第一次遍历到的一定是最小的,初始的起点每个点都是走一圈,每次都是如此。所有对于每个点都是一样的
                q.push({x,y});
            }
       }
    }
}

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

2.最小步数模型

最小步数模型常见的有八数码问题。
这里记录模板和八数码。
核心思想:普通的最小步数只需要记录点的坐标,也就是普通意义上的‘点’。然而可以更加抽象‘点’。将一个状态表示为一个点。以点为面,以面为点。八数码问题就是如此。

1.魔板

在这里插入图片描述

思路:将每个数组的状态当作一个点,然后进行一个“扩展”,这里的扩展就是三个基本操作。(对比普通bfs,扩展点就是搜索可以达到的周围邻点)

#include<iostream>
#include<cstring>
#include<queue>
#include<unordered_map>
#include<algorithm>
using namespace std;

unordered_map<string,pair<char,string>>pre;  //记录当前的状态是由哪个状态转移过来的。并且记录上一步转移过来使用的是哪种方案
unordered_map<string,int>dist;  //记录从起始状态转移到当前状态用了多少步数
char g[2][4];


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 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)
{
    //start通过广搜得到end
    if(start==end)return 0;
    queue<string>q;
    q.push(start);
    dist[start]=0;
    while(!q.empty())
    {
        auto t=q.front();
        q.pop();
        
        //t有三种转移方式
        string m[3];
        m[0]=move0(t);
        m[1]=move1(t);
        m[2]=move2(t);
        
        for(int i=0;i<3;i++)
        {
            if(!dist.count(m[i]))  //当前的状态没有出现过
            {
                dist[m[i]]=dist[t]+1;
                pre[m[i]]={'A'+i,t};
                q.push(m[i]);
                if(m[i]==end)return dist[m[i]];
            }
        }
    }
    return -1;
}
int main()
{
    string start,end;
    for(int i=0;i<8;i++)
    {
        int x;
        cin>>x;
        end+=char(x+'0');
    }
    for(int i=0;i<8;i++)start+=char(i+'1');
    int step=bfs(start,end);
    cout<<step<<endl;
    //cout<<start<<" "<<end<<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;
}


2.八数码问题

3.双端队列广搜

在这里插入图片描述

题意:从左上角到右下角,保证电路连通的情况下,最少扭转多少次线路。
假设两个点直接不需要扭转直接的线路,则两点的距离设置为0,否则为1.那么就变成了一个求最短路问题.

双端队列主要解决图中边的权值只有0或者1的最短路问题
每次从队头取出元素,并进行拓展其他元素时、

若拓展某一元素的边权是0,则将该元素插入到队头
若拓展某一元素的边权是1,则将该元素插入到队尾

#include<iostream>
#include<cstring>
#include<algorithm>
#include<deque>
using namespace std;

const int N=510;
typedef pair<int,int>PII;
#define x first
#define y second
char g[N][N];
int dist[N][N];
bool st[N][N];

int T;
int n,m;

//定义搜索周围的方向,左上,右上,左下,右下
int dx[4]={-1,-1,1,1};
int dy[4]={-1,1,1,-1};
int ix[4]={-1,-1,0,0};
int iy[4]={-1,0,0,-1};
int bfs()
{
    memset(dist,0x3f,sizeof dist);
    memset(st,false,sizeof st);
    deque<PII>q;
    dist[0][0]=0;
    q.push_back({0,0});
    char cs[5]={'\\','/','\\','/'};
    while(!q.empty())
    {
        PII t=q.front();
        q.pop_front();
        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];
            int d = dist[t.x][t.y] + (g[ca][cb] != cs[i]);

            if (d < dist[a][b])
            {
                dist[a][b] = d;

                if (g[ca][cb] != cs[i]) q.push_back({a, b});
                else q.push_front({a, b});
            }
        }
    }
    return dist[n][m];
}

int main()
{
    cin>>T;
    while(T--)
    {
        cin>>n>>m;
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
                cin>>g[i][j];
        int ans=bfs();
        if(m+n&1)cout<<"NO SOLUTION\n";
        else cout<<ans<<endl;
    }
    return 0;
}

4.双向广搜

bfs问题当规模很大的时候,不妨从两端向中间搜索。这样复杂度会降低.但是代码难写一点
在这里插入图片描述
题意:给两个字符串,分别是起始状态和结束状态,然后给定几个变换规则。要求找到最少变化次数。
发现,如果单词bfs,那么时间复杂度会超时,所以尝试使用双向bfs。
思路:
两个字符串都同时作为起始状态,只要保证在搜索过程中,两者有一个状态是一样的,那么返回两者路径之和

关键在于每次扩展的时候,都需要将那一层所有的状态去和另一边去比较。否则答案不正确。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<unordered_map>
#include<queue>
using namespace std;


const int N=6;

string A,B;
string a[N],b[N];
int n;

int expand(queue<string>&q,unordered_map<string,int>&da,unordered_map<string,int>&db,string a[N],string b[N])
{
    int d=da[q.front()];
    while(q.size()&&d==da[q.front()])
    {
        string t=q.front();
        q.pop();
        for(int i=0;i<n;i++)
            for(int j=0;j<t.size();j++)
            {
                if(t.substr(j,a[i].size())==a[i])
                {
                    string r=t.substr(0,j)+b[i]+t.substr(j+a[i].size());
                    if(db.count(r))return da[t]+1+db[r];
                    if(da.count(r))continue;
                    da[r]=da[t]+1;
                    q.push(r);
                }
            }
    }
    return 10000;
}
int bfs()
{
    if(A==B)return 0;
    queue<string>qa,qb;
    unordered_map<string,int>da,db;
    da[A]=0,db[B]=0;
    qa.push(A),qb.push(B);
    int step=0;
    while(qa.size()&&qb.size())
    {
        int t;
        if(qa.size()>qb.size())
            t=expand(qb,db,da,b,a);
        else
            t=expand(qa,da,db,a,b);
        if(t<=10)return t;
        if(++step==10)return -1;
    }
    return -1;
}

int main()
{
    cin>>A>>B;
    while(cin>>a[n]>>b[n])n++;
    int t=bfs();
    if(t==-1)cout<<"NO ANSWER!";
    else cout<<t;
    return 0;
}

5.A*算法

在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<unordered_map>
using namespace std;

typedef pair<int,string>PIS;
string start,x;

int f(string m)  //计算当前状态到终点的状态的估计距离
{
    int dt=0;
    for(int i=0;i<9;i++)
    {
        if(m[i]!='x')
        {
            int t=m[i]-'1';  //实际位置
            dt=dt+abs(i/3-t/3)+abs(i%3-t%3);
        }
    }
    return dt;
}

string bfs()
{
    string end="12345678x";
    unordered_map<string,int>dist;
    unordered_map<string,pair<string,int>>last;
    priority_queue<PIS,vector<PIS>,greater<PIS>>heap;
    dist[start]=0;
    heap.push({f(start),start});  //加入起点,记录当前的点和总距离
    char oper[]="udlr";
    int dx[4]={-1,1,0,0};
    int dy[4]={0,0,-1,1};
    while(heap.size())
    {
        auto t =heap.top();
        heap.pop();
        
        string state=t.second;
        if(state==end)break;  //找到最近距离
        
        int x,y;
        for(int i=0;i<9;i++)
            if(state[i]=='x')
            {
                x=i/3,y=i%3;
                break;
            }
        
        string init=state;
        for(int i=0;i<4;i++)
        {
            int a=x+dx[i],b=y+dy[i];
            if(a<0||b<0||a>2||b>2)continue;
            swap(state[a*3+b],state[x*3+y]);
            if(!dist.count(state)||dist[state]>dist[init]+1)
            {
                dist[state]=dist[init]+1;
                heap.push({f(state)+dist[state],state});
                last[state]={init,oper[i]};
            }
            state=init;
        }
    }
    
    string ans;
    while(end!=start)
    {
        ans+=last[end].second;
        end=last[end].first;
    }
    reverse(ans.begin(),ans.end());
    return ans;    
}
int main()
{
    
    char c;
    while(cin>>c)
    {
        start+=c;
        if(c!='x')x+=c;
    }
    //逆序对个数为奇数不能到达终点(12345678x)
    //终点是没有逆序对的。九宫格里面,x左右交换不改变逆序对数量,上下交换改变偶数对,所以如果起始
    //状态是奇数个逆序对,则无法到达终点。
    int cnt=0;
    for(int i=0;i<8;i++)
        for(int j=i+1;j<8;j++)
            if(x[i]>x[j])cnt++;
    if(cnt%2)cout<<"unsolvable";
    else cout<<bfs();
    return 0;
}

先记录到这,,,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值