双向搜索的理解和板子

"互相奔赴,各司其职。“ ——双向搜索

双搜的要求:

当我们发现,要从一种状态开始,经过很多次操作,来得到一种给定的状态。

这时候,我们就可以考虑用双向搜索。

从起点和终点开始搜。当二者相遇,输出答案即可。

我们需要用到的算法就是bfs。

让我们用一道题来领略一下双搜。

P1379 八数码难题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意很简单,我们需要不断交换0,与其他数的位置,使得其能到达最终状态。

而我们要求到达最终状态的路径最短,所以考虑bfs。

无论0在哪个位置,它都可以往四个方向交换,当然,这里面有很多无效的操作,所以我们考虑剪枝,如果它交换后的坐标,是合法坐标,那么我们认为这次交换时有效的。

2  8  3
1  0  4
7  6  5

在0处,我们上下左右都进行交换,然后判断交换后的0的坐标是否在这个3x3的矩阵里面就行了。

预处理:

记录状态、

由于双向搜索,那么我们需要同时记录每一边的这些信息:

  • 走到当前状态时的字符串

  • 走到当前状态时的步数

  • 走到当前状态时的0的坐标

这些我们可以开一个类/结构体,然后再定义两个队列,数据类型为结构体。

检查状态、

双向搜索时,我们需要判断当前状态是否被另一边搜过了。

所以我们考虑用map来存状态。

map的键就是字符串,map的值就是走到这个字符串的步数。

所以我们只需要判断map里这个键是否存在就可以知道有没有被搜过。

关键步骤:

搜索过程

假如当前状态是:

2  8  3
1  0  4
7  6  5

我们写一个坐标偏移数组:

int Movex[] = { 0,0,-1,1 }; //在字符串中,向左移动,向右移动,向上移动,向下移动。
int Movey[] = { -1,1,0,0}; //在字符串中,向左移动,向右移动,向上移动,向下移动。

然后我们移动之后,判断一下是否在矩形内部:

for(int j=0 ; j<4 ;j++){
​int nowx = x + Movex[j];int nowy = y + Movey[j];
​if (nowx >= 0 and nowx <= 2 and nowy >= 0 and nowy <= 2){
​..........}
​
}

如果在矩形内部,我们直接通过二维坐标,反解出0在字符串里的位置即可。

2  8  3
1  0  4  ----> 283104765
7  6  5

然后交换0和移动的位置上的数。

再进行一次判断,判断交换完了之后,是否当前状态被对方搜到过,如果搜到,那么返回答案,否则将新状态加入队列和map中。

步数直接由上一个状态的步数+1.

记得交换完了之后再交换回去。

关键思想

双向搜索,又叫做meet in the middle

为了最大程度减少空间复杂度,所以我们尽可能让两边搜的次数相等。

我们可以设计一个while循环,当哪一边的队列里的元素多了,我们就搜另一边。

假如一边的队列中所有的元素已经弹完了,那么我们只要把剩下一边的队列单独搜完即可。

下面是我写的此题的代码:

#include <iostream>
#include<cstring>
#include <cmath>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
//    0   1   2 
//   __________       
// 0 | 1   2   3|
// 1 | 8   0   4|
// 2 | 7   6   5|
//   ___________
​
// 每次走就交换一下两个位置。
// 并且将交换后的字符串,存入map里作为键,然后走到这里的步数作为值。
// 交换完之后,检测一下,当前字符串在对方map里是否存在.
// 如果不存在,说明没被搜过。否则被搜过,我们之间返回二者步数之和。
// 
// 并且,为了meet in mid , 也就是最省时。
// 我们要保证,两边搜的步数是一致的。
// 
// 如果一边搜索的步数比另一边大,那么,搜另一半,否则搜这一边。
// 
// 如果,其中已经搜空了一边,那就继续搜另一边。
// 
// 
//
​
class Step {// 每一个类实例,都是一种状态
public:int num; //这种状态下,用的步数string status; // 这种状态下的,字符串int x,y; //0在字符串中的位置Step(int num, string status, int x,int y) :num(num), status(status), x(x),y(y) {}; //类的初始化列表
};
​
​
queue<Step> q[2]; //开一个二维队列,q[i].push(x), i表示从哪里出发。
map<string, ll> m[2]; //开一个二维map,m[i][string]= step ; 第一个参数0代表从答案出发,1代表从样例出发 ,第二个参数是map的键。
​
string target = "123804765";
string now1;
​
int Movex[] = { 0,0,-1,1 }; //在字符串中,向左移动,向右移动,向上移动,向下移动。
int Movey[] = { -1,1,0,0}; //在字符串中,向左移动,向右移动,向上移动,向下移动。
​
​
void expand(int i) {
​Step p = q[i].front(); //取出队头
​q[i].pop(); //删除队头string t = p.status; // 创建一个副本,此时还未交换的状态。bool judge = m[1-i].count(t); // count函数,用来查找m中是否存在该键。也就是查询状态if (judge!=0) {cout<< (m[i][t] + m[1-i][t])<<endl; //如果已经相遇了,就不需要更新,直接返回步数即可exit(0); //不用return,直接退出}
​int x = p.x; //0的横坐标int y = p.y; // 0的纵坐标int pos = 3 * x + y; //0在字符串里的坐标
​for (int j = 0; j < 4; j++) {
​int nowx = x + Movex[j];int nowy = y + Movey[j];
​if (nowx >= 0 and nowx <= 2 and nowy >= 0 and nowy <= 2) { //如果这一次的变化在矩阵内部int nextpos = 3 * nowx + nowy;swap(t[pos], t[nextpos]); //我们直接交换0在字符串里的下标judge = m[1-i].count(t); // 然后判断一下,这个更新的t是否被对面走过
​if (judge == 0) {
​Step temp = Step(p.num + 1, t, nowx, nowy); //将一个step变量初始化q[i].push(Step(p.num + 1, t, nowx, nowy));m[i][t] = p.num + 1; //将新的状态记录到map里
​}else {
​cout << p.num + 1 + m[1 - i][t] << endl;exit(0);
​}swap(t[pos], t[nextpos]);
​}}
​
}
​
​
int main() {
​
​cin >> now1;
​// 将初始化的0的位置信息,存入队列和map中int pos = target.find('0');Step t = Step(0, target, pos / 3, pos % 3);q[0].push(t);m[0][target] =0;
​
​int pos1 = now1.find('0');Step t1 = Step(0, now1, pos1 / 3, pos1 % 3);q[1].push(t1);m[1][now1] = 0;
​while (!q[0].empty() and !q[1].empty()) {if (q[0].size() < q[1].size()) {expand(0);}else expand(1);}while (!q[0].empty())expand(0);while (!q[1].empty())expand(1);
}
​
​

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

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

