A * 8拼图运行时间过长

时间:2015-02-19 06:40:31

标签: java a-star heuristics

对不起,如果这是一个很长的问题,但我不确定我的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次移动来回答):

  • 8 0 6
  • 5 4 7
  • 2 3 1

......它应该达到这个目的状态:

  • 0 1 2
  • 3 4 5
  • 6 7 8
再次,当我使用&#34;曼哈顿距离&#34;作为我的启发式功能,我的代码可以工作,但是需要30秒来为这种输入产生答案...但是当我使用&#34;错位的瓷砖数量时#34;作为我的启发式功能,即使在15分钟后它也不会产生解决方案,但是当我使用这个矩阵时给出答案:

  • 5 8 6
  • 2 7 1
  • 3 0 4 //此矩阵可从前面提到的开始状态矩阵
  • 获得

...感谢那些愿意帮助的人!...我很抱歉,如果它有点长,但我想我应该发布我的代码,而不仅仅是陈述我的代码的逻辑,因为我可能犯了错误在实现我的逻辑......

2 个答案:

答案 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)

在查看代码之后,我可以指出两个可能会提高代码效率的事情:

  1. A *存储一个有序列表,其中包含要扩展的节点(OPEN列表),按升序f(n)排序。在您的代码中,我了解您正在选择第一个循环中具有最低f(n)的节点,遍历所有生成状态的树。这是非常低效的,因为搜索树随着算法的每次迭代而增长,而您只需要遍历该树的叶子(这些叶子已经生成为其他状态的后继但未展开的那些)。您可以更好地存储将在PriorityQueue中按A *扩展的节点。队列的第一个元素是f(n)最低的元素,因此您不必遍历每次迭代中的所有搜索树。

  2. 正如@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;
    }
    
  3. EDITED:  这将避免您在循环开始时沿着搜索树进行迭代,从而改进最有希望的节点的选择,直至O(n)。使用字典,您在checkerNode中执行的代码,以避免循环&#34;祖先 - 继承者 - 祖先&#34;将不再是必要的。并且考虑到如果没有正确实现OPEN和CLOSED列表,您的搜索可能会进入无限循环。要完成,您可能会感兴趣查看full example of the 8-puzzle problem solved with A*中包含的Hipster library

    我希望我的答案有所帮助,