迷路問題を解くアルゴリズム*(アルゴリズムの説明と証明、Python実装と可視化)

目次

1. はじめに

2. 具体的な内容

1、BFS(幅優先検索)

2、ダイクストラ(均一原価探索)

3. ヒューリスティック検索

4.A*アルゴリズム

4.1 アルゴリズムの詳細

4.2 A および A* アルゴリズム

4.3 A* アルゴリズムの証明

4.4 アルゴリズム処理

3. 実現

1. 実験要件

2. コードの実装

4. ソースコード


1. はじめに

       私がアルゴリズムについて学び始めたとき、オンラインにはすでに優れた入門記事がたくさんありました。記事内に赤い星★紫の×が表示されている次のロードマップが表示されている場合:

        そして、この記事はRed Blob Games の A* アルゴリズム チュートリアル (クリックしてジャンプ)を参照している可能性が高くなります。このチュートリアルは、生き生きとしたインタラクティブな図プロセスのわかりやすい説明を備えた優れた入門チュートリアルです。

        以下では、BFSグリーディ BFS、およびダイクストラ アルゴリズムを導入として使用して、 A* アルゴリズムを段階的に紹介します。これにより、 A* アルゴリズムについての予備的な全体的な理解が得られます。

        Red Blob Gamesには、 BFS Dijkstra、およびA*アルゴリズムの違いを示す3 つの図があります。少しの基礎知識がある場合は、それを読むと一般的な概念がわかるでしょう。

        BFS (Breadth First Search):すべての方向が均等に拡張されるため、探査軌道は同心円、円、円となり、波紋のように均等に円ごとに広がります。

BFS アルゴリズム
BFS アルゴリズム

BFS が拡張する場合、ノードのすべての隣接ノードが順番に拡張され、各ノードが拡張される機会は公平です。つまり、各隣接ノードの拡張機会は同じであり、順序は任意であることが        わかっています。迷路の問題ではキューに追加するノードの右、上、左、下(必要に応じて、右、左、下、上、下、左、右である必要はありません) のノードを選択します。を展開し、追加された順序でノードをキューから取り出して続行します。展開するには、このノードの右、上、左、下のノードもキューに追加されます。すると迷路上に表示される処理は、このノードから距離1の円を探し、次に距離2の円距離3の円を探し……というものです。

        具体的なアニメーション効果は次のとおりです。

        上記からわかるように、BFS は、迷路問題やパスの重みが等しいその他のグラフ検索に非常に役立つアルゴリズムです。BFS は、到達可能なすべてのノードを検索できる必要があります。これは暴力的な徹底的な検索アルゴリズムであり、最短パスを見つけることができます(すべてのエッジの重みが等しい場合)。

        ダイクストラのアルゴリズム(ダイクストラのアルゴリズム) : いくつかのノード (方向、パス) が最初に探索され、通常はコストが小さいノードとパスが最初に探索されます。このアルゴリズムは最短経路を見つけるために使用されるため、アルゴリズムが正確であるためには、現在知られている最短経路が毎回探索のために選択される必要があるため、その探索軌道は尾根の等高線図と同様にランダム不均一になります。 

ダイクストラアルゴリズム

        ダイクストラBFS違いは、現時点ではグラフの各エッジのエッジの重みが異なるため、 BFSは現時点では適用できなくなっていることです。

これはまだ迷路問題です.        ボックス内の値は、開始点からボックスまでのコスト (コスト) を示します(距離コストとして理解できます) .正方形のによって異なります。茶色 ■に移動するコストは1 、緑 ■に移動するコスト5灰色 ■は突破できない障害物を表します。

左の写真はBFS、右の写真はダイクストラ

        左側の図では、各ブロックのコストが同じであるため、BFS アルゴリズムを使用すると、アルゴリズムは緑色の領域を直接通過して終点 ×に到達します右側の図では、ブロックのコストが異なるためダイクストラ アルゴリズムを使用すると、アルゴリズムは緑色の領域をバイパスして終点 ×に到達します

        2 つのアルゴリズムの実行アニメーションは次のとおりです。       

左の写真はBFS、右の写真はダイクストラ

        特定の BFS およびダイクストラ アルゴリズム プロセスについては、以下で詳しく紹介します。ここでは、それらのアプリケーションの違いを理解するだけで済みます。

       A* アルゴリズム:ゴールに「近いと思われる」パスを優先しますこのアルゴリズムはまた、ダイクストラのアルゴリズムのような不均一な尾根マップである、推定された「現在最も可能性の高い」ノードを常に探していますただし、ダイクストラ アルゴリズムカオスリッジ軌道比較すると、目的があり、方向性があります軌道の拡張方向は常にターゲットに近い側を選択します。下の図では、先端側に伸びていることがわかります。A* アルゴリズムの目から見て、それは終わりに近づいています。

A* アルゴリズム

        これはダイクストラのアルゴリズムを修正したもので、単一の目的に最適化されますダイクストラのアルゴリズムは、すべての場所へのパスを見つけることができます。しかし、私たちの経路探索アルゴリズムでは、1 つの場所への経路を見つけるだけで済みます。これは、ダイクストラのアルゴリズムにおける余分なオーバーヘッドの一部が不要であることを意味します。A* アルゴリズムは、ポイントツーポイントの経路検索アルゴリズムです。LOL の小さな地図上の特定の位置をクリックするのと同じように、システムは自動的に経路を検索し、それを表す目的地の位置につながる「白線」を取得します。が最短の道です。

        既知のヒューリスティック情報の一部を使用して独自の探索方向を合理的に計画し、ダイクストラの盲目さを回避します。

2. 具体的な内容

ここで言及する必要があるのは、以下で説明する A* アルゴリズムは経路探索アルゴリズム        としてのみ使用され、議論はグラフ内の経路の探索に限定されるということです。しかし実際には、A* アルゴリズムは経路探索に適用できるだけでなく、ヒューリスティックなアイデアであり、8 つのデジタル問題などの他の問題を解決するためにも使用できますが、ほとんどの問題はグラフ理論 (またはグラフ理論)に還元できます。したがって、アルゴリズム全体の概念を理解するには、パス検索を理解するだけで済みます。

これは、A* アルゴリズムを使用した 8 数問題の解法であり、各正方形をグラフのノード、ポインタを重み 1 のエッジと見なすと、木になります (特殊な場合)。グラフ)を最短経路で解決するプロセス

これは、 BFS アルゴリズム        とまったく同じです。BFS は、アルゴリズムのトピックで一般的に使用される総当り枯渇などのパス検索に適しているだけではありません。しかし、このアルゴリズムを学習するときは、総当り枯渇であるた​​め、パス検索だけを気にします。は本質的にルートです。検索ツリーを開始するノードです。

