引言

AC这道八数码问题,你和楼教主就是兄弟了。。。


题目描述

在一个3*3的九宫格棋盘里,放有8个数码,数码的数字分别是1~8。棋盘中还有一个位置是空着的,用0表示。可以通过在九宫格里平移数码来改变状态(即空格位在九宫格内能上下左右移动)。数码在任何情况下都不能离开棋盘。给出8个数码的初始状态(没放数码的空格用0表示)和目标状态,问从初始状态到目标状态,最少需要经过多少次移动操作。 例如 初始状态为:

目标状态为:

输入格式

两行 第一行9个数字,用空格隔开,表示初始状态 第二行9个数字,用空格隔开,表示目标状态

输出格式

一个数,即最短路径,如果没有答案,则输出-1

样例

样例输入

2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2

样例输出

31

双向BFS求解

(直接讲解有点难,看一下思路,直接看代码吧,代码详解)

看到这道百度之星的难题,内心不知所措,只知道这个要搜索,而且知道DFS一直搜下去必定超时,所以选用BFS,而BFS也会超,就用双向BFS优化,而且我们知道他的起始状态和最终状态。

首先定义一个 map 判定当前状态是否被访问,分为正向搜索(从初始开始)和反向搜索(从最终开始),每次执行一个换位操作,然后判定是否访问,如果没有,则标记为已访问,再看另一向的搜索是否访问过这个状态,如果访问过则找到答案,输出解即可。

在换位操作时,要判定换位后是否在同一行或同一列

​
if( nstep >= 0 && nstep <= 8 && ( nstep/3==step/3 || nstep%3==step%3 ) )
//判定是否越界或换后是否在同一行列​

如果1~9的全排列,最大的数是987654321,如果我们用一个数组vis[]去记录这个数出现过没有,那该数组最少需要开vis[987654321],空间复杂度相当大!由于全排列中每一个数不重复,所以我们可以把每一个数对应一个映射(即离散化),如123456789最小,它的映射就是1,这样我们发现,离散化后,数字的数量变为了全排列的个数(即9!,362880)。那么我们的vis数组就从亿级变为了十万级,空间复杂度大大缩减!

那么该怎么求这个映射呢?用康托展开

康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,由于全排列的不重复性,因此是可逆的。

康托展开:​

那么如何求这个映射呢?​

X = A[0] * (n-1)! + A[1] * (n-2)! + … + A[n-1] * 0!

A[i] 指的是位于位置i后面的数小于A[i]值的个数,后面乘的就是后面还有多少个数的阶乘​

说明 :这个算出来的数康拖展开值,是在所有排列次序-1的值,因此X+1即为在全排列中的次序(我们喜欢从第1大开始,而这个是从第0大开始的,所以X+1)

(copy的课件。。。)

康托展开(一种hash吧)板子

​
int cantor(int *a , int n ) {int sum = 0 ;for(int i = 0 ; i <= 8 ; ++ i ) {int x = 0 ;for(int j = i ; j <= n ; ++ j )if( a[i] > a[j] ) x ++ ;sum += x*C[n-i];}return sum ;
}
//C[i] 表示 i 的阶乘
​

神级优化

我们知道对于一个初始状态如:264137058               最终状态:815736402

移动0的过程中逆序对奇偶性不变,那么就可以求一个逆序对,如果初始状态与最终状态奇偶不同,则直接判定无解

int cnt1 = 0 , cnt2 = 0 ;for(int i = 0 ; i <= 8 ; ++ i ) //优化,判定是否无解 for(int j = i + 1 ; j <= 8 ; ++ j ) {if( a[i] > a[j] && a[j] != 0 ) cnt1 ++ ;if( g[i] > g[j] && g[j] != 0 ) cnt2 ++ ;}if( cnt1 % 2 == cnt2 % 2 ) printf("%d", tbfs());elseprintf("-1");

代码

详解,仔细看看,理解消化。。

