BFS——双向广搜+A—star

news2024/11/26 11:58:31

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

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.coloradmin.cn/o/1433392.html

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

相关文章

新开发板-正点原子的rk3568

有好长一段时间没有更新博客了&#xff0c;上次更新还是在上次...哈哈开个玩笑&#xff0c;上次stm32f407的定时器还没写完&#xff0c;就备战期末去了&#xff08;电信学院&#xff0c;你懂的&#xff09;&#xff0c;一直没更新&#xff0c;原因是我实习去了&#xff0c;在忙…

Java项目管理01-Maven基础

一、Maven的常用命令和生命周期 1.Maven的常用命令使用方式 complie&#xff1a;编译&#xff0c;将java文件编译为class字节码文件 clean&#xff1a;清理&#xff0c;删除字节码文件 test&#xff1a;测试&#xff0c;运行项目中的test类 package&#xff1a;打包&#x…

IDEA新建文件夹后右击不能创建class类排错方法

目录 1 查看自身文件名是否为关键词 2 查看是否被“蓝色文件夹”给包含了 3 检查设置那边的class模板 4 报错解决 1 查看自身文件名是否为关键词 如下使用了 Java中的关键词"class"所以才无法创建包 ---------------------------------------------------------…

51单片机之LED灯模块篇

御风以翔 破浪以飏 &#x1f3a5;个人主页 &#x1f525;个人专栏 目录 点亮一盏LED灯 LED的组成原理 LED的硬件模型 点亮一盏LED灯的程序设计 LED灯闪烁 LED流水灯 独立按键控制LED灯亮灭 独立按键的组成原理 独立按键的硬件模型 独立按键控制LED灯状态 按键的抖动 独立按键…

KubeMQ简介

如今&#xff0c;企业组织之间的竞争是残酷的。每个组织都希望在其系统之间即时、实时或近乎实时地交换信息&#xff0c;以便做出更好、更快的决策。为了使此类信息持续流动&#xff0c;应用程序组件之间的集成需要无缝。为了充分利用云计算的所有优势&#xff0c;如今构建的应…

双非本科准备秋招(16.1)—— 力扣二叉树

1、101. 对称二叉树 检查是否对称&#xff0c;其实就是检查左节点等不等于右节点&#xff0c;我们可以用递归来做。 如果左右节点都为null&#xff0c;说明肯定对称呀&#xff0c;返回true。 如果一个为null一个不为null&#xff0c;或者左右的值不相等&#xff0c;则为false。…

k8s-深入理解Service(为Pod提供负载均衡和发现)

一、Service存在的意义 二、Service的定义和创建 Pod与Service的关系 Service的定义和创建 三、Service使用NodePort对外暴露应用 四种类型&#xff0c;常用的三种&#xff1a; 指定Service的NodePort端口 在实际生产中&#xff0c;k8s的集群不会直接暴露在公网中&#xff0c…

free5GC+UERANSIM

使用arp、ifconfig、docker inspect及网桥brctl 相关命令&#xff0c;收集容器IP及Mac地址相关信息&#xff0c;可以梳理出UERANSIMfree5GC模拟环境组网&#xff0c;如下图所示&#xff1a; 如上图所示&#xff1a;环境基于ubuntu 18.04 VMware虚机部署&#xff0c;5GC网元分别…

【Redis】字符串原理--简单动态字符串SDS

一.SDS定义 free 属性值为0&#xff0c;标识SDS没有分配任何未使用空间。len 属性值为5&#xff0c;标识SDS保存了一个5字节长度的字符串。buf 属性是一个char类型数组&#xff0c;数组的前5个字节保存了&#xff0c;R e d i s 五个字符&#xff0c;最后一个保存空字符串 \0…

uniapp android和微信小程序实现PDF在线预览

在使用uniapp开发移动端时&#xff0c;微信开发者工具里webview能正常打开后端接口返回的pdf文件流。正式发布后&#xff0c;在配置了业务域名和服务器域名的前提下&#xff0c;预览pdf文件却只能看到白屏&#xff0c;因此我猜测微信小程序不能通过webview读取文件流。这个想法…

