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

news2024/9/23 9:36:44

bfs

  • BFS
  • 1. 多源bfs
  • 2.最小步数模型
    • 1.魔板
    • 2.八数码问题
  • 3.双端队列广搜
  • 4.双向广搜
  • 5.A*算法

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;
}

先记录到这,,,

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/341252.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenWrt路由器设置IPv6域名动态解析,同时实现IPv4设备访问IPv6节点

文章目录0、前言1、准备工作2、详细步骤2.1、OpenWrt路由器软件包安装2.2、防火墙放行入站数据&#xff08;修改为“接受”并保存应用&#xff09;2.3、路由器做好ipv6设置&#xff08;略&#xff09;2.4、域名解析服务商对域名的解析设置2.5、路由器中动态域名插件的设置3、关…

23.2.12 LC每日一题 —— 极尽地高效利用题目中所提供的有效信息

文章目录23.2.12 LC每日一题 —— 极尽地高效利用题目中所提供的有效信息题目链接&#xff1a;题目大意&#xff1a;注意&#xff1a;示例&#xff1a;参考代码&#xff08;py3&#xff09;&#xff1a;总结23.2.12 LC每日一题 —— 极尽地高效利用题目中所提供的有效信息 题目…

Redis内存存储效率问题

目录 内存碎片是如何形成的&#xff1f; 如何判断是否有内存碎片&#xff1f; 如何清理内存碎片&#xff1f; INFO命令 实习期间&#xff0c;了解到&#xff0c;企业级开发中多个项目使用Redis&#xff0c;运行Redis实例的有可能是同一台物理机器&#xff0c;那么&#xff…

【水文模型】评价指标

水文模型模拟效果评价指标1 皮尔逊相关系数&#xff08;Pearson’s correlation coefficient, PCC&#xff09;2 百分比偏差&#xff08;Percent bias, Pbias&#xff09;3 纳什效率系数&#xff08;the Nash-Sutcliffe efficiency coefficient, NSE&#xff09;4 克林-古普塔效…

【大前端 合集】包管理工具差异性

包管理工具 这里会对市场上使用最多的包管理工具 yarn/ npm 以及新秀 pnpm 做一个横向分析 1. 前言 在做分析以及学习之前&#xff0c;最好可以读下 pnpm 官网。可以理解下 pnpm 的核心宗旨 当使用 npm 或 Yarn 时&#xff0c;如果你有 100 个项目&#xff0c;并且所有项目都有…

matlab搭建IAE,ISE,ITAE性能指标

目录前言准备IAEISEITAE前言 最近在使用matlab搭建控制系统性能评价指标模型&#xff0c;记录一下 准备 MATLAB R2020 IAE IAE函数表达式如下所示&#xff1a; IAE函数模型如下所示&#xff1a; ISE ISE函数表达式如下所示&#xff1a; ISE函数模型如下所示&#xff…

来看看你的是否会正确的使用索引

索引&#xff0c;可以有效提高我们的数据库搜索效率&#xff0c;各种数据库优化八股文里都有相关的知识点可背&#xff0c;不过单纯的被条目其实很容易忘记。 所以我想和大家聊一聊这个索引的正确使用方法&#xff0c;结合一些具体的例子来帮助大家理解索引优化。 1、索引列独…

Redis使用方式

一、Redis基础部分: 1、redis介绍与安装比mysql快10倍以上 *****************redis适用场合**************** 1.取最新N个数据的操作 2.排行榜应用,取TOP N 操作 3.需要精确设定过期时间的应用 4.计数器应用 5.Uniq操作,获取某段时间所有数据排重值 6.实时系统,反垃圾系统7.P…

开源、低成本的 Xilinx FPGA 下载器(高速30MHz)

目前主流的Xilinx下载器主要有两种&#xff1a;一种是Xilinx官方出品的Xilinx Platfom Cable USB&#xff0c;还有一个就是Xilinx的合作伙伴Digilent开发的JTAG-HS3 Programming Cable。 JTAG-HS系列最大支持30MHz下载速度&#xff0c;基于FTDI的FT2232方案。 JTAG-HS系列对比…

