具有节点之间不规则距离的A *算法的启发式算法

时间:2014-12-02 12:04:21

标签: java algorithm path-finding a-star heuristics

我目前正致力于A* Algorithm的实现,两个节点之间的距离不规则。包含节点的图是定向和加权图。每个节点连接到至少一个其他节点,也可以存在具有不同距离的对称连接。节点只不过是一个标签,不包含任何特殊信息

我需要的是一种启发式算法,以确定从任何节点A到另一个节点B的最短路径尽可能准确。我试图使用一种启发式方法来返回到节点最近邻居的距离,但当然这并不像没有启发式算法那样有效(= Dijkstra)。


我对A *算法的实现主要包括2个类,算法本身的类(AStar)和节点的一个类(Node)。该代码主要基于Wikipedia伪代码。

AStar.java

的源代码
public class AStar {
    private AStar() {}

    private static Node[] reconstructPath(Map<Node, Node> paths, Node current) {
        List<Node> path = new ArrayList<Node>();
        path.add(0, current);
        while (paths.containsKey(current)) {
            current = paths.get(current);
            path.add(0, current);
        }
        return path.toArray(new Node[0]);
    }

    public static Node[] calculate(Node start, Node target, IHeuristic heuristic) {
        List<Node> closed = new ArrayList<Node>();
        PriorityQueue<Node> open = new PriorityQueue<Node>();
        Map<Node, Double> g_score = new HashMap<Node, Double>();
        Map<Node, Double> f_score = new HashMap<Node, Double>();
        Map<Node, Node> paths = new HashMap<Node, Node>();

        g_score.put(start, 0d);
        f_score.put(start, g_score.get(start) + heuristic.estimateDistance(start, target));
        open.set(start, f_score.get(start));

        while (!open.isEmpty()) {
            Node current = null;

            // find the node with lowest f_score value
            double min_f_score = Double.POSITIVE_INFINITY;
            for (Entry<Node, Double> entry : f_score.entrySet()) {
                if (!closed.contains(entry.getKey()) && entry.getValue() < min_f_score) {
                    min_f_score = entry.getValue();
                    current = entry.getKey();
                }
            }

            if (current.equals(target)) return reconstructPath(paths, target);

            open.remove(current);
            closed.add(current);

            for (Node neighbor : current.getAdjacentNodes()) {
                if (closed.contains(neighbor)) {
                    continue;
                }
                double tentative_g_score = g_score.get(current) + current.getDistance(neighbor);

                if (!open.contains(neighbor) || tentative_g_score < g_score.get(neighbor)) {
                    paths.put(neighbor, current);
                    g_score.put(neighbor, tentative_g_score);
                    f_score.put(neighbor, g_score.get(neighbor) + heuristic.estimateDistance(neighbor, target));
                    if (!open.contains(neighbor)) {
                        open.set(neighbor, f_score.get(neighbor));
                    }
                }
            }
        }
        throw new RuntimeException("no path between " + start + " and " + target);
    }
}

Node.java

的源代码
public class Node {
    private Map<Node, Double> distances = new HashMap<Node, Double>();

    public final String       name;

    public Node(String name) {
        this.name = name;
    }

    public Set<Node> getAdjacentNodes() {
        return Collections.unmodifiableSet(distances.keySet());
    }

    public double getDistance(Node node) {
        return distances.get(node);
    }

    public void setDistance(Node node, double distance) {
        distances.put(node, distance);
    }

    @Override
    public String toString() {
        return (name == null ? "Node@" + Integer.toHexString(hashCode()) : name);
    }
}

PriorityQueue.java

的源代码
public class PriorityQueue<T> {
    transient ArrayList<PriorityEntry<T>> elements     = null;

    private static final int              DEFAULT_SIZE = 10;

    public PriorityQueue() {
        elements = new ArrayList<PriorityEntry<T>>(DEFAULT_SIZE);
    }

    public PriorityQueue(int initialCapacity) {
        elements = new ArrayList<PriorityEntry<T>>(initialCapacity);
    }

    public boolean push(T element, double priority) {
        PriorityEntry<T> entry = new PriorityEntry<T>(element, priority);
        if (elements.contains(entry)) return false;
        elements.add(entry);
        elements.sort(null);
        return true;
    }

    public void set(T element, double priority) {
        PriorityEntry<T> entry = new PriorityEntry<T>(element, priority);
        int index = elements.indexOf(entry);
        if (index >= 0) {
            elements.get(index).setPriority(priority);
        } else {
            elements.add(entry);
        }
        elements.sort(null);
    }

