一、数字推盘游戏

  数字推盘游戏(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 }

算法:数字推盘游戏--重排九宫(8-puzzle)的更多相关文章

  1. Java基础知识强化之IO流笔记70:Properties练习之 如何让猜数字小游戏只能玩5次的案例

    1. 使用Properties完成猜数字小游戏只能玩5次的案例: 2. 代码实现: (1)猜数字游戏GuessNumber: package cn.itcast_08; import java.uti ...

  2. 洛谷P1118 数字三角形游戏

    洛谷1118 数字三角形游戏 题目描述 有这么一个游戏: 写出一个1-N的排列a[i],然后每次将相邻两个数相加,构成新的序列,再对新序列进行这样的操作,显然每次构成的序列都比上一次的序列长度少1,直 ...

  3. java猜数字小游戏

    /* * * 猜数字小游戏 * * 先由系统生成一个2-100之间的随机数字, * * 然后捕获用户从控制台中输入的数字是否与系统生成的随机数字相同, * * 如果相同则统计用户所猜的次数,并给出相应 ...

  4. 简单的C语言猜数字小游戏

    猜数字小游戏可谓是C语言最为基础的一个知识点了,我们可以在此基础上进行延伸,实现随机数的猜测,然后是加入再来一局的模式,等等.这里是抛砖引玉,希望你能做出你的经典之作. #include <st ...

  5. python 游戏(数字推理游戏Bagels)

    1.游戏思路和流程图 实现功能:玩家猜测三位不一样的数字,猜错了有提示,提示分别为(位置错误数字正确),(位置和数字正确),(数字和位置都不正确) 游戏流程图 2. 使用模块和游戏提示 import ...

  6. jquery开发的数字相加游戏(你能玩几分)

    jquery开发的数字相加游戏,我在一轮中玩了632分(如下图),你能玩几分,哈哈... 我要试一试 下面贡献下这款“数字相加游戏”的开发过程. html部分: <div class=" ...

  7. 一款纯css3实现的数字统计游戏

    今天给大家分享一款纯css3实现的数字统计游戏.这款游戏的规则的是将所有的数字相加等于72.这款游戏的数字按钮做得很美观,需要的时候可以借用下.一起看下效果图: 在线预览   源码下载 实现的代码. ...

  8. 洛谷P1132 数字生成游戏

    P1132 数字生成游戏 题目描述 小明完成了这样一个数字生成游戏,对于一个不包含0的数字s来说,有以下3种生成新的数的规则: 将s的任意两位对换生成新的数字,例如143可以生成314,413,134 ...

  9. 【转】Java数字抽奖游戏核心代码

    1. [代码][Java]代码    package com.luiszhang.test; import java.util.Arrays; /** * NumberLotteryGame * 一个 ...

随机推荐

  1. golang 注释 exported function xxx should have comment or be unexported

    0x00 问题 exported function xxx should have comment or be unexported. 0x01 解决 https://golang.org/s/sty ...

  2. 【转】Gitlab CI & Docker & Dockerfile & Docker-compose的博客文章

    gitlab权限设置 GitLab功能系列报道-标签(Labels) Gitlab CI 使用高级技巧 docker-compose是个好东西,越用越香 基于docker-compose的Gitlab ...

  3. Mybatis公司开发常用!

    Mybatis核心 本文重点:注解开发,mybatis多表操作,动态SQL(WHERE,SET,IF,SQL-ID减少复用) 代码地址--https://gitee.com/zhangjzm/my-b ...

  4. javascript 字符串反转 strip_tags 字符串常用的自定义函数,加载css etc.

    字符串反转 String.prototype.reverse = function() { var a = this.split(''); for (var i = 0, j = a.length-1 ...

  5. 鸿蒙内核源码分析(自旋锁篇) | 当立贞节牌坊的好同志 | 百篇博客分析OpenHarmony源码 | v26.02

    百篇博客系列篇.本篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 当立贞节牌坊的好同志 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 当立贞节牌坊 ...

  6. 踩坑经验总结之go web开源库第一次编译构建

    前言:记录一个go新手第一次构建复杂开源库的经历.go虽然是新手,但是编程上还是有多年的经验,除了c/c++,用过IDEA能进行简单的java编程.甚至scala编程.所以最开始还是有点信心的.所以也 ...

  7. 4-让线程睡眠的sleep方法

    让线程睡眠的sleep方法 Thread类有一个静态的sleep方法,当一个执行中的线程调用了Thread的sleep方法,调用线程就会让出指定时间的执行权,也就是在这期间不参与CPU调度,但是该线程 ...

  8. 【.Net vs Java? 】 先来看一下Java和C#的数据类型区别。

    新工作.Net和Java都要做,早期也做过一段Java的项目,但没有系统的深入学习过.一直觉得这两门语言估计是最相近的两门语言了,好多代码可以说直接拷过来都不带报错的,但仔细推敲还是有很多的不同. 1 ...

  9. linux kill信号详解

    大家对kill -9 肯定非常熟悉,在工作中也经常用到.特别是你去重启tomcat时.可是多半看来,我们对-9的理解只是表面而已. 很少有人(包括我)认真的去了解一下 kill -n 这个n到底是什么 ...

  10. 洛谷4234最小差值生成树 (LCT维护生成树)

    这也是一道LCT维护生成树的题. 那么我们还是按照套路,先对边进行排序,然后顺次加入. 不过和别的题有所不同的是: 在本题中,我们需要保证LCT中正好有\(n-1\)条边的时候,才能更新\(ans\) ...