Dijkstra 返回路径

Dijkstra Returning Path

我一直在寻找一种方法来实现 A* 和 Dijkstra,以便能够获得最短路径并开始完成。

我从 SQL 数据库中检索节点和边的列表,将这些项目放入两个字典(节点和边)中,并以 node/edge id 作为键。

我用来测试的开始(148309)和结束(1483093)节点,return是一个结果,但是它访问了其他21个节点,应该return3个节点(108.75m)

尝试在下面的链接中使用伪代码,我设法让它找到了路径,但我正在努力通过回溯来获得它所采用的实际最短路径。下面的链接在他们的例子中没有提到这一点。

https://www.csharpstar.com/dijkstra-algorithm-csharp/

https://www.programmingalgorithms.com/algorithm/dijkstra's-algorithm/

https://www.dotnetlovers.com/article/234/dijkstras-shortest-path-algorithm

对象

public class Node
{
    public Node()
    {
        Edges = new Dictionary<long, Edge>();
    }

    public long Id { get; set; }

    public double Latitude { get; set; }

    public double Longitude { get; set; }

    /// <summary>
    /// The edges coming out of this node.
    /// </summary>
    public Dictionary<long, Edge> Edges { get; set; }

    public double DistanceFromStart { get; set; }

    public double DistanceToEnd { get; set; }

    public bool Visited { get; set; }

    /// <summary>
    /// Specified the distance in KM between this node and the
    /// specified node using their lat/longs.
    /// </summary>
    /// <param name="node"></param>
    /// <returns></returns>
    public double DistanceTo(ref Node node)
    {
        return DistanceHelper.DistanceTo(this, node);
    }
}

public class Edge
{
    public long UID { get; set; }

    public long StartNodeId { get; set; }

    public long EndNodeId { get; set; }

    public double Distance { get; set; }

    public Node EndNode { get; set; }

}

public class SPResult
{
    public double Distance { get; set; }

    public long[] Nodes { get; set; }

    public long[] Edges { get; set; }
}

到目前为止的代码。

public static Graph graph = new Graph();

    static void Main(string[] args)
    {
        Console.WriteLine("Starting");

        //Loads the nodes and edges from a SQL database.
        LoadInfrastructure(3);
        var res = graph.GetShortestPathDijkstra(1483099, 1483093);
        //var res = graph.GetShortestPathDijkstra(1483129, 3156256);

        Console.WriteLine("Done. Press any key to exit.");
        Console.ReadKey();
    }

   public class Graph
{

    public Graph()
    {
        Nodes = new Dictionary<long, Node>();
        Edges = new Dictionary<long, Edge>();
    }

    Dictionary<long, Node> Nodes { get; set; }
    Dictionary<long, Edge> Edges { get; set; }

    Dictionary<long, double> queue;

    Stopwatch stopwatch = new Stopwatch();
    public void AddNode(Node n)
    {
        if (Nodes.ContainsKey(n.Id))
            throw new Exception("Id already in graph.");

        Nodes.Add(n.Id, n);
    }

    public void AddEdge(Edge e)
    {
        if (Edges.ContainsKey(e.UID))
            throw new Exception("Id already in graph.");

        e.EndNode = Nodes[e.EndNodeId];
        Edges.Add(e.UID, e);
        Nodes[e.StartNodeId].Edges.Add(e.UID, e);
    }

    public SPResult GetShortestPathDijkstra(long start, long end)
    {
        return GetShortestPathDijkstra(Nodes[start], Nodes[end]);
    }

