一、题意

经典的八数码问题,有人说不做此题人生不完整,哈哈。给出一个含数字1~8和字母x的3 * 3矩阵,如:

1  2  X

           3 4  6

           7  5  8

现在要你移动x的位置(方向为上、下、左、右),使得这个矩阵为:

1  2  3 

           4  5  6

           7  8 x

求出最后能得到这个解的移动方案,输出移动的操作。(不要求最优解,也就是不要求移动次数最少)

二、题解

这个8数码问题,我们可以把它看成是一个全排列,从一个初始的排列通过移动元素的位置,到达最终的123456789的这种排列,这里的X我们用9代替。

我们知道X有四个选择移动的方向,但是不一定有四个,如果在左上角就只能右移和下移了,还有很多种限制情况。由于数组是从0开始的,所以他的下标应该是:

0  1  2

3  4  5

6  7  8

if( id % 3 != 2)如果id不是2,5,8就可以向右移动;

     if (id % 3 != 0) 如果id不是0,3,6就可以向左移动;

     if( id > 2 ) 如果id大于2就可以向上移动;

     if( id < 6)  如果id小于6就可以向下移动。

     现在我们有了移动的方向了,但是我们知道可能这些选择中包含着以前就走过的状态。那怎么办来避免已经访问过的状态呢,这就用到了把全排列转换成数字的hash函数(这里其实还可以用康托展开),每一个排列都能对应一个数字,如果这个数字出现过就不访问。这可以用一个Boolean数组实现。

这个hash函数的原理用到了变进制和序数对的知识,详细信息请参考:http://blog.sina.com.cn/s/blog_6635898a0100p4re.html

接下来就要用到BFS来找到状态转换路径了,每一个状态用一个类表示,包括前一个状态到本状态的操作本状态的数本状态X所处位置索引前一个状态的hash值

遍历每个状态从0开始,记录可以到达的状态(一次最多可以到达四个),直到num=123456789。例如,0可以到1、2、3,而1可以到4、5,2可以到6、7,...并用

q[tail].pre = head记录之前的的结点,这样到最后的最终结点往前遍历就可以了。q[tail].op = op记录了操作,输出操作就可以了。这就相当于找到了一条路径,路径上的结点记录了到这一步的操作,输出操作就行了。但想之前所说的,这不一定是最优解,这是最快到达的,不一定是操作最少的。

三、java代码

import java.util.Scanner;

class Status{
char operation;
int number, index, previous;
} public class Main{
static int Max = 363000; //总得状态数量, 9!=362880
static Status[] q=new Status[Max];
static int head;
static int tail;
static int factorial[] = {1,1,2,6,24,120,720,5040,40320};
static int Pow[] = {100000000,10000000,1000000,100000,10000,1000,100,10,1};
static boolean[] vis=new boolean[Max];
// 全排列的hash函数。
static int permutationToNumberHash(int num){
int i, j;
int[] n=new int[10];
for(i = 0; i < 9; i ++){
n[i] = num % 10;
num /= 10;
}
int c, key = 0;
for(i = 1; i < 9; i ++){
for(c = 0, j = 0; j < i; j ++)
if(n[j] < n[i])
c ++;
key += c * factorial[i];
}
return key;
} static void exchangeLocation(int num, int a, int b, char op){ // 操作:第a个数和第b个数交换。
int n1, n2;
n1 = num / Pow[a] % 10;
n2 = num / Pow[b] % 10;
num = num - (n1-n2)*Pow[a] + (n1-n2)*Pow[b]; //移动后的数字大小
int key = permutationToNumberHash(num);
if(!vis[key]){
vis[key] = true;
q[tail].operation = op;
q[tail].number = num;
q[tail].previous = head;
q[tail ++].index = b;
}
} static void output(int k){
if(q[k].operation != 0){
output(q[k].previous);
System.out.print( q[k].operation);
}
}
public static void main(String args[]){
Scanner sc=new Scanner(System.in);
int i, num, id , t;
char c;
id=-1;
//读入数据,用9代替x,用一个十进制数表示读入数据。
for(num = i = 0; i < 9; i ++){
c=sc.next().charAt(0);
if(c == 'x'){
t = 9;
id = i;
}else
t = c - '0';
num = 10 * num + t;//顺序存储
}
//初始化每一个状态
for(i=0;i<Max;i++){
q[i]=new Status();
}
boolean flag = false;
head = 0;
tail = 1;
//q[0]表示x所在的的id和状态数
q[0].index = id;
q[0].number = num;
//广度搜索找出一条路径,最后的数是123456789.
/*具体实现:
* 遍历每个状态从0开始,记录可以到达的状态(一次最多可以到达四个)。直到num=123456789。
* 例如,0可以到1、2、3,而1可以到4、5,2可以到6、7,...并用 q[tail].pre = head记录之前的的结点,这样到最后
* 的最终结点往前遍历就可以了。q[tail].op = op记录了操作,输出操作就可以了。
* */
while(tail > head && !flag){
int cnt = tail - head;
while(cnt --!=0){
num = q[head].number;
if(num == 123456789){
flag = true;
break;
}
id = q[head].index;
//注意这里的移动方向是指数的移动方向,不是X的移动方向
//还有id的范围是0~8
/* 0 1 2
* 3 4 5
* 6 7 8
*/
if(id % 3 != 2) //如果id不是2,5,8就可以向右移动
exchangeLocation(num, id, id + 1, 'r');
if(id % 3 != 0) //如果id不是0,3,6就可以向左移动
exchangeLocation(num, id, id - 1, 'l');
if(id > 2) //如果id大于2就可以向上移动
exchangeLocation(num, id, id - 3, 'u');
if(id < 6) //如果id小于6就可以向下移动
exchangeLocation(num, id, id + 3, 'd');
head ++;
}
}
if(flag)
output(head);
else
System.out.println("unsolvable");
}
}

