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

news2024/10/5 19:09:10

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

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

相关文章

【QT 5 +Linux下软件桌面快捷方式+qt生成软件创建桌面图标+学习他人文章+第二篇:编写桌面文件.desktop】

【QT 5 Linux下软件桌面快捷方式qt生成软件创建桌面图标学习他人文章第二篇&#xff1a;编写桌面文件.desktop】 1、前言2、实验环境3、自我学习总结-本篇总结1、新手的疑问&#xff0c;做这件事目的2、了解.desktop3、三个关键目录以及文件编写1、目录&#xff1a;/opt/2、目录…

threeJS 全屏或非全屏状态下鼠标点击获取屏幕位置

使用threeJS引入模型进行点击事件&#xff0c;其实有一个是将获取到坐标位置进行webgl坐标系的转换 全屏状态&#xff1a; 全屏状态下直接利用window.innerWidth和 window.innerHeight进行计算即可&#xff0c;代码如下 // 校验控制器旋转的时候不触发点击事件boxClickEvent(…

【2024软件测试面试必会技能】Selenium(6):元素定位_xpath定位

XPATH是什么 XPATH是一门在XML文档中查找信息的语言&#xff0c;XPATH可用来在XML文档中对元素和属性进行遍历&#xff0c;主流的浏览器都支持XPATH&#xff0c;因为HTML页面在DOM中表示为XHTML文档。Selenium WebDriver支持使用XPATH表达式来定位元素。 Xpath常用如下6种定位…

《论文阅读》e-CARE:探索可解释因果推理的新数据集 ACL2022

《论文阅读》e-CARE:探索可解释因果推理的新数据集 ACL2022 前言简介数据集优势数据集语料级别的统计数据集示例评分标准前言 今天为大家带来的是《e-CARE: a New Dataset for Exploring Explainable Causal Reasoning》 出版:ACL 时间:2022 类型:因果推理 关键词:情绪…

virtualbox虚拟机运行中断,启动报错“获取 VirtualBox COM 对象失败”

文章目录 问题现象排查解决总结 问题现象 2月7日下午四点多&#xff0c;我已经休假了&#xff0c;某县的客户运维方打来电话&#xff0c;说平台挂了&#xff0c;无法访问客户是提供的一台Windows server机器部署平台&#xff0c;是使用virtualbox工具安装的CentOS7.9虚拟机和运…

Linux基础知识——Linux是什么及发展史

文章目录 Linux是什么Linux之前Unix发展史MulticsUnicsUnixUNIX分支--BSDUNIX分支--System VMinixGUN计划GPLXFree86Linux 开源软件和闭源软件开源软件闭源软件/专利软件(copyright) Linux的内核版本Linux发行版 Linux是什么 Linux到底是操作系统还是应用程序呢&#xff1f;Li…

2024最佳住宅代理IP服务商

跨境出海已成为了近几年的最热趋势&#xff0c;大批量的企业开始开拓海外市场&#xff0c;而海外电商领域则是最受欢迎的切入口。新兴的tiktok、Temu&#xff0c;老牌的Amazon、Ebay&#xff0c;热门的Etsy、Mecari等等都是蓝海一片。跨境入门并不难&#xff0c;前期的准备中不…

论文精读--Noisy Student

一个 EfficientNet 模型首先作为教师模型在标记图像上进行训练&#xff0c;为 300M 未标记图像生成伪标签。然后将相同或更大的 EfficientNet 作为学生模型并结合标记图像和伪标签图像进行训练。学生网络训练完成后变为教师再次训练下一个学生网络&#xff0c;并迭代重复此过程…

图形系统开发实战课程:进阶篇(上)——6.图形交互操作:拾取

图形开发学院&#xff5c;GraphAnyWhere 课程名称&#xff1a;图形系统开发实战课程&#xff1a;进阶篇(上)课程章节&#xff1a;“图形交互操作:拾取”原文地址&#xff1a;https://www.graphanywhere.com/graph/advanced/2-6.html 第六章 图形交互操作:拾取 \quad 在图形系统…