    public SPResult GetShortestPathDijkstra(Node start, Node end)
    {
        if (!Nodes.ContainsKey(start.Id))
            throw new Exception("Start node missing!");

        if (!Nodes.ContainsKey(end.Id))
            throw new Exception("End node missing!");

        Console.WriteLine($"Finding shortest path between {start.Id} and {end.Id}...");

        ResetNodes(null);
        stopwatch.Restart();

        Node current = start;
        current.DistanceFromStart = 0;
        queue.Add(start.Id, 0);

        while (queue.Count > 0)
        {
            long minId = queue.OrderBy(x => x.Value).First().Key;
            current = Nodes[minId];
            queue.Remove(minId);

            if (minId == end.Id)
            {
                current.Visited = true;
                break;
            }
            foreach (var edge in current.Edges.OrderBy(ee => ee.Value.Distance))
            {
                var endNode = edge.Value.EndNode;
                if (endNode.Visited)
                    continue;

                double distance = current.DistanceFromStart + edge.Value.Distance;

                if (queue.ContainsKey(endNode.Id))
                {
                    if (queue[endNode.Id] > distance)
                    {
                        queue[endNode.Id] = endNode.Id;
                        Nodes[endNode.Id].DistanceFromStart = distance;
                    }
                }
                else
                {
                    Nodes[endNode.Id].DistanceFromStart = distance;
                    queue.Add(endNode.Id, distance);
                }
            }

            current.Visited = true;
        }

        stopwatch.Stop();
        Console.WriteLine($"Found shortest path between {start.Id} and {end.Id} in {stopwatch.ElapsedMilliseconds}ms.");
        Debug.WriteLine($"Found shortest path between {start.Id} and {end.Id} in {stopwatch.ElapsedMilliseconds}ms.");

**//Get path used.**

        var rr = Nodes.Values.Where(nn => nn.Visited).OrderBy(nn => nn.DistanceFromStart).ToList();

        return null;
    }

    public SPResult GetShortestPathAstar(long start, long end)
    {
        return GetShortestPathAstar(Nodes[start], Nodes[end]);
    }

    public SPResult GetShortestPathAstar(Node start, Node end)
    {
        ResetNodes(end);
        start.DistanceFromStart = 0;

        throw new NotImplementedException();
    }

    private void ResetNodes(Node endNode)
    {
        queue = new Dictionary<long, double>();
        foreach (var node in Nodes)
        {
            node.Value.DistanceFromStart = double.PositiveInfinity;
            node.Value.Visited = false;

            if (endNode != null)
                node.Value.DistanceToEnd = node.Value.DistanceTo(ref endNode);
        }
    }

}

我设法使用 YouTube 视频通过伪代码并实现了 Dijkstra 和 A* 算法。

https://www.youtube.com/watch?v=nhiFx28e7JY

https://www.youtube.com/watch?v=mZfyt03LDH4

Program.cs (片段)

LoadInfrastructureFromSQL();
var resA5 = graph.GetShortestPathAstar(startId, endId);

节点

public class Node
{
    public Node()
    {

    }

    public Node(long id, double lat, double lon) : this()
    {
        Id = id;
        Latitude = lat;
        Longitude = lon;
    }

    public long Id { get; set; }

    public double Latitude { get; set; }

    public double Longitude { get; set; }

    public double Gcost { get; set; }

    public double Hcost { get; set; }

    public double Fcost => Gcost + Hcost;

    public Node Parent { get; set; }

    /// <summary>
    /// The edges coming out of this node.
    /// </summary>
    public Dictionary<long, Edge> Edges { get; set; }


    public void AddEdges(Edge e)
    {
        if (Edges == null)
            Edges = new Dictionary<long, Edge>();

        if (Edges.ContainsKey(e.UID))
            throw new Exception($"Edge id {e.UID} already exists.");

        Edges.Add(e.UID, e);

    }
    public double DistanceTo(Node point)
    {
        double p = 0.017453292519943295;
        double a = 0.5 - Math.Cos((point.Latitude - Latitude) * p) / 2 + Math.Cos(Latitude * p) * Math.Cos(point.Latitude * p) * (1 - Math.Cos((point.Longitude - Longitude) * p)) / 2;
        return 12742 * Math.Asin(Math.Sqrt(a));
    }

}

边缘

public class Edge
{
    public Edge()
    {

    }

    public Edge(long uid, long id, double distance)
    {
        UID = uid;
        WayId = id;
        Distance = distance;
    }

    public Edge(long uid, long id, double distance, long startNode, long endNode) : this(uid, id, distance)
    {
        StartNodeId = startNode;
        EndNodeId = endNode;
    }

