HDU1043 解题报告

HDU1043:Eight(八数码,经典题型)

  • HDU1043Eight八数码经典题型
    • 题意
    • 思路
    • 方案一BFSHash打表
      • 康托展开
    • 方案二双向BFSHash
    • 方案三AHash曼哈顿距离优先队列
    • 小结
    • 附录

原题地址: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 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:
HDU1043 解题报告_第1张图片

双向BFS:
HDU1043 解题报告_第2张图片

​ 然后此处还有一个小的优化点,在上一个方案中,我们对于无解的状态只需要直接判断是否能从目标状态搜索到,对于搜索不到的在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 Squeue = new ArrayDeque<>();
        Deque 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:
HDU1043 解题报告_第3张图片
A*:
HDU1043 解题报告_第4张图片

对于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 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 {
    //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();
        }
    }

你可能感兴趣的:(ACM解题报告)