【问题标题】:Java BinaryHeap not working on A* (not sorted)Java BinaryHeap 不适用于 A*(未排序)
【发布时间】:2018-01-16 11:17:42
【问题描述】:

我正在使用 Heap for A* 算法的这种实现:

https://courses.cs.washington.edu/courses/cse373/11wi/homework/5/BinaryHeap.java

我稍微修改了一下,只是添加了contains方法,并将remove重命名为poll,因为我之前用过PriorityQueue,但是没有用。

这是我对Comparable<Spot> 接口的实现:

@Override
public int compareTo(Spot o) {
    Double.compare(getF(), o.getF());
}

getF() 返回双倍...

但是,当我用所有 getF()s 打印堆时,我看到了这个:

1. 176.0
2. 175.0
3. 180.0
4. 176.0
5. 223.0
6. 182.0
7. 146.0
8. 177.0
9. 87.0
10. 202.0
...

现在 175 是错误的,因为它低于 176,87 也是错误的......

PriorityQueue 也发生了同样的事情,我做错了什么?

编辑 这是我的 A* 实现:

public List<Spot> process(GameBodyObject me, Point target, ArrayList<GameBodyObject> others) throws Exception {

    if(grid == null) {
        throw new Exception("You have to initialize AStar first.");
    }

    grid.unsetObstacleForObject(me);

    Spot start = grid.getSpotAtPosition(me.getPosition().getX(), me.getPosition().getY());
    Spot end = grid.getSpotAtPosition(target.getX(), target.getY());

    end = grid.moveSpotSoThatItDoesntCollide(end, me.getRadius());

    Heap<Spot> openSet = new Heap<Spot>(grid.getMaxSize());
    List<Spot> closedSet = new ArrayList<>();
    List<Spot> path = new ArrayList<>();

    openSet.add(start);

    while(openSet.size() > 0) {

        /*int winner = 0;
        for(int i = 1; i < openSet.size(); i++) {
            if(openSet.get(i).getF() < openSet.get(winner).getF()) {
                winner = i;
            }
        }*/

        Spot current = openSet.poll(); //openSet.get(winner);

        int i = 1;
        for(Spot s : Arrays.asList(openSet.getArray())) {
            if(s != null) {
                System.out.println(i + ". " + s.getF());
                i++;
            }
        }

        if(current.equals(end)) {
            // We are done, reconstruct the path...
            Spot temp = current;
            path.add(temp);
            while(temp.getPrevious() != null) {
                path.add(temp.getPrevious());
                temp = temp.getPrevious();
            }

            grid.resetObstacles();
            return path;
        }

        closedSet.add(current);

        List<Spot> neighbors = current.getNeighbors();

        for(Spot neighbor : neighbors) {
            if(!closedSet.contains(neighbor) && !grid.isCollidingWithObstacle(neighbor, me.getRadius())) {
                double tempG = current.getG() + 1;
                if(openSet.contains(neighbor)) {
                    if(tempG < neighbor.getG()) {
                        neighbor.setG(tempG);
                    }
                } else {
                    neighbor.setG(tempG);
                    openSet.add(neighbor);
                }

                neighbor.setH(heuristic(neighbor, end));
                neighbor.setF(neighbor.getG() + neighbor.getH());
                neighbor.setPrevious(current);
            }
        }

    }

    grid.resetObstacles();
    return new ArrayList<>();
}

【问题讨论】:

  • 这是问题不是你的主要问题,但你应该解决你的compareTo不知道它应该做什么但它不尊重合同。
  • 你可以在你的compareTo方法中使用return Double.compare(getF(), o.getF());,我认为这应该和你尝试的一样。

标签: java heap


【解决方案1】:

使用 Java 的 PriorityQueue 或类似的堆实现来实现 Dijkstra 或 A* 算法时的一个常见问题是缺少 decreaseKey 方法。 Dijkstra 或 A* 有时都需要更改插入到堆中的元素的优先级。避免缺少此功能的唯一方法是删除元素(这在 Java 的 PriorityQueue 实现中很慢),然后重新插入。

但是这样做有一个问题:当从 PQ/Heap 中删除对象时,它必须仍然包含“旧”优先级(getF() 在您的情况下仍然必须返回旧值),否则元素可能找不到。如果元素已经包含新值,则 PQ/Heap 将在要删除的新位置中查找它,而不是在与旧值匹配的位置。

因此,顺序必须是:(1) 从 PQ 中移除元素,(2) 调整其权重/优先级,(3) 将其重新插入 PQ。

在您的代码中,您有以下部分:

            if(openSet.contains(neighbor)) {
                if(tempG < neighbor.getG()) {
                    neighbor.setG(tempG); // *1
                }
            } else {
                neighbor.setG(tempG);
                openSet.add(neighbor);
            }

            neighbor.setH(heuristic(neighbor, end));
            neighbor.setF(neighbor.getG() + neighbor.getH()); // *2
            neighbor.setPrevious(current);

如果你仔细观察,你会因为*1 行的变化而在标记*2 的行修改neighbor 的权重。要修复它,您可以尝试以下方法:

            if(openSet.contains(neighbor)) {
                if(tempG < neighbor.getG()) {
                    openset.remove(neighbor); // *3
                    neighbor.setG(tempG);
                }
            } else {
                neighbor.setG(tempG);
                // openSet.add(neighbor); // *4
            }

            neighbor.setH(heuristic(neighbor, end));
            neighbor.setF(neighbor.getG() + neighbor.getH());
            openSet.add(neighbor); // *5
            neighbor.setPrevious(current);

如果元素已经存在(*3),这会将元素从堆中移除,并仅在正确计算权重后插入它(*4*5)。

现在的问题:您的堆实现不提供这样的remove(Object) 方法。 Java 的PriorityQueue does。因此,您必须更改为 Java 的 PriorityQueue 或另一个提供 remove(Object) 方法的堆实现。 (或者甚至更好:decreaseKey 方法,因此根本不必删除重新插入元素)。

在我看到的许多使用 Java 的 PQ 的 Dijkstra/A*-Implementations 中,这在第一次尝试时就做错了,导致堆的顺序不正确。

tl;dr:不要修改已插入 PriorityQueue 或 Heap 的元素的权重/优先级。

【讨论】:

  • 那么我认为 Java 中的堆方法并没有更快。我刚刚看到 youtube 上的那个人在 C# 中将 24 ms 执行为 4 - 5 ms。我应该使用堆吗?还是在经典的List&lt;Spot&gt; 中找到最小值?
  • Heap 应该比 List 快,但不一定比 PriorityQueue 快。
  • 但是堆方法不起作用:)即使我使用了return Double.compare(getF(), o.getF());,正如你上面提到的那样。
  • 当您的 A* 实现中节点的权重/优先级发生变化时,您如何使用堆处理代码中的情况?
  • 我要编辑我的问题并添加 A* 实现
猜你喜欢
  • 2023-03-16
  • 1970-01-01
  • 2021-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-23
相关资源
最近更新 更多