阅读前准备
建议先看这篇文章同一个世界游戏 算法破解
改进思路
对于前篇文章(同一个世界游戏 算法破解)的Node类是通过Map<Integer, Integer>的对象来记录之前的走过的位置,这占用了大量的内存空间,检查的效率也低;节点的节点值等于父节点的值加本身的值(long childValue = parent.getValue() + (long) ),但是针对这个游戏(编码保存方式)而言,是否有可能有更好的方式
对以上两点考虑得到了更好的解决方式
重复位置检查
该游戏的状态是以二进制的方式保存的,经过的点对应的位标志为1,否则为0,也就是说任意一个节点的值实际上就已经保存了它所经过点,所以它的子节点只要检查节点值的二进制对应的位是否为1,即可知道是否走过该点
// 未经过该点
private boolean notPassed(long oldPosition, int index) {
return (oldPosition >> index & 1) == 0;
}
这样就节省了大量的时间和内存空间
节点值求和
有了上面的严谨检查,确保了子节点不会走过重复的点,这意味着一个节点值的二进制不会在相同的位上重复出现1的情况,也就是没有进位的情况,这样就可以把原来的加法运算改为或运算
long childValue = parent.getValue() | getValue(childIndex);
但是原本的加法也不可能会出现进位的情况,我想这并没有减少多少时间
其它问题
PathUtils类生成路径的时候可能是闭合路径但是还没有达到指定的路径深度,会造成死循环
重构NodeTree类的代码
代码
Node.java
public class Node {
private Node parent; // 父节点
private long value; // 当前路径值
private int row; // 当前节点的行
private int col; // 当前节点的列
private int index; // 起点序号
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public long getValue() {
return value;
}
public void setValue(long value) {
this.value = value;
}
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public int getCol() {
return col;
}
public void setCol(int col) {
this.col = col;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
NodeTree.java
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class NodeTree {
private int[][] state; // 盘面
private int row, col; // 二维数组行、列
private long target; // 目标状态值
private Node[] solveNodes; // 解决路径
private List<Map<Long, Node>> nodes; // 所有路径
private Deque<Node> deque; // 可能路径
private long realSum; // 实际路径数量
public NodeTree(int[][] state, int[][] startPoint, long target) {
this.state = state;
this.target = target;
this.row = state.length;
this.col = state[0].length;
nodes = new ArrayList<>(startPoint.length);
for (int i = 0; i < startPoint.length; i++) {
nodes.add(new HashMap<>());
}
deque = new LinkedList<>();
// 起点
for (int i = 0; i < startPoint.length; i++) {
int index = getIndex(startPoint[i][0], startPoint[i][1]);
long value = getValue(index);
Node node = new Node();
node.setParent(null);
node.setValue(value);
node.setRow(startPoint[i][0]);
node.setCol(startPoint[i][1]);
node.setIndex(i);
deque.push(node);
nodes.get(i).put(value, node);
realSum++;
}
solveNodes = new Node[startPoint.length];
}
// 搜索
public void search(boolean dfs) {
long startTime = System.currentTimeMillis();
Node parent, childNode;
while (!deque.isEmpty()) {
parent = deque.poll();
// System.out.println("nodeValue : " + parent.getValue());
for (int i = 0; i < 4; i++) {
if (!solved(parent)) {
childNode = nextStep(parent, i);
if (childNode != null) {
realSum++;
if (dfs) {
deque.offerFirst(childNode);
} else {
deque.offerLast(childNode);
}
}
} else {
printSearchInfo(startTime, "success");
printPath();
return;
}
}
}
printSearchInfo(startTime, "fail");
}
private final int[] offsetRow = {-1, 1, 0, 0}; // 行的偏移量
private final int[] offsetCol = {0, 0, -1, 1}; // 列的偏移量
// 走一步
private Node nextStep(Node parent, int direction) {
int childRow = parent.getRow() + offsetRow[direction];
int childCol = parent.getCol() + offsetCol[direction];
int childIndex = getIndex(childRow, childCol);
if (0 <= childRow && childRow < this.row
&& 0 <= childCol && childCol < this.col
&& state[childRow][childCol] != 0
&& notPassed(parent.getValue(), childIndex)) {
long childValue = parent.getValue() | getValue(childIndex);
Node childNode = new Node();
childNode.setParent(parent);
childNode.setValue(childValue);
childNode.setRow(childRow);
childNode.setCol(childCol);
childNode.setIndex(parent.getIndex());
nodes.get(parent.getIndex()).put(childValue, childNode);
return childNode;
}
return null;
}
// 搜索完成后的信息
private void printSearchInfo(long startTime, String result) {
System.out.println("搜索时间 : " + (System.currentTimeMillis() - startTime) + "毫秒");
System.out.println(result);
long sum = 0;
for (int i = 0; i < nodes.size(); i++) {
sum += nodes.get(i).size();
}
System.out.println("搜索路径数 : " + sum);
System.out.println("真实搜索路径数 : " + realSum);
}
// 打印路径
private void printPath() {
for (int i = 0; i < solveNodes.length; i++) {
System.out.println("--------------------");
Node node = solveNodes[i];
while (node != null) {
System.out.println(node.getRow() + " " + node.getCol());
node = node.getParent();
}
}
}
// 检查是否是符合目标的路径
private boolean solved(Node node) {
long nextTarget = target;
for (int i = 0; i < solveNodes.length && node != null; i++) {
nextTarget = nextTarget ^ node.getValue();
solveNodes[i] = node;
if (nextTarget == 0) {
return true;
} else if (nextTarget > 0) {
node = nodes.get((node.getIndex() + 1) % nodes.size()).get(nextTarget);
} else {
break;
}
}
return false;
}
// 二维数组对应的一维数组索引
private int getIndex(int row, int col) {
return row * this.col + col;
}
// 2的index次方
private long getValue(int index) {
return (long) Math.pow(2, index);
}
// 未经过该点
private boolean notPassed(long oldPosition, int index) {
return (oldPosition >> index & 1) == 0;
}
}
PathUtils.java
/**
* 路径工具类
* @author L
*
*/
public class PathUtils {
/**
* 生成路径
* @param state 盘面
* @param depth 路径深度
* @param startPoint 起点
* @return 路径
*/
public static int[][] createPath(int[][] state, int[] depth, int[][] startPoint) {
int[][] path = new int[depth.length][];
for (int i = 0; i < startPoint.length; i++) {
path[i] = new int[depth[i]];
int startRow = startPoint[i][0];
int startCol = startPoint[i][1];
int k = 0;
path[i][k++] = startRow * state[i].length + startCol;
while (k < depth[i]) {
double random = Math.random();
if (random < 0.25 && positionCheck(state, path[i], startRow - 1, startCol)) {
path[i][k++] = --startRow * state[i].length + startCol;
} else if (random < 0.5 && positionCheck(state, path[i], startRow + 1, startCol)) {
path[i][k++] = ++startRow * state[i].length + startCol;
} else if (random < 0.75 && positionCheck(state, path[i], startRow, startCol - 1)) {
path[i][k++] = startRow * state[i].length + --startCol;
} else if (positionCheck(state, path[i], startRow, startCol + 1)) {
path[i][k++] = startRow * state[i].length + ++startCol;
}
if (isClosedPath(state, path[i], startRow, startCol)) {
break;
}
}
}
return path;
}
// 该节点是否是闭合路径
private static boolean isClosedPath(int[][] state, int[] path, int row, int col) {
int[] offsetRow = {-1, 1, 0, 0}; // 行的偏移量
int[] offsetCol = {0, 0, -1, 1}; // 列的偏移量
int count = 0;
for (int i = 0; i < 4; i++) {
if (positionCheck(state, path, row + offsetRow[i], col + offsetCol[i])) {
++count;
}
}
return count == 0;
}
// 配合createPath方法使用,防止再次访问已访问过的位置
private static boolean positionCheck(int[][] state, int[] path, int row, int col) {
if (0 <= row && row < state.length
&& 0 <= col && col < state[0].length
&& state[row][col] != 0) {
int index = row * state[0].length + col;
for (int i = 0; i < path.length; i++) {
if (index == path[i]) {
return false;
}
}
return true;
}
return false;
}
// 配合createPath使用
public static long getPathValue(int[][] path) {
long value = 0;
for (int i = 0; i < path.length; i++) {
for (int j = 0; j < path[i].length; j++) {
value ^= (long) Math.pow(2, path[i][j]);
}
}
return value;
}
// 盘面值
public static long getStateValue(int[][] state, int v) {
long value = 0;
for (int i = 0; i < state.length; i++) {
for (int j = 0; j < state[i].length; j++) {
if (state[i][j] == v) {
int index = i * state[i].length + j;
value ^= (long) Math.pow(2, index);
}
}
}
return value;
}
}
改进后的效率
测试条件
PC:i5处理器,8G内存
盘面大小:5行6列、6行6列
起点:对角线起点(路径数量最大)
搜索方式:深度优先
测试结果
改进前
5行6列
结果
6行6列
结果
跑了一段时间后就内存溢出了
改经后
5行6列
6行6列
结论
1.从5行6列的测试结果对比,改进后的程序极大的节省了时间
2.从6行6列的测试结果对比,改进后的程序极大的节省了内存空间