1. 程式人生 > >C#中的尋路演算法

C#中的尋路演算法

目錄

介紹

問題

Dijkstra演算法

A *演算法

結果

結論

挑戰


DijkstraAstar的比較

Download source - 571.3 KB

Visual Studio 2017中解壓縮並開啟解決方案

介紹

你有沒有想過GPS應用程式如何計算到選定目的地的最快方式?正如您將看到的,它實際上非常簡單。

本文將對此進行說明並提供您可以隨意使用的示例程式碼。本文還比較了兩種常見的基本演算法,DijkstraA *

問題

假設你有一張地圖。你知道你在哪裡以及你想去哪裡。地圖具有連線節點(具有座標的位置)的道路(它們被稱為邊)。

從每個節點,您可以轉到一個或多個邊。邊是具有成本的(例如,旅行所需的路程長度或時間)。對於小地圖,可以計算到目的地的所有可能路線並選擇最短路線。但是對於具有許多節點的地圖來說,這不是很實用,因為組合會呈指數級增長。

Dijkstra演算法

Dijkstra演算法於1959年由Edsger Dijkstra發現。這是它的工作原理:

  1. 從起始節點,將所有連線的節點新增到優先順序佇列。
  2. 按最低成本對優先順序佇列進行排序,並將第一個節點作為當前節點。
    對於每個子節點,選擇導致最短路徑到開始節點的最佳節點。
    當從一個節點調查所有邊時,該節點為“ Visited,您不需要再次訪問該節點。
  3. 將連線到當前節點的每個子節點新增到優先順序佇列。
  4. 轉到步驟2,直到佇列為空。
  5. 遞迴地建立從最開始到結束的最短路徑的每個節點的節點列表。
  6. 反轉列表,您找到了最短的路徑

換句話說,遞迴地為節點的每個子節點測量它到開始節點的距離。儲存距離和儲存導致到開始節點最短路徑的節點。當你到達終點節點時,遞迴地以最短的路徑返回到開始節點,反轉該列表並且你將擁有最短的路徑。

下面是我在C#程式碼中的Dijkstra演算法實現。它可能比上面更容易理解。

public List<Node> GetShortestPathDijkstra()
{
    DijkstraSearch();
    var shortestPath = new List<Node>();
    shortestPath.Add(End);
    BuildShortestPath(shortestPath, End);
    shortestPath.Reverse();
    return shortestPath;
}

private void BuildShortestPath(List<Node> list, Node node)
{
    if (node.NearestToStart == null)
    {
        return;
    }
    list.Add(node.NearestToStart);
    BuildShortestPath(list, node.NearestToStart);
}

private void DijkstraSearch()
{
    Start.MinCostToStart = 0;
    var prioQueue = new List<Node>();
    prioQueue.Add(Start);

    do {
        prioQueue = prioQueue.OrderBy(x => x.MinCostToStart).ToList();
        var node = prioQueue.First();
        prioQueue.Remove(node);
        foreach (var cnn in node.Connections.OrderBy(x => x.Cost))
        {
            var childNode = cnn.ConnectedNode;
            if (childNode.Visited)
            {
                continue;
            }
            if (childNode.MinCostToStart == null ||
                node.MinCostToStart + cnn.Cost < childNode.MinCostToStart)
            {
                childNode.MinCostToStart = node.MinCostToStart + cnn.Cost;
                childNode.NearestToStart = node;
                if (!prioQueue.Contains(childNode))
                {
                    prioQueue.Add(childNode);
                }
            }
        }

        node.Visited = true;
        if (node == End)
        {
            return;
        }
    } while (prioQueue.Any());
}

這是我測試程式中隨機生成的地圖。點是節點,它們之間是表示邊的線。該地圖由5000個節點和15000個邊組成。

搜尋演算法訪問較淺的彩色點,最佳路徑以綠色繪製。

A *演算法

Dijkstra演算法有很多改進。其中最常見的是A *。它與Dijkstra基本相同,只有一個簡單的修改。

邊的優先順序也取決於邊與目標的直線距離有多近。因此,在執行A *搜尋之前,必須為每個節點測量出到最終目的地的直線距離,如果您知道每個節點的座標,這很容易。這是A *的最簡單形式,其定義也允許對啟發式函式的改進。(在本例中為StraightLineDistanceToEnd

該演算法具有很大的效能優勢,因為當終點的路徑的方向已知時,它不需要訪問儘可能多的節點。

請參閱下面的實現

public List<Node> GetShortestPathAstar()
{
    foreach (var node in Map.Nodes)
    {
        node.StraightLineDistanceToEnd = node.StraightLineDistanceTo(End);
    }
    AstarSearch();
    var shortestPath = new List<Node>();
    shortestPath.Add(End);
    BuildShortestPath(shortestPath, End);
    shortestPath.Reverse();
    return shortestPath;
}

private void AstarSearch()
{
    Start.MinCostToStart = 0;
    var prioQueue = new List<Node>();
    prioQueue.Add(Start);
    do {
        prioQueue = prioQueue.OrderBy(x => x.MinCostToStart + x.StraightLineDistanceToEnd).ToList();
        var node = prioQueue.First();
        prioQueue.Remove(node);
        NodeVisits++;
        foreach (var cnn in node.Connections.OrderBy(x => x.Cost))
        {
            var childNode = cnn.ConnectedNode;
            if (childNode.Visited)
            {
                continue;
            }
            if (childNode.MinCostToStart == null ||
                node.MinCostToStart + cnn.Cost < childNode.MinCostToStart)
            {
                childNode.MinCostToStart = node.MinCostToStart + cnn.Cost;
                childNode.NearestToStart = node;
                if (!prioQueue.Contains(childNode))
                {
                    prioQueue.Add(childNode);
                }
            }
        }
        node.Visited = true;
        if (node == End)
        {
            return;
        }
    } while (prioQueue.Any());
}

這與上面的地圖相同,但路徑是使用A *演算法計算的。如您所見,需要訪問的節點要少得多。

結果

在同一個500,000個節點的地圖上執行這兩種演算法時,我得到了這些結果。

 

Dijkstra演算法

A*

訪問過的節點

330871

19410

計算時間(ms

850

127

最佳路徑的成本

14322

22994

最短路徑的距離

0,82446

0,82446

正如您在上表中所看到的,A *演算法比Dijkstra快約7倍,並且它們都找到了最短的路徑。

但是,當為邊的成本生成隨機數時,Dijkstra找到一個較低成本的路徑。例如,在實際地圖中,最短路徑並不總是最好的。在速度限制較高的道路上行駛可能會讓您更快到達目的地。這就是為什麼在邊成本中新增隨機數使得這個實驗更加真實。

結論

那麼什麼演算法是DijkstraA *的最佳路徑尋找演算法?

我認為這要視情況而定。如果您只對最短路徑感興趣,那就是A *

速度要快得多,它與Dijkstra的效果相同。但是如果邊成本還有其他方面的長度,那麼Dijkstra在尋找最佳路徑方面比這個版本的A *更好。畢竟,它仍然非常快。我認為500,000個節點是一個非常大的資料集。我也認為我的實現可以進行很多優化。

挑戰

如果你還天真地喜歡程式設計挑戰,也許你想給機器人程式設計,讓它走出迷宮?

您可能需要一些路徑查詢演算法來解決它。
參考這個網站:http://airobots.azurewebsites.net/

感謝閱讀,我希望你發現路徑查詢演算法和我現在一樣有趣。

祝你今天愉快!

 

原文地址:https://www.codeproject.com/Articles/1221034/Pathfinding-Algorithms-in-Csharp