参考:http://blog.sina.com.cn/s/blog_6635898a0100p4sx.html

版权声明:本文为博主原创文章,未经博主允许不得转载。

Poj 1077 eight(BFS+全序列Hash解八数码问题)的更多相关文章

  1. hdu 1043 pku poj 1077 Eight (BFS + 康拓展开)

    http://acm.hdu.edu.cn/showproblem.php?pid=1043 http://poj.org/problem?id=1077 Eight Time Limit: 1000 ...

  2. 【算法】BFS+哈希解决八数码问题

    15拼图已经有超过100年; 即使你不叫这个名字知道的话,你已经看到了.它被构造成具有15滑动砖,每一个从1到15上,并且所有包装成4乘4帧与一个瓦块丢失.让我们把丢失的瓷砖“X”; 拼图的目的是安排 ...

  3. 【基础练习】【BFS+A*】codevs1225八数码难题题解

    题目描写叙述 Description Yours和zero在研究A*启示式算法.拿到一道经典的A*问题,可是他们不会做,请你帮他们. 问题描写叙述 在3×3的棋盘上,摆有八个棋子,每一个棋子上标有1至 ...

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

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

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

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

  6. POJ 1077 Eight (BFS+康托展开)详解

    本题知识点和基本代码来自<算法竞赛 入门到进阶>(作者:罗勇军 郭卫斌) 如有问题欢迎巨巨们提出 题意:八数码问题是在一个3*3的棋盘上放置编号为1~8的方块,其中有一块为控制,与空格相邻 ...

  7. HDU 1043 & POJ 1077 Eight(康托展开+BFS+预处理)

    Eight Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 30176   Accepted: 13119   Special ...

  8. HDU 1043 & POJ 1077 Eight(康托展开+BFS | IDA*)

    Eight Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 30176   Accepted: 13119   Special ...

  9. Eight POJ - 1077 HDU - 1043 八数码

    Eight POJ - 1077 HDU - 1043 八数码问题.用hash(康托展开)判重 bfs(TLE) #include<cstdio> #include<iostream ...

随机推荐

  1. 九度OJ 1323:World Cup Betting(世界杯) (基础题)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:492 解决:219 题目描述: With the 2010 FIFA World Cup running, football fans th ...

  2. PHP计算多少秒/分/时/天/周/月/年之前 : timeago

    function timeago( $ptime ) { $etime = time() - $ptime; if ($etime < 59) return '刚刚'; $interval = ...

  3. python(pytest)+allure+jenkins 实现接口自动化的思路

    效果图镇楼: 上述各模块作用: python(pytest): 1:用于读测试用例(本次用例写在csv文件中) 2:环境配置相关 3:提取1中的测试数据,组成请求体 4:发送请求 5:获取结果 6:断 ...

  4. 全栈JavaScript之路( 二十四 )DOM2、DOM3, 不涉及XML命名空间的扩展

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/hatmore/article/details/37658167 (一)DocumentType 类型 ...

  5. Linux shell join命令详解

    Linux join命令 2012-02-09 17:49:00| 分类: SHELL | 标签:linux join 文件连接 |字号 订阅join命令 功能:“将两个文件里指定栏位同样的行连接起来 ...

  6. Effective java -- 8 异常

    第五十七条:只针对异常的情况才使用异常应该都有这个意识吧,就像什么抓索引越界什么的,没有必要. 第五十八条:对可恢复情况使用受检查异常,对编程错误使用运行时异常三种可抛的异常:受检的异常(checke ...

  7. Data Structure Array: Given an array of of size n and a number k, find all elements that appear more than n/k times

    http://www.geeksforgeeks.org/given-an-array-of-of-size-n-finds-all-the-elements-that-appear-more-tha ...

  8. ubuntu配置jdk环境

    简单记录如下: 1. 下载JDK并解压后,复制到想要放置的目录,本文以“/usr/lib/jdk”为例: 2. 编辑配置文件,可以是“/etc/profile”或者“~/.bashrc”. 输入命令“ ...

  9. 常量池、perm(持久代)、方法区、栈

      常量池.perm(持久代).方法区.栈 常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据. 除了包含代码中所定义的各种基本类型(如:int.long等等)和对象型(如St ...

  10. Docker-Mac安装

    1. 下载安装包2. 安装3. 运行,允许docker获得系统权限,它需要将Mac网卡链接至Docker app.4. 验证 打开terminaldocker versionFengZhendeMac ...