ウィキペディアA *パスファインディングアルゴリズムには時間がかかる


9

C#でA *パ​​スファインディングを正常に実装しましたが、速度が非常に遅く、理由がわかりません。私はopenNodesリストをソートしないように試みましたが、それでも同じです。

マップは80x80で、ノードは10〜11です。

ここからウィキペディアの疑似コードを取り出しました

そして、これは私の実装です:

 public static List<PGNode> Pathfind(PGMap mMap, PGNode mStart, PGNode mEnd)
    {
        mMap.ClearNodes();

        mMap.GetTile(mStart.X, mStart.Y).Value = 0;
        mMap.GetTile(mEnd.X, mEnd.Y).Value = 0;

        List<PGNode> openNodes = new List<PGNode>();
        List<PGNode> closedNodes = new List<PGNode>();
        List<PGNode> solutionNodes = new List<PGNode>();

        mStart.G = 0;
        mStart.H = GetManhattanHeuristic(mStart, mEnd);

        solutionNodes.Add(mStart);
        solutionNodes.Add(mEnd);

        openNodes.Add(mStart); // 1) Add the starting square (or node) to the open list.

        while (openNodes.Count > 0) // 2) Repeat the following:
        {
            openNodes.Sort((p1, p2) => p1.F.CompareTo(p2.F));

            PGNode current = openNodes[0]; // a) We refer to this as the current square.)

            if (current == mEnd)
            {
                while (current != null)
                {
                    solutionNodes.Add(current);
                    current = current.Parent;
                }

                return solutionNodes;
            }

            openNodes.Remove(current);
            closedNodes.Add(current); // b) Switch it to the closed list.

            List<PGNode> neighborNodes = current.GetNeighborNodes();
            double cost = 0;
            bool isCostBetter = false;

            for (int i = 0; i < neighborNodes.Count; i++)
            {
                PGNode neighbor = neighborNodes[i];
                cost = current.G + 10;
                isCostBetter = false;

                if (neighbor.Passable == false || closedNodes.Contains(neighbor))
                    continue; // If it is not walkable or if it is on the closed list, ignore it.

                if (openNodes.Contains(neighbor) == false)
                {
                    openNodes.Add(neighbor); // If it isn’t on the open list, add it to the open list.
                    isCostBetter = true;
                }
                else if (cost < neighbor.G)
                {
                    isCostBetter = true;
                }

                if (isCostBetter)
                {
                    neighbor.Parent = current; //  Make the current square the parent of this square. 
                    neighbor.G = cost;
                    neighbor.H = GetManhattanHeuristic(current, neighbor);
                }
            }
        }

        return null;
    }

これが私が使っている発見的方法です:

    private static double GetManhattanHeuristic(PGNode mStart, PGNode mEnd)
    {
        return Math.Abs(mStart.X - mEnd.X) + Math.Abs(mStart.Y - mEnd.Y);
    }

何が悪いのですか?私が同じコードを見続けているのは丸一日です。


2
ヒューリスティックを使用しないと、多くのノードを通過するので、最終的に見つかるまで(通常)より長い時間がかかります。また、ソートされたままのソートされたリストを使用してみてください(リストにアイテムが存在するかどうかを確認する必要がないように、ソートされたセットが望ましいので、追加することができます)
Elva

回答:


10

3つのこと、1つは間違い、2つは疑わしいことがわかります。

1)イテレーションごとにソートしています。しないでください。プライオリティキューを使用するか、最低でも線形検索を行って最小値を見つけます。実際には常にリスト全体をソートする必要はありません!

2)openNodes.Contains()はおそらく遅いです(C#のリストの詳細についてはわかりませんが、線形検索を実行すると思います)。各ノードにフラグを追加し、O(1)でこれを行うことができます。

3)GetNeighborNodes()が遅くなる可能性があります。


2
2)ええ、Contains()はかなり遅くなります。すべてのノードをリストに格納するのではなく、Dictionary <int、PGNode>を使用してください。次に、O(1)ルックアップ時間を取得し、リストを繰り返し処理できます。ノードにidフィールドがある場合は、それをキーに使用します。それ以外の場合は、PGNode.GetHashCode()が機能します。
Leniency

