550 likes | 993 Views
アルゴリズム概論 講義資料. 関先生の講義資料を利用させていただきました。 資料の利用を快諾していただいた関先生に感謝 します。 参考書 石畑 清著: 「アルゴリズムとデータ構造」 (岩波書店). グラフのアルゴリズム. 探索 5/12 連結性 5/14 最短経路 5/19. 有向グラフ (directed graph). 頂点(節点) (vertex, node). 道 (path) :辺で結ばれた頂点の列. v1,v2,v3,v4,v2 (長さ4)など.
E N D
アルゴリズム概論 講義資料 • 関先生の講義資料を利用させていただきました。 資料の利用を快諾していただいた関先生に感謝 します。 • 参考書 石畑 清著: 「アルゴリズムとデータ構造」 (岩波書店)
グラフのアルゴリズム • 探索 5/12 • 連結性 5/14 • 最短経路 5/19
有向グラフ (directed graph) 頂点(節点) (vertex, node) • 道 (path) :辺で結ばれた頂点の列.v1,v2,v3,v4,v2(長さ4)など. • 閉路 (cycle):先頭と末尾の頂点が等しい道.v2,v3,v4,v2など. v1 v2 v5 v3 辺(枝) (edge, arc) v4
無向グラフ (undirected graph) • 道 v1,v2,v3,v4,v2 や v2,v3,v1,v3 • 閉路 v1,v2,v3,v1 など v1 v2 v5 v3 v4
重みつきグラフ 2 3 4 8 1 3 2 5 • 辺に数値(重み)をつけることがある(有向グラフでも). • (重みの例)頂点 辺 重み 駅 路線 距離または運賃 計算機 回線 コスト v1 v2 v5 v3 v4
仮定 有向グラフ 無向グラフ • 多重辺 • 自己閉路 はないと仮定.
グラフのサイズ(大きさ)の尺度 • 頂点数n, 辺の数m nとmの関係は? 完全グラフ(すべての頂点間に辺が存在するグラフ)のとき 無向グラフ 有向グラフ m=n(n-1)/2 m=n(n-1)
グラフのサイズ(つづき) 無向グラフ 有向グラフ 0≦m≦n(n-1)/2 0≦m≦n(n-1) O(n2) O(n2) m∈ O(n2) : 密なグラフ m∈ O(n) : 疎なグラフ 計算量 m∈ O(n2) m∈ O(n) O(m+n) O(n2) O(n) O(n log n) O(n log n) O(n log n) (どちらが効率が良いかはグラフの疎/密に依存)
グラフの表現法 0 1 (*を0にするか 1にするかは任意) 必要な領域 O(n2) O(m+n) 0 1 2 3 0 * 1 1 0 1 0 * 0 1 2 0 1 * 1 3 0 0 0 * 2 3 隣接行列 隣接リスト
深さ優先探索 (depth first search) • 訪問順:1 • 2 • 3 • 5 • 6 • 7 • グラフの探索:すべての頂点をちょうど1回ずつ訪問する方法. A C A E B C 木の辺 D G F E 逆辺 F D G B
寄り道(C言語のデータ型) int x ; (宣言: xはint(整数)型の変数) struct { int height ; int weight ; } y ; (yはheight, weightという二つのメンバーをもつ構造体の変数) y.height = 169 ; y.weight = 58 ; z = y.height – 110 ; height: weight:
C言語(構造体,ポインタ) struct hwdata { int height ; int weight ; } ; struct hwdata y ; でも同じ (hwdataと型名をつけた). int *p ; (pはint型の値を指すポインタ型変数) *p = 315 ; struct hwdata *p ; (*p).height=169 ; (p->height=169 と略記) height: weight: hwdata型 p p
C言語(リスト構造の表現) dest next struct cell { int dest ; %辺の行き先頂点 struct cell *next ; } ; struct cell *p ; p->dest = 15 ; p->next = NULL ; typedef struct cell *elist; と型定義しておくと, struct cell *p の代わりに, elist pと書ける. cell型 p NULL(=0, 何も 指していない)
隣接リスト(C言語の構造体配列) struct { int state ; elist adjlist ; } vertex[1000] ; (vertexはサイズ1000の 配列で,その要素は int型のstateと elist型のadjlistという 二つのメンバーをもつ 構造体) state adjlist 0 1 2 3 NULL visited(=1)または unvisited(=0). 配列vertex
深さ優先探索(抽象的アルゴリズム) { for (i=0; i<n; i++) %nは頂点数 vertex[i].state=unvisited ; %初期化 for (i=0; i<n; i++) %各頂点iから探索開始 if (vertex[i].state==unvisited) dfs(i) ; % 頂点をまだ訪問していなければdfs(i)を行う. } dfs(v) { vertex[v].state=visited ; 頂点vに対する処理 ; for (vを始点とする各辺について) { d=辺の行き先の頂点 ; 辺(v, d)に対する処理 ; if(vertex[d].state==unvisited)dfs(d); } }
深さ優先探索(詳細化) dfs(v) { int d; elist p; vertex[v].state=visited ; 頂点vに対する処理 ; p=vertex[v].adjlist ; while (p!=NULL) { d=p->dest ; 辺(v, d)に対する処理 ; if(vertex[d].state==unvisited) dfs(d); p=p->next ; } }
深さ優先探索(有向グラフ) 訪問順:1 6 2 4 7 3 5 A F A F 上昇辺 B G G E C C D 交差辺 D 木の辺 下降辺 B E
トポロジカルソート(有向グラフ) • 道v1→v2→...→vn(v1≠vn)があるとき,v1→+ vnと書く. • DAG (directed acyclic graph): 閉路をもたない有向グラフ. • トポロジカルソート問題:与えられたDAGの全頂点を,関係→+に矛盾しないように並べよ. u1 u2 u3 u4 ... un と並べたとき, ui→+ uj ならば i<j でなければならない.(ujがuiより前に現れてはならない.) ×
トポロジカルソート(つづき) v1 → v2 ← v5 v3 → v4 v1 → v5 → v2 → v3 → v4 v1 v2 v5 v3 v4 トポロジカルソート ではない. トポロジカルソート
③ ④ ① ② トポロジカルソート(アルゴリズム) • 3 • 2 • 1 v1 v2 Step1 深さ優先探索をし, 帰りがけに番号をふる. Step2 その番号の大きい 順に頂点を並べる. v1 → v5 → v2 → v3 → v4 ji ... uv ... ( j>i) と並んだとき,v →+ uとはならない.なぜなら,もしv →+ uならばvからuへの道があるので,vに番号づけする前にuに番号づけするはずだから. v5 v3 v4 5 4 3 2 1
連結性 • 無向グラフが連結:任意の2頂点間に道があること. 連結 連結でない • 有向グラフが強連結:任意の2頂点 u, v について,uからvへの道があること. 強連結 強連結でない
2重連結性 (biconnected) • 無向グラフが2重連結:どの1頂点を取り除いても,残ったグラフが連結であること. • 関節点:それを取り除くと連結でなくなるような頂点.(関節点をもたないグラフは2重連結.よって,関節点の検出法があれば2重連結かどうかも分かる.) • 2重連結成分:2重連結であるような極大な部分グラフ. 関節点
関節点の検出アルゴリズム • 根頂点Aが関節点である⇔ Aが子頂点を2つ以上もつ A A
B D C B F G D E E G C H H F 関節点の検出(つづき) ↑1 ↑1 ↑ 3 ↑ 1 ↑ 1 ↑ 5 ↑ 5 1 2 3 4 5 6 7 • 根頂点以外の場合 • 深さ優先探索を行う. • 1, 2, ... : 行きがけに番号付け • (順序数) • ↑1, ↑3, ... : 木の辺を下向きに0回 • 以上たどった後,逆辺を0または1回 • たどって至る頂点の順序数の最小値
関節点の検出(正当性) 根頂点でないAが関節点である⇔ 深さ優先探索木中,Aの下の部分木で,Aの祖先への逆辺をもたないものが存在する. (証明)(⇒を証明する.逆も同様に証明できる.)対偶を背理法により証明する.Aのどの部分木からも,Aのある祖先への逆辺があるとし,Aが関節点であると仮定する.あるB, Cが存在して,BC間の道は全てAを通る.
関節点の検出(正当性つづき) • BがAの祖先,CがAの子孫のとき. 仮定より右のような逆辺がある.よってAは関節点でない.∴矛盾. B A C :道を表す
関節点の検出(正当性つづき) • B, CがAの子孫のとき. 仮定より右のような逆辺がある.よってAは関節点でない.∴矛盾. A B C
有向グラフの強連結成分 • 強連結であるような極大な部分グラフ. 縮約(簡約)グラフ
強連結成分を求めるアルゴリズム ↑1 ↑1 ↑7 ↑1 ↑3 ↑9 ↑1 ↑5 ↑5 • 深さ優先探索を行い,各頂点vに,順序数seq(v)と lowlink(v)をふる. lowlink(v) : 木の辺を下向きに0回以上たどった後,同じ強連結成分内の上昇辺または交差辺を0または1回たどって至る頂点の順序数の最小値. 1 2 8 3 7 9 4 5 6 ↑1: lowlink 3 :順序数
アルゴリズムの正当性 頂点vが強連結成分の根⇔lowlink(v)=seq(v) (証明)(⇒)lowlink(v)≠seq(v)と仮定. lowlink(v)<seq(v)である.seq(w)=lowlink(v) とすると, どちらの場合も, vは強連結成分 の根ではない. W W V V
アルゴリズムの正当性(つづき) (⇐)vが強連結成分の根ではないと仮定.vから強連結成分の根への道が存在する.その道上でvの子孫でない最初の頂点をwとすると, lowlink(v)≦seq(w)<seq(v). w v
強連結成分(アルゴリズムの概要) ↑1 ↑1 ↑7 ↑1 ↑3 ↑9 ↑1 ↑5 ↑5 1 12345678 1 2 8 12345678 3 7 9 123456789 1234 4 5 1234 123456 強連結成分 6 123456 lowlink(v)=seq(v)となったら スタックをvまでポップ
強連結成分(抽象的アルゴリズム) struct { int seq ; %序数を記憶 elist adjlist ; %隣接リスト } vertex[1000] ; %入力グラフ int count, sp, i, stack[1000] ; for (i=0; i<n; i++) vertex[i].seq=0; %初期化 count=0 ; sp=-1 ; for (i=0; i<n; i++) if (vertex[i].seq==0)%頂点iをまだ訪問していない scc(i) ;
強連結成分(アルゴリズム,つづき) scc(v) { % lowlink(v)を計算しながら, % 強連結成分が検出されたら出力 count++; vertex[v].seq=count; min=count; sp++; stack[sp]=v; for (vを始点とする各辺(v,d)について) { if (まだdを訪問していない) m=scc(d); else m=vertex[d].seq ; %(v,d)は上昇辺か交差辺 if (m<min) min=m ; if (min==vertex[v].seq) %vは強連結成分の根 スタックstackを先頭からvまでポップしながら出力; return(min) ; } }
最短経路 (shortest path) 1 2 5 3 6 vからwへの最短経路:vからwへの道で辺 の重み和が最小のもの(複数存在することも) v1からv3への道: v1 v2 v3 4 v1 v3 5 v1 v4 v3 8 v1からv3への最短経路:v1 v2 v3 v1 v2 v4 v3
Dijkstraのアルゴリズム(概要) 出発点sからの最短経路が求まっている頂点集合をV, 求まっていない頂点集合をUとする. • V=空集合; U=全頂点集合; s.distance=0 ; s以外の頂点vについてv.distance=∞と初期化; • U=空集合となるまで以下を繰り返す; • Uからdistanceが最小の頂点pを選びVに移す; • Uに属する頂点xで辺p→xが存在するものについて,x.distance>p.distance+(p→xの重み)なら,x.distanceをp.distance+(p→xの重み)に変更する; 6 6 3 3 ∞ 5 5 8 2 2 V V U U 9 7
Dijkstraのアルゴリズム(実行例) (辺の重みは,すべて正か0とする) U V 1 1 3 0 ∞ 3 0 1 5 5 2 3 2 3 5 3 ∞ ∞ 5 3 5 ∞ 2 2 3 ∞ ∞ 3 2 5 1 1 1 1 3 0 1 5 3 0 1 5 2 3 2 3 5 3 4 ∞ 5 3 4 ∞ 2 2 3 2 4 3 2 3 1 1
Dijkstraのアルゴリズム(つづき) 1 1 3 0 1 5 3 0 1 5 2 3 2 3 5 3 4 6 2 5 3 4 6 2 3 2 3 3 1 2 3 1 1 3 0 1 5 2 3 5 3 4 6 2 3 2 3 1
Dijkstraのアルゴリズム(正当性) V U (性質)V内の各頂点wには,V内の頂点のみを経由するsからwへの最短経路が求まっていると仮定する.アルゴリズムが頂点pを選んだとする.sからpへの最短経路で,V内の頂点だけを経由するものがある. p s(出発点)
Dijkstra(正当性,つづき) V U p (証明)背理法.sからpへの最短経路はすべてU内の頂点を通るとする.最初に訪れるU内の頂点をqとする.上の図で,重み和について, sqp < sp qp ≧ 0 より, sq < sp これはアルゴリズムがpを選んだことに矛盾. (重み≧0の仮定が本質的.) s(出発点) q
Dijkstraのアルゴリズム(データ構造) struct { int state ; int disance ; %始点からの重み和を記憶 elist adjlist ; } vertex[1000] ; %入力グラフ struct cell { int dest ; %辺の行き先の頂点 int weight ; %辺の重み struct cell *next } ;%隣接リストで使う辺を表す構造体
Dijkstraのアルゴリズム(詳細化) dijkstra(s) { % sは出発点 for (i=0; i<n; i++) { vertex[i].state=unvisited ; vertex[i].distance=∞ ; } vertex[s].distance=0 ; % 出発点sからs自身への重み和は0
Dijkstraのアルゴリズム(つづき) for (step=0; step<n; step++) { min=∞ ; for (i=0; i<n; i++) { if (vertex[i].state==unvisited && vertex[i].distance<min) { p=i; min=vertex[i].distance; } } if (min==∞) error ; vertex[p].state=visited ; for(e=vertex[p].adjlist;e!=NULL;e=e->next){ x=e->dest; vertex[x].distace =min(vertex[x].distance, vertex[p].distance+(e->weight)) ; } } }
Dijkstraのアルゴリズム(計算量) • 最初のfor文 n回 • 2つめのfor文 n回 • 内側の2つのfor文 いずれもn回 • よって全体で,O(n2). • ヒープを使うとO((m+n)log n)にできる.疎なグラフだとこの方が早い. 補足:前ページで, min(x,y) { if (x>=y) return(x) ; else return(y) ; }
Greedy(グリーディ)アルゴリズム • 局所的に(その都度)最適なものを選択するような戦略. • 全体として最適解が得られるとは限らない(例:荷物の箱づめ,ゲーム). • Dijkstraのアルゴリズムは,グリーディアルゴリズムで全体の最適解を求めることのできる典型例.
Floydの方法 • 全頂点間の最短経路を求める. • ak[i,j] : 頂点0,1,...,kだけを通って,頂点iからjへ至る道の最短長. • 特に,a0[i,j]: 辺 i→j の重み.ただし,i=jのとき0, 辺i→jが存在しないとき∞. 考察 • an-1[i,j]は,iからjへの最短経路長(重み和)となる. • ak-1[*,*]からak[*,*]を求める方法が見つかれば,a0[*,*]から順に,an-1[*,*]を求めることができる.
Floydのアルゴリズム(アイディア) ak-1[i,j] ak[i,j]=min(ak-1[i,k]+ak-1[k,j], ak-1[i,j]) i j どちらか短い方がak[i,j] つまり, ak-1[k,j] ak-1[i,k] k
Floydのアルゴリズム(詳細化) { for (i=0; i<n; i++) for (j=0; j<n; j++) a[i,j]=辺i→jの重み; %i=jのとき0, 辺i→jがないとき∞ for (k=0; k<n; k++) for (i=0; i<n; i++) for (j=0; j<n; j++) a[i,j]=min(a[i,k]+a[k,j], a[i,j]); } 時間計算量 O(n3)
Floydのアルゴリズム(メモリ管理) 前ページのアルゴリズム:一つの配列で,a0[*,*], a1[*,*], ... を重ね書きしている. → 重ね書きして大丈夫か?
Floydのアルゴリズム(つづき) ak[i,k]=min(ak-1[i,k]+ak-1[k,k], ak-1[i,k]) =ak-1[i,k].同様に,ak[k,j]=ak-1[k,j]. よって, の値を使ってもOK! ak[i,j]=min(ak-1[i,k]+ak-1[k,j], ak-1[i,j]) 0 1 2 3 4 0 1 2 3 4 5 1 6 7 8 9 10 2 11 12 13 14 15 3 16 . . . 4 :計算済 を求める のに, k=1のとき, を使う.