    public T peek() {
        return size() <= 0 ? null : elements.get(0).getValue();
    }

    public T pop() {
        return size() <= 0 ? null : elements.remove(0).getValue();
    }

    public boolean remove(T element) {
        return elements.remove(new PriorityEntry<T>(element, 0));
    }

    public int size() {
        return elements.size();
    }

    public boolean isEmpty() {
        return elements.isEmpty();
    }

    public boolean contains(T element) {
        return elements.contains(new PriorityEntry<T>(element, 0));
    }

    private class PriorityEntry<E> implements Comparable<PriorityEntry<? extends T>> {
        private final E value;
        private double  priority = Double.MIN_VALUE;

        public PriorityEntry(E value, double priority) {
            this.value = value;
            this.priority = priority;
        }

        public E getValue() {
            return value;
        }

        public double getPriority() {
            return priority;
        }

        public void setPriority(double priority) {
            this.priority = priority;
        }

        @Override
        @SuppressWarnings("unchecked")
        public boolean equals(Object o) {
            if (!(o instanceof PriorityEntry)) return false;
            PriorityEntry<?> entry = (PriorityEntry<?>) o;
            return value.equals(entry);
        }

        @Override
        public int compareTo(PriorityEntry<? extends T> entry) {
            return (int) (getPriority() - entry.getPriority());
        }
    }
}

4 个答案:

答案 0 :(得分:1)

在尝试为您的问题定义启发式函数之前,请考虑在许多情况下,使用对目标成本的差(或不正确)估计就像弄丢自己一样完全不使用启发式算法。

对于具有加权弧的图形,您需要考虑节点中是否存在可以导致获取启发式值的某些信息(例如,如果您的节点是城市,则可以是一个好的估计长度它们之间的直线;或者如果你的节点是数组,它们之间的相似性测量)。如果您的节点只是标签,并且没有可用于获取启发式值的信息,那么最好的解决方案可能根本就不使用启发式算法。对于大多数此类问题,这不是最糟糕的情况。最好使用Dijkstra搜索(与A *相同,但使用启发式= 0),让算法从一开始就基于成本扩展节点,而不是使用不一致的不良启发式,因为在此您可能正在扩展基于对目标成本的错误估计似乎很有希望的不一致节点的情况。

我不知道你的图表有多大,但是对于大多数问题,使用启发式和完全不使用它之间的计算时间没有显着差异。特别是在不良启发式的情况下。

我可以看到你有自己的A *实现。我建议你看一下像Hipster这样的启发式搜索库。该库允许您定义图形并测试不同的搜索算法,以了解最适合您的问题的算法。一些代码示例完全描述了您的情况:在加权有向图中搜索。它可能对您的问题有用。

我希望我的回答有所帮助。的问候,

答案 1 :(得分:1)

在没有涉及其他可能的问题的情况下,我想指出主要问题 - 你缺乏飞机。如果城市之间的距离最短,则有

  • 节点 - 城市
  • 重量 - 描述从城市a 城市b 的成本的数值
  • 平面 - 描述环境ex:城市位置(在方格中)

从飞机上你可以推断出有意义的启发式。例如,您可以首先查看具有最低算术距离的城市位置的假设。

如果您没有飞机,则无法预测有意义的启发式方法。 A *仍然可以使用,但它与穷举搜索几乎没有区别。你可以从重量创建平面,但它。

前:

迭代权重并找到权重分位数20/60/20 - 现在你有一个相对平面

- good weight threshold (lowest 20%) 
- average weight threshold (middle 60%)
- bad weight threshold (highest 20%)

使用优先级队列,您的算法会选择好的移动,然后是平均值,最后是坏的。 如果你想要,你可以有超过3个段。


提醒一下:A *快速返回足够好的结果。使用详尽的搜索,您可以找到最佳解决方案,但如果问题规模增大,它将成倍增长。

答案 2 :(得分:0)

要添加到上面的@kiheru评论。您的解决方案只会与启发式提供的解决方案一样好。

如果以下行和heuristic.estimate,范围太窄。算法将快速达到局部最小值。或者,如果启发式不可接受,则算法将导致无法解决方案或不正确的随机解决方案。

    f_score.put(start, g_score.get(start) + heuristic.estimateDistance(start, target));

仔细研究你的启发式并确认它是可以接受的。如果可以接受,可能需要对其进行改进,以便提供更准确的估算。

答案 3 :(得分:0)

对于你的节点类,它似乎有一个X和Y,如果它们代表节点在2D空间中的位置,也许你可以使用一个基于从X和Y值计算的节点之间的直线距离的启发式算法