代码可能有点长,分块看,慢慢理解。。。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#define ll long long
using namespace std;int a[10] , g[10] , sta , end ;//a存初始状态,g存最终状态,sta初始0的位置 ,end存最终0的位置
int ste[2][4000000] ;//步数
int C[10] = { 1 , 1 , 2 , 6 , 24 , 120 , 720 , 5040 , 40320 , 362880 };//手打阶乘表
int dir[4] = { 1 , -1 , 3 , -3 };//移动
bool vis[2][4000000] ;//两方向搜索访问 struct node{int mape[10];//状态 int id;//0的位置 int flag;//方向 node(){};node( int *N , int ID , int F ) {//构造函数 memcpy( mape , N , sizeof( mape ) );id = ID ;flag = F ;}
};int cantor(int *a , int n ) {//求康托展开 int sum = 0 ;for(int i = 0 ; i <= 8 ; ++ i ) {int x = 0 ;for(int j = i ; j <= n ; ++ j )if( a[i] > a[j] ) x ++ ;sum += x*C[n-i];}return sum ;
}int tbfs()
{memset( vis , 0 , sizeof(vis) );queue<node> R ;//定义队列 R.push(node( a , sta , 0 )) ;//最终状态搜索方向为 R.push(node( g , end , 1 )) ;//最终状态搜索方向为 1 vis[0][cantor(a,8)] = vis[1][cantor(g,8)] = 1 ;int tem1 , tem2 , step , nstep ;node x ;while( !R.empty() ) {x = R.front() ;//取出队头 step = x.id ;//0的位置 R.pop() ;//去除队头 tem1 = cantor(x.mape,8);//求康拓展开 if( vis[!x.flag][tem1] )//反向搜索访问过,得接,输出 return ste[0][tem1] + ste[1][tem1];for(int i = 0 ; i < 4 ; ++ i ) {nstep = step + dir[i] ;//置换后的0的位置 if( nstep >= 0 && nstep <= 8 && ( nstep/3==step/3 || nstep%3==step%3 ) ) {//判定是否越界或换后是否在同一行列swap( x.mape[nstep] , x.mape[step] );//交换 tem2 = cantor( x.mape , 8 );//新的康托值 if( !vis[x.flag][tem2] ) {vis[x.flag][tem2] = 1 ;//标记为访问 ste[x.flag][tem2] = ste[x.flag][tem1] + 1 ;//步数加1 R.push(node( x.mape, nstep , x.flag ) );//加入搜索队列 }swap( x.mape[nstep] , x.mape[step] );//回溯 }}}
}int main()
{for(int i = 0 ; i <= 8 ; ++  i ) {scanf("%d", &a[i] );if( !a[i] ) {sta = i ;}}for(int i =0 ; i <= 8 ; ++ i ) {scanf("%d", &g[i] );if( !g[i] ) {end = i ;}}//初始输入 int cnt1 = 0 , cnt2 = 0 ;for(int i = 0 ; i <= 8 ; ++ i ) //优化,判定是否无解 for(int j = i + 1 ; j <= 8 ; ++ j ) {if( a[i] > a[j] && a[j] != 0 ) cnt1 ++ ;if( g[i] > g[j] && g[j] != 0 ) cnt2 ++ ;}if( cnt1 % 2 == cnt2 % 2 ) printf("%d", tbfs());elseprintf("-1");return 0 ;
}

A*启发式搜索求解

直接搜索,加一个启发函数,启发函数即 f[n] = g[n] + d[n],g[n] 是实际花费,d[n] 是预估花费(要使 f[n] <= 到目标状态的实际步数),然后直接对当前最优解进行搜索,这就要用到优先队列了,每次取出最优的解进行搜索,就可以使用优先队列,每次取出最优解,而优先队列如果定义的是结构体的话,就要重载一下判定小于或大于

struct node{int a[18] ; //当前的图 int dif , step , now ;node() {}node( int *A , int Dif , int Step , int Now ) {//构造函数 memcpy( a , A , sizeof(a) );dif = Dif ;step = Step ;now = Now ;}bool operator < ( node x ) const{//重载函数,用于优先队列排序 if( dif + step > x.dif + x.step )return 1 ;return 0;}
}

其次就是处理当前图与目标图的不同处的预估,如果直接找不同的块会导致启发函数过小,而增大搜索时间,所以最好的就是用当前图到目标图的曼哈顿距离