Linux中安装Nginx及日常配置使用

高性能的http服务器/反向代理服务器。官方测试支持5万并发&#xff0c;CPU、内存等消耗较低且运行稳定 使用场景 Http服务器。 Nginx可以单独提供Http服务&#xff0c;做为静态网页的服务器。虚拟主机。 可以在一台服务器虚拟出多个网站。反向代理与负载均衡。 Nginx做反向代理…

创建型设计模式 - 原型设计模式 - JAVA

原型设计模式 一 .简介二. 案例三. 补充知识 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 一 .简介 原型模式提供了一种机制&#xff0c;可以将原始对象复制到新对象&#xff0…

Linux篇:进程

一. 前置知识 1.1冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系 为什么计算机要采用冯诺依曼体系呢&#xff1f; 在计算机出现之前有很多人都提出过计算机体系结构&#xff0c;但最…

vite是什么

vite 是什么 vite —— 一个由 vue 作者尤雨溪开发的 web 开发工具 Vite由两个主要部分组成 dev server&#xff1a;利用浏览器的ESM能力来提供源文件&#xff0c;具有丰富的内置功能并具有高效的HMR生产构建&#xff1a;生产环境利用Rollup来构建代码&#xff0c;提供指令用…

基于SSM的绿色农产品销售系统的设计与实现

随着电子商务在各行各业中的广泛应用,为更多的产品提供了销售渠道。但就目前来看&#xff0c;这些以工业产品为热销的大型综合性电商平台&#xff0c;农产品销售量很不理想。另外&#xff0c;市面上存在专门销售农产品的网站&#xff0c;大部分消费者没有形成在网上购买农产品的…

C语言每日一题(60)对链表进行插入排序

题目链接 力扣网 147 对链表进行插入排序 题目描述 给定单个链表的头 head &#xff0c;使用 插入排序 对链表进行排序&#xff0c;并返回 排序后链表的头 。 插入排序 算法的步骤: 插入排序是迭代的&#xff0c;每次只移动一个元素&#xff0c;直到所有元素可以形成一个有…

虚拟列表【vue】等高虚拟列表/非等高虚拟列表

文章目录 1、等高虚拟列表2、非等高虚拟列表 1、等高虚拟列表 参考文章1 参考文章2 <!-- eslint-disable vue/multi-word-component-names --> <template><divclass"waterfall-wrapper"ref"waterfallWrapperRef"scroll"handleScro…

Kubernetes部署CNI网络组件

目录 1.概述 K8S的三种网络 VLAN和VXLAN的区别 K8S中Pod网络通信 flannel的三种模式 flannel的UDP模式工作原理 flannel的VXLAN模式工作原理 2.部署flannel 在node01节点上操作 在master01节点上操作 3.部署Calico Calico主要由三个部分组成 calico的IPIP模式工作…

Spring6学习技术|Junit

学习材料 尚硅谷Spring零基础入门到进阶&#xff0c;一套搞定spring6全套视频教程&#xff08;源码级讲解&#xff09; Junit 背景 背景就是每次Test都要重复创建容器&#xff0c;获取对象。就是ApplicationContext和getBean两个语句。通过Spring整合Junit&#xff0c;可以…

集合框架之List集合

目录 ​编辑 一、什么是UML 二、集合框架 三、List集合 1.特点 2.遍历方式 3.删除 4.优化 四、迭代器原理 五、泛型 六、装拆箱 七、ArrayList、LinkedList和Vector的区别 ArrayList和Vector的区别 LinkedList和Vector的区别 一、什么是UML UML&#xff08;Unif…

【《高性能 MySQL》摘录】第 3 章 服务器性能剖析

文章目录 3.1 性能优化简介3.1.1 通过性能剖析进行优化3.1.2 理解性能剖析 3.2 对应用程序进行性能剖析3.3 剖析 MySQL 查询3.3.1 剖析服务器负载捕获 MySQL 的查询到日志文件中分析查询日志 3.3.2 剖析单挑查询使用 SHOW PROFILE &#xff08;现已过时&#xff09;使用SHOW ST…