墨盘游戏 算法求解 改进版

上个版本的问题

上个版本(墨盘游戏 算法求解)对于有些情况到了内存溢出都搜索不到结果,我以为是与初始状态、结束状态有关、以及估值函数写的不够好,但实际上与游戏状态无关,这个估值函数确实不是很好
但是最大的原因并不是这个,今天我才意识到问题所在。看下面的图
在这里插入图片描述  在这里插入图片描述
这是对称的,接下来再点击对方的点击位置都会达到下面的状态
在这里插入图片描述
这样就会有两个相同的节点了,以此类推,会出现不少相同的节点,感觉上很少,但是粗略计算一下,假设可以点击的点有 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)656=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();
		}
	}
	
}

改经后测试结果

还是以上一个版本的例子做测试
在这里插入图片描述
改进后搜索时间和空间都有了质的提升

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值