题目大意

8数码问题,在3x3的矩阵中填入0-8九个数字,0可以和它相邻的数字进行交换。从初始状态到达状态F(3x3的方格从上到下,从左到右,形成的数字串为123456780)所需要最少移动的次数。

题目分析

将3x3矩阵中的当前情形记为一个状态,用9个字符表示。然后根据方格0和它相邻的方格交换来进行状态的转移。可以采用广度优先的方式来进行搜索遍历,直到到达状态F,或者将所有的可能情况都遍历过来。 
    那么,如何判断一个状态是否被遍历过了呢?由于 用9个字符表示状态,每个字符有0-8 共9种可能,因此总共的情形为 9^9 = 387420489,空间复杂度太高,因此需要压缩: 由于9个方格中的数字均不相同,那么通过移动方格能够达到的合理的状态数就只有 9! =362880. 复杂度就可以接受了。因此需要实现将 0-8的一个排列定位到它在0-8全排列中的位置。 
    通过康托展开来实现:

康托展开法, 长度为n的排列 num[1,2....n],其在n的全排列中的序号为 
X = a[1](n-1)! + a[2](n-2)! +... +a[n]*0!; 
其中a[i] 表示在num[i+1, ...n]中比num[i]小的数量

将空间进行压缩之后,可以用广度优先搜索来解决,但是也可以通过A-star 算法来进行优化: 
对每个状态都维护一个 F = G + H,表示该状态的代价。 G表示从初始状态到达该状态所花费的代价,H表示从该状态到达终点状态的估计代价。要求估计代价 H 一定小于等于 从该状态到达终点状态的实际代价! 
将当前可进入的状态按照F进行排序,选择F最小的那个进行扩展。 
H 可以通过估计函数来获得,本题中的估计函数就可以设置为 当前3x3矩阵中方格中的数字和最终状态对应方格中数字不同的方格的总数目。很容易证明 H 一定小于等于 实际从该状态到达终点状态的代价。

实现

#include<iostream>
#include<string.h>
#include<iostream>
#include<queue>
using namespace std;
char init_digit[3][3];
bool visited[362890];
struct Node{
char digit[3][3]; //状态记录
int g; //从起始状态到达该状态,已经花费的代价
int f; //该状态的价值, f = g + h(估价函数)
};
struct Cmp{
bool operator()(const Node& node1, const Node& node2){
return node1.f > node2.f;
}
};
void Init(){
memset(visited, false, sizeof(visited));
}
//康托展开法, 长度为n的排列 num[1,2....n],其在n的全排列中的序号为
//X = a[1]*(n-1)! + a[2]*(n-2)! +... +a[n]*0!;
//其中a[i] 表示在num[i+1, ...n]中比num[i]小的数量 /*逆康托展开
X = a[1]*(n-1)! + a[2]*(n-2)! + ... + a[n]*0!, a[i] 表示 num[i+1, ... n]中小于a[i]的数目
已知X,求出对应的 num[1,2...n]
可知, a[i]肯定 <= n - i. 则 a[i]*(n-i)! <= (n-i)*(n-i)!, 那么 a[i+1]*(n-i-1)! + a[i+2]*(n-i-2)! + ... a[n]*0!
<= (n-i-1)*(n - i - 1)! + (n-i-2)*(n - i - 2)! + .... 1*1! + 0*0! 由数学归纳法可以知道, 1*1! < 2!, 1*1! + 2*2! < 3!, .... 因此
(n-i-1)*(n - i - 1)! + (n-i-2)*(n - i - 2)! + .... 1*1! + 0*0! < (n-i)!
a[i+1]*(n-i-1)! + a[i+2]*(n-i-2)! + ... a[n]*0! < (n-i)! 因此, X / (n-1)! 的商c 即为 a[1], X / (n-1)! 的余数 r / (n-2)! 的商即为 a[2]....
*/ int digit2Int(char arr[3][3]){
// arr[0][0], arr[0][1] ... arr[2][2] 记为 num[1], num[2] ... num[9]
int result = 0, t = 1;
for (int i = 8; i >= 0; i--){
int row = i / 3, col = i % 3;
int k = 0;
for (int j = i + 1; j < 9; j ++)
k += (arr[j / 3][j % 3] < arr[row][col]);
result += k*t;
t *= (9 - i);
}
return result;
} int Estimate(char arr[3][3]){
int sum = 0;
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
if (3 * i + j == 8)
continue;
if (arr[i][j] != 3 * i + j + '1')
sum++;
}
}
return sum;
}
bool Succeed(char arr[3][3]){
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
int target = (3 * i + j + 1) % 9;
if (arr[i][j] != target + '0')
return false;
}
}
return true;
}
int move_step[4][2] = { { -1, 0 }, { 0, 1 }, { 1, 0 }, { 0, -1 } };
int Search(){
priority_queue<Node, vector<Node>, Cmp> pq;
//queue<Node> pq;
int X = digit2Int(init_digit);
visited[X] = true;
Node node; for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
node.digit[i][j] = init_digit[i][j];
}
}
node.g = 0;
node.f = 0 + Estimate(node.digit);
pq.push(node);
while (!pq.empty()){
//node = pq.front();
node = pq.top();
pq.pop();
if (Succeed(node.digit))
return node.g;
char cur_digit[3][3];
int cur_g = node.g;
int zero_r = 0, zero_c = 0;
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
cur_digit[i][j] = node.digit[i][j];
if (cur_digit[i][j] == '0'){
zero_r = i;
zero_c = j;
}
}
}
for (int i = 0; i < 4; i++){
int next_r = zero_r + move_step[i][0];
int next_c = zero_c + move_step[i][1];
if (next_r >= 0 && next_r <= 2 && next_c >= 0 && next_c <= 2){
memcpy(node.digit, cur_digit, sizeof(cur_digit));
node.digit[next_r][next_c] = '0';
node.digit[zero_r][zero_c] = cur_digit[next_r][next_c];
int X = digit2Int(node.digit);
if (visited[X])
continue;
visited[X] = true;
node.g = cur_g + 1;
node.f = node.g + Estimate(node.digit);
pq.push(node);
}
}
}
return -1;
}
int main(){
int T;
scanf("%d", &T);
while (T--){
for (int i = 0; i < 3; i++){
getchar();
scanf("%c %c %c", &init_digit[i][0], &init_digit[i][1], &init_digit[i][2]);
}
Init();
int step = Search();
if (step == -1)
printf("No Solution!\n");
else
printf("%d\n", step);
}
return 0;
}

