450 likes | 617 Views
09. 最短路. 五島. 深さ優先以外の探索. 当然こちらを先に選ぶことも可能. 深さ優先以外の探索. 深さ優先探索: 「 visit した先にある」頂点を次に visit 一般 に は, 訪問済み 頂点に隣接したどの頂点も次に 訪問可能. 深さ優先探索が選ぶ頂点 :. 様々な探索戦略. 一般の優先度探索: 目的に応じた判断基準で優先度を決める 深さ優先探索 (depth first search) 開始点から遠い頂点を先に 幅優先探索 (breadth first search) 開始点に近い頂点を先に. 0. 1. 2. 3.
E N D
09. 最短路 五島
当然こちらを先に選ぶことも可能 深さ優先以外の探索 • 深さ優先探索: • 「visit した先にある」頂点を次に visit • 一般には, • 訪問済み頂点に隣接したどの頂点も次に訪問可能 深さ優先探索が選ぶ頂点:
様々な探索戦略 • 一般の優先度探索: • 目的に応じた判断基準で優先度を決める • 深さ優先探索 (depth first search) • 開始点から遠い頂点を先に • 幅優先探索 (breadth first search) • 開始点に近い頂点を先に 0 1 2 3 深さ優先 4 0 1-1 1-2 5 2-1 2-2 2-3 2-4 幅優先
visited[v] = 1 or 探索の一般形 最前線: 訪問済みから1 hop visit(s, G, visited) {F = { s }; /* Frontier */visited[s] = 1; while (F {}) {u = F.delete_one(); for v adjacents(u) { if (visited[v] == 0) {visited[v] = 1;F.add(v); } } }} 訪問済み ここでどのノードを選ぶか(だけ)で探索順序が決まる 訪問済みから>1 hop
様々な探索戦略の実現 • 深さ優先探索 • Fの中で最後に Fに入ったものを選ぶ • Fをスタック (Last-In-First-Out; LIFO) にする • コードでは,再帰呼び出しのコール・スタックで実現 • 幅優先探索 • Fの中で,開始点からの深さが最小のものを選ぶ • Fをキュー (First-In-First-Out: FIFO) にする • 一般の優先度探索 • 目的に応じた判断基準で優先度を決定 • Fを優先度付きキュー (priority queue) とする
反復深化探索 • 深さ制限つき深さ優先探索 • 深さ優先探索を,開始点からの距離(再帰呼び出しを使うならその呼び出しの深さ)に制限をつけて行う • 反復深化 • その深さ制限を徐々に増やしながら深さ優先探索を繰り返す • 利益: • 深さ優先と同じメモリ量 • visited配列を記憶しなくても,無限ループはしない • 幅優先に似た広く浅い探索 • 浅い探索の結果を,「解の有望度」として,深い探索の順序制御に用いることができる
幅優先探索の性質 • 幅優先探索によってたどられた辺(visited[v] = 1の実行を引きおこしたu v)の集合は,sから各頂点への最短路(最小の辺数で到達する道)を与える「重みなしグラフ(重み一律)の最短路の問題」 bfs_visit(s, G, visited) {F = queue(s); /* C++ deque/list, Java : LinkedList */visited[s] = 1; while (not F.is_empty()) {u = F.deq(); /* 前から出す */ for v adjacents(u) { if (visited[v] == 0) {visited[v] = 1;F.enq(v); /* 後ろに入れる */ }}}} 1 4 7 9 3 8 6 0 5 2
最短路の問題 • 2点間を結ぶ最短の道を求める • 道の長さ: • 重みなしグラフ: 含まれる辺の数,ホップ数 • 重みつきグラフ: 含まれる辺についた重みの和 1 4 3 2 3 4 3 7 1 3
わかりやすい応用例 (1/2) • 渋滞考慮無しのカーナビ • 頂点:交差点 • 辺: 2交差点を結ぶ一本道 • 辺の重み:その一本道を抜けるのにかかる時間 • 渋滞無し 重みが時刻によらず一定 • 2頂点間の最短路: 2つの交差点間を最も早く移動するドライブ・ルート • クイズ: • 一方通行,Uターン禁止,右折禁止などをどうモデル化する?
わかりやすい応用例 (2/2) • インターネットのルーティング • 頂点:ネットワーク・ルータ • 辺:ケーブル • インターネットより下のレイヤ(2)で直接通信できるルータの対 • 辺の重み:パケットがそこを通ることに対するコスト • ex) バンド幅の関数(バンド幅が大きければ小さい) • 2頂点間の最短路: パケットを配送する適切な道
少し自明でない応用例 (1/2) • 時刻表を考慮した路線検索 • 頂点 (t, A) : 時刻 t に駅 A にいるという「状態」 • 辺: • (t, A) (u, B):時刻 tに A駅を発車して時刻 uに B駅につく列車がある • (t, A) (u, A):時刻 t から u まで A駅にとどまる • 辺の重み: かかる時間(上の例では u – t) • (t, A) から (*, B)への最短路:時刻 tに A駅にいる人が最早で B駅にたどり着く経路 • 一見グラフではないものが,しばしばグラフとして表現できる
少し自明でない応用例 (2/2) • 編集距離 • 例 • 数字間の類似度は,その絶対差 • 3 と 4の類似度は,|3−4|=1 • ギャップ-と数字の類似度は3 • 数字列の類似度は,対応する数字,ギャップ間の類似度の総和 • 1234と124-の類似度は,0+0+1+3=4. • 右の例の答え: • 01-49 と • 0263-
アルゴリズム • 幅優先探索 • 重みなしグラフのみ • Dijkstra (ダイクストラ)法 (必修) • 幅優先探索とほぼ同じ考え方 • 指定した一頂点から他の全頂点までの最短路を求める (一対全) • Floyd のアルゴリズム • 全ての頂点対間の最短路を求める (全体全)
幅優先探索と最短路 • 幅優先探索によって visit された頂点は,開始点からの最短路「重みなしグラフ(重み一律)の最短路の問題」 bfs_visit(s, G, visited) {F = queue(s); /* C++ deque/list, Java : LinkedList */visited[s] = 1; while (not F.is_empty()) {u = F.deq(); /* 前から出す */ for v adjacents(u) { if (visited[v] == 0) {visited[v] = 1;F.enq(v); /* 後ろに入れる */ }}}} 1 4 7 9 3 8 6 0 5 2
Dijkstra 法 の 概要 • 優先度つきグラフ探索の一種: • これまでに見つかっている s* u の距離が最短の u を優先して visit • あとはほぼ「一般的な探索方法」に従うだけ • 入力: 重みつきグラフ G = A, Eとその一頂点 s (A) • 辺の重みは非負 • アルゴリズムは無向でも同様 • 出力: D[i] (iA) が,sから iへの最短距離を与えるような配列 • 最短路を与えるように変更するのは容易
Dijkstra 法の概要 F ∞ 2 12 10 ∞ 1 0 9 10 V U 1 ∞ p 10 11 1 V : visited q ∞ U : unvisited F : frontier
4 8 3 1 6 2 1 start goal 4 5 dijkstra(s, G) {V= {};D = new int[n]; for (i = 0; i < n; i++) { if (i == s) D[i] = 0; else D[i] = ; } while (V A) { pick uVthat minimizes D;V= V+ { u }; for v adjacents(u) {d = D[u] + Wu,v ; if (d < D[v]) D[v] = d; } }} Dijkstra 法:擬似コード Wu,v:辺u vの重み(距離)
1 1 1 1 4 4 5 4 1 6 0 0 0 0 3 3 3 3 7 8 9 7 0 4 Dijkstra法の動き • 赤:S (意図:最短距離確定) • 緑:暫定最短距離(startから赤だけを使う道の中での最短距離) 4 8 3 1 6 2 1 start goal 4 5
証明 F ∞ 2 12 10 ∞ 1 0 9 10 V U 1 ∞ p 10 11 1 V : visited q ∞ U : unvisited F : frontier
証明 • 前半 • V 内の最短路は確定していると仮定 • U 内の仮距離は,V 内の頂点のみを経由して到達できる最短の長さ • 仮距離が最小の頂点 p(F) を選んだとき,p までの最短路が V 内の頂点のみからなることを言えば,p までの最短路を確定できる. • pまでの最短路が V 内の頂点のみからなる: • U 内の頂点を経由して p に至る最短路があると仮定, • その経路上,F 内の頂点を q とする. • distance(s, p)> distance(s, q) + distance(p, q), distance(p, q) ≥ 0 • distance(s, p)>distance(s, q) となり,矛盾.
dijkstra(s, G) {V= {}; D = new int[n]; for (i = 0; i < n; i++) { if (i == s) D[i] = 0; else D[i] = ; } while (V A) { pick uVthat minimizes D; V= V+ { u }; for v adjacents(u) {d = D[u] + Wu,v ; if (d < D[v]) D[v] = d; } }} 計算量 • 基本 • while ループは常に n(頂点数)回繰り返す • ポイント • 集合 D の表現と操作(2行) • 単純アプローチ • S:配列 V[i] = 1 iffi V; • :線形探索 O(n) • 全体の計算量 O(n2) • 密なグラフならこれ以上は難しそう
dijkstra(s, G) {S = {};D = new int[n]; for (i = 0; i < n; i++) { if (i == s) D[i] = 0; else D[i] = ; } while (S V) { pick uV – S that minimizes D; S = S + { u }; for v adjacents(u) {d = D[u] + Wu,v ; if (d < D[v]) D[v] = d; } }} dijkstra(s, G) {T = priority_queue(V);D = new int[n]; for (i = 0; i < n; i++) { if (i == s) D[i] = 0; else D[i] = ; } while (F {}) { delete uF that minimizes D; for v adjacents(u) {d = D[u] + Wu,v ; if (d < D[v]) {D[v] = d; change priority of v in Fto d; } } }} 疎なグラフ用の Dijkstra 法 • 集合 Fをヒープで表現 • 最小値の除去: O(log n) • 優先度の変更: O(log n) • 合計計算量 • O((n + m) log n) • m O(n) ならば O(n log n) • 現実の道路・鉄道・ネットワークetc.は?
ヒープ • 二進木 • 親 < 子(または,親 > 子) • 挿入,削除: O(log n) 1 0 1 2 3 2 0 0 1 1 1 1 2 3 2 2 3 2
全対全 最短距離 • 方法 1: Dijkstra 法を各頂点を開始点として行う • O(n3) または O(n (n + m) log n) • 方法 2: Floydのアルゴリズム • O(n3)
Floyd のアルゴリズム • 概要: • 全対全 最短距離 • O(n3) • 入出力: • 入力: 辺重みつき有向グラフ G = V, E • 出力:D[i][j] が iから jへの最短距離を与えるような 2次元配列 D • 仮定: • Dijkstra 法と異なり,負の重みがあってもよい • 重みの和が負となる閉路がない
Floyd のアルゴリズム • (*) のループの不変条件: • D[k][i][j] は, • iから jへの道で, • 0, 1, …, kのみを経由する • 最短路の長さ int D[−1:n−1][0:n−1][0:n−1]; floyd(graph G) { for (i = 0; i < n; i++) for (j = 0; j < n; j++) D[−1][i][j] = G.weight(i,j); for (k = 1; k < n+ 1; k++) // (*) for (i = 0; i < n; i++) for (j = 0; j < n; j++) D[k][i][j] = min(D[k − 1][i][j] , D[k − 1][i][k] + D[k − 1][k][j]; }
考え方 • 考え方: • D[k − 1][i][j]:0, 1, …, k − 1 のみを経由する i, j間の最短距離 から • D[k][i][j]: 0, 1, …, k − 1, k のみを経由する i, j間の最短距離 を求める • 言い替え: • D[k − 1][i][j]: 0, 1, …, k − 1 のみを経由する i, j間の最短距離 が正しく分かっているとして,さらに k も経由していいとしたら,どうすればいいか?
k回目のイタレーション 0, 1, …, k − 1 のみを経由する i, j間の最短距離 D[k − 1][i][j] i j D[k − 1][i][k] D[k − 1][k][j] 0, 1, …, k − 1 のみを経由する i, k間の最短距離 0, 1, …, k − 1 のみを経由する k, j間の最短距離 k D[k][i][j] = min(D[k − 1][i][j], D[k − 1][i][k]+ D[k − 1][k][j]) 0, 1, …, kのみを経由する i, j間の最短距離
k = 0回目のイタレーション どこも経由しない i, j間の最短距離 D[−1][i][j] i j D[−1][i][0] D[−1][0][j] どこも経由しない i, 0 間の最短距離 どこも経由しない 0, j間の最短距離 0 D[0][i][j] = min(D[−1][i][j], D[−1][i][0]+ D[−1][0][j]) 0 のみを経由する i, j間の最短距離
k = 1 回目のイタレーション 0 のみを経由する i, j間の最短距離 D[0][i][j] i j D[0][i][1] D[0][1][j] 0 のみを経由する i, 1 間の最短距離 0 のみを経由する 1, j間の最短距離 1 D[1][i][j] = min(D[0][i][j], D[0][i][1]+ D[0][1][j]) 0, 1 のみを経由する i, j間の最短距離
動作例 0 3 1 1 1 3 1 2 1 D[k = 0]:0 を経由 D[k = 2]:0, 1, 2 を経由 D[k = −1]:初期化後 D[k = 1]:0, 1 を経由 D[k = 3]:0, 1, 2, 3 を経由
Floyd のアルゴリズム • D は2次元でよい • 単に3次元目を省略 → • 記憶量は O(n2) でよい int D[n][n]; floyd(graph G) { for (i = 0; i < n; i++) for (j = 0; j < n; j++) D[i][j] = G.weight(i,j); for (k = 0; k < n; k++) // (*) for (i = 0; i < n; i++) for (j = 0; j < n; j++) D[i][j] = min(D[i][j] , D[i][k] + D[k][j]; }
一対一 最短距離? • 一対一 最短距離: 与えられた2点間の最短距離を求める • Dijkstra 法(一対全)を使って全頂点までの距離をまず求め,目的地までの最短距離を取り出す • 一対一だけを(一対全よりも)高速に計算する方法は知られていない
抽象的(暗黙的)なグラフ • データ構造として表せないようなグラフ • ex) 巨大 • 抽象的(暗黙的)なグラフ: • 隣接点の集合 adjacents(u) が計算できれば,「グラフ」と見ることが可能 • ex) ゲームのグラフ • 頂点:ゲームの状態(盤面) • u v:状態 uから状態 vへ「一手で」移ることが可能
探索問題 • これまで述べた探索アルゴリズムは,抽象的なグラフに対しても適用可能 • 巨大な状態空間の中からある性質を持った「ゴール・解」の「探索」 • このような問題をしばしば「探索問題」と呼ぶ
練習問題 • グラフ探索の考え方を使って,「8パズル」を解くプログラムを書いてみよ グラフのノード数(の上界)は? 連結(どこからでもゴールできる)か? 最短(最小手数)でゴールするには? 2 3 4 7 1 5 8 6 2 3 4 7 1 5 8 6 1 2 3 4 5 6 7 8 ゴール
探索問題の困難と戦略 • 困難:探索空間が巨大(頂点数 nが大きい) • すべてを探索するには時間がかかりすぎる • メモリに収まりきらない (visited配列すべてを記憶できない) • 目的: • 特定の頂点(ゴール,解)を見つけることで, • 全頂点を見る必要はない • ゴールに早くたどり着く戦略(経験則,heuristics)が必要
いくつかの経験則・戦略 • 隣接頂点 adjacents(u) の順序付け • 答えに到達する可能性が高い頂点を先に探索 • 頂点の「有望度」(例:解への近さ)を測る経験則が必要 • メモリの節約 • 深さ優先探索で訪問済み頂点 (visited) を記憶しない • 同じ頂点を 2度訪問してしまう危険性? • グラフが木で,あり得ない場合 • 許容される場合 • 浅く広いグラフの場合になお有用(必要メモリ量 O(深さ))
挑戦しがいのある課題 • 15パズル,ルービックキューブなど,状態空間が大きいパズルの解法 • ルービックキューブくらいなら課題2として良いかも