C#中的尋路演算法
目錄
Dijkstra和Astar的比較
在Visual Studio 2017中解壓縮並開啟解決方案
介紹
你有沒有想過GPS應用程式如何計算到選定目的地的最快方式?正如您將看到的,它實際上非常簡單。
本文將對此進行說明並提供您可以隨意使用的示例程式碼。本文還比較了兩種常見的基本演算法,Dijkstra和A *。
問題
假設你有一張地圖。你知道你在哪裡以及你想去哪裡。地圖具有連線節點(具有座標的位置)的道路(它們被稱為邊)。
從每個節點,您可以轉到一個或多個邊。邊是具有成本的(例如,旅行所需的路程長度或時間)。對於小地圖,可以計算到目的地的所有可能路線並選擇最短路線。但是對於具有許多節點的地圖來說,這不是很實用,因為組合會呈指數級增長。
Dijkstra演算法
Dijkstra演算法於1959年由Edsger Dijkstra發現。這是它的工作原理:
- 從起始節點,將所有連線的節點新增到優先順序佇列。
- 按最低成本對優先順序佇列進行排序,並將第一個節點作為當前節點。
對於每個子節點,選擇導致最短路徑到開始節點的最佳節點。
當從一個節點調查所有邊時,該節點為“ Visited”,您不需要再次訪問該節點。 - 將連線到當前節點的每個子節點新增到優先順序佇列。
- 轉到步驟2,直到佇列為空。
- 遞迴地建立從最開始到結束的最短路徑的每個節點的節點列表。
- 反轉列表,您找到了最短的路徑
換句話說,遞迴地為節點的每個子節點測量它到開始節點的距離。儲存距離和儲存導致到開始節點最短路徑的節點。當你到達終點節點時,遞迴地以最短的路徑返回到開始節點,反轉該列表並且你將擁有最短的路徑。
下面是我在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找到一個較低成本的路徑。例如,在實際地圖中,最短路徑並不總是最好的。在速度限制較高的道路上行駛可能會讓您更快到達目的地。這就是為什麼在邊成本中新增隨機數使得這個實驗更加真實。
結論
那麼什麼演算法是Dijkstra和A *的最佳路徑尋找演算法?
我認為這要視情況而定。如果您只對最短路徑感興趣,那就是A *。
速度要快得多,它與Dijkstra的效果相同。但是如果邊成本還有其他方面的長度,那麼Dijkstra在尋找最佳路徑方面比這個版本的A *更好。畢竟,它仍然非常快。我認為500,000個節點是一個非常大的資料集。我也認為我的實現可以進行很多優化。
挑戰
如果你還天真地喜歡程式設計挑戰,也許你想給機器人程式設計,讓它走出迷宮?
您可能需要一些路徑查詢演算法來解決它。
參考這個網站:http://airobots.azurewebsites.net/
感謝閱讀,我希望你發現路徑查詢演算法和我現在一樣有趣。
祝你今天愉快!
原文地址:https://www.codeproject.com/Articles/1221034/Pathfinding-Algorithms-in-Csharp