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.hqwc.cn/news/454191.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

19113133262(微信同号)2024年光电信息与机器视觉国际学术会议(ICOIMV 2024)

【征稿进行时|见刊、检索快速稳定】2024年光电信息与机器视觉国际学术会议(ICOIMV 2024) 2024 International Conference Optoelectronic Information and Machine Vision(ICOIMV 2024) 一、【会议简介】 本次会议的主题为“光电信息与机器视觉的未来发展”。围绕这一主题&…

06 - python操作xml

认识XML 与HTML很像&#xff0c;是一种将数据存储在标记之间的标记语言&#xff0c;用户可以自定义自己的标记。 XML文件可以表示称为&#xff1a;XML树。这个XML树从根元素开始&#xff0c;根元素进一步分支到子元素。XML文件的每个元素都是XML树的一个节点&#xff0c;没有…

kafka-splunk数据通路实践

目的&#xff1a; 鉴于目前网络上没有完整的kafka数据投递至splunk教程&#xff0c;通过本文操作步骤&#xff0c;您将实现kafka数据投递至splunk日志系统 实现思路&#xff1a; 创建kafka集群部署splunk&#xff0c;设置HTTP事件收集器部署connector服务创建connector任务&a…

Linux文件编译

目录 一、GCC编译 1.直接编译 2.分步编译 预处理&#xff1a; 编译&#xff1a; 汇编&#xff1a; 链接&#xff1a; 3.多文件编译 4.G 二、Make 1.概述 2.使用步骤 3.makefile创建规则 3.1一个基本规则 3.2两个常用函数 4.示例文件 三、GDB 示例&#xff1a;…

优量汇的广告填充率有多高?

一些量级小的媒体在广告变现初期&#xff0c;以接入广告联盟为主&#xff0c; 广告填充率是衡量广告平台表现和广告变现效率的重要指标。 当用户打开APP时&#xff0c;开发者收到一个对应的广告请求。这个广告请求可以被填充或不被填充&#xff0c;广告填充率广告填充数/广告请…

代码随想录算法训练营第27天| Leetcode 39. 组合总和、40.组合总和II、131.分割回文串

目录 Leetcode 39. 组合总和 Leetcode 40.组合总和II Leetcode 131.分割回文串 Leetcode 39. 组合总和 题目链接&#xff1a;Leetcode 39. 组合总和 题目描述&#xff1a;给定一个无重复元素的数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使…

070:vue+cesium: 利用canvas设置径向渐变色材质

相关API参考: https://cesium.com/learn/cesiumjs/ref-doc/ColorMaterialProperty.html 第070个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中设置线性渐变色的材质,这里使用canvas的辅助方法。 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. …

uniapp+uView 【详解】录音,自制音频播放器

效果预览 代码实现 <template><view class"btnListBox"><view class"audioBox" v-if"audioLength"><u-row><u-col span"2"><u--text aligncenter :text"currentTime"></u--text>…

android inset 管理

目录 简介 Insets管理架构 Insets相关类图 app侧的类 WMS侧的类 inset show的流程 接口 流程 WMS侧确定InsetsSourceControl的流程 两个问题 窗口显示时不改变现有的inset状态 全屏窗口上的dialog 不显示statusbar问题 View 和 DecorView 设置insets信息 输入法显…

图文并茂讲解Travelling Salesman

题目 思路 一道lca板子题&#xff0c;不会的同学可以先康康 详解最近公共祖先(LCA)-CSDN博客 我们可以发现&#xff0c;商人是从1开始&#xff0c;旅行到第一个城镇&#xff0c;再到第二个&#xff0c;第三个…… 那么我们只需要求出1~第一个城镇的距离&#xff0c;第一个城…

机器视觉系统设计:视觉系统中的成像基准

开发视觉系统的一个重要活动是验证其部署是否符合工程规范。一个成功的视觉应用程序的两个特点是它无需工程师干涉情况下正常工作了多长时间&#xff0c;以及它的维护和复制部署是多么简易。实现所有如上所述目标的一个关键步骤是确定视觉系统的基准。 在这里使用的上下文中&a…

第十三篇【传奇开心果系列】Python的OpenCV库技术点案例示例:光流估计

传奇开心果短博文系列 系列短博文目录Python的OpenCV库技术点案例示例:光流估计短博文目录前言一、光流估计介绍二、Lucas-Kanade光流介绍和示例代码三、Horn-Schunck光流介绍和示例代码四、cv::calcOpticalFlowPyrLK()函数实现光流估计介绍和示例代码五、光流估计用于运动分析…