hiho_100_八数码的更多相关文章

  1. A*算法 -- 八数码问题和传教士过河问题的代码实现

    前段时间人工智能的课介绍到A*算法,于是便去了解了一下,然后试着用这个算法去解决经典的八数码问题,一开始写用了挺久时间的,后来试着把算法的框架抽离出来,编写成一个通用的算法模板,这样子如果以后需要用到 ...

  2. 八数码问题:C++广度搜索实现

    毕竟新手上路23333,有谬误还请指正. 课程设计遇到八数码问题(这也是一坨),也查过一些资料并不喜欢用类函数写感觉这样规模小些的问题没有必要,一开始用深度搜索却发现深搜会陷入无底洞,如果设定了深度限 ...

  3. ACM/ICPC 之 BFS-广搜进阶-八数码(经典)(POJ1077+HDU1043)

    八数码问题也称为九宫问题.(本想查查历史,结果发现居然没有词条= =,所谓的历史也就不了了之了) 在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同.棋盘上还有一个 ...

  4. BFS(八数码) POJ 1077 || HDOJ 1043 Eight

    题目传送门1 2 题意:从无序到有序移动的方案,即最后成1 2 3 4 5 6 7 8 0 分析:八数码经典问题.POJ是一次,HDOJ是多次.因为康托展开还不会,也写不了什么,HDOJ需要从最后的状 ...

  5. 双向广搜+hash+康托展开 codevs 1225 八数码难题

    codevs 1225 八数码难题  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond   题目描述 Description Yours和zero在研究A*启 ...

  6. UVALive 6665 Dragon’s Cruller --BFS,类八数码问题

    题意大概就是八数码问题,只不过把空格的移动方式改变了:空格能够向前或向后移动一格或三格(循环的). 分析:其实跟八数码问题差不多,用康托展开记录状态,bfs即可. 代码: #include <i ...

  7. P1379 八数码问题

    aoapc上的八数码问题,在luogu上也有类似的题,p1379,经典题目,lrj给出了一个算法,同时给出了三种判重的方法.本来想用std::queue改写一下,但是出了各种问题,只好抄代码ac掉这道 ...

  8. [cdoj1380] Xiper的奇妙历险(3) (八数码问题 bfs + 预处理)

    快要NOIP 2016 了,现在已经停课集训了.计划用10天来复习以前学习过的所有内容.首先就是搜索. 八数码是一道很经典的搜索题,普通的bfs就可求出.为了优化效率,我曾经用过康托展开来优化空间,甚 ...

  9. hdu 1043 Eight 经典八数码问题

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1043 The 15-puzzle has been around for over 100 years ...

随机推荐

  1. 分页sql存储过程算法

    /****** Object: StoredProcedure [dbo].[PRO_Pub_FenYe] Script Date: 08/04/2014 11:14:22 ******/ SET A ...

  2. JAVA基础知识之IO——对象序列化

    对象序列化 Java对象序列化(Serialize)是指将Java对象写入IO流,反序列化(Deserilize)则是从IO流中恢复该Java对象. 对象序列化将程序运行时内存中的对象以字节码的方式保 ...

  3. mysql replication之binlog-do-db、binlog-ignore-db

    再次整理管理mysql复制的资料,一直搞不明白为什么mysql的启动参数里面有两个binlog-do-db.binlog-ignore-db 这次仔细的阅读了mysql的帮助手册,整理一下对于一条语句 ...

  4. thinkphp 删除多条记录

    删除id为123456的记录 $ids=array(1,2,3,4,5,6);$maps["id"] = array("in",$ids);$this-> ...

  5. Duilib将UI资源文件打包到exe教程

    转载:http://www.voidcn.com/blog/w839687571/article/p-6001921.html 转载:http://www.voidcn.com/blog/x35698 ...

  6. layoutSubviews,setNeedsDisplay

    一 , layoutSubviews何时调用的问题 //layoutSubviews何时调用的问题,这个方法是当你需要在调整subview的大小的时候需要重写(我这个翻译不严谨,以下是原文:You s ...

  7. 【leetcode❤python】 8. String to Integer (atoi)

    #-*- coding: UTF-8 -*-#需要考虑多种情况#以下几种是可以返回的数值#1.以0开头的字符串,如01201215#2.以正负号开头的字符串,如'+121215':'-1215489' ...

  8. unity3d 游戏对象消失三种方法的区别(enabled/Destroy/active)

    gameObject.renderer.enabled //是控制一个物体是否在屏幕上渲染或显示 而物体实际还是存在的 只是想当于隐身 而物体本身的碰撞体还依然存在的 GameObject.Destr ...

  9. mysql查询中通配符的使用

    mysql查询中通配符的使用     在mysql查询中经常会使用通配符,并且mysql的通配符和pgsql的存在区别(稍候再讨论),而且mysql中还可以使用正则表达式. SQL模式匹配: “_” ...

  10. Java中正则表达式的使用

    public class Test{ public static void main(String args[]) { String str="@Shang Hai Hong Qiao Fe ...