ipv6上网配置

一般现在的宽带都已经支持ipv6了&#xff0c;但是需要一些配置才能真正用上ipv6。记录一下配置过程。 当前测试环境为移动宽带&#xff0c;光猫下面接了一个路由器&#xff0c;家里所有的设备都挂到这个路由器下面的。 1. 光猫改桥接 光猫在使用路由模式下&#xff0c;ipv6无…

一款针对EF Core轻量级分表分库、读写分离的开源项目

更多开源项目请查看&#xff1a;一个专注推荐.Net开源项目的榜单 在项目开发中&#xff0c;如果数据量比较大&#xff0c;比如日志记录&#xff0c;我们往往会采用分表分库的方案&#xff1b;为了提升性能&#xff0c;把数据库查询与更新操作分开&#xff0c;这时候就要采用读写…

谈谈SpringBoot(三)

1. SpringBoot依赖管理 1.1 父依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.8</version><relativePath/></parent> 点击进去&#xf…

Redis中的hash结构和扩容机制

1.rehash原理 hash包含两个数据结构为字典数组ht[0]和ht[1]。其中ht[0]用来存放数据&#xff0c;ht[1]在rehash时使用。 扩容时&#xff0c;ht[1]的大小为第一个大于等于ht[0].used*2的2的幂次方的数&#xff1b; 收缩时&#xff0c;ht[1]的大小为第一个大于等于ht[0].used的…

大数据时代的小数据神器 - asqlcell

自从Google发布了经典的MapReduce论文&#xff0c;以及Yahoo开源了Hadoop的实现&#xff0c;大数据这个词就成为了一个行业的热门。在不断提高的机器性能和各种层出不穷的工具框架加持下&#xff0c;数据分析开始从过去的采样抽查变成全量整体&#xff0c;原先被抽样丢弃的隐藏…

【Java开发笔记】线程池

【Java开发笔记】线程池 线程池 ThreadPoolExecutor 的七大核心参数&#xff1a; 核心线程数 corePoolSize最大线程数 maxinumPoolSize超过核心线程数的闲余线程存活时间 keepAliveTime存活时间单位 unit:keepAliveTime任务队列&#xff08;阻塞队列&#xff09; workQueue生…

内网渗透(二十)之Windows协议认证和密码抓取-域认证(Kerberos协议)

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

【LeetCode】1138. 字母板上的路径

1138. 字母板上的路径 题目描述 我们从一块字母板上的位置 (0, 0) 出发&#xff0c;该坐标对应的字符为 board[0][0]。 在本题里&#xff0c;字母板为board [“abcde”, “fghij”, “klmno”, “pqrst”, “uvwxy”, “z”]&#xff0c;如下所示。 我们可以按下面的指令规…

Spring Security in Action 第一、二章 第一个Spring Security项目的建立以及基本

本专栏将从基础开始&#xff0c;循序渐进&#xff0c;以实战为线索&#xff0c;逐步深入SpringSecurity相关知识相关知识&#xff0c;打造完整的SpringSecurity学习步骤&#xff0c;提升工程化编码能力和思维能力&#xff0c;写出高质量代码。希望大家都能够从中有所收获&#…

Docker 搭建本地私有仓库

一、搭建本地私有仓库有时候使用Docker Hub这样的公共仓库可能不方便&#xff0c;这种情况下用户可以使用registry创建一个本地仓库供私人使用&#xff0c;这点跟Maven的管理类似。使用私有仓库有许多优点&#xff1a;1&#xff09;节省网络带宽&#xff0c;针对于每个镜像不用…

小灰的算法之旅---createBinaryTree 的一点点疑问

前言 深知自己算法薄弱&#xff0c;所以最近在补充自己算法方面的知识&#xff0c;《小灰的算法之旅》这本书作为入门书籍不错&#xff0c;当时在看到《树-深度优先遍历》的代码时&#xff0c;我碰到了一点疑问&#xff0c;经过我多次代码验证&#xff0c;确实是代码不太严谨。…