簡體   English   中英

節點間距離不規則的A *算法的啟發式

[英]Heuristic for A*-Algorithm with irregular distances between nodes

我目前正在研究兩個節點之間的距離不規則的A *算法的實現。 包含節點的圖是有向圖和加權圖。 每個節點都連接到至少一個其他節點,也可能存在具有不同距離的對稱連接。 節點不過是標簽,不包含任何特殊信息

我需要的是一種啟發式方法,以盡可能准確地確定從任何節點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());
        }
    }
}

在嘗試為您的問題定義啟發式功能之前,請考慮在許多情況下,對目標成本的評估不正確(或不正確)與不使用啟發式方法一樣是自欺欺人的。

對於帶有加權弧的圖,您需要考慮節點中是否有一些信息可以導致獲得啟發式值(例如,如果您的節點是城市,則良好的估計可以是直線的長度)它們之間的直線;或者如果您的節點是數組,則它們之間的相似性度量)。 如果您的節點只是標簽,並且沒有可用於獲取啟發式值的信息,那么最好的解決方案就是根本不使用啟發式。 對於大多數此類問題,這不是最壞的情況。 最好使用Dijkstra搜索(與A *相同,但使用啟發式= 0),讓算法從一開始就根據成本擴展節點,而不是使用不一致的錯誤啟發式,因為在這種情況下在這種情況下,您可能正在擴展不必要的節點,這些節點似乎基於對目標成本的錯誤估計而很有希望。

我不知道您的圖有多大,但是對於大多數問題,在使用啟發式算法和完全不使用啟發式算法之間,計算時間沒有顯着差異。 特別是在啟發式錯誤的情況下。

我可以看到您有自己的A *實現。 我建議您看一下Hipster之類的啟發式搜索庫。 該庫使您可以定義圖形並測試不同的搜索算法,以了解適合您問題的最佳算法。 一些代碼示例准確地描述了您的情況:在加權有向圖中進行搜索。 這可能對您的問題很有用。

希望我的回答對您有所幫助。 問候,

在不涉及其他可能問題的情況下,我想指出一個主要問題-您缺少飛機。 如果城市之間的距離最短,您可以

  • 節點 -城市
  • 權重 -描述從城市a城市b的成本的數值
  • 平面 -描述環境,例如:城市位置(在您的正方形網格中)

您可以從飛機上推斷出有意義的啟發式方法。 例如,您可以從城市位置進行假設,例如應該首先查找具有最小算術距離的城市。

如果您沒有飛機,那么您將沒有任何手段來預測有意義的啟發式方法。 A *仍然可以使用,但與窮舉搜索幾乎沒有區別。 您可以根據重量創建平面,但是可以。

例如:

遍歷權重並找到權重分位數20/60/20-現在您有了相對平面

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

使用優先級隊列,您的算法將選擇好的動作,然后平均,最后選擇壞的動作。 如果需要,可以有3個以上細分。


提醒一下:A *快速返回足夠好的結果。 使用詳盡搜索可以找到最佳解決方案,但是如果問題規模增大,它將以指數級的速度變慢。

要添加到上面的@kiheru評論。 您的解決方案將僅與提供的啟發式方法一樣好。

如果以下行和heuristic.estimate,則范圍太窄。 該算法將迅速達到局部最小值。 或者,如果不允許使用啟發式算法,則該算法將導致無解或不正確的隨機解。

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

請仔細查看您的啟發式方法,並確認它是可以接受的。 如果可以接受,則可能需要對其進行改進以提供更准確的估算。

在您的節點類的情況下,如果它們代表2D空間中節點的位置,則似乎具有X和Y,也許您可​​以根據從X和Y值計算得出的節點之間的直線距離使用啟發式方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM