個固定なので、各パネルの距離を一覧表として作成してあるだけである。リアルタイムで計算しても良いが、どうせ同じ答えになるのなら、このように配列化しておくと実行速度が速い。インタフェースを噛ませて、PriorityQueueで比較している。探索の優先順位などを変えたければここをいじるのが一番手っ取り早いだろう。差分を計算しているが、わかりにくければ、空白と数字パネルを入れ替え、空白のインデクス更新した後に、全パネルのマンハッタン距離の合計を求めるメソッド「Puzzle.calcMD()」を呼び出す方法でも良い。実行速度を優先したければ、差分の計算のままが良いだろう。使っているが、Map(連想配列)でも良い。並びを表示できる)の文字列を、手抜き(笑)でそのまま使ってしまっているが、パネルは15個(16)なので、16進数にするなど別の文字列に置き換えても良いだろう。メモリ節約になるかも知れない。自分なりに書き換えたものと言って良い。内容的にはほぼ同じはずだが、手本はあくまでも単純な書き方(すべての機能を一続きに書くような)になっているものが多いので、今回は問題提示部分と探索部分、パズル内での動作をそれぞれにまとめ、なるべく機能分離するようにしてある。すると似たような構成のパズルなら、構造体(クラス)やプロパティ、メソッドを少し改造するだけで、再利用できる可能性が増えるからだ。解法を見なくてもそらで書けるようになったら、自分なりのコードも書けるようになる。前回の迷路探索とあまり変わらないのはそのためだ。いじってみると良いだろう。エースター)探索アルゴリズムとは簡単に言えばダイクストラ法に推定値を用いた最良優先探索アルゴリズムである。「推定値」というくらいなので正確さに欠けるときもあるが、通常は枝刈り(一部探索を打ち切ること)が行われるので、実行速度が速いという特徴がある。ヒューリスティクス探索と呼ばれることもある。いう特徴からゲームなどに使われることも多い。敵AIがプレイヤーを追跡するのにも使える。以前、頂点と辺でのダイクストラ法を書いたが、比較してみると、ほとんどダイクストラ法と変わらない事がわかるだろう。実際「推定値」を使って比較している部分を「移動コスト」に書き換えればダイクストラ法そのものになる。値(Integer.MAX_VALUE)になる。ダイクストラ法と同じように、コストに負の値は使えない(大小関係が崩れる、無限ループするなど)。今回は各位置への移動コストは1固定だが、迷路データによってコストを変えることもできる。毒を受ける道はコスト2かかるなどという感じだ。{~}」の「+1」など、コストに関わる部分をそのコストに置き換えれば良い。今回は4方向の移動にしてるので、推定値は「ゴールまでのマンハッタン距離+スタートからの移動量」にしている。8方向移動にするのなら、チェビシェフ距離の方が良いだろう。正確さを加えるために、推定値が同じときは、移動コストで比較するという推定値を2重にする方法もあるが、実際にやってみると、例えば障害物があまりない、広いフロアのような迷路データではダイクストラ法と同じ動作になってしまうため、4方に探索の手が伸ばされ、枝刈り効果がなくなる。実行速度と正確さのどちらかで使い分けた方が無難だろう。推定値に移動コストを足す理由を少し考えてみよう。ゴールまでの距離だけだと最短は円形(4方向の場合はひし形になるが)に広がるので、反対方向も同じになる。そこに辿り着くまでの移動コストが足されていれば、遠回りは最短候補から外れて枝刈りができるわけだ。逆に遠回りが最短のときは効果が少ない。この推定値の意味を考えるなら「ここを通ってスタートからゴールへ行くには、たぶんN歩かかる」という値(N)と考えればわかりやすい。インタフェースを噛ませて、PriorityQueueで比較している。探索の優先順位などを変えたければここをいじるのが一番手っ取り早いだろう。o.cost」に書き換えればダイクストラ法となる。推定値は基本的に最短かそれ以下にしておかないと上手く動作できないが、探索の目的によって色々変えてみたり、条件で分岐させるのも良いかも知れない。いえ私も色々計算方法を試してみたが、この「ゴールまでの最短距離+移動コスト」が平均的に効率が良いようだ。斜め移動を含む8方向での距離計算用に、チェビシェフ距離を求める「getChevyshevDistance()」をオマケとして付けてあるが、今回は使ってない。以下はデバッグ用に出力した各データをキャプチャしてみたものだ。「cost」はスタートからの移動量、「sequence」は探索した順序、「estimate」は計算された推定値、「count」は探索した回数、「path」はゴールまでの移動手順、色を付けてある「-1」は壁で、「X」は枝刈りされて未探索の場所だ。「Dijkstra」(ダイクストラ)は先に述べたように、比較関数を移動コストに変更しただけだ。動きの違いを見てみると、「A-Star」の方が大きく枝刈りされていて、探索回数が少なくなっている。実行速度を優先したい場合には有用だということがわかるだろう。>クリックで拡大この例では戻値はスタートからゴールまでの移動コストを返しているが、簡単な移動経路(移動方向の記録)もついでに返している(グローバル変数なのは手抜き(笑))。本来ならリストで座標を返すべきかも知れないが、まぁ、ラーニング用コードなのでこれで十分だろう。この移動経路から座標を計算するか、リストなどに順次座標を追加していくように改造すれば、簡単に実現できる。今回のコードは幅優先探索をベースとした枝刈りアルゴリズムということになるが、深さ優先探索をベースとした「反復深化」という手法もある。一般的に深さ優先探索は幅優先探索よりメモリが少なくて済むので、携帯デバイスなどで使うときにはメリットがあるかも知れない。ゲームのようにリアルタイムで状況が変化するものは、正確さより実行速度を要する場合が多いので、ヒューリスティクス的なアルゴリズムは学習価値があるだろう。パフォーマンスが大きく改善される可能性もある。試してみると良いだろう。(始点から到達点までの最短距離)の他には全点対間最短経路(各頂点間の最小コスト[最短距離])を求めるアルゴリズムとしてワーシャル-フロイド法というものがある。j)のコストを全パターン比較して、最小を求めることによって、各頂点同士の最短距離を記録していくと考えれば良い。A)のようにぐるぐる基準点を回転して、小さいコストを記録していく感じだ。頂点が増えても、その三角路が組み合わさっている感じになる(実際には見えない頂点間も最短計算に使用している)。このアルゴリズムも負の閉路(辺の重みの総和が負になるような閉路)を検出できるという特徴がある。なり、比較したとき負の値に更新される。正の場合は0以下になることはない)。今回は前回までの辺情報の構造体は使用しない。例(図)はこちらと同じものを使わせてもらった。}}今回の戻値は各頂点間での最小コスト(距離)の表となる。戻すようにしても良いだろう(負の閉路があった場合、値が使えないので)。まま使っている(ただし、無向グラフ用に逆方向を追加している。有向グラフでも構わない)。最短路計算(warshallFloyd())内で結果となる表(d[][])を初期化しているが、この「初期化~隣接行列のように、重みを入れる」部分を別の関数に独立すれば、他の形式に対応することもできるだろう。値のままでやるとオーバーフローを起こすので、処理をスキップしている。continue;」のように1行にするのも良いだろう。全パターンやることを考えれば、こちらの方が簡潔だろう。考えれば、一気にそれぞれの頂点同士の最短を知りたいときに使える。ネットワーク上の負荷の軽い箇所を探すとかね。結果の○で囲んである値を図と比較してみると、最短に更新されているのがわかる。ような)コストも計算されていることがわかるだろう。[edit]前回同様の単一始点最短経路アルゴリズムにベルマン-フォード法がある。見ればわかるが、ダイクストラ法の最短距離の更新処理をそのまま抜き出した感じだったりする。ダイクストラ法との違いは、計算順序が辺の総当り的な方法になっていることだろう。O(VE)[頂点数×辺の数]になっている。O(VE)[頂点数×辺の数]から、負の閉路(辺の重みの総和が負になるような閉路)があったとき、最短距離の更新が無限ループすることから、負の閉路を検出することもできる。今回も前回と比較するために、同じ例、同じ辺情報の構造体を使って試してみよう。値(Integer.MAX_VALUE)になる。値(Integer.MIN_VALUE)を返している。変更しても良いだろう。ベルマン-フォード法は辺の総当りなので、隣接リストではなく、より簡潔に、線形的な配列(Edge[])にしている。負の閉路の検出には各頂点の更新数(count[])で判別している。超えることはないからだ(例えば頂点が全部で5コなら、1つの頂点には辺は4つまでしか繋がらない=更新も4回まで)。edges.length」にしても良いだろう。加算をすると、オーバーフローを起こし、マイナスの値になってしまうためだ。)にすれば、この条件文を消すことができる。この例では戻値は単一始点最短経路のコストを返しているが、選択した辺を知りたければ、少し改造して、各頂点の最短距離を更新するときに、その接続元を記録しておき、逆から辿れば(到達点→始点)わかる。頂点番号だけでも良いが、今回も前回に合わせて、辺情報の構造体で返すようにしてみよう。//選択した辺を返す。追加していけば良い(引数部分も型に合わせる)。ベルマン-フォード法は負の閉路を持っている可能性がある場合や、迷路というより、網の目のようなネットワーク経路などに向いているかも知れない。このサンプルでは更新がなかったとき、探索を打ち切っているので、計算量は「辺の数×頂点数」よりもずっと小さい回数で済む。デバッグ出力して確かめてみると、負の閉路がない場合は「辺の数+数回の更新」で終わることが多いので(データの順序にもよる。始点に近い順に並んでいると計算が速い)、ダイクストラ法とそれほど変わらない気がする。