启发式搜索求解八数码问题

  • 问题的定义
  • 问题的解决
    • 解的表示
      • 康托展开
      • 逆康托展开
    • 不可达状态的识别
    • 启发函数
    • open表和close表
    • 其他
    • 搜索过程
  • 结果演示
  • 源代码

问题的定义

又称九宫问题。在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,空格可以不超过边界地上下左右移动

要求解决的问题是:以启发式搜索方法求解给定初始状态和目标状态的最优搜索路径。
例如,那么空格应该向上走一步:

问题的解决

解的表示

将九宫格中数字从上到下、从左到右顺序排列后形成一个9个数字的序列(空格用0表示)来标识九宫格的一种状态。那么初始状态为:

1 2 3 4 0 5 6 7 8

目标状态为:

1 0 3 4 2 5 6 7 8

那么如何唯一的确定一个状态?这里用到了康托展开。

康托展开

cantor展开的公式:
X=an∗(n−1)!+an−1∗(n−2)!+an−1∗(n−3)!+...+a1∗0!X = a_n * (n-1)! + a_{n-1}*(n-2)! + a_{n-1} * (n-3)!+...+a_1*0!X=an​∗(n−1)!+an−1​∗(n−2)!+an−1​∗(n−3)!+...+a1​∗0!
其中,aia_iai​为整数,且0<=ai<i,1<=i<=n0 <= a_i < i, 1<=i<=n0<=ai​<i,1<=i<=n。

aia_iai​表示原数的第i位在当前未出现的元素中是排在第几个。

康拓展开可以求一个排列是所有排列中的第几大,给排列分配了一个唯一的id。


例如:(1,2,3)(1, 2, 3)(1,2,3)组成的排列,怎样知道x=213x = 213x=213是所有排列中的第几大的数?

  • a1=2a_1 = 2a1​=2,比2小的只有1,那么小于2开头的数有1∗2!1*2!1∗2!个。第一个数固定是1,第二个数有两种可能的情况,第三个数只有一种情况。
  • a2=1a_2 = 1a2​=1,比1小的数没有(1~n的排列),那么小于第二位为1的数有0∗1!0*1!0∗1!
  • 因此小于213的(1,2,3)(1, 2, 3)(1,2,3)排列数有1∗2!+0∗1!=21*2! + 0*1! = 21∗2!+0∗1!=2个,可知213是第3大的数,可以认为数213的id为2。
  • 1∗2!+0∗1!=21*2! + 0*1! = 21∗2!+0∗1!=2就是一个康托展开式,实现了全排列到自然数的双向映射

const int factorial[]={1,1,2,6,24,120,720,5040,40320,362880,3628800};//阶乘0-10
//cantor展开,n表示是n位的全排列,num[]表示全排列的数(用数组表示)
int cantor(int num[],int n){int ans=0,sum=0; //sum中存放a[i]的值for(int i=1;i<n;i++){for(int j=i+1;j<=n;j++)if(num[j]<num[i])sum++;ans+=sum*factorial[n-i];//累积sum=0;//计数器归零}return ans+1;
}

逆康托展开

知道一个排列是第几大的数,同样可以反过来求出这个排列是什么。
但是注意反向求时用的是id,而不是第几大。比如刚刚的213是第3大的数,id是2。
那么对排列(1, 2, 3),n=3n = 3n=3 和id=2id = 2id=2做逆康托展开:

  • 2/2!=12/2! = 12/2!=1余0,则a3=1a_3 = 1a3​=1,可知比第一位小的数有1个,所以首位为2
  • 0/1!=00/1! = 00/1!=0余1,则a2=1a_2= 1a2​=1,比第二位小的数有0个,所以第二位为1
  • 自然最后一个数就是3了
  • 得到了213,实现了十进制数到全排列的双射
//康托展开逆运算
void decantor(int x, int n)
{vector<int> v;  // 存放当前可选数vector<int> a;  // 所求排列组合for(int i=1;i<=n;i++)v.push_back(i);for(int i=n;i>=1;i--){int r = x % factorial[i-1];int t = x / factorial[i-1];x = r;sort(v.begin(),v.end());// 从小到大排序a.push_back(v[t]);      // 剩余数里第t+1个数为当前位v.erase(v.begin()+t);   // 移除选做当前位的数}for(int i=0; i<a.size(); i++){cout<<a[i]<<" "; }
}

那么就可以把每个状态用一个十进制数id来表示了,并能够方便的判断两个状态是否相同,搜索过程中经常要判断是否搜索到了目标状态。

需要注意的是,因为空格我是用0表示,所以康托展开和逆康托展开是对于0到n-1的排列做,和1到n的计算过程有一点点区别,当然也可以直接用9表示空格。

不可达状态的识别

如果用户输入的初始状态和目标状态本身不可达,而我们又能提前识别出这种不可达情况,就可以避免很多无谓的尝试和计算。

可以用两个状态的序列逆序值的奇偶性来判断是否可达。注意判断逆序性时不考虑0

  • 数的逆序值:位于这个数前面的比这个数大的数的个数。
  • 序列的逆序值:数列中每个数的逆序值之和

比如:

序列值 2 3 1 5 8 4 6 7
逆序值 0 0 2 0 0 2 1 1

那么这个序列的逆序值就是0+0+2+0+0+2+1+1=60+0+2+0+0+2+1+1 = 60+0+2+0+0+2+1+1=6

结论:
如果两个状态的数字序列的逆序值奇偶性一致,则两状态互相可达。

证明肯定是不会证明的…

启发函数

启发式搜索就是利用知识来引导搜索,尽量避免搜索无效的搜索、减少搜索范围,降低时间复杂度。启发信息的强弱对搜索的过程有重大影响。

对于九宫格问题启发信息一般有两种,分别是:

  • 取目标状态与当前状态相同的节点个数
  • 当前状态每个结点到目标状态相应结点所需步数的总和(曼哈顿距离)

感觉两个都比较合理,但是第二个更加合适。

因为在第一种算出来相同的情况下,往往需要再看第二种谁的步数总和小说明哪个解更优秀。

open表和close表

  • open表中用来记录考察过的点
  • close表中用来记录当前待考察的点

因为已经实现了每个状态都用一个十进制数id标识,那么open表用一个int型的数组就可以了。

而close表需要时刻按照待考察的解的启发信息高低来排序,比较适合存在一个堆里。

其他