     /// <summary>
    /// Unique way id for every single edge.
    /// </summary>
    public long UID { get; set; }

    /// <summary>
    /// Duplicate edges will share the same WayId i.e. if the way is bi-directional.
    /// </summary>
    public long WayId { get; set; }

    public long StartNodeId { get; set; }

    public long EndNodeId { get; set; }        

    public Node EndNode { get; set; }

    public double Distance { get; set; }
}

结果(可选)

public class SPResult
{
    public double Distance { get; set; }

    public Node[] Nodes { get; set; }

    public long NodesChecked { get; set; }

    public Edge[] Edges { get; set; }

    public long CalculationTime { get; set; }
}

图形与算法

   //Routing algorithm taken from https://www.youtube.com/watch?time_continue=5&v=-L-WgKMFuhE

public class Graph
{
    public Graph()
    {
        Nodes = new Dictionary<long, Node>();
        Edges = new Dictionary<long, Edge>();
    }

    Dictionary<long, Node> Nodes { get; set; }
    Dictionary<long, Edge> Edges { get; set; }

    Dictionary<long, Node> open;
    Dictionary<long, Node> closed;

    Stopwatch stopwatch = new Stopwatch();
    public void AddNode(Node n)
    {
        if (Nodes.ContainsKey(n.Id))
            throw new Exception("Id already in graph.");

        Nodes.Add(n.Id, n);
    }


    public void AddEdge(Edge e)
    {           
        e.EndNode = Nodes[e.EndNodeId];
        Edges.Add(e.UID, e);
        Nodes[e.StartNodeId].AddEdges(e);
    }

    public Node FindClosestNode(double lat, double lon, int radius)
    {
        Node n = new Node();
        n.Latitude = lat;
        n.Longitude = lon;

        return FindClosestNode(n, radius);
    }

    public Node FindClosestNode(Node node, int radius)
    {
        Console.WriteLine($"Finding closest node [Latitude: {Math.Round(node.Latitude, 6)}, Longitude:{Math.Round(node.Longitude, 6)}]...");
        Stopwatch sw = new Stopwatch();
        sw.Start();
        var res = Nodes.Select(x => new { Id = x.Key, Distance = x.Value.DistanceTo(node) }).Where(nn => nn.Distance < radius).OrderBy(x => x.Distance).FirstOrDefault();

        if (res != null)
        {
            Debug.WriteLine($"Found nearest node in {sw.ElapsedMilliseconds}ms [{res.Id}]");
            return Nodes[res.Id];
        }

        Debug.WriteLine($"No nearest node {sw.ElapsedMilliseconds}ms [{res.Id}]");
        return null;
    }



    #region ROUTING
    public async Task<SPResult> GetShortestPathDijkstra(long start, long end)
    {
        return await GetShortestPathDijkstra(Nodes[start], Nodes[end]);
    }

    public async Task<SPResult> GetShortestPathDijkstra(Node start, Node end)
    {
        if (!Nodes.ContainsKey(start.Id))
            throw new Exception("Start node missing!");

        if (!Nodes.ContainsKey(end.Id))
            throw new Exception("End node missing!");

        Console.WriteLine($"Finding Dijkstra shortest path between {start.Id} and {end.Id}...");

        ResetNodes(null);
        stopwatch.Restart();

        open.Add(start.Id, start);
        start.Gcost = 0;
        Node current = start;

        while (true)
        {
            long lowest = open.OrderBy(qq => qq.Value.Fcost).FirstOrDefault().Key;
            current = Nodes[lowest];

            open.Remove(lowest);
            closed.Add(lowest, current);

            if (current.Id == end.Id)
                break;

            foreach (var neigh in current.Edges)
            {
                Node edgeEnd = neigh.Value.EndNode;
                if (closed.ContainsKey(edgeEnd.Id))
                    continue;

                double distance = current.Gcost + neigh.Value.Distance;
                if (distance < edgeEnd.Gcost || !open.ContainsKey(edgeEnd.Id))
                {
                    edgeEnd.Gcost = distance;
                    edgeEnd.Parent = current;

                    if (!open.ContainsKey(edgeEnd.Id))
                        open.Add(edgeEnd.Id, edgeEnd);                           
                }
            }
        }

        stopwatch.Stop();
        Console.WriteLine($"Found shortest path between {start.Id} and {end.Id} in {stopwatch.ElapsedMilliseconds}ms.");
        Debug.WriteLine($"Found shortest path between {start.Id} and {end.Id} in {stopwatch.ElapsedMilliseconds}ms.");

        SPResult spr = new SPResult();
        spr.CalculationTime = stopwatch.ElapsedMilliseconds;
        spr.Distance = current.Gcost;

        var traceback = Traceback(current);
        spr.Nodes = traceback.Item1;
        spr.Edges = traceback.Item2;
        spr.NodesChecked = closed.Count + open.Count;
        return spr;
    }