同時に、理解を容易にするために、すべてのグリッド グラフ         が使用されていますが、実際には、アプリケーションはすべてのタイプのグラフ構造で同じであり、正確です。これを理解するのは難しくありません。グリッド グラフは、最終的にはノードとエッジの重みが1である一般的なグラフに変換できるからです

        私たちはBFS とダイクストラ アルゴリズムについてよく知っており、実際、それについて話す必要はありません。私たちが焦点を当てているのはA* アルゴリズムです。ここでBFS およびダイクストラアルゴリズムのプロセスを具体的に開発する理由は、一方では、 A* アルゴリズムがどのように得られるかを誰もが明確に理解できることを願っているからですが、他方では、私は個人的にこの Web サイトのコードがとても気に入っています。写真やインタラクティブなアニメーションは本当に無限です。

1、BFS(幅優先検索)

       BFS の考え方の鍵は、フロンティアを継続的に拡大することです

       BFSの Python コードは次のとおりです。

BFS の最も基本的なコード

        アイデアは次のとおりです。

        まずキュー (キュー、先入れ先出し: 最初に入れられたノードが最初に展開されます) フロンティア を作成します。フロンティアは展開されるノードを格納するために使用されるため、開始点start ★を始まりのフロンティア

        次に、コレクション (Set、コレクションの要素は順序が乱れており、繰り返されません) を作成しますこれは、訪問されたノードを保存するために使用されます。これは、私たちがよく訪問と呼ぶものです。

フロンティアが空でない        限り、要素の現在の要素は拡張のためにフロンティアから取得されます。現在のすべての近傍nextについて、 nextが到達していない(つまり、到達していない)限り、 next をfrontierに配置し、次に、到達済みとしてマークするために、それを到達済みに配置します。

        上記のBFSコードはパスを構築するものではなく、グラフ上のすべての点を横断する方法を示すだけです。したがって、各ポイントに到達するまでの経路を記録するように変更する必要があります。

        黄色の部分が変更部分です。

改良されたBFSコードはパス情報を記録できるようになりました

        ここで、Come_from は、到達したに置き換えられます

        Came_from辞書(辞書、キーと値のペア、キーと値が対応する)で到達同じ機能を表現できるだけでなくがあるかどうかも判定できます。キーは、 Came_from記録にCame_from[node i]を使用することで、エンド ポイントが見つかったときに、プリシーケンス ノードをたどって見つけることができます。出発点。

変更には別の部分があります:到達し        たかどうかを判断する前は、到達したセットに直接入れられました;今度は、 Came_fromにあったかどうかが判断され、そうでない場合は、Come_from[ノードの隣接ノードの次のノードに格納されます]を現在の拡張電流として使用します。

         各ノードが情報の出所を格納すると、グラフ全体の状況は次のようになります。

         上のインジケーター矢印は前のノードが誰であるかを示しているため、 BFS が終点を見つけたときに、どのようにして終点に到達したかを知るのに十分な情報が得られ、このパスを再構築できます。以下のような方法:

以前のノード情報に基づいてパスを見つける方法

goal         から開始してCame_fromに格納されている前のノードをたどって、開始点まで 1 つずつバックトラックします。その間、パスは配列pathに連続的に入れられます。これは、ノードが終点に近づくほど早く配置されるためです。したがって、最後に保存されたパスは終点から始点への、最後にパス全体が逆になります。

        上記のBFS は、最終的にはグラフ内のすべてのノードを横断します。つまり、開始点からすべてのノードへの最短パスがわかります(グラフ上の重みが等しい場合、そうでない場合は最短パスではありません)。しかし実際には、通常は特定のターゲット ノードへのパスを要求するだけでよいため、多くのプロセスは不要です。元の BFS はすべてのノードを通過した後に終了する必要がありますが、今後は停止するためにターゲットノードまで通過するだけで済むため、時間内に終了できます。

        プロセスは次のとおりです。ターゲット ノードが見つかると、BFS は拡張を停止します。

BFS がエンドポイントを見つけたら、現時点でフロンティア内のどのノードが拡張されていないとしても、BFS は直ちに停止する必要があります。

         変更されたコードは次のとおりです。タイムリーな終了条件を追加するだけです。

BFS コードの完全版。ターゲット ノードへの最短パスを検索し、時間内に終了します。

        現在展開中のノードが終点ゴール×であることが判明した場合、コードは終了する。

2、ダイクストラ(均一原価探索

        上で説明したBFS は、グラフ上の各パスの重みが等しい場合にのみ使用できます。グラフ内の各パスの重みがまったく同じでない場合は、BFS を再度使用してすべてのノードを走査できますが、最短パスは通過しません。最初に通過したパスが必ずしも最短であるとは限らないため、取得できません。

        ダイクストラのアルゴリズムも非常に単純で、始点から開始され、終点に到達するまで総コストが最も短いノードに拡張され続けます (エッジの重みが負でない場合)。単純な証明の考え方は、現在選択されているノードが最もコストが短く、他のノードを経由してこのノードに到達するコストは現在のコストより大きくなければならないということです。

合計コストが最も短いノード        を見つける必要があるため、元のキュー (キュー、先入れ先出し) を優先キュー (優先キュー、要素には優先順位があり、最も高い優先順位を持つ要素を使用できます)に変更する必要があります。返却されます)。

        同時に、このノードの前のノードCome_from を記録することに加えて、このノードに到達するまでの現在のコストCost_so_farも記録する必要があります

        BFSコードに基づく変更:

         まず優先キューフロンティアを作成し、そこに開始点を置き、コストを0に設定しますPythonではPriorityQueue優先値が小さいほど優先度が高く、より早く取り出されます。しかし、PriorityQueueは優先度が高いという印象があります)のput([ priority, value])最初のパラメータは優先度、2 番目のパラメータは値です)

        次に、come_fromを作成してどこから来たかを記録し、cost_so_farを作成して現在到達している各ノードの合計パス コストを記録します。

        そして、最終的にゴールの終わりに到達するまで、フロンティアから最小のコストでノード電流を取り出して拡張続けます。

        拡張メソッドは次のようになります: currentの隣にあるすべての隣接ノードを検索しnextnew_cost を計算します。計算方法は、 currentの現在のコストcost_so_far [ current ]をこの隣接エッジのコストchart.cost( current , next )に加算することです。 。next がまだ到達していないノードであるか、 new_costが既知の最短パス ノードより小さい場合は、現在の次のコストcost_so_far [ next ]を追加または変更し、優先順位を新しいコストnew_costに設定し、このキーと値のペアを追加します。拡張子へ プライオリティキューフロンティアでは、 nextの最後のレコードノードがcurrentになります。

