BFS——双向广搜+A—star

有时候从一个点能扩展出来的情况很多,这样几层之后搜索空间就很大了,我们采用从两端同时进行搜索的策略,压缩搜索空间。

190. 字串变换(190. 字串变换 - AcWing题库)

思路:这题因为变化规则很多,所以我们一层一层往外扩展的时候,扩展几层后空间就会变得很大 ,那么就需要换一个思路,我们这里采用双向广搜,从两个方向来进行搜索,具体执行的时候,肯定得先从一个方向开始,那么从哪里开始呢?显然要从状态少的方向开始,我们就比较两个队列,从小的那个队列开始,然后又有新问题了,如果从一个方向开始,什么时候判停去找下一个方向,显然我们可以只往外扩展一层,扩展结束,或者两者头尾交汇的时候停止。

现在理一下我们需要哪些数据结构,首先得有两个队列分别记录从头搜索和从尾搜索的队列,另外要有两个数据结构来记录每个状态从头和从尾更新到它们时的距离。另外还要记录一下变换规则。

还有就是我们每次只往外扩展一层,所以有交汇和不交汇两种情况,如果交汇的话,那么我们可以直接返回从头到这个点和从尾到这个点的步数和,如果这个值小于10,那么显然就是最小距离,因为我们每次只从头或者从尾往外扩展一层,下次扩展时找到的,一定比当前扩展找到的多一步。当然也可能不交汇,因为我们只往外扩展一层,不交汇的时候返回一个大于10的数即可。同时我们每一次往外扩展都要记录步数,当步数大于10的时候直接停下。因为题目要求的上限就是10步。

对了如果想把两个扩展函数写在一起,一定要传入它们的更新规则,两者的扩展规则是不一样的。

#include<bits/stdc++.h>
using namespace std;
string a[10],b[10];
int n;
int expend(queue<string>&q,unordered_map<string,int>&dl,unordered_map<string,int>&dr,string a[],string b[])
{
    int d=dl[q.front()];
    while(q.size()&&dl[q.front()]==d)
    {
        auto t=q.front();
        //cout<<t<<endl;
        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 tmp=t.substr(0,j)+b[i]+t.substr(j+a[i].size());
                    if(dr.count(tmp)) return dl[t]+dr[tmp]+1;
                    if(dl.count(tmp)) continue;
                    q.push(tmp);
                    dl[tmp]=dl[t]+1;
                }
            }
        }
    }
    return 11;
}
int bfs(string s,string e)
{
    if(s==e) return 0;
    queue<string>ql,qr;
    unordered_map<string,int>dl,dr;
    ql.push(s),qr.push(e);
    dl[s]=dr[e]=0;
    int step=0;
    while(ql.size()&&qr.size())
    {
        int t;
        if(ql.size()<qr.size()) t=expend(ql,dl,dr,a,b);
        else t=expend(qr,dr,dl,b,a);
        if(t<=10) return t;
        if(++step>=10) return -1;
    }
    return -1;
}
int main()
{
    string s,e;
    cin>>s>>e;
    n=0;
    while(cin>>a[n]>>b[n])n++;
    int ans=bfs(s,e);
    if(ans==-1) cout<<"NO ANSWER!";
    else cout<<ans;
}

 另外这里补充一下substr()的用法:

形式 : s.substr(pos, len)
返回值: string,包含s中从pos开始的len个字符的拷贝(pos的默认值是0,len的默认值是s.size() - pos,即不加参数会默认拷贝整个s)
异常 :若pos的值超过了string的大小,则substr函数会抛出一个out_of_range异常;若pos+n的值超过了string的大小,则substr会调整n的值,只拷贝到string的末尾

参考链接:C++中substr()函数用法详解_substr c++-CSDN博客

 A—star算法

A—star算法也是对搜索空间进行压缩进而优化搜索。这里我们选择下一个搜索位置的时候,并不是根据它们到起点的位置来选择的,而是根据它们到起点的位置+到终点的预估值来进行选择,可以这么来理解,我们之前的bfs都是假设到终点的预估值为0,那么直接放入队列即可,这里需要用到优先队列,将从起点搜到的实际距离+到终点预估距离作为权重,放入优先队列。而预估距离要满足的条件就是小于真实距离,而且要是答案有解的情况下才能使用这个算法,否则不如bfs。

而且可以证明终点第一次出队的时候是得到的距离是最小距离。

参考链接:

AcWing 178. 第K短路(A* 反向计算最短路作为到终点的估计值) - AcWing

178. 第K短路(178. 第K短路 - AcWing题库)

思路:这道题要求起点到终点的第k短路,我们已知终点第一次出队的时候是最短路,那么第二次出队就是第儿短路,因为这条路径是被其他点更新了放进队列的,以此类推,我们只要获得终点第k次出队时的距离则可以得到答案。

