HDU1043 解題報告

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

雙向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:
BFS
A*:
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();
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章