Leetcode—32. 最长有效括号【困难】(动态规划及ranges::max()使用)

2024每日刷题&#xff08;110&#xff09; Leetcode—32. 最长有效括号 栈实现代码 class Solution { public:int longestValidParentheses(string s) {stack<int> st;st.push(-1);int n s.size();int maxn 0;for(int i 0; i < n; i) {if(s[i] () {st.push(i);}…

vulnhub靶场之Thales

一.环境搭建 1.靶场描述 Description : Open your eyes and change your perspective includes 2 flags:user.txt and root.txt. Telegram: machineboy141 (for any hint) This works better with VIrtualBox rathe than VMware 2.靶场地址 https://www.vulnhub.com/entry/t…

platform tree架构下i2c应用实例(HS3003)

目录 概述 1 探究platform tree下的i2c 1.1 platform tree下的i2c驱动 1.2 查看i2c总线下的设备 1.3 使用命令读写设备寄存器 2 认识HS3003 2.1 HS3003特性 2.2 HS3003寄存器 2.2.1 温湿度数据寄存器 2.2.2 参数寄存器 2.2.3 一个参数配置Demo 2.3 温湿度值转换 2.…

ubuntu22.04 经常死机,鼠标,键盘无响应

一、现象说明 1. 开机一小时后&#xff0c;突然之间网络掉线&#xff0c;鼠标、键盘无反应。 2.强制重启后&#xff0c;恢复正常。 3.多次重复出现该问题。 二、环境说明&#xff1a;内核、显卡 三、异常日志&#xff1a; /var/log/syslog: 四、问题解答&#xff1a; 1.…

Linux 路由配置与使用

概念 路由信息用于指导数据包从源地址查找到目的地址传输路径的信息&#xff1b; 路由分类 根据路由信息的来源分为静态路由和动态路由 静态路由 由管理员手动配置的路由表项信息&#xff0c;根据路由形式的不同&#xff0c;静态路由又可细分为&#xff1a; 直连路由&#xf…

asp.net core 依赖注入 实例化对象实例

在面向对象编程中&#xff0c;推荐使用面向接口编程&#xff0c;这样我们的代码就依赖于服务接口&#xff0c;而不是依赖于实现类&#xff0c;可以实现代码解耦。 名称解释&#xff1a; 我们把负责提供对象的注册和 获取功能的框架叫作“容器”&#xff0c; 注册到容器中的对象…

java设计模式:观察者模式

在平常的开发工作中&#xff0c;经常会使用到设计模式。合理的使用设计模式&#xff0c;可以提高开发效率、提高代码质量、提高代码的可拓展性和维护性。今天来聊聊观察者模式。 观察者模式是一种行为型设计模式&#xff0c;用于对象之间一对多的依赖关系&#xff0c;当被观察对…

vue前端+nodejs后端通信-简单demo

本文记录vue前端nodejs后端通讯最简单的方法&#xff0c;供广大网友最快速进入全栈开发。 技术架构 前端 vue axios 后端 nodejs express 一、前端部分-搭建VUE 项目 vue create Vnodenpm run serve 启动&#xff1b; 具体操作步骤&#xff0c;请自行百度&#xff0c;这里没…

MySQL-----约束

目录​​​​​ 约束 一 主键约束 1-1 操作-添加单列主键 1-2 操作-添加多列主键 1-3 修改表结构添加主键 1-4 删除主键约束 二 自增长约束 2-1 指定自增长字段的初始值 2-2 删除自增列 三 非空约束 3-1 创建非空约束 3-2 删除非空约束 四 唯一约束…

算法学习——华为机考题库10(HJ64 - HJ69)

算法学习——华为机考题库10&#xff08;HJ64 - HJ69&#xff09; HJ64 MP3光标位置 描述 MP3 Player因为屏幕较小&#xff0c;显示歌曲列表的时候每屏只能显示几首歌曲&#xff0c;用户要通过上下键才能浏览所有的歌曲。为了简化处理&#xff0c;假设每屏只能显示4首歌曲&a…