zoukankan      html  css  js  c++  java
  • 算法:数字推盘游戏--重排九宫(8-puzzle)

    一、数字推盘游戏

      数字推盘游戏(n-puzzle)是一种最早的滑块类游戏,常见的类型有十五数字推盘游戏和八数字推盘游戏等。也有以图画代替数字的推盘游戏。可能Noyes Palmer Chapman在1874年发明十五数字推盘,但Sam Loyd则在1891年也宣称为其发明

      八数字推盘(又名重排九宫)则同样是Noyes Palmer Chapman在1870年代发明,并且马丁·加德纳在科学科普杂志上寻求更快的解答。也有人宣称重排九宫是传统中国游戏,来自洛书,并且为华容道的祖先。

    二、分支界定法

      给定一个具有 8 个图块的 3×3 板(每个图块都有一个 1 到 8 的数字)和一个空白空间(用 0 代表)。目的是将数字放置在图块上,以使用空白的空间匹配最终配置。我们可以将四个相邻的(左,右,上方和下方)图块滑动到空白区域。

      通常可以使用 DFSBFS 搜索算法来进行暴力破解。本文利用分支界定法,来“智能”的排名函数(近似于成本函数)来加快对成本节点的搜索,这里每一个节点都存有当前移动后整个方块的分布,从而避免在找不到最终答案的子树继续搜索。

      分支界定法基本上涉及三种类型的节点:

    1. 存活节点,是已生成但尚未生成其子节点的节点;
    2. 当前正在扩展的节点,探索它的子节点;
    3. 死亡节点,死节点是生成的节点,将不再扩展或探索。死节点的所有子节点均已扩展。

      成本函数:C(x) = g(x) + h(x)

      g(x) 是当前节点到根节点的成本(即路径长度)。h(x) 是当前除开空白块外答案节点错位(放错位置)的成本,假设在往上下左右任一方向移动图块的成本为 1。  

      给定初始状态和目的状态:

      下图显示了上述算法从给定的8-Puzzle初始配置达到最终配置所遵循的路径。注意,仅具有最小成本函数值的节点被扩展。

     (注意:以上是本算法的流程图)

    三、分支界定法的实现

    节点的构造:

     1     /**
     2      * 节点
     3      */
     4     private class Node {
     5         private Node parent;
     6         private int[][] mat;
     7         private int x, y;
     8         private int cost;
     9         private int level;
    10         private Node() {
    11             mat = new int[N][N];
    12         }
    13     }

    插入一个新节点:

     1   /**
     2      * 分配一个新节点
     3      *
     4      * @param mat
     5      * @param x
     6      * @param y
     7      * @param newX
     8      * @param newY
     9      * @param level
    10      * @param parent
    11      * @return
    12      */
    13     private Node newNode(int[][] mat, int x, int y, int newX, int newY, int level, Node parent) {
    14         Node node = new Node();
    15         node.parent = parent;
    16 
    17         copyMatrix(mat, node.mat);
    18 
    19         swap(node.mat, x, y, newX, newY);
    20 
    21         node.cost = Integer.MAX_VALUE;
    22         node.level = level;
    23 
    24         node.x = newX;
    25         node.y = newY;
    26 
    27         return node;
    28     }

    计算错位成本:

     1     /**
     2      * 计算错位方块的数量, 即不在目标位置的非空白块的数量
     3      *
     4      * @param initial
     5      * @param finals
     6      * @return
     7      */
     8     private int calculateCost(int[][] initial, int[][] finals) {
     9         int count = 0;
    10         for (int i = 0; i < N; i++)
    11             for (int j = 0; j < N; j++)
    12                 if (initial[i][j] != 0 && initial[i][j] != finals[i][j]) {
    13                     count++;
    14                 }
    15         return count;
    16     }

    利用优先队列(PriorityQueue)来实现分支界定法:

     1     /**
     2      * 分支界定法解决问题
     3      *
     4      * @param initial
     5      * @param x
     6      * @param y
     7      * @param finals
     8      */
     9     private void solve(int[][] initial, int x, int y, int[][] finals) {
    10         // 创建优先级队列以存储搜索树的活动节点
    11         PriorityQueue<Node> pq = new PriorityQueue<>(new Comp());
    12 
    13         // 创建一个根节点并计算其成本
    14         Node root = newNode(initial, x, y, x, y, 0, null);
    15         root.cost = calculateCost(initial, finals);
    16 
    17         // 将根添加到活动节点列表中;
    18         pq.add(root);
    19 
    20         // 查找成本最低的活动节点,
    21         // 将其子级添加到活动节点列表中,并最后将其从列表中删除。
    22         while (!pq.isEmpty()) {
    23             // 查找估计成本最低的活动节点, 找到的节点将从活动节点列表中删除
    24             Node min = pq.poll();
    25 
    26             // 如果min是一个答案节点
    27             if (min.cost == 0) {
    28                 printPath(min);
    29                 return;
    30             }
    31 
    32             // 为每个min节点的孩子
    33             // 一个节点最多4个孩子
    34             for (int i = 0; i < 4; i++) {
    35                 if (isSafe(min.x + row[i], min.y + col[i])) {
    36                     // 创建一个子节点并计算它的成本
    37                     Node child = newNode(min.mat, min.x, min.y,
    38                             min.x + row[i], min.y + col[i],
    39                             min.level, min);
    40                     child.cost = calculateCost(child.mat, finals);
    41 
    42                     // 将min的孩子添加到活动节点列表
    43                     pq.add(child);
    44                 }
    45             }
    46         }
    47     }

      分支定界是一种算法设计范例,通常用于解决组合优化问题。 这些问题通常在时间复杂度上呈指数关系(2^N),在最坏的情况下可能需要探索所有可能的排列(扩展完堆中所有可能的节点)。 分支界定相对较快地解决了这些问题。但是在最坏的情况下,我们需要完全计算整个树。充其量,我们只需要完全计算一条穿过树的路径,然后修剪其余路径即可。

    本文源代码:

      1 package algorithm;
      2 
      3 import java.util.Comparator;
      4 import java.util.PriorityQueue;
      5 
      6 /**
      7  * 重排九宫,或者称之为八码数问题,或是说数字推盘问题4,使用分支界定法实现
      8  */
      9 public class EightPuzzle {
     10     // 方阵边长
     11     private static final int N = 3;
     12 
     13     // 坐标的行列索引向下、左、上、右
     14     private static final int[] row = {1, 0, -1, 0};
     15     private static final int[] col = {0, -1, 0, 1};
     16 
     17     /**
     18      * 节点
     19      */
     20     private class Node {
     21         private Node parent;
     22         private int[][] mat;
     23         private int x, y;
     24         private int cost;
     25         private int level;
     26         private Node() {
     27             mat = new int[N][N];
     28         }
     29     }
     30 
     31     /**
     32      * 用于堆排序的比较对象
     33      */
     34     class Comp implements Comparator<Node> {
     35         @Override
     36         public int compare(Node o1, Node o2) {
     37             return (o1.cost + o1.level) - (o2.cost + o2.level);
     38         }
     39     }
     40 
     41     /**
     42      * 打印矩阵
     43      *
     44      * @param mat
     45      */
     46     private void printMatrix(int[][] mat) {
     47         for (int i = 0; i < N; i++) {
     48             for (int j = 0; j < N; j++)
     49                 System.out.print(mat[i][j] + " ");
     50             System.out.println();
     51         }
     52     }
     53 
     54     /**
     55      * 交换二维矩阵中的值
     56      *
     57      * @param mat
     58      * @param x
     59      * @param y
     60      * @param newX
     61      * @param newY
     62      */
     63     private void swap(int[][] mat, int x, int y, int newX, int newY) {
     64         int tmp = mat[x][y];
     65         mat[x][y] = mat[newX][newY];
     66         mat[newX][newY] = tmp;
     67     }
     68 
     69     /**
     70      * 矩阵复制
     71      *
     72      * @param arr1
     73      * @param arr2
     74      */
     75     private static void copyMatrix(int[][] arr1, int[][] arr2) {
     76         for (int i = 0; i < arr1.length; i++)
     77             System.arraycopy(arr1[i], 0, arr2[i], 0, arr1[0].length);
     78 
     79     }
     80 
     81     /**
     82      * 分配一个新节点
     83      *
     84      * @param mat
     85      * @param x
     86      * @param y
     87      * @param newX
     88      * @param newY
     89      * @param level
     90      * @param parent
     91      * @return
     92      */
     93     private Node newNode(int[][] mat, int x, int y, int newX, int newY, int level, Node parent) {
     94         Node node = new Node();
     95         node.parent = parent;
     96 
     97         copyMatrix(mat, node.mat);
     98 
     99         swap(node.mat, x, y, newX, newY);
    100 
    101         node.cost = Integer.MAX_VALUE;
    102         node.level = level;
    103 
    104         node.x = newX;
    105         node.y = newY;
    106 
    107         return node;
    108     }
    109 
    110     /**
    111      * 计算错位方块的数量, 即不在目标位置的非空白块的数量
    112      *
    113      * @param initial
    114      * @param finals
    115      * @return
    116      */
    117     private int calculateCost(int[][] initial, int[][] finals) {
    118         int count = 0;
    119         for (int i = 0; i < N; i++)
    120             for (int j = 0; j < N; j++)
    121                 if (initial[i][j] != 0 && initial[i][j] != finals[i][j]) {
    122                     count++;
    123                 }
    124         return count;
    125     }
    126 
    127     /**
    128      * 检查(x,y)是否为有效矩阵坐标
    129      *
    130      * @param x
    131      * @param y
    132      * @return
    133      */
    134     private boolean isSafe(int x, int y) {
    135         return (x >= 0 && x < N && y >= 0 && y < N);
    136     }
    137 
    138     /**
    139      * 打印路径
    140      *
    141      * @param root
    142      */
    143     private void printPath(Node root) {
    144         if (root == null)
    145             return;
    146         printPath(root.parent);
    147         printMatrix(root.mat);
    148         System.out.println();
    149     }
    150 
    151     /**
    152      * 分支界定法解决问题
    153      *
    154      * @param initial
    155      * @param x
    156      * @param y
    157      * @param finals
    158      */
    159     private void solve(int[][] initial, int x, int y, int[][] finals) {
    160         // 创建优先级队列以存储搜索树的活动节点
    161         PriorityQueue<Node> pq = new PriorityQueue<>(new Comp());
    162 
    163         // 创建一个根节点并计算其成本
    164         Node root = newNode(initial, x, y, x, y, 0, null);
    165         root.cost = calculateCost(initial, finals);
    166 
    167         // 将根添加到活动节点列表中;
    168         pq.add(root);
    169 
    170         // 查找成本最低的活动节点,
    171         // 将其子级添加到活动节点列表中,并最后将其从列表中删除。
    172         while (!pq.isEmpty()) {
    173             // 查找估计成本最低的活动节点, 找到的节点将从活动节点列表中删除
    174             Node min = pq.poll();
    175 
    176             // 如果min是一个答案节点
    177             if (min.cost == 0) {
    178                 printPath(min);
    179                 return;
    180             }
    181 
    182             // 为每个min节点的孩子
    183             // 一个节点最多4个孩子
    184             for (int i = 0; i < 4; i++) {
    185                 if (isSafe(min.x + row[i], min.y + col[i])) {
    186                     // 创建一个子节点并计算它的成本
    187                     Node child = newNode(min.mat, min.x, min.y,
    188                             min.x + row[i], min.y + col[i],
    189                             min.level, min);
    190                     child.cost = calculateCost(child.mat, finals);
    191 
    192                     // 将min的孩子添加到活动节点列表
    193                     pq.add(child);
    194                 }
    195             }
    196         }
    197     }
    198 
    199     public static void main(String[] args) {
    200         int[][] initial =
    201         {
    202             {1, 2, 3},
    203             {5, 6, 0},
    204             {7, 8, 4}
    205         };
    206 
    207         int[][] finals =
    208         {
    209             {1, 2, 3},
    210             {5, 8, 6},
    211             {0, 7, 4}
    212         };
    213 
    214         // 0的位置(空白块)
    215         int x = 1, y = 2;
    216 
    217         EightPuzzle eightPuzzle = new EightPuzzle();
    218         eightPuzzle.solve(initial, x, y, finals);
    219 
    220     }
    221 }
    View Code
  • 相关阅读:
    Django 用ModelForm批量保存form表单(非常实用的方法) mfor_verity项目
    jquery ajax异步提交表单数据的方法
    python字符串转换成变量的几种方法
    django 线上线下使用不同的数据库 上线:mysql 线下sqlite3 以及debug模式的开和关
    django admin 或xdmin list_display search_fields list_filter 如果显示搜索外键或多对多字段
    nonce和timestamp在Http安全协议中的作用
    Web API接口 安全验证
    .Net环境下的缓存技术介绍
    .Net缓存管理框架CacheManager
    在asp.net web api中利用过滤器设置输出缓存
  • 原文地址:https://www.cnblogs.com/magic-sea/p/12081180.html
Copyright © 2011-2022 走看看