    public async Task<SPResult> GetShortestPathAstar(long start, long end)
    {
        return await GetShortestPathAstar(Nodes[start], Nodes[end]);
    }

    public async Task<SPResult> GetShortestPathAstar(Node start, Node end)
    {
        if (!Nodes.ContainsKey(start.Id))
            throw new Exception("Start node missing!");

        if (!Nodes.ContainsKey(end.Id))
            throw new Exception("End node missing!");

        Console.WriteLine($"Finding A* shortest path between {start.Id} and {end.Id}...");

        ResetNodes(end);
        stopwatch.Restart();

        start.Gcost = 0;
        Node current = start;
        open.Add(start.Id, current);            

        while (true)
        {
            if (open.Count() == 0)
                return null;

            long lowest = open.OrderBy(qq => qq.Value.Fcost).FirstOrDefault().Key;
            current = Nodes[lowest];

            open.Remove(lowest);
            closed.Add(lowest, current);

            if (current.Id == end.Id)
                break;

            foreach (var neigh in current.Edges)
            {
                Node edgeEnd = neigh.Value.EndNode;
                if (closed.ContainsKey(edgeEnd.Id))
                    continue;

                double distance = current.Gcost + neigh.Value.Distance;
                if (distance < (edgeEnd.Gcost) || !open.ContainsKey(edgeEnd.Id))
                {
                    edgeEnd.Gcost = distance;
                    edgeEnd.Hcost = current.DistanceTo(end);
                    edgeEnd.Parent = current;

                    if (!open.ContainsKey(edgeEnd.Id))
                        open.Add(edgeEnd.Id, edgeEnd);
                }
            }
        }

        stopwatch.Stop();
        Console.WriteLine($"Found shortest path between {start.Id} and {end.Id} in {stopwatch.ElapsedMilliseconds}ms.");
        Debug.WriteLine($"Found shortest path between {start.Id} and {end.Id} in {stopwatch.ElapsedMilliseconds}ms.");

        SPResult spr = new SPResult();
        spr.CalculationTime = stopwatch.ElapsedMilliseconds;
        spr.Distance = current.Gcost;

        var traceback = Traceback(current);
        spr.Nodes = traceback.Item1;
        spr.Edges = traceback.Item2;
        spr.NodesChecked = closed.Count + open.Count;
        return spr;
    }

    private (Node[],Edge[]) Traceback(Node current)
    {

        Stopwatch sw = new Stopwatch();
        sw.Start();
        List<Node> nodes = new List<Node>();
        List<Edge> edges = new List<Edge>();

        nodes.Add(current);
        while (current.Parent != null)
        {
            edges.Add(current.Parent.Edges.FirstOrDefault(ee => ee.Value.EndNodeId == current.Id).Value);
            current = current.Parent;
            nodes.Add(current);
        }
        Debug.WriteLine($"Traceback in {sw.ElapsedMilliseconds}ms.");
        return nodes.Reverse<Node>().ToArray();
    }

    private void ResetNodes(Node endNode)
    {
        open = new Dictionary<long, Node>();
        closed = new Dictionary<long, Node>();
        foreach (var node in Nodes)
        {
            node.Value.Gcost = double.PositiveInfinity;
            node.Value.Hcost = double.PositiveInfinity;
        }
    }

    #endregion
}