HDU1043:Eight(八數碼,經典題型)
原題地址:http://acm.hdu.edu.cn/showproblem.php?pid=1043
題意
這是一道經典的八數碼問題,題目給定一個初始狀態,要求將這個初始狀態轉換爲目標狀態的步驟,目標狀態都是一樣的,都爲1 2 3 4 5 6 7 8 x,注意這道題是Special Judge,即轉換的步驟其實是不止一種,而這裏只要求輸出一種方案即可。
思路
這道題目的解決方案不止一種,事實上有大神總結出了一共有八種方案,詳見這裏,當然這些方案中有的是鐵定TLE的,而有的是小的優化,而我這裏主要實現了其中的三種,分別是BFS+Hash+打表,雙向BFS+Hash,A*+Hash+曼哈頓距離+優先隊列
方案一:BFS+Hash+打表
BFS這裏就不對其進行贅述了,如果不清楚的請看這裏,而對於Hash我想也沒必要進行太多的講解,其實很簡單,當我們對所有解空間進行搜索的時候,每搜索到一種新的狀態我們都需要對其進行保存,而hash就是用來對每一個搜索到的狀態進行一個唯一的編碼,例如我們搜索到一個爲1 2 3 4 5 6 7 8 x
的狀態,我們要將其進行保存,以便後續的訪問,如果保存在list中的話查找十分麻煩,而如果存在數組中,那我們就需要一種編碼方式,對該狀態進行唯一的標識,這就是hash函數的作用,而hash函數是有很多種,所以我們就需要一種最爲合適的。
在該題中,對於每一個狀態,如果我們都將x視爲0的話,那麼這就是一個從0到8的全排列,既然是全排列的話那就簡單了,我們可以直接用該數字作爲一個hash編碼方式,例如狀態爲1 2 3 4 5 6 7 8 0
就直接保存在數組中該數字對應的位置上,但這就會導致大量的空間浪費,那麼我們就需要另一種更優的hash編碼方式,我們可以用每一個排列在所有全排列中的位置作爲hash編碼,例如:0 1 2 3 4 5 6 7 8
在所有全排列中的位置是1,那就將該狀態存在數組1這個位置上。 然而隨之而來的問題是怎麼求這個位置,此時我們就需要引入一個概念,即康託展開。
康託展開
康拓展開定義:
計算公式:X=an*(n-1)!+an-1*(n-2)!+…+ai*(i-1)!+…+a2*1!+a1*0!
注:X爲比該數小的個數,其中ai爲當前元素在未出現的元素中是排在第幾個(從0開始),n指的是該數的位數
例:比如2143 這個數,求其展開:
從頭判斷,至尾結束,注意順序是從左至右。
① 比 2(第一位數)小的數有多少個->1個,換而言之就是2排在第1位,因爲第0位排的是1,所以ai就是1,又因爲該數一共4位,所以n是4,因此這裏ai*(n-1)! -> 1*3!=6
② 比 1(第二位數)小的數有多少個->0個0*2!=0
③ 比 4(第三位數)小的數有多少個->3個就是1,2,3,但是1,2之前已經出現,所以是1*1!=1
將所有乘積相加 6+0+1=7
比該數小的數有7個,所以該數排第8的位置。
1234 1243 1324 1342 1423 1432
2134 2143 2314 2341 2413 2431
3124 3142 3214 3241 3412 3421
4123 4132 4213 4231 4312 4321
康託展開代碼實現:
private static int Cantor(int[] a) {//計算該狀態的康託展開
int res = 0;
int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};//0到9各個數的階乘
for (int i = 0; i < a.length; i++) {
int temp = 0;//比a[i]小的數的個數
for (int j = i + 1; j < a.length; j++) {
if (a[i] > a[j]) temp++;//在i之前出現過比a[i]小的數要去掉
}
res += temp * fac[a.length - i - 1];//計算
}
return res + 1;//返回a在全排列中排第幾位
}
到這裏,我們其實已經可以對該題進行解答了,我們使用BFS對解答樹進行搜索,每搜索到一個新的狀態就求其康拓展開,將到達該狀態的路徑保存到對應的數組中直到搜索到了目標狀態1 2 3 4 5 6 7 8 x
,但這就會出現一個效率問題,因爲該題有N組測試數據,這就導致了我們對每一組測試數據都要重新進行計算,很明顯會TLE,這就需要用到一個信息學競賽的技巧,即打表,所謂打表就是提前對問題所有的解進行求解,並保存在內存中,之後對於每一組測試用例直接返回之前提前解答的結果。
但又有一個問題就是我們並不知道每一次詢問的初始狀態是什麼,那怎麼進行打表呢?這也很簡單,初始狀態我們是不知道,但目標狀態呢?這就是我們要進行打表的對象。我們以目標狀態作爲初始狀態進行逆向打表,這樣在每一次獲得初始狀態時就可以直接輸出結果。
AC代碼:
import java.io.BufferedInputStream;
import java.util.ArrayDeque;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
*
* @author wanyu
* @Date: 2018-01-31
* @Time: 15:09
* To change this template use File | Settings | File Templates.
* @desc 逆向打表+BFS+哈希
*/
public class Main {
private static int[] move = {-1, -3, 1, 3};//移動數組
private static int[] map;//存儲八數碼
private static boolean[] visited;//判斷是否已經訪問過
private static String[] path = new String[363000];//保存已經遍歷過的狀態,使用哈希進行空間壓縮
private static void init(Scanner in) {//初始化信息
String[] line = in.nextLine().split("");//讀取輸入
map = new int[9];
int j = 0;
for (int i = 0; i < line.length; i++) {
if (line[i].equals(" ")) continue;
if (line[i].equals("x")) {
map[j++] = 0;//對X進行轉換
continue;
}
map[j++] = Integer.parseInt(line[i]);
}
}
private static void create() {//進行打表操作
int[] num = {1, 2, 3, 4, 5, 6, 7, 8, 0};//以目標狀態作爲初始狀態進行逆向打表
int cantor = Cantor(num);//計算康拓展開
path[cantor] = "lr";//初始狀態
visited = new boolean[363000];//用於判重
Node node = new Node(num, new StringBuffer(""), cantor, 8);//初始節點
BFS(node);
}
public static void main(String[] args) {
Scanner in = new Scanner(new BufferedInputStream(System.in));
create();//進行打表
while (in.hasNext()) {
init(in);//初始化
int cantor = Cantor(map);//計算初始狀態的康託
if (path[cantor] == null) {//如果沒有到該狀態的路徑表示無解
System.out.println("unsolvable");
continue;
}
StringBuffer stringBuffer = new StringBuffer(path[cantor]);
//注意因爲是以目標狀態作爲初始狀態進行打表的
// 所以該路徑是從目標狀態到初始狀態的,所以在輸出時需要進行反轉
System.out.println(stringBuffer.reverse());
}
}
private static void BFS(Node start) {//使用BFS進行解答樹的搜索
ArrayDeque<Node> queue = new ArrayDeque<>();
queue.add(start);
while (!queue.isEmpty()) {
Node node = queue.poll();//拋出隊首元素
for (int i = 0; i < 4; i++) {//對該節點進行擴展
int index = node.index;
int new_index = index + move[i];//確定x下一個位置
//判斷當前位置是否可以移動
if ((index == 2 || index == 5 || index == 8) && i == 2) continue;
if ((index == 0 || index == 3 || index == 6) && i == 0) continue;
if (new_index >= 0 && new_index <= 8) {//邊界處理
Node new_node = new Node(node.state, node.path, node.index, node.cantor);//定義新的節點
//更新數據
new_node.state[index] = new_node.state[new_index];//直接賦值
new_node.state[new_index] = 0;
new_node.index = new_index;
int cantor = Cantor(new_node.state);//計算該狀態的康託展開
if (visited[cantor]) continue;//如果該狀態已經訪問過,直接進入下一個狀態
new_node.cantor = cantor;
visited[cantor] = true;//標記該狀態已經訪問過了
switch (move[i]) {//添加路徑
case 1:
new_node.path.append("l");
break;
case -1:
new_node.path.append("r");
break;
case -3:
new_node.path.append("d");
break;
case 3:
new_node.path.append("u");
break;
default:
break;
}
path[cantor] = new_node.path.toString();//保存路徑信息
queue.add(new_node);//將該節點添加到隊列中
}
}
}
}
private static int Cantor(int[] a) {//計算該狀態的康託展開
int res = 0;
int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};//0到9各個數的階乘
for (int i = 0; i < a.length; i++) {
int temp = 0;//比a[i]小的數的個數
for (int j = i + 1; j < a.length; j++) {
if (a[i] > a[j]) temp++;//在i之前出現過比a[i]小的數要去掉
}
res += temp * fac[a.length - i - 1];//計算
}
return res + 1;//返回a在全排列中排第幾位
}
}
class Node {
int[] state;//當前狀態
StringBuffer path;//路徑
int index;//x的位置
int cantor;//康託展開
public Node(int[] state, StringBuffer path, int cantor, int index) {
this.state = new int[state.length];
System.arraycopy(state, 0, this.state, 0, state.length);
this.path = new StringBuffer(path);
this.index = index;
this.cantor = cantor;
}
}
方案二:雙向BFS+Hash
雙向BFS思路其實和單向BFS一樣,只不過這裏從兩個節點開始搜索,即同時從初始狀態和目標狀態進行搜索,如果二者在中間狀態相遇就表示找到了解,雙向BFS相較於單向的BFS解空間減少了,所以搜索效率上優於單項的BFS。
圖示:
單向BFS:
雙向BFS:
然後此處還有一個小的優化點,在上一個方案中,我們對於無解的狀態只需要直接判斷是否能從目標狀態搜索到,對於搜索不到的在path中直接保存爲null即可,但現在我們沒法這樣做,此時我們就需要先判斷該初始狀態是否有解。對於判斷八數碼有無解就需要用到逆序對,具體的證明見:八數碼有解證明,這裏直接給出結論。
八數碼問題的有解無解的結論:
將八數碼的一個狀態表示成一維的形式,求出除0之外所有數字的逆序數之和,也就是每個數字前面比它大的數字的個數的和,稱爲這個狀態的逆序數和。
若兩個狀態的逆序奇偶性相同,則可相互到達,否則不可相互到達。
由於原始狀態的逆序爲0(偶數),則逆序爲偶數的狀態有解。
注:此處之所以將0作爲偶數,是因爲0%2=0,所以將之視爲偶數。
AC代碼:
import java.io.BufferedInputStream;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
*
* @author wanyu
* @Date: 2018-02-02
* @Time: 18:10
* To change this template use File | Settings | File Templates.
* @desc HDU1043 雙向廣搜+HASH
*/
public class Main {
private static int[] move = {1, -3, -1, 3};//移動數組
private static int[] map;//存儲八數碼
private static boolean[] visited1;//判斷從start擴展來的是否已經訪問過
private static boolean[] visited2;//判斷從end擴展來的是否已經訪問過
private static String[] path;
private static int start = 0;//起始點
private static void init(Scanner in) {//初始化信息
String[] line = in.nextLine().split("");//讀取輸入
map = new int[9];
path = new String[363000];
visited1 = new boolean[363000];
visited2 = new boolean[363000];
int j = 0;
for (String aLine : line) {
if (aLine.equals(" ")) continue;
if (aLine.equals("x")) {
start = j;//起始點
map[j++] = 0;
continue;
}
map[j++] = Integer.parseInt(aLine);
}
}
public static void main(String[] args) {
Scanner in = new Scanner(new BufferedInputStream(System.in));
while (in.hasNext()) {
init(in);//初始化
if (judge()) {//判斷有無解
//狀態可達
//進行雙向廣搜
Node snode = new Node(map, Cantor(map), start);//初始化開始節點
int[] num = {1, 2, 3, 4, 5, 6, 7, 8, 0};
int cantor = Cantor(num);
Node enode = new Node(num, cantor, 8);//初始化結束節點
BFS(snode, enode);
} else {
//狀態不可達
System.out.println("unsolvable");
}
}
}
private static void BFS(Node start, Node end) {//雙向廣搜
//兩個初始點已訪問
visited1[start.cantor] = true;
visited2[end.cantor] = true;
//初始化兩個節點的初始路徑
path[start.cantor] = "";
path[end.cantor] = "";
Deque<Node> Squeue = new ArrayDeque<>();
Deque<Node> Equeue = new ArrayDeque<>();
//添加節點
Squeue.add(start);
Equeue.add(end);
while (!Squeue.isEmpty() && !Equeue.isEmpty()) {//兩個同時不爲空,同時對兩個狀態進行擴展
//先擴展初始狀態
Node snode = Squeue.poll();
int Sindex = snode.index;
for (int i = 0; i < 4; i++) {
//邊界情況處理
if ((Sindex == 0 || Sindex == 3 || Sindex == 6) && move[i] == -1) continue;
if ((Sindex == 2 || Sindex == 5 || Sindex == 8) && move[i] == 1) continue;
int newIndex = Sindex + move[i];//新的x的位置
if (newIndex >= 0 && newIndex <= 8) {//新的位置是合法的
Node newNode = new Node(snode.state, snode.cantor, snode.index);//初始化新的節點
newNode.state[Sindex] = newNode.state[newIndex];
newNode.state[newIndex] = 0;
newNode.index = newIndex;
int cantor = Cantor(newNode.state);
if (!visited1[cantor]) { //是否訪問過了
String p = path[snode.cantor] + transform(move[i], 1);//暫時保存路徑
newNode.cantor = cantor;
visited1[cantor] = true;//設爲已訪問
if (visited2[cantor]) {
//如果該節點兩邊都擴展到了,則表示已經找到了
System.out.println(p + new StringBuilder(path[cantor]).reverse());
//因爲從目標狀態過來的,所以需要將路徑進行翻轉操作
return;
}
path[cantor] = p;//保存路徑
Squeue.add(newNode);//入隊
}
}
}
//再擴展目標狀態
Node enode = Equeue.poll();
int Eindex = enode.index;
for (int i = 0; i < 4; i++) {
//邊界情況處理
if ((Eindex == 0 || Eindex == 3 || Eindex == 6) && move[i] == -1) continue;
if ((Eindex == 2 || Eindex == 5 || Eindex == 8) && move[i] == 1) continue;
int newIndex = Eindex + move[i];//新的x的位置
if (newIndex >= 0 && newIndex <= 8) {//新的位置是合法的
Node newNode = new Node(enode.state, enode.cantor, enode.index);//初始化新的節點
newNode.state[Eindex] = newNode.state[newIndex];
newNode.state[newIndex] = 0;
newNode.index = newIndex;
int cantor = Cantor(newNode.state);
if (!visited2[cantor]) { //是否訪問過了
StringBuilder p = new StringBuilder(path[enode.cantor] + transform(move[i], 0));//暫時保存路徑
newNode.cantor = cantor;
visited2[cantor] = true;//設爲已訪問
if (visited1[cantor]) {
//如果該節點兩邊都擴展到了,則表示已經找到了
System.out.println(path[cantor] + p.reverse());
return;
}
path[cantor] = p.toString();
Equeue.add(newNode);//入隊
}
}
}
}
}
/**
* @param x 擴展方向
* @param state 從開始還是結尾擴展而來的
* @return 方向
*/
private static String transform(int x, int state) {
String res = "";
if (state == 1) {//開始節點
switch (x) {
case 1:
res = "r";
break;
case -1:
res = "l";
break;
case 3:
res = "d";
break;
case -3:
res = "u";
break;
}
} else {
switch (x) {
case 1:
res = "l";
break;
case -1:
res = "r";
break;
case 3:
res = "u";
break;
case -3:
res = "d";
break;
}
}
return res;
}
private static boolean judge() {//判斷是否有解只需要判斷逆序對的個數
int num = 0;//逆序數和
for (int i = 0; i < map.length - 1; i++) {
if (map[i] == 0) continue;//如果是0就跳過
for (int j = i + 1; j < map.length; j++) {
if (map[i] > map[j] && map[j] != 0) {
num++;
}
}
}
return (num & 1) == 0;//如果是偶數,表示該狀態可達
}
private static int Cantor(int[] nums) {//計算康託展開式
int res = 0;
int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};//0到9各個數的階乘
for (int i = 0; i < nums.length - 1; i++) {
int sum = 0;//比nums[i]小的數
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] < nums[i]) sum++;
}
res += sum * fac[nums.length - i - 1];
}
return res + 1;
}
}
class Node {
int state[];//狀態
int cantor;//hash值
int index;//x的位置
Node(int[] state, int cantor, int index) {
this.state = new int[state.length];
System.arraycopy(state, 0, this.state, 0, state.length);
this.cantor = cantor;
this.index = index;
}
}
方案三:A*+Hash+曼哈頓距離+優先隊列
A*算法是一類啓發式搜索算法,相比於DFS或BFS這類盲目的搜索更具有了目的性,它對於節點的擴展具有選擇性,它會優先選擇距離目標狀態更近的節點進行擴展。
BFS與A*比較
從圖中可以看出A*比BFS更快速的找到了一條更好的路徑。
BFS:
A*:
對於A*算法,未曾接觸過的人可能會覺得有些許複雜,但對於掌握的人來說其實非常簡單,其實只要將BFS加上一個評估函數就是A*算法,然而雖然形式上非常簡單,但是這個評估函數的選擇卻十分重要,如果選擇了錯誤的評估函數就會導致無法求得結果,對於A*算法可以看這篇文章,我個人認爲講的還是十分通俗易懂的。
在這裏我們A*算法的評估函數選用F=G+H
G:表示從初始狀態到達當前狀態的代價。
H:表示從當前狀態到目標狀態的估計代價,注意這是一個估計值而不是準確值,同時要注意該估計值需要小於等於準確值,雖然H值越大算法運行越快,但是如果大於準確值會漏掉最優解。
在該處我們選用曼哈頓距離作爲H函數,即將1-8這八個數字的當前位置到目標位置的曼哈頓距離之和作爲估計代價。
所謂曼哈頓距離就是指兩個在平面座標中的點,假設分別爲A1(x1,y1)和A2(x2,y2),而曼哈頓距離等於|x1-x2|+|y1-y2|
。除了曼哈頓距離其實還有諸如切米雪夫距離,歐氏距離等等,這些不在我們的討論範圍內,想了解的可以看這裏。
八數碼曼哈頓距離計算:
例:
2 3 4 1 2 3
1 5 x ——> 4 5 6
7 6 8 7 8 x
數字 | 初始位置 | 目標位置 | 曼哈頓距離 |
---|---|---|---|
1 | (1,0) | (0,0) | 1 |
2 | (0,0) | (0,1) | 1 |
3 | (0,1) | (0,2) | 1 |
4 | (0,2) | (1,0) | 3 |
5 | (1,1) | (1,1) | 0 |
6 | (2,1) | (1,2) | 2 |
7 | (2,0) | (2,0) | 0 |
8 | (2,2) | (2,1) | 1 |
x | (1,2) | (2,2) | 1 |
綜上所以該初始狀態到目標狀態的曼哈頓距離之和爲:1+1+1+3+0+2+0+1+1=10
AC代碼:
import java.io.BufferedInputStream;
import java.util.PriorityQueue;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
*
* @author wanyu
* @Date: 2018-02-05
* @Time: 17:07
* To change this template use File | Settings | File Templates.
* @desc A*+Hash
* 1.使用逆序對來判斷是否有解
* 2.有解的話使用A*進行路徑搜索
* 3.對於空間使用hash進行空間壓縮
*/
public class Main {
private static int[] map;
private static int start;//起始點
private static String[] path;//從起點到終點的路徑
private static int[] x = {0, 1, 2, 0, 1, 2, 0, 1, 2};//將一維數組轉化爲二維數組
private static int[] y = {0, 0, 0, 1, 1, 1, 2, 2, 2};//將一維數組轉化爲二維數組
private static boolean[] visited;
private static int[] move = {1, -1, 3, -3};//移動數組
private static int end;//結束狀態
private static void init(Scanner in) {//初始化,讀取數據
String[] line = in.nextLine().split("");
map = new int[9];
path = new String[363000];
visited = new boolean[363000];
int j = 0;
for (String aLine : line) {
if (aLine.equals(" ")) continue;
if (aLine.equals("x")) {
start = j;
map[j++] = 9;
continue;
}
map[j++] = Integer.parseInt(aLine);
}
}
private static void A_Star(Node1 start) {//使用A*算法求解路徑
PriorityQueue<Node1> queue = new PriorityQueue<>();//優先隊列
queue.add(start);//加入初始節點
path[start.cantor] = "";//初始化節點路徑
while (!queue.isEmpty()) {
Node1 temp = queue.poll();//從隊列中彈出f值最小的節點
visited[temp.cantor] = true;
//對其周圍進行遍歷,將符合要求的節點加入到隊列中
int index = temp.index;//x的位置
for (int i = 0; i < 4; i++) {
//確保邊界
if ((index == 0 || index == 3 || index == 6) && i == 1) continue;//左邊界
if ((index == 2 || index == 5 || index == 8) && i == 0) continue;//右邊界
int new_index = index + move[i];//新的x的位置
if (new_index >= 0 && new_index <= 8) {
Node1 node = new Node1(new_index, temp.state, temp.cantor);//新的節點
node.state[index] = temp.state[new_index];
node.state[new_index] = 9;
node.index = new_index;
int cantor = cantor(node.state);//對應的hash值
node.cantor = cantor;
node.flush(temp.g + 1, h(node.state));//更新f值
if (!visited[cantor]) {//是否已經訪問過了
queue.add(node);//入隊
switch (move[i]) {//更新路徑
case 1:
path[cantor] = path[temp.cantor] + "r";
break;
case -1:
path[cantor] = path[temp.cantor] + "l";
break;
case -3:
path[cantor] = path[temp.cantor] + "u";
break;
case 3:
path[cantor] = path[temp.cantor] + "d";
break;
}
if (cantor == end) {//是否找到節點了
System.out.println(path[cantor]);
return;
}
}
}
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(new BufferedInputStream(System.in));
end = cantor(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9});
while (in.hasNext()) {
init(in);
if (judge()) {
//構造初始節點
Node1 node1 = new Node1(0, h(map), start, map, cantor(map));
if (node1.cantor == end) {//如果目標狀態等於初始狀態則直接輸出
System.out.println("lr");
continue;
}
A_Star(node1);//A*
} else {
System.out.println("unsolvable");
}
}
}
//評估函數
private static int h(int[] a) {//估計從當前節點到終點的代價
//使用曼哈頓距離
int sum = 0;
for (int i = 0; i < 9; i++) {
int n = a[i] - 1;
int dis = Math.abs(x[i] - x[n]) + Math.abs(y[i] - y[n]);
sum += dis;
}
return sum;
}
private static boolean judge() {//判斷是否有解
int sum = 0;
for (int i = 0; i < map.length; i++) {
if (map[i] == 9) continue;//跳過x的位置
for (int j = i + 1; j < map.length; j++) {
if (map[i] > map[j]) sum++;
}
}
return (sum & 1) == 0;
}
private static int cantor(int[] num) {
int sum = 0;
int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};//0到9各個數的階乘
for (int i = 0; i < 8; i++) {
int temp = 0;
for (int j = i + 1; j < 9; j++) {
if (num[i] > num[j]) temp++;
}
sum += temp * fac[8 - i];
}
return sum;
}
}
class Node1 implements Comparable<Node1> {
//A*算法的評估函數F=G+H
//G表示從起點到當前節點的實際代價,H表示從當前節點到終點的估計代價
int f, g, h;//評估函數
int index;//x所在的位置
int state[];//當前節點的狀態
int cantor;//hash值
Node1(int g, int h, int index, int[] state, int cantor) {
this.index = index;
this.cantor = cantor;
this.state = new int[state.length];
System.arraycopy(state, 0, this.state, 0, state.length);
flush(g, h);
}
public void flush(int g, int h) {
this.g = g;
this.h = h * 10;
this.f = g + h;//刷新f值
}
Node1(int index, int[] state, int cantor) {
this.index = index;
this.cantor = cantor;
this.state = new int[state.length];
System.arraycopy(state, 0, this.state, 0, state.length);
}
@Override
public int compareTo(Node1 n) {
return f - n.f;
}
}
小結
八數碼問題的解法除了以上的還有一種比較好的方法是IDA*算法,但IDA*算法用的比較少,所以這裏就不做展開了。
附錄
此處我將我在解決該問題時寫的自動生成測試用例的代碼,以及根據結果自動求解的代碼奉上。
測試用例自動生成
public static void main(String[] args) throws FileNotFoundException {
Scanner in = new Scanner(new BufferedInputStream(System.in));
File file = new File("C:\\Users\\wanyu\\Desktop\\Test.txt");//測試用例輸出的路徑,按需修改
PrintWriter printWriter = new PrintWriter(file);
int n = in.nextInt();//輸入測試用例的個數
for (int i = 0; i < n; i++) {
boolean[] visted = new boolean[9];
StringBuffer stringBuffer = new StringBuffer();
int start = RandomUtils.nextInt(1, 9);//隨機生成起點
for (int j = 0; j < 9; j++) {
if (j == start) {
stringBuffer.append("x ");
continue;
}
int x = RandomUtils.nextInt(1, 9);
while (visted[x]) x = RandomUtils.nextInt(1, 9);
visted[x] = true;
stringBuffer.append(x);
if (j != 8)
stringBuffer.append(" ");
}
stringBuffer.append("\n");
printWriter.append(stringBuffer);
}
printWriter.flush();
}
簡易自動求解代碼:
public static void main(String[] args) {
//輸入初始狀態和步驟,自動進行求解
Scanner in = new Scanner(new BufferedInputStream(System.in));
while (true) {
int now = 0;
int j = 0;
String map[] = new String[9];
String line1[] = in.nextLine().split("");
for (String aLine : line1) {
if (aLine.equals(" ")) continue;
if (aLine.equals("x")) {
now = j;//起始點
}
map[j++] = aLine;
}
String line = in.nextLine();//步驟
if (line.equals("unsolvable")) {
System.out.println("unsolvable");
continue;
}
String step[] = line.split("");
for (String aStep : step) {
switch (aStep) {
case "u":
map[now] = map[now - 3];
map[now - 3] = "x";
now -= 3;
break;
case "l":
map[now] = map[now - 1];
map[now - 1] = "x";
now -= 1;
break;
case "r":
map[now] = map[now + 1];
map[now + 1] = "x";
now += 1;
break;
case "d":
map[now] = map[now + 3];
map[now + 3] = "x";
now += 3;
break;
}
}
for (String s : map) {
System.out.print(s + " ");//輸出結果
}
System.out.println();
}
}