C++ 八数码问题理解 `IDA*` 算法原则:及时止损,缘尽即散

1.前言

八数码是典型的状态搜索案例。如字符串转换问题、密码锁问题都是状态搜索问题。

状态搜索问题指由一种状态转换到到最终状态,求解中间需要经过多少步转换,或者说最小需要转换多少步,或者说有多少种转换方案。本文和大家聊聊八数码问题的IDA*算法解决方案,也是想通过此问题,深入理解IDA*算法的的底层思维逻辑。

2. 八数码问题

问题描述:

八数码问题,也称为拼图问题。指由9块可滑动的方块构成一个3×3的二维拼图,在每一块上都有一个1~9的数字,其中一块方块丢失,称之为0方块。通过0方块与上、下、左、右四个方向的方块交换位置实现移动,求解经过最少的步数实现拼图由最初状态转换到最终状态的路径。如下为八数码问题的最终状态:

1 2 3
4 5 6
7 8 0 

输入描述:

输入一个初始状态。如下所示:

1 2 3
0 4 6
7 5 8    

输出描述:

如果没有答案,则输出unsolvable,否则输出由字母r、l、u、和d组成的字符串,描述需要经过的一系列转换操作。

样例解释:

如上的初始状态只需要经过rdr三步就能转换到最终状态。

2.png

问题分析:

八数码问题中的每一种状态可以看成一个节点,节点与节点之间最终构建的是一棵树模型。八数码问题本质上就是最短路径搜索问题。可以使用深度搜索或者广度搜索进行查找。

对于当前状态,有4个方向可以选择,无论使用广度或者深度搜索,必然会有些搜索的方向会与目标方向背道而驰。背道而驰意味着无谓的消耗。可以使用A*或者IDA*双向BFS进行优化。本文使用IDA*算法优化。

2.1 IDA*算法

IDA*算法本质还是DFS算法。

我们知道,树的特点就是分支繁杂,而答案往往只可能在众多分支中的一条分支上。可以使用剪枝操作,剪掉不必要的分支,这是提高深度搜索性能的最基础优化方案。

深度搜索一旦在一条分支上搜索不到目标时自己会回溯,然后再搜索另一条分支。如果一条分支的深度很深,而此分支上又没有我们所需要的答案,显然,深度搜索会陷入一个无底深渊。所以,需要采用一种策略,及时阻止这种无劳的搜索,让其提前回溯。

如下图所示,DFS正在搜索长度为n的分支线,答案是另一条分支上的值为8的节点。因为搜索的无目性,它会一根筋式的不见黄河不死心向前走。因此DFS会在无效分支线上浪费大量的时间。最好的方式,就是让它及时悬崖勒马,及时止损。

1.png

D*算法的设计目标就是提前阻止这种无底深渊式的搜索。IDA*算法是带有评估函数的迭代加深DFS算法。通俗而言,在搜索过程中设置深度(depth)限制,一旦超过这个深度,便回溯。

迭代加深只有在状态呈指数级增长时才有较好的效果(如八数码问题共有 9!种状态),而A*就是为了防止状态呈指数级增长的。IDA*算法其实是同时运用迭代加深与全局最优性剪枝。IDA*算法发明出来后,可以应用在生活的各个方面,小到你看电脑的屏幕节能,大到LED灯都采用了此算法,加进了LED灯的研发,举个例子,计算机的节能,使用了IDA*算法根据光亮调整亮度,可以减少蓝光辐射以保护长时间盯着电脑的人们,保护了诸如程序员,OIer等等。—摘抄自百度百科。

评估函数f(x)

IDA*算法会初始一个默认最小深度,期待在这个最小深度能搜索到目标。如果找不到,会逐步增加搜索深度。

怎么计算当前的搜索是否能在指定深度内找到或找不到?

IDA*算法通过评估函数f(x)的值评估当前搜索深度的合理性。f(x)=当前深度+未来估计步数。当f(x)>depth(指定深度)时立即回溯。f(x)函数中的当前深度为当前搜索的层次,此值易得。那么未来估计步数怎么计算?

可以使用曼哈顿距离。如下图所示,初始状态可以向如下的 2 个子状态转换。这两个子状态的搜索深度都为1

3.png

最终状态是当0在原来数字8所在位置。站在上帝视角,知道子状态1离最终状态很远,如果继续基于这个状态朝更远的方向搜索是没有必要。可以在搜索过程计算子状态与目标状态的曼哈顿距离判断是否继续还是提前中止。

曼哈顿距离指两点所在的横坐标的绝对值加上坚坐标的绝对值,其值越大,表示两点间隔的较远。如下图子状态中值1和值8的曼哈顿值为4