然后问题就是这里的预估距离该怎么处理,因为终点固定,所以我们可以用dijkstra算法计算各点到终点的距离作为预估距离。因为它满足小于等于真实距离的条件,所以符合作为预估距离的要求。

#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=20010;
typedef pair<int,int> pii;
typedef pair<int,pii> piii;
int n,m;
int rh[N],h[N],e[M],ne[M],w[M],idx;
int s,t,k;
int d[N],st[N];
void add(int h[],int a,int b,int c)
{
    w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dijkstra()
{
    memset(d,0x3f,sizeof d);
    d[t]=0;
    priority_queue<pii, vector<pii>, greater<pii>> q;
    q.push({0,t});
    while(q.size())
    {
        auto it=q.top();
        q.pop();
        int dist=it.first,v=it.second;
        if(st[v]) continue;
        st[v]=1;
        for(int i=rh[v];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(d[j]>dist+w[i])
            {
                d[j]=dist+w[i];
                q.push({d[j],j});
            }
        }
    }
}
int cnt[N];
int bfs()
{
    priority_queue<piii, vector<piii>, greater<piii>> q;
    q.push({d[s],{0,s}});
    while(q.size())
    {
        auto it=q.top();
        q.pop();
        int dist=it.second.first,v=it.second.second;
        cnt[v]++;
        if(v==t&&cnt[v]==k) return dist;
        for(int i=h[v];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(cnt[j]<k)
            q.push({dist+w[i]+d[j],{dist+w[i],j}});
        }
    }
    return -1;
}
int main()
{
    memset(h,-1,sizeof h);
    memset(rh,-1,sizeof rh);
    scanf("%d%d",&n,&m);
    for(int i=1;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++;//因为最初s就会被放进去,每条最短路中至少要包含一条边。
    dijkstra();
    cout<<bfs();
}

 179. 八数码(179. 八数码 - AcWing题库)

这题是将棋盘整个视为一种状态,我们每一个点最多只能扩展出四种状态,我们可以用之前魔板那道题的写法,稍微变一下,变成双向广搜,自然也可以用A—star算法,因为状态还是有些许多。

A—star算法需要保证有解,那么我们就提前预判一下是否有解,如果有解再去进行查找。

八数码问题有个预判的技巧,我们按行读入数字,得到的一个数组中,如果逆序对的数量为奇数,那么就无解,如果逆序对的数量为偶数,那么就有解。

可以这么来理解,如果我们在行内进行移动,实际上并没有改变序列,也就是并未改变逆序对数量,如果在行与行之间进行移动,那么相当于只改变了它前面或者后面两个数的位置,我们以一种情况为例,它实际上就只改变了3个数内部的相对顺序,所以实际上逆序对的数量要么不变要么就多2或者少2,所以起始状态和结束状态中逆序对的奇偶性相同,我们可以顺序排列的时候逆序对的数量是0,那么起始状态中逆序对的数量应该是偶数。

然后就是考虑估价函数,我们计算出每个数当前位置和目标位置的曼哈顿距离,很显然,每次移动最好的情况只会让一个数和它的实际位置的曼哈顿距离减1,所以我们可以将每个状态中每个数当前位置与目标位置的曼哈顿距离和算出来,作为预估距离。 

#include<bits/stdc++.h>
using namespace std;
string s,e;
char a[4][4];
unordered_map<string,int>d;
unordered_map<string,pair<string,char>>pre;
char op[]={'u','d','l','r'};//x的位置
void toa(string t)
{
    for(int i=0;i<t.size();i++)
    {
        a[i/3][i%3]=t[i];
    }
}
string tos()
{
    string res="";
    for(int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            res += a[i][j];
    return res;
}
string move0(string t)//u
{
    toa(t);
    int x,y;
    for(int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            if(a[i][j]=='x') 
                x=i,y=j;
    if(x!=0)
    {
        swap(a[x][y],a[x-1][y]);
    }
    return tos();
}
string move1(string t)//d
{
    toa(t);
    int x,y;
    for(int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            if(a[i][j]=='x') 
                x=i,y=j;
    if(x!=2)
    {
        swap(a[x][y],a[x+1][y]);
    }
    return tos();
}
string move2(string t)//l
{
    toa(t);
    int x,y;
    for(int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            if(a[i][j]=='x') 
                x=i,y=j;
    if(y!=0)
    {
        swap(a[x][y],a[x][y-1]);
    }
    return tos();
}
string move3(string t)//r
{
    toa(t);
    int x,y;
    for(int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            if(a[i][j]=='x') 
                x=i,y=j;
    if(y!=2)
    {
        swap(a[x][y],a[x][y+1]);
    }
    return tos();
}
typedef pair<int,pair<int,string>> piis;
int tj(string g)
{
    int cnt=0;//逆序对数量
    for(int i=0;i<g.size();i++)
    {
        for(int j=i+1;j<g.size();j++)
        {
            if(g[i]>g[j]) cnt++;
        }
    }
    return cnt;
}
void bfs()
{
    priority_queue<piis,vector<piis>,greater<piis>>q;
    int cnt=tj(s);
    q.push({cnt,{0,s}});
    d[s]=0;
    if(s==e) return;
    while(q.size())
    {
        auto t=q.top();
        q.pop();
        string tv=t.second.second;
        int dist=t.second.first;
       // cout<<tv<<endl;
        string tmp[5];
        tmp[0]=move0(tv);
        tmp[1]=move1(tv);
        tmp[2]=move2(tv);
        tmp[3]=move3(tv);
        for(int i=0;i<4;i++)
        {
           // cout<<tmp[i]<<endl;
            if(d.count(tmp[i])) continue;
            d[tmp[i]]=dist+1;
            pre[tmp[i]]={tv,op[i]};
            cnt=tj(tmp[i]);
            q.push({cnt+d[tmp[i]],{d[tmp[i]],tmp[i]}});
            if(tmp[i]==e) return;
        }
        //cout<<endl;
    }
}
int main()
{
    string g="";
    char c;
    while(cin>>c)//不会录入空格
    {
        s += c;
        if(c!='x') g+=c;
    }
    e="12345678x";
    int cnt=0;//逆序对数量
    for(int i=0;i<g.size();i++)
    {
        for(int j=i+1;j<g.size();j++)
        {
            if(g[i]>g[j]) cnt++;
        }
    }
    if(cnt%2) cout<<"unsolvable";
    else
    {
        bfs();
        //cout<<d[e]<<endl;
        if(d[e])
        {
            string res="";
            while(e!=s)
            {
                res += pre[e].second;
                e=pre[e].first;
            }
            reverse(res.begin(),res.end());
            cout<<res;
        }
    }
}

ps:代码看似复杂,但复用率极高,只要把逻辑盘清楚,实际上并不麻烦。

总的来说,A—star可以提高效率,但是估价函数一定要写明白。

另外补充一点,cin录单个字符的时候不会把空格录进去,如果没有更好的方法避免空格的话,可以用cin。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python实战:爬取小红书

有读者在公众号后台询问爬取小红书&#xff0c;今天他来了。 本文可以根据关键词&#xff0c;在小红书搜索相关笔记&#xff0c;并保存为excel表格。 爬取的字段包括笔记标题、作者、笔记链接、作者主页地址、作者头像、点赞量。 一、先看效果 1、爬取搜索页 2、爬取结果保存到…

2-12 SDATR的训练与测试

2.12 SDATR的训练与测试 使用环境:3卡服务器SDATR 服务器代码地址:/home/lihuanyu/code/036SDATR 本地代码地址:F:\BaiduNetdiskDownload\code\036SDATR 2.12.1 训练文件修改 输入数据修改 载入词汇修改 短点保存修改 权重保存修改 其他位置修改:

docker搭建Mysql集群准备(一)

docker搭建Mysql集群准备 Linux基本知识&#xff1a; 修改机器 IP&#xff0c;变成静态 IP vim /etc/sysconfig/network-scripts/ifcfg-ens33 文件 TYPEEthernet PROXY_METHODnone BROWSER_ONLYno BOOTPROTOstatic IPADDR192.168.190.67 NETMASK255.255.255.0 GAT…

优秀学习网站推荐-第一辑

原文地址&#xff1a;https://jaune162.blog/2024/02/15/study-website-recommend Developer Roadmaps&#xff08;开发者路线图&#xff09; 官网地址&#xff1a;https://roadmap.sh/ 该网站包含了各个方向、各个语言的开发人员从零开始学习的路线图。 下图为Java方向的学…

1997-2022年中央对各省份一般公共预算转移支付数据

1997-2022年中央对各省份一般公共预算转移支付数据 1、时间&#xff1a;1997-2022年 2、范围&#xff1a;31省 3、指标&#xff1a;一般公共预算转移支付 4、来源&#xff1a;wind 财政部 5、指标解释&#xff1a;一般性转移支付又称体制性转移支付&#xff0c;是指上级政…

回归预测 | Matlab基于OOA-LSSVM鱼鹰算法优化最小二乘支持向量机的数据多输入单输出回归预测

回归预测 | Matlab基于OOA-LSSVM鱼鹰算法优化最小二乘支持向量机的数据多输入单输出回归预测 目录 回归预测 | Matlab基于OOA-LSSVM鱼鹰算法优化最小二乘支持向量机的数据多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab基于OOA-LSSVM鱼鹰算法…

Linux---进程间通信 | 管道 | PIPE | MKFIFO | 共享内存 | 消息队列

管道 管道是UNIX中最古老的进程间通信的形式&#xff0c;我们把从一个进程连接到另一个进程的数据流称为一个管道。 一个文件&#xff0c;可以被多个进程打开吗&#xff1f;可以&#xff0c;那如果一个进程打开文件&#xff0c;往文件里面写数据&#xff0c;另一个进程打开文…

SQL Server之DML触发器

一、如何创建一个触发器呢 触发器的定义语言如下&#xff1a; CREATE [ OR ALTER ] TRIGGER trigger_nameon {table_name | view_name}{for | After | Instead of }[ insert, update,delete ]assql_statement从这个定义语言我们可以知道如下信息&#xff1a; trigger_name&…

supervision区域行人计数和轨迹追踪初步尝试

1、背景介绍 最近&#xff0c;一位朋友向我介绍了定位与视觉融合的需求&#xff0c;我发现这个想法非常有价值。恰逢我了解到了Supervision框架&#xff0c;便决定尝试运用它来进行初步的测试。这样做不仅有助于探索可以实际应用的项目&#xff0c;还能促进我自己在研究创新方…

035 Arrays类

示例 int[] nums new int[10]; // fill Arrays.fill(nums, 666); System.out.println(Arrays.toString(nums)); // sort nums new int[]{1, 3, 5, 7, 9, 2, 4, 6, 8}; Arrays.sort(nums); System.out.println(Arrays.toString(nums)); // equals int[] nums2 new int[]{1,…

Linux 驱动开发基础知识——内核对设备树的处理与使用(十)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…

angular2 开发遇到的问题

1&#xff1a;插件使用&#xff0c;要一同引入 不然报错 “ \ Changes detected. Rebuilding...X [ERROR] NG8001: sf-dashboard-overview is not a known element:”

Golang 并发控制方式有哪些

Go语言中的goroutine是一种轻量级的线程&#xff0c;其优点在于占用资源少、切换成本低&#xff0c;能够高效地实现并发操作。但如何对这些并发的goroutine进行控制呢&#xff1f; 一提到并发控制&#xff0c;大家最先想到到的是锁。Go中同样提供了锁的相关机制&#xff0c;包…

C++进阶(十)哈希的应用——位图布隆过滤器

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、位图1、位图概念2、位图的实现3、位图的应用 二、布隆过滤器1、布隆过滤器提出2、布隆过滤…

ZYNQ:CAN总线功能应用

前言 上篇文章解决了ZYNQ搭建PS和PL系统的问题&#xff0c;相当于完成最小系统板搭建。因此&#xff0c;本篇文章主要用于记录搭建CAN外设系统会出现的问题。由于ZYNQ系统包含PS和PL两个部分&#xff0c;PS部分往往问题较少&#xff0c;所以考虑先搭建PS系统的CAN外设系统。熟…

微信网页授权之使用完整服务解决方案

目录 微信网页授权能力调整造成的问题 能力调整的内容和理由 原有运行方案 is_snapshotuser字段 改造原有方案 如何复现测试场景 小结 微信网页授权能力调整造成的问题 依附于第三方的开发&#xff0c;做为开发者经常会遇到第三方进行规范和开发的调整&#xff0c;如开…

PCL安装以及CGAL构建三维凸包

基础理论专栏目录 - 知乎 (zhihu.com) 凸包问题——概述 - 知乎 (zhihu.com) 1、安装PCL 安装pcl,我的是window10,vs2019。我安装的是1.13 win10系统下 VS2019点云库PCL1.12.0的安装与配置_windows 10使用pcl-CSDN博客 照着上述博客进行配置&#xff0c;再结合这个设置环境变…

微信小程序(三十三)promise异步写法

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.promise异步与普通异步的写法区别 2.promise异步的优势 源码&#xff1a; index.wxml <view class"preview" bind:tap"onChoose"><image src"{{avatar}}" mode"…

WorkPlus Meet视频会议系统,支持局域网部署

随着科技的不断发展&#xff0c;视频会议系统已经成为企业、教育机构和医疗领域等各行各业远程协作和沟通的重要工具。恒拓高科的WorkPlus Meet视频会议系统以其强大的功能和便捷的操作&#xff0c;满足了不同行业的实际需求&#xff0c;成为市场上备受青睐的解决方案。 在金融…

Vue3+TS+Vite+Pinia最全学习总结

VUE3介绍 vue2和vue3之间的区别 因为需要遍历data对象上所有属性&#xff0c;所以如果data对象属性结构嵌套很深&#xff0c;就会存在性能问题。因为需要遍历属性&#xff0c;所有需要提前知道对象上有哪些属性&#xff0c;才能将其转化为getter和setter,所以vue2中无法将data新…