上个版本的问题
上个版本(墨盘游戏 算法求解)对于有些情况到了内存溢出都搜索不到结果,我以为是与初始状态、结束状态有关、以及估值函数写的不够好,但实际上与游戏状态无关,这个估值函数确实不是很好
但是最大的原因并不是这个,今天我才意识到问题所在。看下面的图
这是对称的,接下来再点击对方的点击位置都会达到下面的状态
这样就会有两个相同的节点了,以此类推,会出现不少相同的节点,感觉上很少,但是粗略计算一下,假设可以点击的点有
n
n
n个,完美步数是
x
x
x,第一层节点就有
3
n
3n
3n(三种操作)个,可得总节点数
f
(
n
)
=
(
3
n
)
x
f(n)={(3n)^x}
f(n)=(3n)x。上图关卡完美步数是7,可得
f
(
19
)
=
1954897493193
f(19)=1954897493193
f(19)=1954897493193(实际上没有这么多,该关卡全部节点数量只有C
(
5
19
)
=
11628
\tbinom{5}{19}=11628
(195)=11628,因为有些点击是无效的,状态未改变,这里理想化计算),如果去除一个重复的节点,第一步就只有
56
56
56个节点,
f
(
19
)
=
(
57
)
6
∗
56
=
1920601045944
f(19)={(57)^6}*56=1920601045944
f(19)=(57)6∗56=1920601045944,搜索量少了
34296447249
34296447249
34296447249,这只是减少一个重复节点的情况,实际上重复节点不少,能减少的搜索量远比
34296447249
34296447249
34296447249多得多
改进方式
使用Set存储节点状态,估值函数也稍稍改了一下
// 不存在相同节点
private boolean unequal(long state, int depth) {
return unequalNodes.add(state ^ getValue(63 - depth));
}
// 估值函数
private long getEstimatedValue(Node node) {
return (depth - node.getStep()) * Math.abs(node.getState() - targetState);
}
代码
import java.util.HashSet;
import java.util.Set;
public class NodeTree {
private long targetState, sum;
private int[] range, previousLength; // 六边形次外圈的范围、六边形对应的行前面的点数量之和
private Node[] nodes;
private int nodesIndex, depth;
private Set<Long> unequalNodes; // 用于判断重复节点
public NodeTree(int[][] startState, int[][] endState, int depth) {
this.targetState = NodeUtil.getStateValue(endState);
this.range = NodeUtil.getRange(endState);
this.previousLength = NodeUtil.getPreviousLength(endState);
this.depth = depth;
this.nodes = new Node[40];
this.unequalNodes = new HashSet<>();
Node root = new Node();
root.setState(NodeUtil.getStateValue(startState));
nodes[nodesIndex++] = root;
sum++;
}
private char[] direction = {'L', 'C', 'R'}; // 对应顺时针、原点交换、逆时针
public void search() {
long startTime = System.currentTimeMillis();
while (nodesIndex > 0) {
Node parent = nodes[0];
// 找到目标,停止搜索
if (parent.getState() == targetState) {
printSearchInfo(startTime, "success");
printPath(parent);
return;
}
shiftDown(--nodesIndex);
// 节点达到最大深度,不再搜索
if (parent.getStep() < depth) {
for (int i = 0; i < range.length; i++) {
for (int j = 1; j <= range[i]; j++) {
for (int d = 0; d < direction.length; d++) {
if (nodesIndex == nodes.length) {
Node[] newNodes = new Node[nodesIndex * 2];
System.arraycopy(nodes, 0, newNodes, 0, nodesIndex);
nodes = newNodes;
}
Node child = nextStep(parent, direction[d], i + 1, j);
if (child != null) {
nodes[nodesIndex++] = child;
shiftUp(nodesIndex - 1);
sum++;
}
}
}
}
}
}
printSearchInfo(startTime, "fail");
}
// 不存在相同节点
private boolean unequal(long state, int depth) {
return unequalNodes.add(state ^ getValue(63 - depth));
}
// 走一步
private Node nextStep(Node parent, char direction, int row, int col) {
long childState = 0;
switch (direction) {
case 'L':
childState = clockwiseRotate(parent.getState(), row, col);
break;
case 'C':
childState = originSymmetric(parent.getState(), row, col);
break;
case 'R':
childState = anticlockwiseRotate(parent.getState(), row, col);
break;
}
if (unequal(childState, parent.getStep() + 1) && childState != parent.getState()) {
Node child = new Node();
child.setParent(parent);
child.setRow(row);
child.setCol(col);
child.setDirection(direction);
child.setState(childState);
child.setStep(parent.getStep() + 1);
child.setWeight(getEstimatedValue(child));
return child;
}
return null;
}
private int[] offsetRow = {-1, -1, 0, 1, 1, 0};
private int[][] offsetCol = { // 行数对应的列变化规则
{-1, 0, 1, 1, 0, -1}, // 上
{-1, 0, 1, 0, -1, -1}, // 中
{0, 1, 1, 0, -1, -1} // 下
};
// 左边,顺时针旋转
private long clockwiseRotate(long parentState, int row, int col) {
long childState = 0;
int choiceCol = getColIndex(row);
for (int i = 0; i < 6; i++) {
int index = previousLength[row + offsetRow[i]] + col + offsetCol[choiceCol][i];
if (isExist(parentState, index)) {
childState |= getValue(previousLength[row + offsetRow[(i + 1) % 6]] + col + offsetCol[choiceCol][(i + 1) % 6]);
parentState ^= getValue(index);
}
}
return childState | parentState;
}
// 原点对称交换
private long originSymmetric(long parentState, int row, int col) {
long childState = 0;
int choiceCol = getColIndex(row);
for (int i = 0; i < 6; i++) {
int index = previousLength[row + offsetRow[i]] + col + offsetCol[choiceCol][i];
if (isExist(parentState, index)) {
childState |= getValue(previousLength[row + offsetRow[(i + 3) % 6]] + col + offsetCol[choiceCol][(i + 3) % 6]);
parentState ^= getValue(index);
}
}
return childState | parentState;
}
// 右边,逆时针旋转
private long anticlockwiseRotate(long parentState, int row, int col) {
long childState = 0;
int choiceCol = getColIndex(row);
for (int i = 5; i >= 0; i--) {
int index = previousLength[row + offsetRow[i]] + col + offsetCol[choiceCol][i];
if (isExist(parentState, index)) {
if (i > 0)
childState |= getValue(previousLength[row + offsetRow[i - 1]] + col + offsetCol[choiceCol][i - 1]);
else
childState |= getValue(previousLength[row + offsetRow[5]] + col + offsetCol[choiceCol][5]);
parentState ^= getValue(index);
}
}
return childState | parentState;
}
// 选择行数对应的列变化规则
private int getColIndex(int row) {
if (row < previousLength.length / 2) {
return 0;
} else if (row == previousLength.length / 2) {
return 1;
} else {
return 2;
}
}
// 点是否存在
private boolean isExist(long state, int index) {
return (state >> index & 1) == 1;
}
// 2的index次方
private long getValue(int index) {
return (long) Math.pow(2, index);
}
// 估值函数
private long getEstimatedValue(Node node) {
return (depth - node.getStep()) * Math.abs(node.getState() - targetState);
}
// 节点下沉
private void shiftDown(int index) {
nodes[0] = nodes[index];
for (int parentIndex = 0, childIndex = (parentIndex << 1) + 1;
childIndex < index;
parentIndex = childIndex, childIndex = (parentIndex << 1) + 1) {
if (childIndex + 1 < index &&
nodes[childIndex + 1].getWeight() <= nodes[childIndex].getWeight()) {
++childIndex;
}
if (nodes[childIndex].getWeight() > nodes[parentIndex].getWeight()) {
break;
}
Node node = nodes[parentIndex];
nodes[parentIndex] = nodes[childIndex];
nodes[childIndex] = node;
}
}
// 节点上浮
private void shiftUp(int index) {
for (int parentIndex = index - 1 >> 1;
parentIndex >= 0 && nodes[parentIndex].getWeight() > nodes[index].getWeight();
index = parentIndex, parentIndex = index - 1 >> 1) {
Node node = nodes[parentIndex];
nodes[parentIndex] = nodes[index];
nodes[index] = node;
}
}
// 打印搜索信息
private void printSearchInfo(long startTime, String result) {
System.out.println("搜索时间:" + (System.currentTimeMillis() - startTime) + "毫秒");
System.out.println(result);
System.out.println("搜索节点数量:" + sum);
System.out.format("节点数组长度:%d 有效长度:%d 存储空间利用率:%f%%%n", nodes.length, nodesIndex, (double) nodesIndex * 100 / nodes.length);
}
// 打印路径
private void printPath(Node node) {
while (node.getParent() != null) {
System.out.format("step:%d direction:%c row:%d col:%d%n", node.getStep(), node.getDirection(), node.getRow(), node.getCol());
node = node.getParent();
}
}
}
改经后测试结果
还是以上一个版本的例子做测试
改进后搜索时间和空间都有了质的提升