Dijkstra Shortest-Paths fast recalculation if only

2019-06-24 11:00发布

问题:

I'm trying to calculate the shortest paths. This does work with the below pasted implementation of Dijkstra. However I want to speed the things up.

I use this implementation to decide to which field I want to go next. The graph represents an two dimensional array where all fields are connected to each neighbours. But over time the following happens: I need to remove some edges (there are obstacles). The start node is my current position which does also change over time.

This means:

  • I do never add a node, never add a new edge, never change the weight of an edge. The only operation is removing an edge

  • The start node does change over time

Questions:

  • Is there an algorithm wich can do a fast recalculation of the shortest-paths when I know that the only change in the graph is the removal of an edge?

  • Is there an algorithm wich allows me to fast recalculate the shortest path when the start node changes only to one of it's neighbours?

  • Is another algorithm maybe better suited for my problem?

Thx for your help

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;

public class Dijkstra<T>
{
    private Node<T> calculatedStart;

    private ReadOnlyCollection<Node<T>> Nodes {
        get ;
        set ;
    }

    private ReadOnlyCollection<Edge<T>> Edges {
        get;
        set;
    }

    private List<Node<T>> NodesToInspect {
        get;
        set ;
    }

    private Dictionary<Node<T>, int> Distance {
        get ;
        set ;
    }

    private Dictionary<Node<T>, Node<T>> PreviousNode {
        get;
        set ;
    }

    public Dijkstra (ReadOnlyCollection<Edge<T>> edges, ReadOnlyCollection<Node<T>> nodes)
    {
        Edges = edges;
        Nodes = nodes;
        NodesToInspect = new List<Node<T>> ();
        Distance = new Dictionary<Node<T>, int> ();
        PreviousNode = new Dictionary<Node<T>, Node<T>> ();

        foreach (Node<T> n in Nodes) {
            PreviousNode.Add (n, null);
            NodesToInspect.Add (n);
            Distance.Add (n, int.MaxValue);
        }
    }

    public LinkedList<T> GetPath (T start, T destination)
    {
        Node<T> startNode = new Node<T> (start);
        Node<T> destinationNode = new Node<T> (destination);

        CalculateAllShortestDistances (startNode);

        // building path going back from the destination to the start always taking the nearest node
        LinkedList<T> path = new LinkedList<T> ();
        path.AddFirst (destinationNode.Value);

        while (PreviousNode[destinationNode] != null) {
            destinationNode = PreviousNode [destinationNode];
            path.AddFirst (destinationNode.Value);
        }

        path.RemoveFirst ();

        return path;
    }

    private void CalculateAllShortestDistances (Node<T> startNode)
    {
        if (startNode.Value.Equals (calculatedStart)) {
            return;
        }

        Distance [startNode] = 0;

        while (NodesToInspect.Count > 0) {
            Node<T> nearestNode = GetNodeWithSmallestDistance ();
            // if we cannot find another node with the function above we can exit the algorithm and clear the
            // nodes to inspect because they would not be reachable from the start or will not be able to shorten the paths...
            // this algorithm does also implicitly kind of calculate the minimum spanning tree...
            if (nearestNode == null) {
                NodesToInspect.Clear ();
            } else {
                foreach (Node<T> neighbour in GetNeighborsFromNodesToInspect(nearestNode)) {
                    // calculate distance with the currently inspected neighbour
                    int dist = Distance [nearestNode] + GetDirectDistanceBetween (nearestNode, neighbour);

                    // set the neighbour as shortest if it is better than the current shortest distance
                    if (dist < Distance [neighbour]) {
                        Distance [neighbour] = dist;
                        PreviousNode [neighbour] = nearestNode;
                    }
                }
                NodesToInspect.Remove (nearestNode);
            }
        }

        calculatedStart = startNode;
    }

    private Node<T> GetNodeWithSmallestDistance ()
    {
        int distance = int.MaxValue;
        Node<T> smallest = null;

        foreach (Node<T> inspectedNode in NodesToInspect) {
            if (Distance [inspectedNode] < distance) {
                distance = Distance [inspectedNode];
                smallest = inspectedNode;
            }
        }

        return smallest;
    }

    private List<Node<T>> GetNeighborsFromNodesToInspect (Node<T> n)
    {
        List<Node<T>> neighbors = new List<Node<T>> ();

        foreach (Edge<T> e in Edges) {
            if (e.Start.Equals (n) && NodesToInspect.Contains (n)) {
                neighbors.Add (e.End);
            }
        }

        return neighbors;
    }

    private int GetDirectDistanceBetween (Node<T> startNode, Node<T> endNode)
    {
        foreach (Edge<T> e in Edges) {
            if (e.Start.Equals (startNode) && e.End.Equals (endNode)) {
                return e.Distance;
            }
        }

        return int.MaxValue;
    }
}

回答1:

Is there an algorithm wich can do a fast recalculation of the shortest-paths when I know that the only change in the graph is the removal of an edge?

Is there an algorithm wich allows me to fast recalculate the shortest path when the start node changes only to one of it's neighbours?

The answer to both of these questions is yes.


For the first case, the algorithm you're looking for is called LPA* (sometimes, less commonly, called Incremental A*. The title on that paper is outdated). It's a (rather complicated) modification to A* that allows fast recalculation of best paths when only a few edges have changed.

Like A*, LPA* requires an admissible distance heuristic. If no such heuristic exists, you can just set it to 0. Doing this in A* will essentially turn it into Djikstra's algorithm; doing this in LPA* will turn it into an obscure, rarely-used algorithm called DynamicSWSF-SP.


For the second case, you're looking for D*-Lite. It is a pretty simple modification to LPA* (simple, at least, once you understand LPA*) that does incremental pathfinding as the unit moves from start-to-finish and new information is gained (edges are added/removed/changed). It is primarily used for robots traversing an unknown or partially-known terrain.


I've written up a fairly comprehensive answer (with links to papers, in the question) on various pathfinding algorithms here.