为了最后输出步骤,需要一个int型数组parent来指示这个状态是怎样从前一个状态到达的,也就是走的方向,故需要记录前驱结点信息(包括id和方向;

为了中间的搜索过程能够动态展示出来,开启了新线程控制组件更新。
Java Swing开启多线程实现实时内容更新

搜索过程

参考文档

Created with Raphaël 2.3.0开始将初始状态放入close表从close表中取出“最具潜力”的状态(解)该状态是目标状态?输出搜索步骤结束扩展该状态,并将扩展的状态加入close表yesno

结果演示

数字会随着指令移动。


源代码

Java实现(IDE为IDEA),已上传至github

启发式搜索求解八数码问题(Java实现,八数码小项目已开源)相关推荐

  1. java编写家庭收支记录,Java家庭收支记账小项目(java基础)

    Java家庭收支记账小项目(java基础) Java家庭收支记账小项目(java基础) 需求说明: 模拟实现基于文本界面的<家庭记账软件>. 该软件能够记录家庭的收入.支出,并能够打印收支 ...

  2. java植物僵尸_Java小项目之:植物大战僵尸,这个僵尸不太冷!内附素材

    Java小项目之:植物大战僵尸! <植物大战僵尸>是由PopCap Games开发的一款益智策略类单机游戏,于2009年5月5日发售,这款游戏可谓是无人不知无人不晓. 在我身边,上到40岁 ...

  3. java 拼图游戏_Java小项目之:拼图游戏!

    Java小项目之:拼图游戏! 今天教大家用java做出一个拼图游戏,很适合java初学者练手. 所用素材: 部分代码: package picture_mosical; import java.awt ...

  4. java 迷宫游戏_Java小项目之迷宫游戏的实现方法

    项目要求: 一个网格迷宫由n行n列的单元格组成,每个大院个要么是空地(用0表示),要么是障碍物(用1表示),你的任务是找一条从起点到终点的移动序列,其中只能上下左右移动到相邻单元格.任何时候都不能在有 ...

  5. java五子棋难度_Java小项目之:五子棋,你下棋下得过电脑吗?

    Java小项目之:五子棋,你下棋下得过电脑吗? Java五子棋功能要求: 1.创建窗口和设计一个棋盘界面 2.实现鼠标点击,棋子出现,黑白棋轮流下 3.能够判断五子相连输赢 4.添加重新开始,悔棋,退 ...

  6. Java基础之账本小项目

    目录 1.前言 2.功能实现效果图 3.使用技能和软件 4.UML图 5.代码 6.总结 1.前言 断断续续学习了将近两个月吧.JavaSe基础差不多学完了,感觉看得懂代码,却不知道怎么自己写程序,这 ...

  7. Java初学练手小项目---基于awt库,swing库以及MySQL数据库制作简易电影管理系统(一)

    前言 本人是个小小白,初学Java语言,想与一众身为程序猿的各位分享一下自己的知识和想法,达到共同学习的目的,所以想通过写博客的方式分享自己的心得体会,这也是本人第一次写博客,希望能够帮助同样在学习的 ...

  8. java收银台程序gui_javaSE小项目---简易收银台

    import java.util.Scanner; public class CheckStand{ public static void main(String[] args){ menu(); } ...

  9. JAVA语言实现计算器小项目(与Windows附件自带计算器的标准版功能、界面相仿)

    文章目录 一.计算器界面 二.已实现功能 三.准备工作和思路搭建 四.代码与注释(已尽力做到条条代码条条注释了) 六.下载 七.写在后面 一.计算器界面 二.已实现功能 BackSpace:实现退格键 ...

  10. Java面向对像小项目 慕课网Java入门第二季滴滴租车系统

    <span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255) ...

最新文章

  1. SpringBoot中实现quartz定时任务
  2. C#实现类似qq的屏幕截图程序
  3. You Don't Know JS: Scope Closures(翻译)
  4. centos7 df 命令卡死
  5. 交换机端口故障问题解决方法
  6. android 获取对象,在Android中获取LayoutInflater对象的方法
  7. MySQL-Front的安装简介
  8. 大数据下的中国女人,看完惊呆了
  9. mysql 生成 javabean_从MySQL快速生成JavaBean
  10. 缓存设计方案 你了解吗 SpringBoot 快速集成实现一级缓存Redis和二级缓存Caffeine 可自定义扩展
  11. php uploadify下载,JQuery上传插件Uploadify的下载与讲解
  12. Discuz论坛项目架构分析
  13. python绘制分形图形教程_#python绘制分形图形教程#如何用Python绘制Circos图
  14. 那些年, 你读错过的IT名词
  15. Java基础学习:尚硅谷项目三 开发团队调度软件
  16. thx是什么意思_thx..是什么意思呢!
  17. python:flatten()参数详解
  18. 二叉树根结点到叶节点的最短距离
  19. Idea中Git和SVN如何切换
  20. java异常说法正确的是什么意思_以下关于Java异常说法不正确的是( )。

热门文章

  1. 基于Arduino和Mixly(超声波+蜂鸣器)实现距离报警
  2. Shapley算法总结
  3. java web实现markdown_editormd实现Markdown编辑器写文章功能
  4. Toplitz矩阵 Hankel矩阵 Hilbert矩阵
  5. iPad安装老版本APP应用软件
  6. VS2008连接TFS 2010
  7. request.getParameter、request.getParameterValues、request.getParameterMap用法详解
  8. 智能客服问题相似度算法设计——第三届魔镜杯大赛第12名解决方案
  9. win10+VS2017+DX11踩的那些雷
  10. 软件测试服务方案ppt,测试方案(测试策略).ppt