Comments
Description
Transcript
プログラミング言語I 第10回 最短経路問題
プログラミング言語I 第10回 最短経路問題 埼玉大学工学部 電気電子システム工学科 伊藤 和人 最短経路問題とは 始点から終点へ行く経路が複数通りある 場合に、最も短い経路を見つける問題 経路の短さの決め方によって様々な応用 Copyright © 2008 Kazuhito Ito 最短経路問題の応用例 カーナビゲーション 現在地から目的地まで最短時間のルート 経路=道路 交差点において走る道路を変更してもよい 経路の短さ=所要時間の短さ 鉄道乗り換え案内 始発駅から目的駅まで料金最低のルート 経路=路線 駅において路線を乗り換えてもよい 経路の短さ=料金の安さ Copyright © 2008 Kazuhito Ito 問題の定式化 定式化: 問題の意味が変化しないことに注 意して、明確な形式に問題を整理すること 条件と解の性質を明確にする あいまいな点をなくす さらに、問題を解くプログラムが容易に作 成できるような定式化を行うことが重要 Copyright © 2008 Kazuhito Ito グラフ プログラミングに適した問題定式化の道具 としてよく用いられる 1個以上の点(ノード)と2つの点を結ぶ枝か らなる 点 枝 グラフの例 Copyright © 2008 Kazuhito Ito グラフ(その2) 点を共有する枝の集合をパス(path、経路) という パスの始点と終点を定める 一般に始点と終点が同じパスは複数通り 存在する(1通りしか存在しない場合や、 1つも存在しない場合あり) 始点 終点 パス Copyright © 2008 Kazuhito Ito グラフ(その3) 枝に重みを与える パスの重みは、枝重みの和とする 5 枝重み 始点 2 6 3 1 9 2 2 終点 パス: 重み12 始点・終点が同じ複数通りのパス パスによって重みは異なる Copyright © 2008 Kazuhito Ito 最短経路問題の定式化 グラフによって問題を入力 (始点、終点、枝重み) 重み最小のパスを見つける 鉄道乗り換え案内の場合 乗換駅を点、枝重みを料金とすればよい 渋谷 横浜 260円 160円 品川 池袋 150円 150円 150円 赤羽 150円 東京 上野 大宮 290円 160円 160円 280円 Copyright © 2008 Kazuhito Ito 新宿 160円 2,750円 最小値原理 始点 始点sから終点tへの最短経路上の点をvと すると、パスp(s,v)とパスp(v,t)はともに 最短経路である s sからvへの 最短経路 v vからtへの 最短経路 t 終点 sからtへの最短経路 終点以外の最短経路を順次求めて、 最終的に終点への最短経路を求める Copyright © 2008 Kazuhito Ito 問題の分類 枝重みが0または正の場合 枝重みが0、正、負で負ループがない場合 枝重みが0、正、負で負ループがある場合 ループ: 始点と終点が一致したパス 負ループ: 枝重み和が負のループ 5 2 1 3 1 -8 Copyright © 2008 Kazuhito Ito 2 0 枝重みが0、正、負で 負ループがある場合 負ループ: 重み-2 最短経路アルゴリズム1 枝重みが0または正の場合を考える 2 始点 s 6 2 始点 a 1 0 b 3 2 4 c 1 3 0 d 2 0 a 1 b 3 2 4 c 1 6 3 0 d 2 Copyright © 2008 Kazuhito Ito s 始点sから他の点 への最短経路を求める 始点sから点bへの 最短経路が1である と決定できる なぜか? 最短経路アルゴリズム1(その2) 枝重みが0または正の場合 2 始点 s 2a 1 0 1 b 3 2 3 4 c 1 6 3 0 d 2 6 aを経由 パス重みは2以上 cを経由 パス重みは3以上 dを経由 パス重みは6以上 枝重みが0または正であるので、 点a, c, dを経由するどのパスも 重みが1以上となるため Copyright © 2008 Kazuhito Ito 最短経路アルゴリズム1(その3) 次に経路が最短な点を求める 2 2 a 0 e 4 1 1b 3 2 5 始点 f s 3 4 c 1 6 3 0 d 6 2 0 e 2 2 a 2 1 1b 3 2 5 f s 3 4 c 1 6 3 0 d 6 2 Copyright © 2008 Kazuhito Ito 2 2 a 1 0 1 b e 3 4→2 2 5 f 3 4 c 1 6 3 0 d 6 2 0 e 2 2 a 2 1 1b 3 2 5→4 f s 3 4 c 1 6 3 0 d 6 2 s 最短経路アルゴリズム1(その4) アルゴリズムのポイント ダイクストラ法 最短経路長の定まった点から、 さらに枝1本で到達する点のうち、 経路長最短の点は、最短経路と決定できる 1 Copyright © 2008 Kazuhito Ito 4 2 3 7 さらに枝1本で 到達する点 7 3 4 2 s 最短経路長の 決まっている点 6 4 2 5 6 経路長が最小 ⇒最短経路を 決定できる点 C言語によるグラフ表現1 2次元配列を用いる方法 枝が結ぶ2点(i,j) i j 配列要素e[i][j]が枝を表す e[i][j]=1: 枝あり e[i][j]=0: 枝なし i⇒j と j⇒i を区別しない場合 e[j][i] = e[i][j] とする Copyright © 2008 Kazuhito Ito C言語によるグラフ表現1(続き) 枝(i,j)の重み ・・ w[i][j]が記憶 i j w[i][j] i⇒j と j⇒i を区別しない場合 w[j][i] = w[i][j] とする Copyright © 2008 Kazuhito Ito 始点s=0とする ダイクストラ法の実装 点数N int s, minLen, j, m, u[N], f[N]={0}; for( j=0 ; j<N ; j++ ) u[j] = 9999; s = 0; u[s] = 0; 十分大きな正数 for( m=1 ; m<N ; m++ ){ 点sは最短経路決定 f[s] = 1; for( j=0 ; j<N ; j++ ){ 枝(s,j)が存在しない if( e[s][j] != 1 ) continue; if( u[s]+w[s][j] < u[j] ) u[j]=u[s]+w[s][j]; } 点jへの経路長を更新 minLen = 9999; 最短経路既定の点は除外 for( j=0 ; j<N ; j++ ){ if( f[j] == 1 ) continue; if( u[j] < minLen ){ minLen = u[j]; s = j; } } 点sは経路長が最短 } Copyright © 2008 Kazuhito Ito 長さだけでなく経路を記録 始点s=0とする 点数N int s, minLen, j, m, u[N], f[N]={0}, prev[N]; for( j=0 ; j<N ; j++ ) u[j] = 9999; s = 0; u[s] = 0; 十分大きな正数 for( m=1 ; m<N ; m++ ){ 点sは最短経路決定 f[s] = 1; for( j=0 ; j<N ; j++ ){ 枝(s,j)が存在しない if( e[s][j] != 1 ) continue; if( u[s]+w[s][j] < u[j] ){ u[j]=u[s]+w[s][j]; prev[j]=s; } /* jの直前はs */ } 点jへの経路長を更新 minLen = 9999; 最短経路既定の点は除外 for( j=0 ; j<N ; j++ ){ if( f[j] == 1 ) continue; if( u[j] < minLen ){ minLen = u[j]; s = j; } } 点sは経路長が最短 } Copyright © 2008 Kazuhito Ito ダイクストラ法の計算複雑度 経路長最短の点を1つ選び経路を決定 その点から1本の枝で接続する点について 経路長を調べなおす すべての点について経路が決定するまで 繰り返す 2 O( N ) Copyright © 2008 Kazuhito Ito プログラムの実行時間 ノード数Nとプログラム実行時間の関係 100.00 N=8000 約500Mバイト 10.00 秒 1つのノード 当たり4本の 予想 枝の場合 N=7000 約400Mバイト 1.00 PentiumIV 2.4GHz 512MBメモリ 0.10 0.01 100 Copyright © 2008 Kazuhito Ito 1000 N 10000 100000 プログラムの問題点 配列による枝表現はメモリを大量に必要と し、かつ効率が悪い 010000000000000000000000000000000000000000000000000000 101000000000000000000011000000000000000000000000000000 010110000000000000000000000000000000000000000000000000 001010000000000000000000000000000000000000000000000000 001101000000000000000000000000000000000000000000000000 000010110000000000000000000000000000000000000000000000 000001010000000000000000000000000000000000000001000000 000001100000000000000000000000000000000000000100000100 000000000110000000000000000000000000000000000000000100 000000001010000000000000000000000000000000000000000000 000000001101000000000000000000000000000000000000000010 000000000010100000000000000000000000000000000000100000 000000000001010000000000000000000000000000000000000000 000000000000101001000000000000000000000000000000000010 000000000000010100100000000000000000000000000000000000 000000000000001000000000000000000000000000000010100000 000000000000000001000000100000000000000000000101000000 000000000000010010100000000000000000000000000000000000 000000000000001001010000000010000000000000000000000000 000000000000000000101000000000000000000000000000000000 000000000000000000010100000000000000000000000000010000 000000000000000000001000000000001001000000000000000000 010000000000000000000001000000000000000000000000001000 010000000000000000000010100000000000000000000000000000 … Copyright © 2008 Kazuhito Ito i j 枝の有無を 表す2次元 配列e[i][j] の例 0がほとんど 配列による枝表現の問題 枝の有無(e[i][j]==1か否か)を調べる処 理が必要 枝が無くても(e[i][j]=0)を記憶する必要が あるためメモリを大量に消費し、速度低下 (スラッシング) 配列に代わる、不規則なデータを 効率よく記録する方式が必要 リスト構造 Copyright © 2008 Kazuhito Ito リストの管理 リストの要素 データ領域の他に、次の要素を指すポイン タを備える ポインタ 次の要素を指す 要素 データ領域 ポインタを用いて要素をつなげる=リスト Copyright © 2008 Kazuhito Ito リストの実装 構造体によって、データ領域と次要素への ポインタをまとめて管理する 要素の構造体型を宣言 例 typedef struct Edge { struct Edge *next; int i,j; // 点1, 点2 int w; // 枝重み } EDGE; 次要素へのポインタ データ領域 リストの先頭を指すポインタを宣言 例 EDGE *edge_top = NULL; Copyright © 2008 Kazuhito Ito リストの実装(その2) 3本の枝を 記録するリスト リストの例 edge_top 点1 点2 枝重み 点1 点2 枝重み NULL 点1 点2 枝重み リストの 末尾では nextメンバは NULL リストを順にたどる処理 EDGE *ep; for( ep=edge_top ; ep != NULL ; ep=ep->next ){ … /* リスト要素に対する処理 */ } Copyright © 2008 Kazuhito Ito ダイクストラ法における枝リスト 最短経路が決まるたびに、その点から 枝1本でつながる点の経路長更新 各点に接続する枝リストがあると便利 最短経路長の 決まっている点 1 s 6 4 2 7 3 4 2 新たに最短経路長 4 3 2 7 の決まった点 Copyright © 2008 Kazuhito Ito 5 6 経路長を 更新する点 ダイクストラ法のための枝リスト 各点に接続する枝のリスト edge[0] 点0に接続する 枝のリスト 0 点2 枝重み 0 点2 枝重み NULL 0 点2 枝重み edge[1] 点1に接続する 枝のリスト 1 点2 枝重み 1 点2 枝重み 1 点2 枝重み 点ごとに 接続する 枝数は変化 NULL 1 点2 枝重み edge[2] Copyright © 2008 Kazuhito Ito 2 点2 枝重み ダイクストラ法と組合わせる 場合、メンバ「点1」は不要 リストを用いたダイクストラ法 始点s=0とする 点数N int s, minLen, j, m, u[N], f[N]={0}; EDGE edge[N], *ep; /* 枝リストを設定する処理は省略 */ for( j=0 ; j<N ; j++ ) u[j] = 9999; s = 0; u[s] = 0; 十分大きな正数 for( m=1 ; m<N ; m++ ){ 点sは最短経路決定 f[s] = 1; for( ep=edge[s] ; ep!=NULL ; ep=ep->next ){ if( u[s]+ep->w < u[ep->j] ){ sに接続する枝 u[ep->j]=u[s]+ ep->w;} を順に調べる } minLen = 9999; 点ep->jへの経路長を更新 for( j=0 ; j<N ; j++ ){ 最短経路既定の点は除外 if( f[j] ) continue; if( u[j] < minLen ){ minLen = u[j]; s = j; } } 点sは経路長が最短 } Copyright © 2008 Kazuhito Ito プログラムの実行時間 ノード数Nとプログラム実行時間の関係 100.00 N=8000 約500Mバイト 秒 10.00 配列利用 N=40000 約1Mバイト N=7000 約400Mバイト 1.00 リスト構造 利用 0.10 PentiumIV 2.4GHz 512MBメモリ 0.01 100 Copyright © 2008 Kazuhito Ito 1000 N 10000 100000 最短経路アルゴリズム2 負の枝重みが存在する場合 2 始点 s 6 a 1 0 b 3 -1 -4 c 1 3 0 d -2 ダイクストラ法では最短経路を見つけられない なぜか? まだ最短経路の決まっていない点を経由した方が 経路長が短くなるパスが存在するかもしれないため Copyright © 2008 Kazuhito Ito Bellman方程式 点jの最短経路長をujとするとき、 ujが満たす関係式・・・Bellman方程式 us = 0 u j = min {ui + wij } ∀j ≠ s i 最小値原理より 始点 Copyright © 2008 Kazuhito Ito s ui w ij i j uj Bellman方程式を解く Bellman方程式の解=最短経路 漸化的に解く u[i]が更新されたら、枝(i,j)が存在する 点jについてu[j]を更新する すべての点jについてu[j]が変化しなく なったら、解が得られている Bellman-Ford法 Copyright © 2008 Kazuhito Ito 始点s=0とする リストを用いたBellman-Ford法実装 int s,update,minL,i,u[N], prev[N]; EDGE *edge_top, *ep; /*リストを正しく作成する必要あり(ここでは省略) */ for( i=0 ; i<N ; i++ ) u[i] = 9999; 十分大きな正数 s = 0; u[s] = 0; do { update = 0; for( ep=edge_top ; ep != NULL ; ep=ep->next ){ if( u[ep->i]+ep->w < u[ep->j] ){ u[ep->j] = u[ep->i]+ ep->w; prev[ep->j] = ep->i; 経路を記録 update = 1; 経路長が短くなったら } 更新フラグを1に } } while ( update ); Copyright © 2008 Kazuhito Ito 経路長更新の場合再度繰り返し do - while文 条件が成り立つ間、文を実行 do 文 while( 式 ); 意味: まず1回文を実行する。 式が真の間、文を実行。 文 式 式 False False True 文 True do-while文の実行の様子 Copyright © 2008 Kazuhito Ito “while(式) 文;”の実行の様子 Bellman-Ford法の計算複雑度 すべての枝を順に調査 経路長が更新されたら、再度調査実行 負ループがなければ最短経路を構成する 枝数はN未満・・・更新は高々N-1回 実行時間 O (NE ) Copyright © 2008 Kazuhito Ito 負ループのある最短経路問題 最短経路は不定 ⇒ 負ループを繰り返すごとに経路長は減少 「経路にループを含まない」という制約を 与えると意味のある問題として定式化される 効率よく最短経路を求めるアルゴリズムは 見つかっていない 負ループが存在する場合の最短経路問題は 負ループが存在しない場合の問題とは 性格が異なる Copyright © 2008 Kazuhito Ito 組み合わせ最適化問題 ある条件を満足する解候補のうち、 ある評価尺度が最適になる解を選ぶ問題 例: 最短経路問題 条件: 始点から終点への連続した枝集合 (経路) 評価尺度: 枝重み和が小さい Copyright © 2008 Kazuhito Ito 組み合わせ最適化問題の困難さ 組み合わせ最適化問題を3つに分類 P: 多項式可解な問題 NP: 非決定的多項式可解な問題 問題サイズの多項式オーダの手数で解が得 られる ⇒計算複雑度が多項式オーダの解法が存在 都合良く選択肢を選ぶと、問題サイズの多項 式オーダの手数で解が得られる PでもNPでもない問題 PやNPよりも難しい問題 Copyright © 2008 Kazuhito Ito 問題の困難さ 組み合わせ最適化問題の分類とその関係 全組み合わせ問題 NP P P問題の例 ソート(並べ替え) 最短経路問題 オペレーションズ・リサーチ Copyright © 2008 Kazuhito Ito NP問題 NP: 非決定的多項式可解な問題 都合良く選択肢を選ぶと、問題サイズの多項 式オーダの手数で解が得られる 工学的に有用な問題が多数含まれる 負ループを含む最短経路問題 セールスマンの巡回問題 タスク・スケジューリング問題 など Copyright © 2008 Kazuhito Ito セールスマンの巡回問題 問題の定義 「N個の都市を順に訪問するための旅費が 最小になる訪問順を求めよ」 都市間の旅費は非負 最短経路(最小費用)を求める問題 一見すると最短経路問題として解けそう? すべての訪問順を列挙して最小費用の順路を 求める方法しか知られていない ⇒ O (N !) Copyright © 2008 Kazuhito Ito NP問題とP問題 NP問題の解には、 今のところ非多項式オーダの手数が必要 多項式オーダの解法が存在しないことは 証明されていない もしかするとNP=Pかもしれない NP問題のどれか1つについて多項式オーダ の解法が見つかればNP=P すべてのNP問題が多項式オーダで解ける! Copyright © 2008 Kazuhito Ito まとめ 問題定式化とグラフ 最短経路問題 効率よい解法 ダイクストラ法 Bellman-Ford法 リスト構造 組み合わせ最適化問題とNP Copyright © 2008 Kazuhito Ito