C#でA *検索の実装を探していました。最終的に自分自身を書いた。普遍的なので、他の人には便利だと思います。C#の簡潔でユニバーサルなA *検索
-2
A
答えて
0
検索ドメインを定義するために、アルゴリズムを使用しAStar<T>
からクラスを継承し、Neighbors
、Cost
とHeuristic
関数をオーバーライドします。タイプT
は、検索ドメイン内の場所を定義します(たとえば、2Dグリッドで検索する場合はPoint)。その後、クラスにFindPath
と電話してください。
また、A *のロケーションごとのデータストレージを最適化するために、StorageClear
,StorageGet
およびStorageAdd
を任意で上書きすることもできます。デフォルトの実装では、比較的高速のDictionary<T, object>
が使用されますが、多くの特殊なケースでは改善することができます。ストレージを上書きしない場合は、タイプTが等価であることを確認してください(例:IEquatable<T>
を実装してください)。そうしないと、辞書のルックアップが非常に遅くなります。
詳細については、コメントを参照してください。
public abstract class AStar<T>
{
#region Fields
private class Node
{
public T Position;
public T PreviousPosition;
public float F, G, H;
public bool IsClosed;
}
private int m_nodesCacheIndex;
private List<Node> m_nodesCache = new List<Node>();
private List<Node> m_openHeap = new List<Node>();
private List<T> m_neighbors = new List<T>();
private Dictionary<T, object> m_defaultStorage;
#endregion
#region Domain Definition
// User must override Neighbors, Cost and Heuristic functions to define search domain.
// It is optional to override StorageClear, StorageGet and StorageAdd functions.
// Default implementation of these storage functions uses a Dictionary<T, object>, this works for all possible search domains.
// A domain-specific storage algorihm may be significantly faster.
// For example if searching on a finite 2D or 3D grid, storage using fixed array with each element representing one world point benchmarks an order of magnitude faster.
/// <summary>
/// Return all neighbors of the given point.
/// Must be overridden.
/// </summary>
/// <param name="p">Point to return neighbors for</param>
/// <param name="neighbors">Empty collection to fill with neighbors</param>
protected abstract void Neighbors(T p, List<T> neighbors);
/// <summary>
/// Return cost of making a step from p1 to p2 (which are neighbors).
/// Cost equal to float.PositiveInfinity indicates that passage from p1 to p2 is impossible.
/// Must be overridden.
/// </summary>
/// <param name="p1">Start point</param>
/// <param name="p2">End point</param>
/// <returns>Cost value</returns>
protected abstract float Cost(T p1, T p2);
/// <summary>
/// Return an estimate of cost of moving from p to nearest goal.
/// Must return 0 when p is goal.
/// This is an estimate of sum of all costs along the best path between p and the nearest goal.
/// This should not overestimate the actual cost; if it does, the result of A* might not be an optimal path.
/// Underestimating the cost is allowed, but may cause the algorithm to check more positions.
/// Must be overridden.
/// </summary>
/// <param name="p">Point to estimate cost from</param>
/// <returns>Cost value</returns>
protected abstract float Heuristic(T p);
/// <summary>
/// Clear A* storage.
/// This will be called every time before a search starts and before any other user functions are called.
/// Optional override when using domain-optimized storage.
/// </summary>
protected virtual void StorageClear()
{
if (m_defaultStorage == null)
{
m_defaultStorage = new Dictionary<T, object>();
}
else
{
m_defaultStorage.Clear();
}
}
/// <summary>
/// Retrieve data from storage at given point.
/// Optional override when using domain-optimized storage.
/// </summary>
/// <param name="p">Point to retrieve data at</param>
/// <returns>Data stored for point p or null if nothing stored</returns>
protected virtual object StorageGet(T p)
{
object data;
m_defaultStorage.TryGetValue(p, out data);
return data;
}
/// <summary>
/// Add data to storage at given point.
/// There will never be any data already stored at that point.
/// Optional override when using domain-optimized storage.
/// </summary>
/// <param name="p">Point to add data at</param>
/// <param name="data">Data to add</param>
protected virtual void StorageAdd(T p, object data)
{
m_defaultStorage.Add(p, data);
}
#endregion
#region Public Interface
/// <summary>
/// Find best path from start to nearest goal.
/// Goal is any point for which Heuristic override returns 0.
/// If maxPositionsToCheck limit is reached, best path found so far is returned.
/// If there is no path to goal, path to a point nearest to goal is returned instead.
/// </summary>
/// <param name="path">Path will contain steps to reach goal from start in reverse order (first step at the end of collection)</param>
/// <param name="start">Starting point to search for path</param>
/// <param name="maxPositionsToCheck">Maximum number of positions to check</param>
/// <returns>True when path to goal was found, false if partial path only</returns>
public bool FindPath(ICollection<T> path, T start, int maxPositionsToCheck = int.MaxValue)
{
// Check arguments
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
// Reset cache and storage
path.Clear();
m_nodesCacheIndex = 0;
m_openHeap.Clear();
StorageClear();
// Put start node
Node startNode = NewNode(start, default(T), 0, 0);
StorageAdd(start, startNode);
HeapEnqueue(startNode);
// Astar loop
Node bestNode = null;
int checkedPositions = 0;
while (true)
{
// Get next node from heap
Node currentNode = m_openHeap.Count > 0 ? HeapDequeue() : null;
// Check end conditions
if (currentNode == null || checkedPositions >= maxPositionsToCheck)
{
// No more nodes or limit reached, path not found, return path to best node if possible
if (bestNode != null)
{
BuildPathFromEndNode(path, startNode, bestNode);
}
return false;
}
else if (Heuristic(currentNode.Position) <= 0)
{
// Node is goal, return path
BuildPathFromEndNode(path, startNode, currentNode);
return true;
}
// Remember node with best heuristic; ignore start node
if (currentNode != startNode && (bestNode == null || currentNode.H < bestNode.H))
{
bestNode = currentNode;
}
// Move current node from open to closed in the storage
currentNode.IsClosed = true;
++checkedPositions;
// Try all neighbors
m_neighbors.Clear();
Neighbors(currentNode.Position, m_neighbors);
for (int i = 0; i < m_neighbors.Count; ++i)
{
// Get position
T position = m_neighbors[i];
// Get existing node at position, if any
Node node = (Node)StorageGet(position);
// If position was already analyzed, ignore step
if (node != null && node.IsClosed == true)
{
continue;
}
// If position is not passable, ignore step
float cost = Cost(currentNode.Position, position);
if (cost == float.PositiveInfinity)
{
continue;
}
// Calculate A* values
float g = currentNode.G + cost;
float h = Heuristic(position);
// Update or create new node at position
if (node != null)
{
// Update existing node if better
if (g < node.G)
{
node.G = g;
node.F = g + node.H;
node.PreviousPosition = currentNode.Position;
HeapUpdate(node);
}
}
else
{
// Create new open node if not yet exists
node = NewNode(position, currentNode.Position, g, h);
StorageAdd(position, node);
HeapEnqueue(node);
}
}
}
}
#endregion
#region Internals
private void BuildPathFromEndNode(ICollection<T> path, Node startNode, Node endNode)
{
for (Node node = endNode; node != startNode; node = (Node)StorageGet(node.PreviousPosition))
{
path.Add(node.Position);
}
}
private void HeapEnqueue(Node node)
{
m_openHeap.Add(node);
HeapifyFromPosToStart(m_openHeap.Count - 1);
}
private Node HeapDequeue()
{
Node result = m_openHeap[0];
if (m_openHeap.Count <= 1)
{
m_openHeap.Clear();
}
else
{
m_openHeap[0] = m_openHeap[m_openHeap.Count - 1];
m_openHeap.RemoveAt(m_openHeap.Count - 1);
HeapifyFromPosToEnd(0);
}
return result;
}
private void HeapUpdate(Node node)
{
int pos = -1;
for (int i = 0; i < m_openHeap.Count; ++i)
{
if (m_openHeap[i] == node)
{
pos = i;
break;
}
}
HeapifyFromPosToStart(pos);
}
private void HeapifyFromPosToEnd(int pos)
{
while (true)
{
int smallest = pos;
int left = 2 * pos + 1;
int right = 2 * pos + 2;
if (left < m_openHeap.Count && m_openHeap[left].F < m_openHeap[smallest].F)
smallest = left;
if (right < m_openHeap.Count && m_openHeap[right].F < m_openHeap[smallest].F)
smallest = right;
if (smallest != pos)
{
Node tmp = m_openHeap[smallest];
m_openHeap[smallest] = m_openHeap[pos];
m_openHeap[pos] = tmp;
pos = smallest;
}
else
{
break;
}
}
}
private void HeapifyFromPosToStart(int pos)
{
int childPos = pos;
while (childPos > 0)
{
int parentPos = (childPos - 1)/2;
Node parentNode = m_openHeap[parentPos];
Node childNode = m_openHeap[childPos];
if (parentNode.F > childNode.F)
{
m_openHeap[parentPos] = childNode;
m_openHeap[childPos] = parentNode;
childPos = parentPos;
}
else
{
break;
}
}
}
private Node NewNode(T position, T previousPosition, float g, float h)
{
while (m_nodesCacheIndex >= m_nodesCache.Count)
{
m_nodesCache.Add(new Node());
}
Node node = m_nodesCache[m_nodesCacheIndex++];
node.Position = position;
node.PreviousPosition = previousPosition;
node.F = g + h;
node.G = g;
node.H = h;
node.IsClosed = false;
return node;
}
#endregion
}
関連する問題
- 1. 簡潔な角形式の検証
- 2. 簡潔な構文
- 3. フォームバリデーション - 簡潔性の面で
- 4. 検索 - text.find(a、b、c)は、
- 5. ScalaのifNotPresent簡潔な形
- 6. "$ a?$ a:$ b"と書くより簡潔な方法はありますか?
- 7. 簡潔なDNGヘッダー情報?
- 8. もっと簡潔なjquery
- 9. C#コンストラクタでより簡潔な構文ですか?
- 10. 簡潔なMooseX :: Declareメソッドのシグネチャ検証エラー
- 11. 簡単な目的のCでテーブルビューを検索
- 12. トリガでWPFの簡潔で簡単なイメージボタン?
- 13. mysqlのPHPでの簡単な検索
- 14. 簡単なテキスト検索Bash
- 15. 簡単なバッチ検索&スクリプト
- 16. Duckduckgoでの簡単なサイト検索
- 17. AngularJSでの簡単なデータベース検索エンジン
- 18. 印刷名簡潔
- 19. .net mvcの簡潔な説明。
- 20. LogCatメッセージの簡潔な色付け?
- 21. 簡潔なエディターの可用性
- 22. C#ハッシュテーブルを初期化する最も簡潔な方法
- 23. 配列の簡潔さ
- 24. F#関数の簡潔
- 25. リダイレクト先の簡単な検索フォーム?s =検索語句
- 26. もっと簡潔なElifステートメント
- 27. より簡潔な正規表現
- 28. java8は二つのマップをマージする任意の簡潔な構文があり、簡潔な
- 29. クラウドの簡単な検索方法は?
- 30. MySQLの簡単な検索クエリ
このような知識を共有することは何も問題ありません。しかし、あなたは質問の部分を質問として綴るべきです。 –
IMHO良いスタックオーバーフローの質問は、特定の質問をします。さらに、ここでA *アルゴリズムを投稿する場合は、インターネット上で既に利用可能な多くのA *アルゴリズムが特定のニーズに対応していない理由を説明してください。その特定の必要性に取り組んでいますが、インターネット上の他のすべての例ではそうではありません。率直に言えば、当初考えていたよりも、Stack Overflowで標準アルゴリズムのQ&Aを作成するのはちょっと難しいことです。コードダンプでは切り取られません。 –