相关文章

【React】组件性能优化、高阶组件

文章目录 React性能优化SCUReact更新机制keys的优化render函数被调用shouldComponentUpdatePureComponentshallowEqual方法高阶组件memo 获取DOM方式refs如何使用refref的类型 受控和非受控组件认识受控组件非受控组件 React的高阶组件认识高阶函数高阶组件的定义应用一 – pro…

如何用Python进行数据分析(保姆级教程)

有小伙伴在学Python新手教程的时候说学Python比较复杂的地方就是资料太多了&#xff0c;比较复杂。 很多网上的资料都是从语法教起的&#xff0c;花了很多时间还是云里雾里&#xff0c;摸不清方向。今天就给大家来捋一捋思路&#xff01;帮助大家提高学习效率&#xff01; Pyt…

mysql 自动生成随机数

在MySQL中&#xff0c;生成随机数可以使用RAND()函数。以下是一些基本用法&#xff1a; 1. **生成0到1之间的随机浮点数**&#xff1a; sql SELECT RAND(); 2. **生成指定范围内的随机整数**&#xff08;例如&#xff0c;生成1到100之间的随机整数&#xff09;&…

QT网络通信-TCP、UDP通信

时间记录&#xff1a;2024/1/17 pro文件添加模块network 一、TCP服务端 &#xff08;1&#xff09;创建TCP服务器对象QTcpServer &#xff08;2&#xff09;为QTcpServer对象的newConnection信号绑定槽&#xff0c;用来监听TCP客户端的新连接&#xff0c;有新的客户端连接便会…

ArrayList 可以添加 null 值吗?

如果你现在需要准备面试&#xff0c;可以关注我的公众号&#xff1a;”Tom聊架构“&#xff0c;回复暗号&#xff1a;”578“&#xff0c;领取一份我整理的50W字面试宝典&#xff0c;可以帮助你提高80%的面试通过率&#xff0c;价值很高&#xff01;&#xff01; ArrayList 中可…

Markdown 类图绘制详解

✍️作者简介&#xff1a;小北编程&#xff08;专注于HarmonyOS、Android、Java、Web、TCP/IP等技术方向&#xff09; &#x1f433;博客主页&#xff1a; 开源中国、稀土掘金、51cto博客、博客园、知乎、简书、慕课网、CSDN &#x1f514;如果文章对您有一定的帮助请&#x1f…

水电站智能监测泄洪预警系统介绍

一、背景 近年来由于危险河道管理措施不到位&#xff0c;调峰电站泄水风险长期存在&#xff0c;信息通报制度缺失以及民众安全警觉性不高等因素导致的水电站在泄洪时冲走下游河道游客以及人民财产的事故频发。 二、系统介绍 水电站智能监测泄洪预警系统是一种集成了物联网、云…

Java SE入门及基础(20)

目录 类和对象 1. 类的由来 2. 如何定义类 语法 示例 3. 类图 4. 类和对象的关系 解释说明 语法 示例 示例 结论 Java SE文章参考:Java SE入门及基础知识合集-CSDN博客 类和对象 1. 类的由来 人们在日常生活中&#xff0c;经常会将具有相同特征或者相同行为的事…

