Heuristic for A * -Algorithm with irregular distances between nodes

I am currently working on an implementation of the A * algorithm with irregular distances between two nodes. A graph containing nodes is a directed and weighted graph. Each node is connected to at least one other node, there may also be symmetrical connections with different distances. A node is nothing more than a label and does not contain any special information.

I need a heuristic to determine the shortest path from any node A to another node B as accurately as possible. I tried using a heuristic that returns the distance to the nearest neighbor of the node, but of course it was not as efficient as no heuristic at all (= Dijkstra).


My implementation of the A * algorithm consists mainly of 2 classes, a class for the algorithm itself ( AStar ) and one for nodes ( Node ). The code is largely based on the Wikipedia pseudo-code.

Source Code 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 Source 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); } } 

Source Code 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()); } } } 
+6
source share
4 answers

Before trying to determine the heuristic function for your problem, consider that in many cases the use of a poor (or incorrect) estimate of the value of the goal is selfless, since it does not use heuristics at all.

In the case of a graph with weighted arcs, it is necessary to consider whether there is any information in the nodes that can lead to heuristic values ​​(for example, if your nodes are cities, a good estimate may be a small straight line between them, or if your nodes are arrays , measurement of similarity between them). If your nodes are just labels, and there is no information that you can use to get your heuristic values, then perhaps the best solution would be to use heuristics at all. This is not the worst option for most problems of this type. It is better to use a search in Dijkstra (the same with A *, but using heuristic = 0), allowing the algorithm to deploy nodes based on cost from the very beginning, than using a bad heuristic that is incompatible, because this is a situation in which you may Expand unnecessary nodes that seem promising based on a poor estimate of the goal’s value.

I don’t know how big your graphs are, but for most problems there is no significant difference in computational time between heuristics and not using it at all. Especially in case of bad heuristics.

I see that you have your own implementation of A *. I recommend that you take a look at the heuristic search library like Hipster . This library allows you to define your schedule and test various search algorithms to find out the best one that suits your problem. Some code examples describe exactly your case: seach in weighted oriented graphs. This may be helpful for your problem.

Hope my answer helps. Yours faithfully,

+1
source

Without going into other possible problems, I would like to point out the main problem - you are missing an airplane. In case of problems with the short distance between cities, you

  • node - city
  • weight - a numerical value describing the cost to go from city to city ​​b
  • airplane - describes the ex environment: the position of the city (in your square grid)

From an airplane, you can extrapolate meaningful heuristics. For example, you can make an assumption from the position of the city, as the city with the smallest arithmetic distance should be looked first.

If you do not have an airplane, you do not have the means to predict meaningful heuristics. A * will still work, but it is hardly different from an exhaustive search. You can create a plane out of weight, but he is.

Example:

Iterate over weights and search for weight quantiles 20/60/20 - now you have a relative plane

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

With a priority queue, your algorithm will choose good moves, and then medium and finally bad ones. If you want, you can have more than 3 segments.


As a reminder: A * returns a pretty good result quickly. Using an exhaustive search, you can find a better solution, but it will be exponentially slower if the size of the problem increases.

+1
source

Add a comment @kiheru above. Your solution will only be as good as with heuristics.

If the next line and the .estimate heuristic have too narrow a scope. The algorithm will quickly reach a local minimum. Alternatively, if the heuristic is invalid, the algorithm will neither lead to a solution nor to an incorrect random solution.

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

Take a look at your heuristic and confirm its validity. If appropriate, it may need to be improved to provide a more accurate estimate.

0
source

In the case of your node class, it has X and Y, if they represent the node position in 2D space, perhaps you can use a heuristic based on the direct distance between the nodes computed from X and Y.

0
source

Source: https://habr.com/ru/post/978991/


All Articles