int h(int *x ) {//求曼哈顿距离 int sum = 0 ;for(int i = 0 ; i <= 8 ; ++ i ) {if( g[i] != 0 ) {for(int j = 0 ; j <= 8 ; ++ j ) {if( x[j] == g[i] )sum += abs(i-j)/3 + abs(i-j)%3 ;}}}return sum ;
}

当然还会用到之前的康托展开

代码

消化理解啦啦啦啦

代码可能有点长,分块看,慢慢理解。。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <map>
#include <queue>
using namespace std;int g[18] ;
bool vis[4000000] ;
int C[18] = { 1 , 1 , 2 , 6 , 24 , 120 , 720 , 5040 , 40320 , 362880 ,3628800 };
int dir[4] = { 1 , -1 , 3 , -3 };struct node{int a[18] ; //当前的图 int dif , step , now ;node() {}node( int *A , int Dif , int Step , int Now ) {//构造函数 memcpy( a , A , sizeof(a) );dif = Dif ;step = Step ;now = Now ;}bool operator < ( node x ) const{//重载函数,用于优先队列排序 if( dif + step > x.dif + x.step )return 1 ;return 0;}
}sta;int contor( int *A ) {//康托展开 int sum  = 0 ;for(int i = 0 ; i <= 8 ; ++ i ) {int x = 0 ;for(int j = i ; j <= 8 ; ++ j )if( A[i] > A[j] ) x ++ ;sum += x*C[8-i];}return sum ;
}int h(int *x ) {//求曼哈顿距离 int sum = 0 ;for(int i = 0 ; i <= 8 ; ++ i ) {if( g[i] != 0 ) {for(int j = 0 ; j <= 8 ; ++ j ) {if( x[j] == g[i] )sum += abs(i-j)/3 + abs(i-j)%3 ;}}}return sum ;
}int count( int *a , int *b ) {//当前图与目标图是否相同 for(int i = 0 ; i <= 8 ; ++ i ) if( a[i] != b[i] ) return 0 ;return 1 ;
}void A_star() {priority_queue <node> R ;int G[18] ;int rent ;int ste ;int id ;int con ;vis[ contor( sta.a ) ] = 1 ;R.push( sta );while( !R.empty() ) {node p = R.top() ;R.pop() ;for(int i = 0 ; i < 4 ; ++ i ) {id = p.now + dir[i] ;con = contor( p.a );ste = p.step ;memcpy( G , p.a , sizeof( G ) );if( id >= 0 && id <= 8 && ( id/3 == p.now/3 || id%3 == p.now%3 ) ) {swap( G[p.now ], G[id] );con = contor( G ) ;if( count( g , G ) ) {printf("%d", ste + 1 ) ;return ;}if( !vis[con] ) {vis[con] = 1 ;int hdif = h( G );R.push( node( G , hdif , ste + 1 , id ) ); }}}} printf("-1");
}int main()
{for(int i = 0 ; i <= 8 ; ++ i ) {scanf("%d", &sta.a[i] );if( sta.a[i] == 0 )sta.now = i ;}for(int i = 0 ; i <= 8 ; ++ i )scanf("%d", &g[i] );sta.dif = h( sta.a ) ;A_star();return 0;
}

C++解题报告:详解经典搜索难题——八数码问题( 双向BFS A* 求解)相关推荐

  1. python编程if语法-Python编程入门基础语法详解经典

    原标题:Python编程入门基础语法详解经典 一.基本概念 1.内置的变量类型: Python是有变量类型的,而且会强制检查变量类型.内置的变量类型有如下几种: #浮点 float_number = ...

  2. HTTP协议详解+经典面试题

    ✨HTTP协议详解+经典面试题 作者介绍:

  3. Jacoco代码覆盖率报告详解

    一.JaCoCo Jacoco从多种角度对代码进行了分析,包括指令(Instructions,C0 Coverage),分支(Branches,C1 Coverage),圈复杂度(Cyclomatic ...

  4. 【蓝桥杯Python组】2022年第十三届蓝桥杯省赛B组Python解题思路详解

    第十三届蓝桥杯省赛B组Python解题思路详解 因为今年采用线上的举办方式进行比赛,所以组委会对题目做了一定的调整,将原来的5道填空+5道编程题变成了2道填空+8道编程题,据说是为了防止抄袭.其实题目 ...

  5. TCPIP详解Protocol 读书笔记(八) Traceroute程序

    TCP/IP详解:Protocol 读书笔记(八) Chapter8 Traceroute程序 文章目录 TCP/IP详解:Protocol 读书笔记(八) Chapter8 Traceroute程序 ...

  6. 机器学习之基于A*搜索解决八数码问题15数码问题

    针对hdu1043,来说一下A* 搜索.这道题不一定用A* 算法,还可以用双向bfs.但是A*搜索更快,在人工智能方面应用也很广泛. A* 搜索不是像深度优先搜索算法和广度优先搜索算法一样的傻瓜式的埋 ...

  7. 性能测试基础之JMeter聚合报告详解

    提示:聚合报告组件的使用和察看结果树组件的使用方式相同.本篇文章主要是详细的介绍一下聚合报告组件内容,不做示例演示. 1.聚合报告介绍 在使用JMeter进行性能测试时,聚合报告(Aggregate ...

  8. JMeter基础 — JMeter聚合报告详解

    提示:聚合报告组件的使用和察看结果树组件的使用方式相同.本篇文章主要是详细的介绍一下聚合报告组件内容,不做示例演示. 1.聚合报告介绍 在使用JMeter进行性能测试时,聚合报告(Aggregate ...

  9. 微软白皮书:47页报告详解中国芯片设计云技术

    来源:智东西 在多方面因素的推动下,中国的芯片设计行业迎来了前所未有的发展契机.当前,我国芯片设计业的产品范围已经涵盖了几乎所有门类,且部分产品已拥有了一定的市场规模,但我国芯片产品总体上仍然处于中低 ...

最新文章

  1. SpringCloud入门[转]
  2. 协同过滤算法 R/mapreduce/spark mllib多语言实现
  3. include详解 shell_Linux 系统结构详解,看这一篇就够了
  4. 聊聊storm TridentBoltExecutor的finishBatch方法
  5. shell编程系列20--文本处理三剑客之awk常用选项
  6. project下查看那些文件夹是空的
  7. Asp.Net MVC 页面代码压缩筛选器-自定义删除无效内容
  8. Error building SqlSession问题
  9. 嵌入式cc2530单片机ZigBee-流水灯的实验
  10. Roblox、Epic Games和Meta,详解三巨头如何引爆元宇宙
  11. Markdown安装/破解/下载
  12. Python多线程抓取网页图片地址
  13. oneDrive 5T网盘空间申请教程
  14. 天上的街市Unity游戏场景制作案例(一)
  15. 根据身份证获取用户的年龄,性别,生日等
  16. matlab设置x轴和y轴的坐标显示范围和刻度
  17. T-SQL数据库修改、删除
  18. 【C语言】贪吃蛇游戏的实现(二)
  19. 金蝶EAS8.61 金蝶SHR8.61 金蝶EAS8.6 金蝶EAS8.5 金蝶EAS8.2 金蝶EAS8.0 金蝶EAS7.5 金蝶EAS7.03 金蝶EAS7.01 金蝶EAS7.0
  20. Python源码学习(一)

热门文章

  1. 关于北大青鸟ACCP4.0二期上机考试
  2. 【计算机体系结构】计算机体系结构(6) 并行处理技术(2) 多处理机
  3. 性别歧视在日本:“我是女生,所以社会不让我学医”
  4. 双工位视觉点胶机原理——维度自动化
  5. 总结html上有什么会影响搜索引擎seo(更新1.1)
  6. Vue.js-Day08【项目实战(附带 完整项目源码)-day03:订单确认页面、美团支付页面、flex弹性盒子布局】
  7. ios swift5 时间戳 时间差 日期格式 Date 日期字符串
  8. 视觉SLAM十四讲——对极约束(2D-2D)笔记
  9. 电话一点通:基础电话用语(2)
  10. 430是什么?430用于哪些领域?