ダイクストラ アルゴリズムの実行には一般に負のエッジは必要ありませんが、上記のダイクストラ実装コードは正および負のエッジを持つグラフを処理できますが、負のサイクルを持つグラフは処理できないこと        に注意してください

        その理由は、拡張中により短いパスが見つかると、優先キューに追加されるためです。一般的なダイクストラ アルゴリズムでは、すべてのノードは一度だけ優先キューに入りますが、上記のコードは、他のノードを経由して到達するノード xのパスが短いことを検出すると、ノード x が優先キューに入れられます。ノードは展開されますが、これはこのノードに最短パスを再度変更する機会を与えることを意味します。したがって、グラフに負のエッジがあり負のサイクルがない場合(すべてのノードに最短パスがあることを意味します)、上記のコードを使用して最短パスも見つけることができます。

        レンダリングは次のとおりです。

        

上の図では、緑色のグリッド        に移動するコストが茶色のグリッドのコストよりも高くなります。つまり、グリッドが移動するたびに、いくつかの茶色のグリッドが最初に探索され、次にいくつかの緑色のグリッドが探索されることがわかります。合計距離が最も短いものが選択され、展開されます。

3. ヒューリスティック検索

        上記の 2 つの方法は全方向に拡張されており、すべての場所または複数の場所へのパスを見つけることが目的の場合にはこれは合理的ですが、 1 つの場所へのパスのみが必要な場合、そのような時間コストは妥当ではありません。

        私たちの目的は、境界の拡張方向を他の方向に盲目的に拡張するのではなく、ターゲット位置に向かって拡張させることです。上記の目的を達成するには、現在の状態が終点からどの程度離れているかを測定するために使用されるヒューリスティック関数 (ヒューリスティック関数) を定義する必要があります。

マンハッタン距離は        グリッド グラフでよく使用され、次のように定義されます。

         ヒューリスティック関数によって計算された距離のみを優先する、つまり終点に最も近い点を常に拡張することを優先する場合、次の結果が得られます。        

        ヒューリスティック関数を使用すると、エンドポイントがより速く見つかることがわかります。これがその利点、つまり高速です。

        この検索方法は、Greedy Best First Search アルゴリズム (Greedy Best First Search)です。

        ただし、障害物がある場合、ヒューリスティック関数の計算結果のみを優先しても正しい結果が得られない場合があります。次のように:       

         ヒューリスティック関数で計算された優先度だけでは最短経路は得られないことがわかり、その都度最短経路ノードを展開するというダイストラアルゴリズムの正しさを保証するという利点を放棄しているため、最短経路が得られる保証はありません。 。

        速度と正確性の両方を達成する方法はありますか? それが、これから紹介する A* アルゴリズムです。

4.A*アルゴリズム

4.1 アルゴリズムの詳細

        ダイクストラのアルゴリズムは最短パスを非常にうまく見つけることができますが、見込みのない方向を探索するために不必要な時間を無駄にします。ヒューリスティックな Greedy Best First Search アルゴリズム (Greedy Best First Search)を使用する場合のみ、探索する最も有望な方向が常に選択されますが、最適なパスが見つからない場合があります。 。

        A* アルゴリズムは、両方の方法からの情報、つまり出発地から現在位置までの実際の距離と、現在位置から目的地までの推定距離を使用します。

        A* アルゴリズムは、各ノードの優先度を総合的に考慮して、次の式によって展開されます。

       f(x)=g(x)+h(x)

        の:

        f(x)つまり、バツ拡張されるノードの総合的な優先度。 sum によって計算されg(x)、拡張のために拡張される最小のh(x)ノードが選択されますf(x)

        g(x)バツ原点からのノードの距離のコストです。

        h(x)バツは、エンドポイントからのノードの推定コストです

        上記の式から、全体としてダイクストラ アルゴリズムの考え方を継承し、常に最短のf(x)ノードを展開します。これにより、終点が検索されたら、それが最短パスでなければならないことが保証され、同時に計算が行われますf(x)。終点からの推定距離も考慮に入れて、個々の見込みのないノードを減らすか拡張しないようにすることで、全体の検索プロセスが見込みのある方向に向かうようにします。

