A*算法的特点是:
- 构成搜索树的节点。
- “邻居”概念的定义。
- 节点的乐观启发式定义。
搜索树中的节点
当您提出问题时,似乎搜索树的每个节点都是显示的 n 位的配置。请注意,这可以很容易地表示为大小为 n 的整数数组,其中数组的每个位置代表显示中的一个数字,而值代表显示中该数字的实际值。例如,如果显示25847 的5 位数字可以用[2, 5, 8, 4, 7] 表示。简单吧?
“邻居”概念
也就是说,你如何改变显示状态?正如您所说,您只能使用每个数字下方的 n 个按钮之一。因此,对于每种状态,如果您在搜索树上思考,您将有完全 n 个可能的“邻居”或“后代”。您将需要一个函数,为您提供按下特定按钮的节点结果(这将模拟代理按下按钮)。类似于以下内容(在 Java 中):
static int[] pressButton(int[] node, int button, int j, int k) {
int n = node.length;
int[] newNode = Arrays.copyOf(node, n);
newNode[button] = (newNode[button] + 1) % 10;
newNode[(button + j) % n] = (newNode[(button + j) % n] + k) % 10;
return newNode;
}
现在你有了这个,你可以生成当前节点的每个子节点,如下所示:
for (int i = 0; i < node.length; i++) {
int[] newNode = pressButton(node, i, j, k);
// compute heuristic of the new node
// add it to the A* priority queue
}
乐观启发式
现在您只需要一个启发式算法,它可以乐观地估计给定节点到目标的距离。一个简单的想法是假设,对于给定的节点,您必须按下按钮的次数至少与与目标不同的位数一样多;例如,如果目标是41243,当前节点是37253,则您必须至少按下按钮三次,因为与目标不同的三个数字。在 Java 中:
static int heuristic(int[] currentNode, int[] goal) {
int h = 0;
for (int i = 0; i < goal.length; i++) {
if (goal[i] != currentNode[i]) {
h = h + 1;
}
}
return h;
}
但是请注意,这种启发式方法是错误的。例如,如果目标是81730,您的当前节点是71230,j = 2 和 k = 5,则此启发式将给出值 2;然而,我们可以在按下按钮一的 1 步内从当前节点到达目标状态,因此在这种情况下,启发式方法将是悲观的。这是因为每次按下按钮时,都会影响两位数,这可能比我们想象的更快获得解决方案。为了避免这种情况,我们可以从启发式中减去一个(如果它大于一个):
static int heuristic(int[] currentNode, int[] goal) {
int h = 0;
for (int i = 0; i < goal.length; i++) {
if (goal[i] != currentNode[i]) {
h = h + 1;
}
}
if (h > 1) {
h = h - 1;
}
return h;
}
不仅可以根据与目标不同的位数,还可以根据它们的差异程度来定义更准确的启发式方法(差异越大,您需要按下按钮的次数越多);但是,我认为没有一种简单的方法可以基于此定义乐观启发式(特别要记住像 j = 0 或 j = n、负k等)。如果允许您在启发式中使用 j 和 k 的值(我假设不是,因为当您说代理不知道这些值时,我就是这么解释的),也许有一些复杂的空间,但即使那样,我也很难定义它。
最后,考虑到问题的性质,从一个节点到另一个节点时很容易到达已经访问过的状态。如果您想将树保持为实际树(并且是有限的),而不是无限图,则必须使用一组已经访问过的节点,以避免在搜索空间中创建循环。