1464138053
不得不说的八数码之九宫重排
距离第七届蓝桥杯决赛还有2天
问题描述
如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。
我们把第一个图的局面记为:12345678.
把第二个图的局面记为:123.46758
显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。
输入格式
输入第一行包含九宫的初态,第二行包含九宫的终态。
输出格式
输出最少的步数,如果不存在方案,则输出-1。
样例输入
12345678.
123.46758
样例输出
3
样例输入
13524678.
46758123.
样例输出
22
解题思路
由此题可以联想到八数码问题,百度查了资料有各种A各种IDA等等解法
此题我先使用了BFS,发现有两个问题,第一是状态转换,二是状态的标记,很麻烦
网上有种标记法是康托展开,是根据a10!+a21!+a32!+a43!+…+a9*8!相加得出的hash值
这个hash值就是当前表示的状态,那么就很方便了
后来我用了5的次方根表示hash码,运算速度比上面的hash快一倍,但是发现有一组测试数据不通过,所以生成的hash还不是唯一的,不能使用
我用的是a15^0+a25^1+a35^2+…+a95^8
那么状态的转换又是一个问题,Java没有C语言C++那么有个swap函数,一开始我用了原来状态每次进行状态变换
发现最后全部值都为0!这是一个很严重的问题,原来每次都没有把值给换过来,后来想了想用了个备份临时数组每次获取他对他进行操作
Oh yeah~终于搞掂晒
后来又用了Java集合类HashMap+BFS双搜算法,速度明显快很多
BFS双搜算法是两个队列分别从起始状态跟目标状态开始,然后每次都相互判断Hash码是否存在以达到可通行出路,即互通状态
BFS+Hash 实现代码
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
/**
BFS+HASH 九宫重排
@author Ape
/
public class BfsHash {
static final int MAX = 3;
static final int[] hash = { 1, 1, 2, 6, 24, 120, 720, 5020, 40320 }; // Hash生成规则
static int[][] dir = new int[][] { { 1, 0 }, { 0, 1 }, { 0, -1 }, { -1, 0 } }; // 移动规则
static int[] vis = new int[400000];
static int ans, sx, sy;
static BfsHash a = new BfsHash();class Node {
int[][] map = new int[MAX][MAX]; int x, y, hash, h; // h为深度,即步数 // map的备份,切记不能直接进行复制 int[][] getCopyMap() { int[][] copy = new int[MAX][MAX]; for (int i = 0; i < MAX; i++) for (int j = 0; j < MAX; j++) copy[i][j] = map[i][j]; return copy; } Node() { } Node(int x, int y, int h, int[][] map, int hash) { this.x = x; this.y = y; this.h = h; this.map = map; this.hash = hash; }
}
// 检查越界
static boolean check(int x, int y) {if (x < 0 || y < 0 || x >= MAX || y >= MAX) return false; return true;
}
// 生成Hash码,康托展开
static int getHash(int[][] map) {int[] t = new int[MAX * MAX]; int k = 0, tHash = 0; for (int i = 0; i < MAX; i++) for (int j = 0; j < MAX; j++) t[k++] = map[i][j]; for (int i = 0; i < 9; i++) { k = 0; for (int j = 0; j < i; j++) if (t[i] < t[j]) k++; tHash += k * hash[i]; } return tHash;
}
static int getHash2(int[][] map) {
int hash = 0, k = 1; for (int i = 0; i < MAX; i++) for (int j = 0; j < MAX; j++) { hash += k * map[i][j]; k *= 5; } return hash;
}
static int bfs(Node start) {
Queue<Node> queue = new LinkedList<Node>(); queue.offer(start); Node top = a.new Node(); int tx, ty, thash, th; while (!queue.isEmpty()) { top = queue.poll(); // 出列 if (top.hash == ans) return top.h; for (int i = 0; i < 4; i++) { tx = top.x + dir[i][0]; ty = top.y + dir[i][1]; if (!check(tx, ty)) continue; // TODO 注意,切记不能直接get原来的map,而且复制map的时候不能直接赋值给一个数组,要遍历赋值 // TODO // 因为直接拿原来的map下一次循环的top.map就不说出列时候的那个,所以每次换位都需要一个临时的数组,并生成hash int[][] tmap = top.getCopyMap(); tmap[top.x][top.y] = tmap[tx][ty]; tmap[tx][ty] = 0; thash = getHash(tmap); if (vis[thash] == 0) { th = top.h + 1; vis[thash] = 1; // 标记 queue.offer(a.new Node(tx, ty, th, tmap, thash)); // 入列,要重新new一个 } } } return -1;
}
// 字符串转int数组
static int[][] string2int(String string) {char[] ch = string.toCharArray(); int[][] m = new int[MAX][MAX]; for (int i = 0; i < ch.length; i++) if (ch[i] != '.') m[i / 3][i % 3] = ch[i] - '0'; else { m[i / 3][i % 3] = 0; // 起始空格位置 sx = i / 3; sy = i % 3; } return m;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); String start = scanner.next(); String end = scanner.next(); Node s = a.new Node(); Node e = a.new Node(); s.map = string2int(start); s.hash = getHash(s.map); s.x = sx; s.y = sy; vis[s.hash] = 1; e.map = string2int(end); e.hash = getHash(e.map); ans = e.hash; // ans为目标状态的hash值 System.out.println(bfs(s)); scanner.close();
}
}
BFS双向搜索 代码实现
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
/**
九宫重排
@author Ape
/
public class JiuGong {static HashMap<String, Integer> hash1, hash2; // 使用Java集合类的MapHash
static int sx, sy, ex, ey, index;
static char[][] start = new char[3][3], end = new char[3][3];
static String startString, endString;
static int[][] dir = new int[][] { { 1, 0 }, { 0, 1 }, { 0, -1 }, { -1, 0 } }; // 移动规则public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); startString = scanner.next(); endString = scanner.next(); long a = System.currentTimeMillis(); index = 0; for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) { start[i][j] = startString.charAt(index); end[i][j] = endString.charAt(index); if (start[i][j] == '.') { sx = i; sy = j; } if (end[i][j] == '.') { ex = i; ey = ; } index++; } Node node1 = new Node(start, sx, sy, 0); Node node2 = new Node(end, ex, ey, 0); Queue<Node> queue1 = new LinkedList<Node>(); Queue<Node> queue2 = new LinkedList<Node>(); hash1 = new HashMap<String, Integer>(); hash2 = new HashMap<String, Integer>(); hash1.put(startString, 0); hash2.put(endString, 0); queue1.offer(node1); queue2.offer(node2); System.out.println(bfs(queue1, queue2)); System.out.println(System.currentTimeMillis() - a); scanner.close();
}
static boolean check(int x, int y) {
if (x < 0 || y < 0 || x >= 3 || y >= 3) return false; return true;
}
static int bfs(Queue
queue1, Queue queue2) { while (!queue1.isEmpty() || !queue2.isEmpty()) { if (!queue1.isEmpty()) { Node node1 = queue1.poll(); // 出列 if (hash2.containsKey(node1.getMapString())) // 判断hash2是否存在该状态 return node1.getCount() + hash1.get(node1.getMapString()); int x = node1.getX(); int y = node1.getY(); for (int i = 0; i < 4; i++) { int tx = x + dir[i][0]; int ty = y + dir[i][1]; if (!check(tx, ty)) continue; char[][] t = node1.getCopyMap(); t[x][y] = t[tx][ty]; t[tx][ty] = '.'; Node n = new Node(t, tx, ty, node1.getCount() + 1); String s = n.getMapString(); if (hash2.containsKey(s)) return n.getCount() + hash2.get(s); if (!hash1.containsKey(s)) { hash1.put(s, n.getCount()); queue1.offer(n); } } } if (!queue2.isEmpty()) { Node node2 = queue2.poll(); // 出列 if (hash1.containsKey(node2.getMapString())) // 判断hash1是否存在该状态 return node2.getCount() + hash1.get(node2.getMapString()); int x = node2.getX(); int y = node2.getY(); for (int i = 0; i < 4; i++) { int tx = x + dir[i][0]; int ty = y + dir[i][1]; if (!check(tx, ty)) continue; char[][] t = node2.getCopyMap(); t[x][y] = t[tx][ty]; t[tx][ty] = '.'; Node n = new Node(t, tx, ty, node2.getCount() + 1); String s = n.getMapString(); if (hash1.containsKey(s)) return n.getCount() + hash1.get(s); if (!hash2.containsKey(s)) { hash2.put(s, n.getCount()); queue2.offer(n); } } } } return -1;
}
}
class Node {
char[][] map = new char[3][3];
int x, y, count;
public Node(char[][] map, int x, int y, int count) {
super();
this.map = map;
this.x = x;
this.y = y;
this.count = count;
}
// 字符数组转成字符串
public String getMapString() {
StringBuffer sb = new StringBuffer("");
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
sb.append(map[i][j]);
return sb.toString();
}
public char[][] getCopyMap() {
char copy[][] = new char[3][3];
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
copy[i][j] = map[i][j];
return copy;
}
public char[][] getMap() {
return map;
}
public void setCMap(char[][] map) {
this.map = map;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
测试数据
起始状态 目标状态 步数 BFS+Hash BFS双搜
12345678. 123.46758 3 0ms 0ms
13524678. 46758123. 22 119ms 19ms
12345678. 152743.86 6 0ms 0ms
.14276385 12345678. 26 240ms 41ms
20步以上的差距明显…