4.png

除了0滑块,计算当前状态和目标状态中每个位置的曼哈顿距离之和。注意,不需要计算0滑块之间的距离。0所在位置可以认为是一个空的位置,空的位置不存在距离。

平面坐标与线性坐标的转换

拼图可以使用二维数组也可以使用一维数组存储。本文使用一维数组存储,拼图从逻辑结构上是二维数组。所以,就需要把物理上的一维数组坐标转换为逻辑上的二维坐标。

如下图,一维数组中数字4的线性坐标为4

5.png

与一维数组相对应的二维数组如下图所示。数字4在二维数组中的坐标为(1,1)

6.png

其转换公式如下:

  • 4(一维数中的坐标) / 3=1(二维数组中的行坐标)
  • 4(一维数中的坐标) % 3=1(二维数组中的列坐标)

一维数组中4的位置转换后在二维数组中的位置为(1,1)

二维数组中的坐标转换为一维数组中的坐标为上面表达式的逆运算。

  • 3*1(二维数组中的行从标)+1(二维数组中的列坐标)=4(一维数组中的坐标)

编码实现

前期准备:

#include <iostream>
#include <cmath>
using namespace std;
//存储拼图的当前状态
int a[9]= {0};
//能移动的四个方向
int dir[4][2]= {{-1,0},{0,1 },{1,0},{0,-1}};
//记录答案
char ans[100];
//D*算法初始设定的DFS最大深度
int depth=0;
//方向的字符描述
string dirChar="urdl";

曼哈顿距离求解流程:

  • 找到当前状态中的数字(除 0 数字)在最终状态中的位置。如下用一维数组描述了当前状态和最终状态。

7.png

  • 计算两者之间的距离。
  • 累加当前状态中每一个数字的曼哈顿距离之和。

编码实现:

//启发函数,曼哈顿距离(行列差的绝对值之和)
int mhd() {// 距离之和int dis=0;//遍历当前状态中的每一个数字for(int i=0; i<9; i++) {//0 位置不计算其曼哈顿距离if(a[i]!=0) {//累加每一个数字的曼哈顿距离dis+=abs( i/3 -  (a[i]-1)/3  )+abs( i%3-  (a[i]-1)%3  ) ;}}return dis;
}

深度搜索算法:

/*
* space:0 所在的位置,即可移动位置
* curDep: 当前递归的深度
* pre: 的上一个状态中 0 所在位置
*/
bool dfs(int space,int curDep,int pre) {//计算曼哈顿位置int t=mhd();if(t==0) {//找到,结束ans[curDep]='\0';return 1;}//如果深度超过指定的值,则说明在这个深度上无法搜索目标,不必要再继续搜索if(curDep+t>depth)return 0;//向 4 个方向搜索for(int i=0; i<4; i++) {//一维坐标转换为二维坐标int row=space/3+dir[i][0];int col=space%3+dir[i][1];//二维坐标转换为一维坐标int newx=row*3+col;//检查坐标是否越界以及是否回流if(row<0||row>2||col<0||col>2|newx==pre) continue;//交换得到新的状态swap(a[newx],a[space]);//记录状态的转换信息ans[curDep]=dirChar[i];//进入新状态if(dfs(newx,curDep+1,space))return 1;//交换回来,回溯swap(a[newx],a[space]);}return 0;
}

D*算法:

/*D* 算法
* space 初始 0 所在位置
*/
void idaStart(int  space) {while(++depth) {//一步一步设置可搜索的深度if( dfs(space,0,-1) )break;}
}

测试代码:

int main(int argc, char** argv) {string s;int space;cin>>s;for(int i=0; i<9; i++) {a[i]=s[i]-'0';if(s[i]=='0')space=i;}idaStart(space);cout<<ans;return 0;
}

8.png

优化D*算法。D*会为DFS搜索设定深度,如果在指定深度内无法搜索到目标,则以步长值为 1 方式增加深度。其实可以从初始状态到目标状态的曼哈顿距离开始,每次都增加上一次搜索失败的最小深度,从而提高搜索效率。

重构上述代码的核心逻辑:

int minDep=999;//初始设定为一个较大值
/*
* x:x的当前位置
* d: 当前搜索的深度
* pre: x 的上一个位置
*/
bool dfs(int space,int curDep,int pre) {//曼哈顿位置int t=mhd();if(t==0) {//找到ans[curDep]='\0';return 1;}//如果深度超过可能的值,则说明在这个深度上无法搜索目标,不必要在继续搜索if(curDep+t>depth) {minDep=min(minDep,curDep+t) ;return 0;}//向 4 个方向搜索for(int i=0; i<4; i++) {int row=space/3+dir[i][0];int col=space%3+dir[i][1];int newx=row*3+col;//转换为数字if(row<0||row>2||col<0||col>2|newx==pre) continue;swap(a[newx],a[space]);ans[curDep]=dirChar[i];if(dfs(newx,curDep+1,space))return 1;//交换回来,回溯swap(a[newx],a[space]);}return 0;
}void idaStart(int x) {//初始设定为当前状态到最终状态的曼哈顿距离 depth=mhd();while(true) {if( dfs(x,0,-1) )break;//如果没有搜索到,指定上一次失败的深度depth=minDep;}
}

3. 总结

行文之初,本是想同时使用A*双向BFSIDA*算法解决八数码问题。如果仅在文中抛出IDA*的代码,行文的意义不大。内心终究是想借此题来深度研究算法细节,探讨此算法的精妙之处。

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

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

相关文章

Java面试题:volatile专题

王有志,一个分享硬核Java技术的互金摸鱼侠 加入Java人的提桶跑路群:共同富裕的Java人 今天是《面霸的自我修养》第4篇文章,我们一起来看看面试中会问到哪些关于volatile的问题吧。数据来源: 大部分来自于各机构(Java之父,Java继父,某灵,某泡,某客)以及各博主整理文档…

MR专题:体验Apple Vision Pro多元生态内容,拥抱MR供应链机遇

今天分享的是MR系列深度研究报告&#xff1a;《MR专题&#xff1a;体验Apple Vision Pro多元生态内容&#xff0c;拥抱MR供应链机遇》。 &#xff08;报告出品方&#xff1a;方正证券&#xff09; 报告共计&#xff1a;15页 来源&#xff1a;人工智能学派 Apple Vision Pro…

消息队列-RabbitMQ:workQueues—工作队列、消息应答机制、RabbitMQ 持久化、不公平分发(能者多劳)

4、Work Queues Work Queues— 工作队列 (又称任务队列) 的主要思想是避免立即执行资源密集型任务&#xff0c;而不得不等待它完成。我们把任务封装为消息并将其发送到队列&#xff0c;在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时&#xff0c;这些工作…

AVEC-为编译后的可执行程序添加资源

AVEvasionCraftOnline 一个在线免杀的web端程序&#xff0c;可以绕过常见杀软 项目地址&#xff1a;https://github.com/yutianqaq/AVEvasionCraftOnline AVEvasionCraftOnline - 小更新 sha256sum AVEvasionCraftOnline.jar AVEvasionCraftOnline-v1.1.zip 896387a21946b1…

vulnhub练习 DC-1复现及分析

一、搭建环境 1.工具 靶机&#xff1a;DC-1 192.168.200.17 攻击机&#xff1a;kali 192.168.200.13 2.注意 攻击机和靶机的网络连接方式要相同&#xff0c;另外DC-1的网络连接方式我这里采用NAT模式&#xff0c;是与kali的网络连接模式相同的&#xff08;当然亦可以选用桥…

前端使用QGIS工具生成地图

1 找到所需要地图的 json 数据 1.1 查找 json 数据的两个网址&#xff08;个人常用&#xff09; 1.1.1 DataV.GeoAtlas 网站 DataV.GeoAtlas 这个网站不能具体到县内包含的城镇分化&#xff0c;但是对于县级以上的地图数据&#xff0c;使用起来很方便。 1.1.2 POI数据 网站 …

创作无版权素材:解放创意的利器

title: 创作无版权素材&#xff1a;解放创意的利器 date: 2024/2/21 13:52:09 updated: 2024/2/21 13:52:09 tags: 无版权创作自由法律合规节省成本提升质量多样素材创意工具 在当今数字化时代&#xff0c;内容创作成为了一种非常重要的方式来传达信息和表达创意。 然而&#…

常见锁策略以及CAS

目录 1.1乐观锁&悲观锁 1.2轻量级锁&重量级锁 1.3自旋锁&挂起等待锁 1.4互斥锁&读写锁 1.5可重入锁&不可重入锁 1.6公平锁&非公平锁 1.7synchronized的特点 2.CAS(Compare and swap) 2.1.是什么 2.2.基于CAS方式实现的线程安全优缺点 2.3.使用场景…

设计模式----工厂模式

工厂模式 工厂模式即建立创建对象的工厂&#xff0c;实现创建者和调用者分离。 简单工厂模式&#xff1a;该模式对对象创建管理方式最为简单&#xff0c;因为他简单的对不同类对象的创建进行了一层薄薄的封装。该模式通过向工厂传递类型来指定要创建的对象。 工厂方法模式&am…

JVM对象的创建流程与内存分配

对象的创建流程与内存分配 创建流程对象内存分配方式内存分配安全问题对象内存分配流程【重要】:对象怎样才会进入老年代?重点 案例演示:对象分配过程大对象直接进入老年代02-对象内存分配的过程: 创建流程 加载 验证 解析 准备 初始化 使用 写在 对象内存分配方式 内存分配…

GPT-SoVITS-WebUI 克隆声音 macos搭建

强大的少样本语音转换与语音合成Web用户界面 macos运行参考 macos conda create -n GPTSoVits python3.9 conda activate GPTSoVits激活环境 conda activate GPTSoVits停用 conda deactivate mkdir GPTSoVits cd GPTSoVits git clone https://github.com/RVC-Boss/GPT-SoVITS…

算法项目(1)—— LSTM+CNN+四种注意力对比的股票预测

本文包含什么? 项目运行的方式(包教会)项目代码(在线运行免环境配置)不通注意力的模型指标对比一些效果图运行有问题? csdn上后台随时售后.项目说明 本项目实现了基于CNN+LSTM构建模型,然后对比不同的注意力机制预测股票走势的效果。首先看一下模型结果的对比: 模型MS…

2024年2月的TIOBE指数,go语言排名第8,JAVA趋势下降

二月头条&#xff1a;go语言进入前十 本月&#xff0c;go在TIOBE指数前10名中排名第8。这是go有史以来的最高位置。当谷歌于2009年11月推出Go时&#xff0c;它一炮而红。在那些日子里&#xff0c;谷歌所做的一切都是神奇的。在Go出现的几年前&#xff0c;谷歌发布了GMail、谷歌…

枚举类(enum)

优质博文&#xff1a;IT-BLOG-CN ​ 枚举类&#xff1a; 就是对象的实例个数是确定的&#xff08;例如&#xff1a;单例模式&#xff09;&#xff0c;也就说我们在创建枚举类的时候&#xff0c;会对构造器进行设置 一、自定义创建枚举类 为什么需要枚举类&#xff1f; 【1】…

我国无水氢氟酸产量逐渐增长 东岳集团市场占比较大

我国无水氢氟酸产量逐渐增长 东岳集团市场占比较大 无水氢氟酸是一种十分重要的化工产品&#xff0c;在常温常压下多表现为一种无色发烟液体。无水氢氟酸具有吸水性强、化学活性高、介电常数高、阻燃性能好等优点。经过多年发展&#xff0c;无水氢氟酸制备方法已经成熟&#xf…

Spring Cloud Alibaba-04-Sentinel服务容错

Lison <dreamlison163.com>, v1.0.0, 2023.09.10 Spring Cloud Alibaba-04-Sentinel服务容错 文章目录 Spring Cloud Alibaba-04-Sentinel服务容错高并发带来的问题服务雪崩效应常见容错方案Sentinel入门什么是Sentinel微服务集成Sentinel安装Sentinel控制台 实现一个接…

【前端】前端三要素之BOM

写在前面&#xff1a;本文仅包含BOM内容&#xff0c;JavaScript传送门在这里&#xff0c;DOM传送门在这里。 本文内容是假期中刷的黑马Pink老师视频&#xff08;十分感谢Pink老师&#xff09;&#xff0c;原文保存在个人的GitLab中&#xff0c;如果需要写的网页内容信息等可以评…

QT设置窗口随窗体变化(窗口文本框随窗体的伸缩)

目录 1.建立新窗口2.最终效果 1.建立新窗口 1&#xff09;在窗体中创建一个 textBrowser&#xff0c;记录坐标及宽高 X-100 Y-130 宽-571 高-281&#xff0c;窗体宽高800*600&#xff1b; 2&#xff09;在.h头文件中插入void resizeEvent(QResizeEvent *event) override;函数 …

挑战!贪吃蛇小游戏的实现(3)

经过&#xff08;1&#xff09;&#xff08;2&#xff09;两篇文章的介绍&#xff0c;相信大家对该游戏的实现已经有了具体的思路&#xff0c;废话不多说&#xff0c;让我们开始实现相关的代码吧&#xff01; 1.游戏主逻辑 void test() {int ch 0;srand((unsigned int)time(NU…

【Unity3D】ASE制作天空盒

找到官方shader并分析 下载对应资源包找到\DefaultResourcesExtra\Skybox-Cubed.shader找到\CGIncludes\UnityCG.cginc观察变量, 观察tag, 观察代码 需要注意的内容 ASE要处理的内容 核心修改 添加一个Custom Expression节点 code内容为: return DecodeHDR(In0, In1);outp…