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