h(x)上記の選択は恣意的なものではなく、最終結果の正確さと検索の速度を確保するための鍵であることに        注意してください。h(x)値が大きいほど検索速度は速くなりますが、無限ではなく、独自の制限があります。バツ終点からの距離の実際のコストが の場合h^{'}(x)h(x)最適な解を確実に見つけるには、次の要件を満たす必要があります。つまり、実際の距離を超えることはできません。

        h(x) \leq h^{'}(x) 

h(x)直感的には、これは保守的な推定値である        と考えることができます

4.2 A および A* アルゴリズム

        注意すべき点の 1 つは、A アルゴリズムと A* アルゴリズムの違いです。現在見つかっている情報は明確に記載されておらず、この 2 つの定義はやや曖昧です。以下に 2 つの A* アルゴリズムを示します。

        1つ目は、 A*アルゴリズムが上記の考え方であると考えることです。つまり、次の式を満たす Ah^{'}(x)アルゴリズムが A* アルゴリズムです。h^{*}(x)h(x)

        h(x) \leq h^{*}(x)

        2 つ目は、アルゴリズムh(x)に多くの評価関数が含まれることが多く、たとえば、マンハッタン距離、対角距離、ユークリッド距離などを使用できるため、多くの評価関数h_{1}(x)h_{2}(x)h_{3}(x)などが存在する必要があります。A アルゴリズムをさらに制限すると、つまり if g^{*}(x)>0and h_{i}(x)\leq h^{*}(x)(つまり、h^{*}(x)任意の評価関数以上)、アルゴリズムは A* アルゴリズムになります。この定義では、A* が最適な A アルゴリズムであることがわかります。ただし、実際のアプリケーションでは、最適な評価関数を判断したり見つけたりすることが難しい場合が多いため、A* アルゴリズムと A アルゴリズムの違いはそれほど重要ではなく、A* アルゴリズムはこの考えを表現するためによく使用されます。 。

4.3 A* アルゴリズムの証明

        上記の A* アルゴリズムのアイデアについて、次のような簡単な反証アイデアを示します。これには欠陥があるかもしれませんが、理解の助けになれば幸いです。

A* アルゴリズムによって見つかったパスが最短パスではないと仮定すると、A* アルゴリズムの終了時には、始点から終点までのより長いパス        が見つかったことになります。矛盾を証明するには、 A* アルゴリズムがこの長いパスではスムーズに実行されないことを証明するだけで済みます

        始点をs、終点を としますt最短パスをT_{1}、A* アルゴリズムによって見つかったパスを としますT_{2}パス上の最初のT_{1}ノードとは異なるこのパス上のノードは、その後に...と続きます(これらのノードの一部はパス上のノードと同じである可能性がありますが、問題ではありません。すでに存在しています)現時点では別のパスです)。パス上ではノードの前のノードは です同時に、ノードから終点までの実際の距離を表します次のように:T_{2}あるbcdetT_{2}T_{2}tメートルh^{'}(x)バツt

        A* アルゴリズムが最後まで実行されたときメートル、何も事故がなければ展開されるとします。つまり、tこのときのtノードがf(t)展開されるすべてのノードの中で最も小さいため、それが選択されますtそして、私たちが証明したいのはまさにこの「偶然」です。そのため、A* アルゴリズムはメートル後で選択するtことはなく、アルゴリズムの最後で最短パスよりも長いパスを選択することはありません。

h^{'}(t)=0(それ自体までtの実際の距離は 0 である) こと        はわかっていますt、 t から t までの推定距離は、つまりより小さい必要があるため、この時点でも 0 になります。したがって:h(t)h^{'}(t)h(t) \leq h^{'}(t)=0h(t)

f(t)=g(t)+h(t)=g(t)

        これは、これまでに到達した実際の距離、つまり経路の長さをg(t)表します。既知のパスの長さはパスの長さよりも長く、パスの長さは次のように表すことができますstT_{2}T_{2}T_{1}T_{1}g(a)+h^{'}(a)

        g(t) \geq g(a)+h^{'}(a)

h(a)代わりに、        推定ある到着距離は実際の到着距離t以下である必要があるため、次のようになります。あるth^{'}(a)

g(a)+h^{'}(a) \geq g(a)+h(a)

        それで:

       g(t) \geq g(a)+h(a)

        たった今:

        g(t)+h(t) \geq g(a)+h(a)

        あれは:

        f(t) \geq f(a)

        したがって、現時点で展開されるノードはt最小値ではなく、ある展開するノードはさらに小さいことがわかります。

        拡張後も同様に起動できるあるため、次の拡張はノードとなります。最短経路 path , , ...上の残りのすべてのノードを類推することができ、それらはすべて次を満たし 、同じ方法で推定できますg(b)+h^{'}(b) \leq g(t)f(t) \geq f(b)bT_{1}cde私g(i)+h^{'}(i) \leq g(t)f(t) \geq f(i)

        つまり、tノードはf(t)展開されるノードの中で最小になることはなく、T_{1}最短パス上の残りのノードが展開されるまでノードは展開されtません。T_{1} 最短経路の最後の非ノードを展開するt、当然、t ノードも展開され、この時点でアルゴリズムは終了します。最後に私たちが見つけた道は、私たちが想定していたものとはsまったく逆だったことがわかりますtT_{1}T_{2}

        したがって、ノードから宛先までのコストがh(x)常に以下である場合バツ、A* アルゴリズムは最短パスを見つけることができることを保証します。h(x)値が小さい場合、アルゴリズムはより多くのノードを通過するため、アルゴリズムが遅くなります。h(x)それが宛先までのノードの実際のコストに正確に等しいほど大きければバツA* アルゴリズムが最適なパスを見つけ、速度は非常に高速になります。残念ながら、これはすべてのシナリオで可能であるわけではありません。なぜなら、終点に到達する前に、終点からどのくらいの距離にいるのかを正確に計算するのは難しいからです。

        評価関数 についてはf(x)=g(x)+h(x)、次の興味深いことがわかります。

        ①h(x)=0その時点ではf(x)=g(x)、 、この時点で到達したノード間の最短距離に完全に基づいていることを示します。これがダイクストラアルゴリズムです。

        ②g(x)=0当時はGreedy Best First Searchアルゴリズム(Greedy Best First Search)でした

4.4 アルゴリズム処理

        以下はアルゴリズムの擬似コードであり、前述のダイクストラアルゴリズムの処理と比較して、ヒューリスティック情報のみが追加されています。

        上記のプロセスはダイクストラプロセスに似ているため、ここでは説明しません。

        現在インターネット上で検索されている情報についても、基本的には上記と同様の手順となりますが、具体的な内容や名称が異なります。一般に、open_set は上記のコードの最前線ですclose_set は、 cost_so_farに配置されたノードに似ていますが、上記の疑似コードは負のエッジを持ち、負のループを持たないグラフを処理できるのに対し、一般的なコードは処理できないという点が異なります。以下は、アルゴリズム プロセスの別のバージョンです。

1.初始化open_set和close_set;
2.将起点加入open_set中,并设置优先级为0(优先级越小表示优先级越高);
3.如果open_set不为空,则从open_set中选取优先级最高的节点x:
    ①如果节点x为终点,则:
        从终点开始逐步追踪parent节点,一直到达起点,返回找到的结果路径,算法结束;
    ②如果节点x不是终点,则:
        1.将节点x从open_set中删除,并加入close_set中;
        2.遍历节点x所有的邻近节点:
            ①如果邻近节点y在close_set中,则:
                跳过,选取下一个邻近节点
            ②如果邻近节点y不在open_set中,则:
                设置节点m的parent为节点x,计算节点m的优先级,将节点m加入open_set中

        コードを実装する際には、主に最初の疑似コードをベースに実装しました。

3. 実現

1. 実験要件

        迷路問題は実験心理学における古典的な問題です。迷路の入り口から出口までの経路は複数ある可能性があり、この実験では入り口から出口までの最短経路を見つける必要があります。

(1,1)下図は4×4の迷路問題の模式図で、各位置は平面座標系の点で表され、図に示すように入口点と出口点        の座標は となります(4,4)2 つの点を結ぶ線は、2 つの場所がつながっていることを意味します。回線が接続されていない場合は、通信が行われていないことを意味します。

2. コードの実装

        上記の迷路の問題を解決するために、私のアイデアは、上記の長方形迷路の各ノードに、(1,1)最初から左から右に 0、1、2、3... と番号を付けることです。この番号付けのもう 1 つの利点は、次のとおりです。ノードがどの行のどの列に位置するかまでは非常に便利です。

        各ノードの隣接リストには隣接するノードが記録されます。これは無向エッジであるため、エッジが 2 回記録されることになります。

        具体的なアルゴリズムの処理は上記の擬似コードに従って記述します。

        以下は実装コードです。

import numpy as np
from queue import PriorityQueue


class Map:  # 地图
    def __init__(self, width, height) -> None:
        # 迷宫的尺寸
        self.width = width
        self.height = height
        # 创建size x size 的点的邻接表
        self.neighbor = [[] for i in range(width*height)]

    # 添加边
    def addEdge(self, from_: int, to_: int):
        if (from_ not in range(self.width*self.height)) or (to_ not in range(self.width*self.height)):
            return 0
        self.neighbor[from_].append(to_)
        self.neighbor[to_].append(from_)
        return 1

    # 由序号获得该点在迷宫的x、y坐标
    def get_x_y(self, num: int):
        if num not in range(self.width*self.height):
            return -1, -1
        x = num % self.width
        y = num // self.width
        return x, y


class Astar:  # A*寻路算法
    def __init__(self, _map: Map, start: int, end: int) -> None:
        # 地图
        self.run_map = _map
        # 起点和终点
        self.start = start
        self.end = end
        # open集
        self.open_set = PriorityQueue()
        # cost_so_far表示到达某个节点的代价,也可相当于close集使用
        self.cost_so_far = dict()
        # 每个节点的前序节点
        self.came_from = dict()

        # 将起点放入,优先级设为0,无所谓设置多少,因为总是第一个被取出
        self.open_set.put((0, start))
        self.came_from[start] = -1
        self.cost_so_far[start] = 0

    # h函数计算,即启发式信息
    def heuristic(self, a, b):
        x1, y1 = self.run_map.get_x_y(a)
        x2, y2 = self.run_map.get_x_y(b)
        return abs(x1-x2) + abs(y1-y2)

    # 运行A*寻路算法,如果没找到路径返回0,找到返回1
    def find_way(self):
        # open表不为空
        while not self.open_set.empty():
            # 从优先队列中取出代价最短的节点作为当前遍历的节点,类型为(priority,node)
            current = self.open_set.get()
            # 找到终点
            if current[1] == self.end:
                break
            # 遍历邻接节点
            for next in self.run_map.neighbor[current[1]]:
                # 新的代价
                new_cost = self.cost_so_far[current[1]]+1
                # 没有到达过的点 或 比原本已经到达过的点的代价更小
                if (next not in self.cost_so_far) or (new_cost < self.cost_so_far[next]):
                    self.cost_so_far[next] = new_cost
                    priority = new_cost+self.heuristic(next, self.end)
                    self.open_set.put((priority, next))
                    self.came_from[next] = current[1]

        if self.end not in self.cost_so_far:
            return 0
        return 1

    def show_way(self):
        # 记录路径经过的节点
        result = []
        current = self.end
        # 不断寻找前序节点
        while self.came_from[current] != -1:
            result.append(current)
            current = self.came_from[current]
        # 加上起点
        result.append(current)
        # 翻转路径
        result.reverse()
        print(result)


# 初始化迷宫
theMap = Map(4, 4)
# 添加边
theMap.addEdge(0, 1)
theMap.addEdge(1, 2)
theMap.addEdge(2, 6)
theMap.addEdge(3, 7)
theMap.addEdge(4, 5)
theMap.addEdge(5, 6)
theMap.addEdge(6, 7)
theMap.addEdge(4, 8)
theMap.addEdge(5, 9)
theMap.addEdge(7, 11)
theMap.addEdge(8, 9)
theMap.addEdge(9, 10)
theMap.addEdge(10, 11)
theMap.addEdge(8, 12)
theMap.addEdge(10, 14)
theMap.addEdge(12, 13)
theMap.addEdge(13, 14)
theMap.addEdge(14, 15)
# A* 算法寻路
theAstar = Astar(theMap, 0, 15)
theAstar.find_way()
theAstar.show_way()

        実行後、次の結果が得られます。

[0, 1, 2, 6, 7, 11, 10, 14, 15]

        つまり、グラフ上のパスは次のようになります。

         上記はコード本体ですが、結果をよりわかりやすく視覚化するために、Python の matploblib ライブラリを使用して視覚化しています。

         matploblib ライブラリは通常、データ チャートを視覚化するために使用されます。私のアイデアは、円描画関数 Circle を使用してノードを描画し、長方形関数 Rectangle を描画してエッジを描画し、plt (matplotlib.pyplot) の ion() 関数を使用して、インタラクションを開いて、ルックアップの各段階を表す動的なグラフを描画します。詳細は以下のとおりです。

import numpy as np
from queue import PriorityQueue
import matplotlib.pyplot as plt
import matplotlib.patches as mpathes
import random

# 画布
fig, ax = plt.subplots()


class Map:  # 地图
    def __init__(self, width, height) -> None:
        # 迷宫的尺寸
        self.width = width
        self.height = height
        # 创建size x size 的点的邻接表
        self.neighbor = [[] for i in range(width*height)]

    def addEdge(self, from_: int, to_: int):    # 添加边
        if (from_ not in range(self.width*self.height)) or (to_ not in range(self.width*self.height)):
            return 0
        self.neighbor[from_].append(to_)
        self.neighbor[to_].append(from_)
        return 1

    def get_x_y(self, num: int):    # 由序号获得该点在迷宫的x、y坐标
        if num not in range(self.width*self.height):
            return -1, -1
        x = num % self.width
        y = num // self.width
        return x, y

    def drawCircle(self, num, color):    # 绘制圆形
        x, y = self.get_x_y(num)
        thePoint = mpathes.Circle(np.array([x+1, y+1]), 0.1, color=color)
        # 声明全局变量
        global ax
        ax.add_patch(thePoint)

    def drawEdge(self, from_, to_, color):    # 绘制边
        # 转化为(x,y)
        x1, y1 = self.get_x_y(from_)
        x2, y2 = self.get_x_y(to_)
        # 整体向右下方移动一个单位
        x1, y1 = x1+1, y1+1
        x2, y2 = x2+1, y2+1
        # 绘长方形代表边
        offset = 0.05
        global ax
        if from_-to_ == 1:  # ← 方向的边
            rect = mpathes.Rectangle(
                np.array([x2-offset, y2-offset]), 1+2*offset, 2*offset, color=color)
            ax.add_patch(rect)
        elif from_-to_ == -1:  # → 方向的边
            rect = mpathes.Rectangle(
                np.array([x1-offset, y1-offset]), 1+2*offset, 2*offset, color=color)
            ax.add_patch(rect)
        elif from_-to_ == self.width:  # ↑ 方向的边
            rect = mpathes.Rectangle(
                np.array([x2-offset, y2-offset]), 2*offset, 1+2*offset, color=color)
            ax.add_patch(rect)
        else:  # ↓ 方向的边
            rect = mpathes.Rectangle(
                np.array([x1-offset, y1-offset]), 2*offset, 1+2*offset, color=color)
            ax.add_patch(rect)

    def initMap(self):    # 绘制初始的迷宫
        # 先绘制边
        for i in range(self.width*self.height):
            for next in self.neighbor[i]:
                self.drawEdge(i, next, '#afeeee')

        # 再绘制点
        for i in range(self.width*self.height):
            self.drawCircle(i, '#87cefa')


class Astar:  # A*寻路算法
    def __init__(self, _map: Map, start: int, end: int) -> None:
        # 地图
        self.run_map = _map
        # 起点和终点
        self.start = start
        self.end = end
        # open集
        self.open_set = PriorityQueue()
        # cost_so_far表示到达某个节点的代价,也可相当于close集使用
        self.cost_so_far = dict()
        # 每个节点的前序节点
        self.came_from = dict()

        # 将起点放入,优先级设为0,无所谓设置多少,因为总是第一个被取出
        self.open_set.put((0, start))
        self.came_from[start] = -1
        self.cost_so_far[start] = 0

        # 标识起点和终点
        self.run_map.drawCircle(start, '#ff8099')
        self.run_map.drawCircle(end, '#ff4d40')

    def heuristic(self, a, b):    # h函数计算,即启发式信息
        x1, y1 = self.run_map.get_x_y(a)
        x2, y2 = self.run_map.get_x_y(b)
        return abs(x1-x2) + abs(y1-y2)

    def find_way(self):    # 运行A*寻路算法,如果没找到路径返回0,找到返回1
        while not self.open_set.empty():  # open表不为空
            # 从优先队列中取出代价最短的节点作为当前遍历的节点,类型为(priority,node)
            current = self.open_set.get()

            # 展示A*算法的执行过程
            if current[1] != self.start:
                # 当前节点的前序
                pre = self.came_from[current[1]]
                # 可视化
                self.run_map.drawEdge(pre, current[1], '#fffdd0')
                if pre != self.start:
                    self.run_map.drawCircle(pre, '#99ff4d')
                else:  # 起点不改色
                    self.run_map.drawCircle(pre, '#ff8099')
                if current[1] != self.end:
                    self.run_map.drawCircle(current[1], '#99ff4d')
                else:
                    self.run_map.drawCircle(current[1], '#ff4d40')
                # 显示当前状态
                plt.show()
                plt.pause(0.5)

            # 找到终点
            if current[1] == self.end:
                break
            # 遍历邻接节点
            for next in self.run_map.neighbor[current[1]]:
                # 新的代价
                new_cost = self.cost_so_far[current[1]]+1
                # 没有到达过的点 或 比原本已经到达过的点的代价更小
                if (next not in self.cost_so_far) or (new_cost < self.cost_so_far[next]):
                    self.cost_so_far[next] = new_cost
                    priority = new_cost+self.heuristic(next, self.end)
                    self.open_set.put((priority, next))
                    self.came_from[next] = current[1]

    def show_way(self):  # 显示最短路径
        # 记录路径经过的节点
        result = []
        current = self.end

        if current not in self.cost_so_far:
            return

        # 不断寻找前序节点
        while self.came_from[current] != -1:
            result.append(current)
            current = self.came_from[current]
        # 加上起点
        result.append(current)
        # 翻转路径
        result.reverse()
        # 生成路径
        for point in result:
            if point != self.start:  # 不是起点
                # 当前节点的前序
                pre = self.came_from[point]
                # 可视化
                self.run_map.drawEdge(pre, point, '#ff2f76')
                if pre == self.start:  # 起点颜色
                    self.run_map.drawCircle(pre, '#ff8099')
                elif point == self.end:  # 终点颜色
                    self.run_map.drawCircle(point, '#ff4d40')
                # 显示当前状态
                plt.show()
                plt.pause(0.1)

    def get_cost(self):  # 返回最短路径
        if self.end not in self.cost_so_far:
            return -1
        return self.cost_so_far[self.end]


# 初始化迷宫
theMap = Map(4, 4)

# 设置迷宫显示的一些参数
plt.xlim(0, theMap.width+1)
plt.ylim(0, theMap.height+1)
# 将x轴的位置设置在顶部
ax.xaxis.set_ticks_position('top')
# y轴反向
ax.invert_yaxis()
# 等距
plt.axis('equal')
# 不显示背景的网格线
plt.grid(False)
# 允许动态
plt.ion()
# 添加边
theMap.addEdge(0, 1)
theMap.addEdge(1, 2)
theMap.addEdge(2, 6)
theMap.addEdge(3, 7)
theMap.addEdge(4, 5)
theMap.addEdge(5, 6)
theMap.addEdge(6, 7)
theMap.addEdge(4, 8)
theMap.addEdge(5, 9)
theMap.addEdge(7, 11)
theMap.addEdge(8, 9)
theMap.addEdge(9, 10)
theMap.addEdge(10, 11)
theMap.addEdge(8, 12)
theMap.addEdge(10, 14)
theMap.addEdge(12, 13)
theMap.addEdge(13, 14)
theMap.addEdge(14, 15)

# 初始化迷宫
theMap.initMap()

# A* 算法寻路
theAstar = Astar(theMap, 0, 15)
theAstar.find_way()
theAstar.show_way()

# 输出最短路径长度
theCost = theAstar.get_cost()
if theCost == -1:
    print("不存在该路径!")
else:
    print("从起点到终点的最短路径长度为: ", theCost)

# 关闭交互,展示结果
plt.ioff()
plt.show()

        ランニング効果は以下の通りです。

         出力は次のとおりです。

从起点到终点的最短路径长度为:  8

         もう少し大きなグラフ (6x6) をテストします。

# 初始化迷宫
theMap = Map(6, 6)

# 设置迷宫显示的一些参数
plt.xlim(0, theMap.width+1)
plt.ylim(0, theMap.height+1)
# 将x轴的位置设置在顶部
ax.xaxis.set_ticks_position('top')
# y轴反向
ax.invert_yaxis()
# 等距
plt.axis('equal')
# 不显示背景的网格线
plt.grid(False)
# 允许动态
plt.ion()

# 添加边
theMap.addEdge(0, 1)
theMap.addEdge(1, 2)
theMap.addEdge(2, 3)
theMap.addEdge(3, 4)
theMap.addEdge(4, 5)
theMap.addEdge(1, 7)
theMap.addEdge(3, 9)
theMap.addEdge(4, 10)
theMap.addEdge(5, 11)
theMap.addEdge(6, 7)
theMap.addEdge(8, 9)
theMap.addEdge(6, 12)
theMap.addEdge(7, 13)
theMap.addEdge(8, 14)
theMap.addEdge(10, 16)
theMap.addEdge(11, 17)
theMap.addEdge(12, 13)
theMap.addEdge(13, 14)
theMap.addEdge(15, 16)
theMap.addEdge(16, 17)
theMap.addEdge(14, 20)
theMap.addEdge(15, 21)
theMap.addEdge(16, 22)
theMap.addEdge(17, 23)
theMap.addEdge(18, 19)
theMap.addEdge(19, 20)
theMap.addEdge(20, 21)
theMap.addEdge(22, 23)
theMap.addEdge(18, 24)
theMap.addEdge(19, 25)
theMap.addEdge(20, 26)
theMap.addEdge(22, 28)
theMap.addEdge(26, 27)
theMap.addEdge(27, 28)
theMap.addEdge(24, 30)
theMap.addEdge(27, 33)
theMap.addEdge(29, 35)
theMap.addEdge(30, 31)
theMap.addEdge(31, 32)
theMap.addEdge(33, 34)
theMap.addEdge(34, 35)

# 初始化迷宫
theMap.initMap()

# A* 算法寻路
theAstar = Astar(theMap, 0, 35)
theAstar.find_way()
theAstar.show_way()

# 输出最短路径长度
theCost = theAstar.get_cost()
if theCost == -1:
    print("不存在该路径!")
else:
    print("从起点到终点的最短路径长度为: ", theCost)

# 关闭交互,展示结果
plt.ioff()
plt.show()

        操作結果:

        

         出力結果:

从起点到终点的最短路径长度为:  10

        演算結果が正しいことがわかります。

        しかし、新しいグラフを入力するたびに、多くのエッジを入力する必要があり、より複雑なグラフのデバッグには非常に不便であることがわかりました。迷路のサイズを設定した後、プログラムに迷路をランダムに生成させる方法はありますか?

        これを行うには、迷路をランダムに生成する関数を作成します。

        私が使用したランダム生成方法は、単純な深層検索方法です。初期状態の迷路にはエッジがなく、指定されたサイズのノードの配列のみが存在します。開始点から開始して、4 つの方向を順番に探索します (4 つの方向の探索の順序はランダムです)。この方向の隣接する点が探索されていない場合は、エッジを生成し、同時にこの点に進みます。この点については、すべての点が探索され、アルゴリズムが終了するまで、上記のプロセスを繰り返し続けます。

    # 寻找
    def search(self, current: int):
        # 四个方向的顺序
        sequence = [i for i in range(4)]
        # 打乱顺序
        random.shuffle(sequence)
        # 依次选择四个方向
        for i in sequence:
            # 要探索的位置
            x = self.direction[i]+current

            # 跨了一行
            if (current % self.width == self.width-1 and self.direction[i] == 1) or (current % self.width == 0 and self.direction[i] == -1):
                continue

            # 要探索的位置没有超出范围 且 该位置没有被探索过
            if 0 <= x < self.width*self.height and self.visited[x] == 0:
                self.addEdge(current, x)
                self.visited[x] = 1
                self.search(x)


    def randomCreateMap(self, start, k):  # 随机生成迷宫
        # 标识每个节点是否被探索过
        self.visited = np.zeros(self.width*self.height)
        self.visited[start] = 1
        # 四个方向,分别代表上、下、左、右
        self.direction = {0: -self.width,
                          1: self.width,
                          2: -1,
                          3: 1}
        # 从起点开始
        self.search(start)

        以下は、ランダムに生成された 10x10、20x20、30x25 の迷路です。

10x10、0 で開始、99 で終了
20x20、0 で開始、399 で終了

         

30x25、0 で開始、500 で終了

         生成された迷路がうまく機能し、基本的なニーズを満たすことができることがわかります。ただし、迷路を生成するアルゴリズムでは深層探索が使用されるため、開始点から終了点までの経路は 1 つだけです。これは、最短経路を見つけることには説明がつかないように思えます。なぜなら、終点が見つかったら、それが最短経路でなければならないからです。したがって、迷路に複雑さを追加します。つまり、グラフ内に複数のパスが存在するように、迷路に k 個のエッジをランダムに追加します。


    # 随机添加k条边
    def randomAddEdges(self, k):
        # 循环k次(可能不止k次)
        for i in range(k):
            node = random.randint(0, self.width*self.height)
            # 随机添加一个方向
            sequence = [i for i in range(4)]
            random.shuffle(sequence)
            isPick = 0
            for d in sequence:
                # 跨了一行,不存在该方向的边
                if (node % self.width == self.width-1 and self.direction[d] == 1) or (node % self.width == 0 and self.direction[d] == -1):
                    continue
                x = self.direction[d]+node
                # 该边存在
                if x in self.neighbor[node]:
                    continue
                # 该边不存在
                self.addEdge(node, x)
                isPick = 1
            # 重新添加一条边,即重新循环一次
            if isPick == 0:
                if i == 0:  # 第一次
                    i = 0
                else:
                    i -= 1

        生成された迷路は次のとおりです。

        冗長なパスが多く、始点から終点まで複数のパスが存在することがわかります。

        A* アルゴリズムをランダムに生成された迷路に適用します。

        

         出力は次のとおりです。

从起点到终点的最短路径长度为:  18

        

        出力は次のとおりです。

从起点到终点的最短路径长度为:  28

        

         出力は次のとおりです。

从起点到终点的最短路径长度为:  50

4. ソースコード

import numpy as np
from queue import PriorityQueue
import matplotlib.pyplot as plt
import matplotlib.patches as mpathes
import random

# 画布
fig, ax = plt.subplots()


class Map:  # 地图
    def __init__(self, width, height) -> None:
        # 迷宫的尺寸
        self.width = width
        self.height = height
        # 创建size x size 的点的邻接表
        self.neighbor = [[] for i in range(width*height)]

    def addEdge(self, from_: int, to_: int):    # 添加边
        if (from_ not in range(self.width*self.height)) or (to_ not in range(self.width*self.height)):
            return 0
        self.neighbor[from_].append(to_)
        self.neighbor[to_].append(from_)
        return 1

    def get_x_y(self, num: int):    # 由序号获得该点在迷宫的x、y坐标
        if num not in range(self.width*self.height):
            return -1, -1
        x = num % self.width
        y = num // self.width
        return x, y

    def drawCircle(self, num, color):    # 绘制圆形
        x, y = self.get_x_y(num)
        thePoint = mpathes.Circle(np.array([x+1, y+1]), 0.1, color=color)
        # 声明全局变量
        global ax
        ax.add_patch(thePoint)

    def drawEdge(self, from_, to_, color):    # 绘制边
        # 转化为(x,y)
        x1, y1 = self.get_x_y(from_)
        x2, y2 = self.get_x_y(to_)
        # 整体向右下方移动一个单位
        x1, y1 = x1+1, y1+1
        x2, y2 = x2+1, y2+1
        # 绘长方形代表边
        offset = 0.05
        global ax
        if from_-to_ == 1:  # ← 方向的边
            rect = mpathes.Rectangle(
                np.array([x2-offset, y2-offset]), 1+2*offset, 2*offset, color=color)
            ax.add_patch(rect)
        elif from_-to_ == -1:  # → 方向的边
            rect = mpathes.Rectangle(
                np.array([x1-offset, y1-offset]), 1+2*offset, 2*offset, color=color)
            ax.add_patch(rect)
        elif from_-to_ == self.width:  # ↑ 方向的边
            rect = mpathes.Rectangle(
                np.array([x2-offset, y2-offset]), 2*offset, 1+2*offset, color=color)
            ax.add_patch(rect)
        else:  # ↓ 方向的边
            rect = mpathes.Rectangle(
                np.array([x1-offset, y1-offset]), 2*offset, 1+2*offset, color=color)
            ax.add_patch(rect)

    def initMap(self):    # 绘制初始的迷宫
        # 先绘制边
        for i in range(self.width*self.height):
            for next in self.neighbor[i]:
                self.drawEdge(i, next, '#afeeee')

        # 再绘制点
        for i in range(self.width*self.height):
            self.drawCircle(i, '#87cefa')

    # 寻找
    def search(self, current: int):
        # 四个方向的顺序
        sequence = [i for i in range(4)]
        # 打乱顺序
        random.shuffle(sequence)
        # 依次选择四个方向
        for i in sequence:
            # 要探索的位置
            x = self.direction[i]+current

            # 跨了一行
            if (current % self.width == self.width-1 and self.direction[i] == 1) or (current % self.width == 0 and self.direction[i] == -1):
                continue

            # 要探索的位置没有超出范围 且 该位置没有被探索过
            if 0 <= x < self.width*self.height and self.visited[x] == 0:
                self.addEdge(current, x)
                self.visited[x] = 1
                self.search(x)

    # 随机添加k条边
    def randomAddEdges(self, k):
        # 循环k次(可能不止k次)
        for i in range(k):
            node = random.randint(0, self.width*self.height)
            # 随机添加一个方向
            sequence = [i for i in range(4)]
            random.shuffle(sequence)
            isPick = 0
            for d in sequence:
                # 跨了一行,不存在该方向的边
                if (node % self.width == self.width-1 and self.direction[d] == 1) or (node % self.width == 0 and self.direction[d] == -1):
                    continue
                x = self.direction[d]+node
                # 该边存在
                if x in self.neighbor[node]:
                    continue
                # 该边不存在
                self.addEdge(node, x)
                isPick = 1
            # 重新添加一条边,即重新循环一次
            if isPick == 0:
                if i == 0:  # 第一次
                    i = 0
                else:
                    i -= 1

    def randomCreateMap(self, start, k):  # 随机生成迷宫
        # 标识每个节点是否被探索过
        self.visited = np.zeros(self.width*self.height)
        self.visited[start] = 1
        # 四个方向,分别代表上、下、左、右
        self.direction = {0: -self.width,
                          1: self.width,
                          2: -1,
                          3: 1}
        # 从起点开始
        self.search(start)
        # 随机添加k条边,使得迷宫尽可能出现多条到达终点的路径
        self.randomAddEdges(k)


class Astar:  # A*寻路算法
    def __init__(self, _map: Map, start: int, end: int) -> None:
        # 地图
        self.run_map = _map
        # 起点和终点
        self.start = start
        self.end = end
        # open集
        self.open_set = PriorityQueue()
        # cost_so_far表示到达某个节点的代价,也可相当于close集使用
        self.cost_so_far = dict()
        # 每个节点的前序节点
        self.came_from = dict()

        # 将起点放入,优先级设为0,无所谓设置多少,因为总是第一个被取出
        self.open_set.put((0, start))
        self.came_from[start] = -1
        self.cost_so_far[start] = 0

        # 标识起点和终点
        self.run_map.drawCircle(start, '#ff8099')
        self.run_map.drawCircle(end, '#ff4d40')

    def heuristic(self, a, b):    # h函数计算,即启发式信息
        x1, y1 = self.run_map.get_x_y(a)
        x2, y2 = self.run_map.get_x_y(b)
        return abs(x1-x2) + abs(y1-y2)

    def find_way(self):    # 运行A*寻路算法,如果没找到路径返回0,找到返回1
        while not self.open_set.empty():  # open表不为空
            # 从优先队列中取出代价最短的节点作为当前遍历的节点,类型为(priority,node)
            current = self.open_set.get()

            # 展示A*算法的执行过程
            if current[1] != self.start:
                # 当前节点的前序
                pre = self.came_from[current[1]]
                # 可视化
                self.run_map.drawEdge(pre, current[1], '#fffdd0')
                if pre != self.start:
                    self.run_map.drawCircle(pre, '#99ff4d')
                else:  # 起点不改色
                    self.run_map.drawCircle(pre, '#ff8099')
                if current[1] != self.end:
                    self.run_map.drawCircle(current[1], '#99ff4d')
                else:
                    self.run_map.drawCircle(current[1], '#ff4d40')
                # 显示当前状态
                plt.show()
                plt.pause(0.01)

            # 找到终点
            if current[1] == self.end:
                break
            # 遍历邻接节点
            for next in self.run_map.neighbor[current[1]]:
                # 新的代价
                new_cost = self.cost_so_far[current[1]]+1
                # 没有到达过的点 或 比原本已经到达过的点的代价更小
                if (next not in self.cost_so_far) or (new_cost < self.cost_so_far[next]):
                    self.cost_so_far[next] = new_cost
                    priority = new_cost+self.heuristic(next, self.end)
                    self.open_set.put((priority, next))
                    self.came_from[next] = current[1]

    def show_way(self):  # 显示最短路径
        # 记录路径经过的节点
        result = []
        current = self.end

        if current not in self.cost_so_far:
            return

        # 不断寻找前序节点
        while self.came_from[current] != -1:
            result.append(current)
            current = self.came_from[current]
        # 加上起点
        result.append(current)
        # 翻转路径
        result.reverse()
        # 生成路径
        for point in result:
            if point != self.start:  # 不是起点
                # 当前节点的前序
                pre = self.came_from[point]
                # 可视化
                self.run_map.drawEdge(pre, point, '#ff2f76')
                if pre == self.start:  # 起点颜色
                    self.run_map.drawCircle(pre, '#ff8099')
                elif point == self.end:  # 终点颜色
                    self.run_map.drawCircle(point, '#ff4d40')
                # 显示当前状态
                plt.show()
                plt.pause(0.005)

    def get_cost(self):  # 返回最短路径
        if self.end not in self.cost_so_far:
            return -1
        return self.cost_so_far[self.end]


# 初始化迷宫,设置宽度和高度
theMap = Map(20, 20)

# 设置迷宫显示的一些参数
plt.xlim(0, theMap.width+1)
plt.ylim(0, theMap.height+1)
# 将x轴的位置设置在顶部
ax.xaxis.set_ticks_position('top')
# y轴反向
ax.invert_yaxis()
# 等距
plt.axis('equal')
# 不显示背景的网格线
plt.grid(False)
# 允许动态
plt.ion()

# 随机添加边,生成迷宫,第一个参数为起点;第二个参数为额外随机生成的边,可以表示为图的复杂程度
theMap.randomCreateMap(0, 20)

# 初始化迷宫
theMap.initMap()

# A* 算法寻路
theAstar = Astar(theMap, 0, 399)  # 设置起点和终点
theAstar.find_way()  # 寻路
theAstar.show_way()  # 显示最短路径

# 输出最短路径长度
theCost = theAstar.get_cost()
if theCost == -1:
    print("不存在该路径!")
else:
    print("从起点到终点的最短路径长度为: ", theCost)

# 关闭交互,展示结果
plt.ioff()
plt.show()

おすすめ

転載: blog.csdn.net/m0_51653200/article/details/127107592