ElasticSearch的常用增删改查DSL和代码

es增删改查常用语法 我们日常开发中&#xff0c;操作数据库写sql倒是不可能忘记&#xff0c;但是操作es的dsl语句有时候很容易忘记&#xff0c;特地记录一下方便查找。 DSL语句 1、创建索引 -- 创建索引 PUT /my_index {"mappings": {"properties": {&…

AWS Cognito 实战指南

Amazon Cognito 是 AWS 提供的一项身份验证和访问控制服务,适用于构建安全的用户身份验证和访问控制功能。本指南将介绍如何使用 AWS Cognito 创建用户池和身份池,并在 Java 、 Python 和JavaScript应用程序中实现用户注册和登录功能。 步骤 1: 创建用户池 登录 AWS 控制台。…

GEE中Landsat、Sentinel、Modis主要数据集区别

一、Landsat 1. Collection 1/2 的区别 Collection 2 是Landsat Level 1 数据的又一次重大再处理&#xff0c;显著提高了绝对地理定位精度。 Collection1Collection2时间跨度1972~2021底1972~至今数据等级level 1level1&#xff1a;1972~2021底 level2&#xff1a;1982~至今 …

docker 安装mysql 并支持远程访问

docker mysql 安装 拉取镜像 docker pull mysql运行 mysql 容器 docker run --name mysql-container -e MYSQL_ROOT_PASSWORDmy-secret-pw -p 3306:3306 -d mysql:tag这里 mysql-container 是您给容器指定的名字&#xff0c;my-secret-pw 应替换为您希望设定的密码。tag 是镜…

路由器初始化配置、功能配置

实验环境 拓扑图 Ip规划表&#xff08;各组使用自己的IP规划表&#xff09; 部门 主机数量 网络地址 子网掩码 网关 可用ip Vlan 市场部 38 192.168.131.0 255.255.255.0 192.168.131.1 2-254 11 研发部 53 192.168.132.0 255.255.255.0 192.168.132.1 2-2…

Oracle21C + PLSQL Developer 15 + Oracle客户端21安装配置完整图文版

一、Oracle21C PLSQL Developer 15 Oracle客户端文件下载 1、Oracl21C下载地址&#xff1a;Database Software Downloads | Oracle 中国 2、 PLSQL Developer 15下载地址&#xff1a;Registered download PL/SQL Developer - Allround Automations 3、 Oracle 客户端下载地址…

全球光伏知名企业-晶科能源联合泛微采知连,建立文控管理平台

晶科能源股份有限公司&#xff08;简称“晶科能源”&#xff09;是一家全球知名、极具创新力的太阳能科技企业。 &#xff08;图片素材来自晶科能源官网&#xff09; 公司战略性布局光伏产业链核心环节&#xff0c;聚焦光伏产品一体化研发制造和清洁能源整体解决方案提供&…

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)--具身智能、强化学习

专属领域论文订阅 VX关注 晓理紫&#xff0c;每日更新论文&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持 分类: 大语言模型LLM视觉模型VLM扩散模型视觉导航具身智能&#xff0c;机器人强化学习开放词汇&#xff0c;检测分割 [晓理紫]每日论文分享…

【LabVIEW FPGA入门】FPGA中的数学运算

数值控件选板上的大部分数学函数都支持整数或定点数据类型&#xff0c;但是需要请注意&#xff0c;避免使用乘法、除法、倒数、平方根等函数&#xff0c;此类函数比较占用FPGA资源&#xff0c;且如果使用的是定点数据或单精度浮点数据仅适用于FPGA终端。 1.整数运算 支持的数…

【Java】IDEA中的JFormDesigner使用教程

目录 1 安装 JFormDesigner 插件2 JFormDesigner 使用教程2.1 新建JFormDesigner Form时的选项2.2 JFormDesigner Form界面布局2.3 JFormDesigner 组件2.3.1 Components基本组件2.3.2 Containers中间容器&#xff08;面板&#xff09;2.3.3 Windows顶级容器&#xff08;窗口&am…

给科研人的 ML 开源发布工具包

什么是开源发布工具包&#xff1f; 恭喜你的论文成功发表&#xff0c;这是一个巨大的成就&#xff01;你的研究成果将为学界做出贡献。 其实除了发表论文之外&#xff0c;你还可以通过发布研究的其他部分&#xff0c;如代码、数据集、模型等&#xff0c;来增加研究的可见度和采…

【从0上手cornerstone3D】如何加载nifti格式的文件

在线演示 支持加载的文件格式 .nii .nii.gz 代码实现 npm install cornerstonejs/nifti-volume-loader// ------------- 核心代码 Start------------------- // 注册一个nifti格式的加载器 volumeLoader.registerVolumeLoader("nifti",cornerstoneNiftiImageVolu…