2
@Leniency:Dictionary <PGNode、PGNode>の方がいいのではないですか?2つのオブジェクトが同じハッシュコードを持っている可能性がありますが、等しくはありません。「その結果、このメソッドのデフォルトの実装は、ハッシュの目的で一意のオブジェクト識別子として使用してはなりません。」msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx - .NET 3.5が優れているのHashSetを、提供- msdn.microsoft.com/en-us/library/bb359438.aspx

良い点、HashSetを忘れてしまった。
Leniency

9

優先ヒープを使用する必要があるとすでに述べた点に加えて、ヒューリスティックを誤解しました。あなたが持っている

if(isCostBetter)
{
    ...
    neighbor.H = GetManhattanHeuristic(current、neighbor);
}
ただし、ヒューリスティックは目的地までの距離の推定値となるはずです。隣人を最初に追加するときに、一度設定する必要があります。
if(openNodes.Contains(neighbor)== false)
{
    neighbor.H = GetHeuristic(neighbor、mEnd);
    ...
}

さらにマイナーな点として、で通過できないノードを除外することにより、A *を簡略化できますGetNeighbourNodes()


+1、私はアルゴリズムの複雑さに焦点を当て、ヒューリスティックの誤った使用を完全に逃しました!
ggambett、2011

4

メタアンサー:パフォーマンスの問題を探すコードをじっと見つめているだけではいけません。プロファイラーを使用して5分すると、ボトルネックがどこにあるかを正確に示します。ほとんどのプロファイラーの無料のトレイルをダウンロードして、数分でアプリに接続できます。


3

異なるノードのFを比較すると、何を比較しているのか明確ではありません。FはG + Hとして定義されたプロパティですか?そのはず。(副業:これは、統一アクセスの原則がくだらない理由の例です。)

さらに重要なのは、フレームごとにノードを並べ替えることです。A *は、効率的な-O(lg n)-ソートされた単一の要素の挿入を可能にする優先キューと、閉じたノードの迅速なチェックを可能にするセットの使用を要求します。アルゴリズムを作成すると、O(n lg n)挿入+ソートが実行され、ランタイムが役に立たない比率に引き上げられます。

(C#に適切なソートアルゴリズムがある場合、O(n)挿入+ソートが発生する可能性があります。それでもまだ多すぎます。実際の優先度キューを使用してください。)


2

http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html

  • 極端な例として、h(n)が0の場合、g(n)のみが役割を果たし、A *がダイクストラのアルゴリズムに変わります。これにより、最短経路が確実に検出されます。
  • h(n)が常にnからゴールに移動するコストよりも低い(または等しい)場合、A *は最短経路を見つけることが保証されます。h(n)が低いほど、ノードA *が拡大し、速度が遅くなります。
  • h(n)がnからゴールに移動するコストと完全に等しい場合、A *は最適なパスのみをたどり、それ以外は何も展開せず、非常に高速になります。すべてのケースでこれを実行できるわけではありませんが、一部の特殊なケースでは正確にすることができます。完全な情報が与えられると、A *は完全に動作します。
  • h(n)がnからゴールに移動するコストよりも大きい場合、A *は最短経路を見つけることが保証されていませんが、より高速に実行できます。
  • 反対に、h(n)がg(n)に比べて非常に高い場合、h(n)のみが役割を果たし、A *がBest-First-Searchになります。

「マンハッテン距離」を使用しています。これはほとんど常に悪いヒューリスティックです。さらに、リンクされたページからその情報を見ると、ヒューリスティックが実際のコストよりも低いことが推測できます。


-1、問題はヒューリスティックではなく、実装です。

2

他の上位の回答(間違いなくこの提案よりも重要です)に加えて、別の最適化は、閉じた「リスト」をある種のハッシュテーブルに変更することです。順序付けされたコレクションである必要はありません。値をすばやく追加して、それらがコレクションに存在するかどうかをすばやく確認できるようにするためだけです。


1

コストとヒューリスティックには関係が必要です。Hが2つの異なる場所で計算されているが、アクセスされていないという手掛かりになるはずです。


これは、プロパティが正しく実装されていないことを前提としています。これは、その定義が示されていないため可能ですが、コードにはさらに2つの直接的な問題があります。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.