对不起,如果这是一个很长的问题,但我不确定我的A * 8拼图java代码是否有效...我发现我的代码运行得很好,简单的输入(很容易平均情况),但我不知道它是否适用于最坏情况......
我尝试修改我的代码,使用每个节点的曼哈顿距离作为我的启发式函数,我的代码即使在最坏的情况下工作,但它只需要太长时间......当我使用"数量为错位的瓷砖"作为我的启发式功能,与使用曼哈顿距离相比,我的代码运行简单到平均情况需要更长的时间。即使在15分钟后,它也无法为最坏的情况提供解决方案......
注意:在最坏的情况下,8个谜题可以通过不超过31步来解决......
...这是我的代码的主要功能:
List<Node> nodeList = new ArrayList<Node>();
nodeList.add(startNode); //"Node startNode" contains the root node of the tree that will be produced
Node currentNode = null;
while (1 == 1) {
//THIS SECTION FINDS THE LEAF NODE WITH THE LEAST f(n)
currentNode = null;
for (Node pickNode : nodeList) {
if (pickNode.isLeaf == true) {
if (currentNode == null)
currentNode = pickNode;
else if (pickNode.fn < currentNode.fn){
currentNode = pickNode;
}
}
}
/*-----------------------------------------------------------*/
//BREAK THE LOOP WHEN THE SOLUTION IS FOUND
if (Arrays.deepEquals(currentNode.state, goalState))
break;
/*-----------------------------------------------------------*/
else {
int xcheck = currentNode.zeroX;
int ycheck = currentNode.zeroY;
int switcher;
int approve = 1;
/*-----------------------------------------------------------*/
//THE FOLLOWING LINES DETERMINES WHICH CHILDREN CAN BE PRODUCED BY A NODE
if ((ycheck - 1) >= 0) {
int subState[][] = new int [3][];
subState[0] = currentNode.state[0].clone();
subState[1] = currentNode.state[1].clone();
subState[2] = currentNode.state[2].clone();
switcher = subState[ycheck-1][xcheck];
subState[ycheck-1][xcheck] = 0;
subState[ycheck][xcheck] = switcher;
Node checkerNode = new Node();
checkerNode = currentNode;
while (checkerNode != null) {
if (Arrays.deepEquals(subState, checkerNode.state)) {
approve = 0;
break;
}
checkerNode = checkerNode.parentNode;
}
if (approve != 0) {
Node childNode = new Node();
childNode.state = subState;
childNode.totalPath = currentNode.totalPath + "*" + "up";
childNode.gn = currentNode.gn + 1;
childNode.hn = computeHn(childNode.state, goalState);
childNode.fn = childNode.gn + childNode.hn;
childNode.isLeaf = true;
childNode.parentNode = currentNode;
childNode.zeroX = xcheck;
childNode.zeroY = ycheck-1;
nodeList.add(childNode);
}
}
approve = 1;
/*-----------------------------------------------------------*/
if ((ycheck + 1) <= 2) {
//same logic with: if (ycheck-1 >= 0)
}
approve = 1;
/*-----------------------------------------------------------*/
if ((xcheck + 1) <= 2) {
//same logic with: if (ycheck-1 >= 0)
}
approve = 1;
/*-----------------------------------------------------------*/
if ((xcheck - 1) >= 0) {
//same logic with: if (ycheck-1 >= 0)
}
approve = 1;
}
currentNode.isLeaf = false;
}
这是计算我的启发式算法的函数(错位的瓦片数量而不是曼哈顿距离):
public static int computeHn (int checkStateH[][], int goalStateH[][]) {
int total = 0;
int rowC = 0;
int columnC = 0;
rowC = 0;
while (rowC < 3) {
columnC = 0;
while (columnC < 3) {
if (goalStateH[rowC][columnC] != checkStateH[rowC][columnC]) {
total++;
}
columnC++;
}
rowC++;
}
return total;
}
这是我的Node类:
public class Node {
int state[][]; //contains the matrix configuration of the node
String totalPath; //contains the path on how to get to this node from the root node
int gn; //contains the number of moves made to get to this node from the root node
int hn; //contains the heuristic (number of misplaced tiles per node)
int fn; // fn = gn + hn
boolean isLeaf; //states whether a node is a leaf or not. used so that I can know whether a node could still be expanded or not
Node parentNode; //points to the node's parent node
int zeroX; //the x position of the zero tile
int zeroY; //the y position of the zero tile
}
这是给定的矩阵,或者&#34;开始状态&#34;矩阵(在最坏的情况下,这可以通过至少31次移动来回答):
......它应该达到这个目的状态:
...感谢那些愿意帮助的人!...我很抱歉,如果它有点长,但我想我应该发布我的代码,而不仅仅是陈述我的代码的逻辑,因为我可能犯了错误在实现我的逻辑......
答案 0 :(得分:1)
曼哈顿距离会更快,因为它是一个更好的启发式。 30秒是等待解决方案的一段时间,特别是对于C ++,但它并不是完全荒谬的。然而,即使对于一个不太好的启发式,也是15分钟。
如果我正确解释您的代码,checkerNode
循环正在检查此状态是否已经存在于您当前正在浏览的路径中,遍历整个路径。这是相当低效的(O(log(n)ish),我认为)。如果您维护一个已经扩展的状态字典,则可以将其降低到恒定时间。
可能存在其他微妙的低效率,但我怀疑这会大大加快您的代码速度。
编辑解释词典:
字典是一种数据结构,可让您快速查找元素是否存在。
对于大多数数据结构,如果要查找具有给定值的元素,则必须将该值与已存储在结构中的每个元素进行比较(例如,如何将checkerNode与所有前任节点进行比较) )。问题是,随着您在数据结构中存储越来越多的东西,这个过程需要更长时间。字典不是这种情况,因为字典使用称为哈希表的东西来立即转到存在给定元素的位置。然后,如果元素在那里,你知道它在数据结构中,如果它不是你知道它不是。
字典通常用于将给定键映射到关联值,但在这里我们并不真正关心该功能。我们只想知道给定的键是否在字典中,所以我们可以将值设置为我们想要的任何值(通常我只存储布尔值&#34; True&#34;或者如果你&#,则指向节点的指针39; ll需要能够再次找到它)。在C ++中,内置字典类是std::map。
要在代码中使用它,您可以执行大致相同的操作:
首先,初始化地图对象
std::map<char,int> already_explored;
执行程序的开始,直到刚刚将值赋给currentNode的点。现在我们正在探索currentNode,我们将它的状态添加到字典中。国家是关键,&#34;真正的&#34;是值。
already_explored[currentNode.state] = True;
继续执行该程序,直到您发现下一个状态想知道它是否已经被看到。现在我们可以在字典中查找:
if (already_explored.count(subState) > 0){
approve = 0;
}
如果按此顺序执行操作,您甚至不必担心检查具有相同状态的其他节点的f(n)值。 A *到达的第一个保证是达到该状态的最快方式。通过更长的路径到同一个州永远不会有任何好处。
答案 1 :(得分:1)
在查看代码之后,我可以指出两个可能会提高代码效率的事情:
A *存储一个有序列表,其中包含要扩展的节点(OPEN列表),按升序f(n)
排序。在您的代码中,我了解您正在选择第一个循环中具有最低f(n)
的节点,遍历所有生成状态的树。这是非常低效的,因为搜索树随着算法的每次迭代而增长,而您只需要遍历该树的叶子(这些叶子已经生成为其他状态的后继但未展开的那些)。您可以更好地存储将在PriorityQueue
中按A *扩展的节点。队列的第一个元素是f(n)
最低的元素,因此您不必遍历每次迭代中的所有搜索树。
正如@seaotternerd在他的回答中指出的那样,你需要以某种方式保持一个&#34;字典&#34;已经通过算法扩展的状态。 A *在OPEN列表中存储作为其他(但尚未扩展)的后继生成的节点,并且在CLOSED列表中已经扩展了节点。如果你已经改进了它f(n)
,那么在达到先前迭代中已经生成的状态的情况下,需要检查这一点。
例如,如果您按照implementation of A*库中的Hipster进行操作,则以下字段将用于存储您的&#34;字典&#34;州:
private Map<int[][], Node> open;
private Map<int[][], Node> closed;
private Queue<Node> queue;
然后,您可以使用此方法查询OPEN列表以检索具有最低f(n)
的节点:
private Node takePromising() {
// Poll until a valid state is found
Node node = queue.poll();
while (!open.containsKey(node.state())) {
node = queue.poll();
}
return node;
}
EDITED:
这将避免您在循环开始时沿着搜索树进行迭代,从而改进最有希望的节点的选择,直至O(n)
。使用字典,您在checkerNode
中执行的代码,以避免循环&#34;祖先 - 继承者 - 祖先&#34;将不再是必要的。并且考虑到如果没有正确实现OPEN和CLOSED列表,您的搜索可能会进入无限循环。要完成,您可能会感兴趣查看full example of the 8-puzzle problem solved with A*中包含的Hipster library。
我希望我的答案有所帮助,