A*(A星)算法优化

3
我是一名学生,我和我的团队需要模拟校园内学生的行为(例如组建"朋友圈"、走路等)。为了找到学生要走的路径,我使用了A*算法(因为我发现它是最快的路径查找算法之一)。不幸的是,我们的模拟运行不流畅(每次迭代之间需要1-2秒时间)。我想优化算法,但是我不知道还能做什么。你们能帮我一下,并分享一些关于如何优化A*算法的信息吗?以下是代码:
  public LinkedList<Field> getPath(Field start, Field exit) {


    LinkedList<Field> foundPath = new LinkedList<Field>();
    LinkedList<Field> opensList= new LinkedList<Field>();
    LinkedList<Field> closedList= new LinkedList<Field>();
    Hashtable<Field, Integer> gscore = new Hashtable<Field, Integer>();
    Hashtable<Field, Field> cameFrom = new Hashtable<Field, Field>();
    Field x = new Field();
    gscore.put(start, 0);
    opensList.add(start);
    while(!opensList.isEmpty()){

        int min = -1;
        //searching for minimal F score
        for(Field f : opensList){
            if(min==-1){
                min = gscore.get(f)+getH(f,exit);
                x = f;
            }else{
                int currf = gscore.get(f)+getH(f,exit);
                if(min > currf){
                    min = currf;
                    x = f;
                }
            }
        }
        if(x == exit){
            //path reconstruction
            Field curr = exit;
            while(curr != start){
                foundPath.addFirst(curr);
                curr = cameFrom.get(curr);
            }
            return foundPath;
        }
        opensList.remove(x);
        closedList.add(x);
        for(Field y : x.getNeighbourhood()){
            if(!(y.getType()==FieldTypes.PAVEMENT ||y.getType() == FieldTypes.GRASS) || closedList.contains(y) || !(y.getStudent()==null))
                            {
                continue;
            }
            int tentGScore = gscore.get(x) + getDist(x,y);
            boolean distIsBetter = false;
            if(!opensList.contains(y)){
                opensList.add(y);
                distIsBetter = true;
            }else if(tentGScore < gscore.get(y)){
                distIsBetter = true;
            }
            if(distIsBetter){
                cameFrom.put(y, x);
                gscore.put(y, tentGScore);
            }               
        }
    }

    return foundPath;
}

 private int getH(Field start, Field end){
    int x;
    int y;
    x = start.getX()-end.getX();
    y = start.getY() - end.getY();
    if(x<0){
        x = x* (-1);
    }
    if(y<0){
        y = y * (-1);
    }
    return x+y;
}
private int getDist(Field start, Field end){
    int ret = 0;
    if(end.getType() == FieldTypes.PAVEMENT){
        ret = 8;
    }else if(start.getX() == end.getX() || start.getY() == end.getY()){
        ret = 10;
    }else{
        ret = 14;
    }

    return ret;
}

//编辑

这是我从jProfiler中获取的内容:

jProfiler

所以getH是一个瓶颈,对吗?也许记住字段的H分数是一个好主意?


3
首先要对代码进行分析,找出哪些指令需要花费过多时间。大多数Java IDE(例如Eclipse、Netbeans等)都配备了性能分析器。您还可以尝试使用VisualVM。 - Stefano Sanfilippo
1
在优化之前,你应该考虑使用单元测试来验证所有代码的正常工作。我认为你的代码存在一个以上的错误,在 getDist(..) 方法中,应该是 start.getY(),而不是 end.getY() - Chriss
@Chriss 是的,你说得对,我的错误。我正在测试算法,它给了我很好的结果,但现在我看到我必须再次检查一切是否正常,谢谢 :) - Bartosz Wyględacz
1
这个页面可能会帮助您选择更有效的集合,但是像其他悲伤的事情一样,您必须先找到瓶颈。 - Chriss
Amit的A页面是学习更多关于A算法的好资源。特别是启发式->打破平局可能会很有趣。PS:为什么closedList从未被读取?此外,列表应该是集合而不是列表(除了路径)。 - fabian
显示剩余2条评论
6个回答

6

6
链表不适合作为开放集的数据结构。你必须从中找到F值最小的节点,可以通过 O(n) 的时间复杂度在链表中搜索或者按照排序位置插入。无论哪种方式都是 O(n) 的时间复杂度。使用堆的话只需要 O(log n) 的时间复杂度。更新 G 分数仍然需要 O(n) 的时间复杂度(因为你必须先找到该节点),除非你还在堆中增加了一个从节点到索引的哈希表。
同样,链表也不是闭合集的好数据结构,因为你需要快速进行“包含”操作,而在链表中这需要 O(n) 的时间复杂度。你应该使用 HashSet 来实现。

哇,将closeList更改为哈希集合使得路径查找速度提高了2倍!感谢您的建议:D - Bartosz Wyględacz
我建议使用最小优先队列作为数据结构。这帮助我将算法加速6倍。 - Roman Malkevych

2

从你的实现看来,似乎你是在使用朴素A*算法。请按照以下方式操作:

  1. A*算法是使用优先队列实现的,类似于BFS。

  2. 启发式函数在每个节点上进行评估,以定义其适合被选为下一个要访问的节点的程度。

  3. 当访问新节点时,将其未访问的相邻节点添加到队列中,其启发值作为关键字。

  4. 重复以上步骤,直到队列中的每个启发值都小于(或大于)目标状态的计算值。


一个优先队列是最好的选择。即使使用了优先队列,启发式算法仍可能导致路径走到死路,因此您需要为算法添加回溯机制。 - Tim Child
@TimChild 我认为这里没有必要进行回溯,因为A*算法同时考虑了所有可能性,如果你到达了死路,你总可以在队列中选择另一个开放的节点。 - Vikram Bhat
但是哪个备用节点?需要深入查看优先队列多深才能找到合适的节点? - Tim Child
@TimChild,你只需要从队列中获取下一个最佳节点并继续即可。不需要回溯。这里有一个维基页面,其中包含伪代码,你可以看到没有使用回溯- http://en.wikipedia.org/wiki/A*_search_algorithm - Vikram Bhat

1

使用另一个线程为每个查找路径(迭代中的一个查找路径一个线程)是否是一个好主意?因为地图上有很多学生,而且他们中的许多人都必须找到路径。 - Bartosz Wyględacz
@BartoszWyględacz 这是个好主意,将每个学生放入线程池中。 - Igor S.

1

a) 如前所述,在A*算法中应使用堆,可以使用基本的二叉堆或理论上更快的配对堆。

b) 在较大的地图中,算法需要一些时间才能运行(即当您请求路径时,它将简单地需要一些时间)。可以采用一些本地导航算法(例如,“直接向目标跑”),而路径计算过程中运行。

c) 如果您有合理数量的位置(例如在navmesh中)和一些时间在代码开始时,为什么不使用Floyd-Warshall算法?使用它,您可以在O(1)中获取下一个要去的位置信息。


0
我开发了一种新的寻路算法,名为Fast*或Fastaer。它类似于BFS和A*,但比A*更快、更高效,准确率达到90% A*。请查看此链接以获取更多信息和演示。

https://drbendanilloportfolio.wordpress.com/2015/08/14/fastaer-pathfinder/

它具有快速的贪婪线追踪器,使路径更加直线化。 演示文件包含所有内容。在使用演示时,请检查任务管理器以获取性能指标。到目前为止,在构建此项目时,分析器结果最大生存代数为4,GC时间很低或几乎没有。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接