THE UNIVERSITY OF TOKYO 数理情報工学演習第一C プログラミング演習 (第9回 ) 2014/06/09 DEPARTMENT OF MATHEMATICAL INFORMATICS 1 THE UNIVERSITY OF TOKYO 今日の内容: 最短路問題(ダイクストラ法) 課題: ダイクストラ法 最終レポート: 行列積の実行時間を調べる 2 THE UNIVERSITY OF TOKYO 最短路問題とダイクストラ (Dijkstra) 法 有向グラフ G = (V, E) を考える V: 節点集合, 節点数 n E: 枝集合, 枝数 m グラフ G の各枝 (i,j) E は長さ aij を持つ ある節点 s V から別の節点 t V への路の中で, 最も長さの短いものを見つける問題を最短路問題という 15 2 50 30 20 10 1 5 80 3 4 3 15 THE UNIVERSITY OF TOKYO 下のネットワークで節点 1 を s, 節点 5 を t とすると,s から t へは 1245, 1235,1345, 12345,135 の5つの路がある 路の長さはそれぞれ 95, 85, 120, 110, 95 なので, 最短路は 1235 である 大規模なネットワークでは全ての路を数え上げる方法は実用的ではない 15 2 50 30 20 10 1 5 80 4 4 3 15 THE UNIVERSITY OF TOKYO 最適性の原理 節点 s から t への最短路 P が得られているとする 路 P に含まれる節点を1つ任意に選び, r とする 路 P は s から r までの部分 P1 と, r から t への 部分 P2 に分割できる.このとき,P1 は s から r への最短路で,P2 は r から t への最短路となる もし P1 より短い s から r への路 P1’が存在するなら, P1’と P2 をつないだ路は P2 より短くなる このような性質を最適性の原理と呼ぶ P1 s 5 r P1’ P2 t THE UNIVERSITY OF TOKYO ある節点 s からネットワークの他の全節点への最短路を求める問題を考える ダイクストラ法は,枝の長さに関する非負条件 aij 0 ((i,j) E) の仮定の下で,節点 s から各節点 i V への最短路の長さ の上限値 d(i) を更新していく反復アルゴリズム 計算の途中では,d(i) の値が s から i への真の最短路の長さに等しいことがわかっ ている節点が存在する.そのような節点の集合を S で表す S 6 は S の補集合 V S を表す THE UNIVERSITY OF TOKYO ダイクストラ (Dijkstra) 法 (0) S := , とおく S := V, d(s) := 0, d(i) := (i V {s}) S = V なら計算終了.そうでないなら,d (v) である節点 v S を選ぶ (2) min d (i) | i S S : S {v}, S : S {v} とし,(v,j) E かつ j S であるような全ての枝 (v,j) に対して d(j) > d(v) + avj ならば d(j) = d(v) + avj, p(j) := v とする.ステップ (1) に戻る p(i) は,s から i までの最短路において i の直前に 位置する節点を示すために用いられる 7 THE UNIVERSITY OF TOKYO [初期化] (0) S , S {1,2,3,4,5} 50 1 d(1)=0 8 d(2)= 2 20 80 d(4)= 4 15 30 10 3 d(3)= d(5)= 5 15 THE UNIVERSITY OF TOKYO [反復1] (1) (2) S {1,2,3,4,5} min d (i ) | i S 0 より v = 1 S {1}, S {2,3,4,5} d(2) = > d(1) + a12 =50 より d(2) = 50, p(2) = 1 d(3) = > d(1) + a13 =80 より d(3) = 80, p(3) = 1 50 1 d(1)=0 9 d(2)=50 2 20 80 d(4)= 4 15 30 10 3 d(3)=80 d(5)= 5 15 THE UNIVERSITY OF TOKYO [反復2] (1) S {2,3,4,5} min d (i) | i S 50 より v = 2 (2) S {1,2}, S {3,4,5} d(3) = 80 > d(2) + a23 =70 より d(3) = 70, p(3) = 2 d(4) = > d(2) + a24 =65 より d(4) = 65, p(4) = 2 50 1 d(1)=0 10 d(2)=50 2 20 80 d(4)=65 4 15 30 10 3 d(3)=70 d(5)= 5 15 THE UNIVERSITY OF TOKYO [反復3] (1) (2) S {3,4,5} min d (i) | i S 65 より v = 4 S {1,2,4}, S {3,5} d(5) = > d(4) + a45 =95 より d(5) = 95, p(5) = 4 50 1 d(1)=0 11 d(2)=50 2 20 80 d(4)=65 4 15 30 10 3 d(3)=70 5 d(5)=95 15 THE UNIVERSITY OF TOKYO [反復4] (1) (2) S {3,5} min d (i) | i S 70 より v = 3 S {1,2,3,4}, S {5} d(5) = 95 > d(3) + a35 =85 より d(5) = 85, p(5) = 3 50 1 d(1)=0 12 d(2)=50 2 20 80 d(4)=65 4 15 30 10 3 d(3)=70 5 d(5)=85 15 THE UNIVERSITY OF TOKYO [反復5] (1) (2) S {5} 自動的に v = 5 S {1,2,3,4,5}, S [反復6] (1) S = V なので終了 50 1 d(1)=0 13 d(2)=50 2 20 80 d(4)=65 4 15 30 10 3 d(3)=70 5 d(5)=85 15 THE UNIVERSITY OF TOKYO アルゴリズムが終了したときの d(i) は,節点 1 から i への最短路の長さを与えている 枝の集合 {(p(i),i)} が各節点への最短路を表す これを最短路木と呼ぶ 50 1 d(1)=0 14 d(2)=50 2 20 80 d(4)=65 4 15 30 10 3 d(3)=70 d(5)=85 5 15 THE UNIVERSITY OF TOKYO ダイクストラ法の実装 • d (v) min d (i ) | i S d(v) をヒープに入れる である点 v を高速に求めるために • しかし,「d(j) > d(v) + avj ならば d(j) = d(v) + avj」を行うには, ヒープ中の d(j) を更新し,ヒープを作り直す (heapify) 必要がある • d(j) は最小値ではないので,ヒープの配列のどこにあるか分からない • 解決法 1. 点番号 j からヒープ中の位置を求める配列を追加する 2. ヒープ中の d(j) を更新しないようにアルゴリズムを変更する 15 THE UNIVERSITY OF TOKYO 解決法1 • ヒープに格納する値は (i, d(i)) で,1 i n で i は相異なるとする • ヒープの配列 A[1],…,A[n] で A[j] = (i, d(i)) のとき, INV[i] = j とする (1 i n ) • (i, d(i)) がヒープ内にないときは INV[i] = -1 などとする • ヒープ内で要素を入れ替えたときは INV の値も更新する 16 THE UNIVERSITY OF TOKYO 解決法2 • d の値を更新するとき,古い値はそのままヒープに入れておき, 新しい値も別の要素として追加する • d(j) = d(v) + avj と更新するとき,ヒープには (j, d(j)) が入っている これの他に (j, d(v) + avj) もヒープに入れる • ヒープ内の j には重複がある • ヒープのサイズは枝数だけ必要 (解決法1では点数) • ヒープから最小値を取り出すときに古い d の値が出ることがあるので, そのときは無視する(後述) 17 THE UNIVERSITY OF TOKYO 初期状態 H = {(1, 0)} ヒープから (1,0) を取り出す 50 1 d(1)=0 18 d(2)= 2 20 d(4)= 4 15 30 10 5 80 3 d(3)= d(5)= 15 THE UNIVERSITY OF TOKYO 1から出ている枝で行ける点 2,3 について d を更新 (2,50), (3,80) をヒープに入れる 50 1 d(1)=0 19 d(2)=50 2 20 d(4)= 4 15 30 10 5 80 3 d(3)=80 d(5)= 15 THE UNIVERSITY OF TOKYO H = {(2,50), (3,80)} ヒープから (2,50) を取り出す 2から出ている枝で行ける点 3,4 について d を更新 (3,70), (4,65) をヒープに入れる 50 1 d(1)=0 20 d(2)=50 2 20 d(4)=65 4 15 30 10 5 80 3 d(3)=70 d(5)= 15 THE UNIVERSITY OF TOKYO H = {(3,80), (3,70), (4,65)} (3が重複していることに注意) ヒープから (4,65) を取り出す 4から出ている枝で行ける点 5 について d を更新 (5,95) をヒープに入れる 50 1 d(1)=0 21 d(2)=50 2 20 d(4)=65 4 15 30 10 5 80 3 d(3)=70 d(5)= 15 THE UNIVERSITY OF TOKYO H = {(3,80), (3,70), (5,95) } ヒープから (3,70) を取り出す 3から出ている枝で行ける点 4,5 について d を更新 (5のみ) (5,85) をヒープに入れる 50 1 d(1)=0 22 d(2)=50 2 20 80 d(4)=65 4 15 30 10 3 d(3)=70 5 d(5)=85 15 THE UNIVERSITY OF TOKYO H = {(3,80), (5,95), (5,85)} ヒープから (3,80) を取り出す しかし d(3) = 70 < 80 なので 3 への最短路はすでに求まっている 50 1 d(1)=0 23 d(2)=50 2 20 80 d(4)=65 4 15 30 10 3 d(3)=70 5 d(5)=85 15 THE UNIVERSITY OF TOKYO H = {(5,95), (5,85)} ヒープから (5,85) を取り出す 5 から出ている枝は無いので何もしない 50 1 d(1)=0 24 d(2)=50 2 20 80 d(4)=65 4 15 30 10 3 d(3)=70 5 d(5)=85 15 THE UNIVERSITY OF TOKYO H = {(5,95)} ヒープから (5,95) を取り出す しかし d(5) = 85 < 95 なので 5 への最短路はすでに求まっている 50 1 d(1)=0 25 d(2)=50 2 20 80 d(4)=65 4 15 30 10 3 d(3)=70 5 d(5)=85 15 THE UNIVERSITY OF TOKYO H = {} ヒープが空なので終了 50 1 d(1)=0 26 d(2)=50 2 20 80 d(4)=65 4 15 30 10 3 d(3)=70 5 d(5)=85 15 THE UNIVERSITY OF TOKYO 課題 • ヒープを用いてダイクストラ法を実装する • 解決法1,2のどちらでも良い • 6/13(金) の正午までに提出 • 宛先:[email protected] 27 THE UNIVERSITY OF TOKYO 最終レポート • 疎行列の2乗を計算し,実行時間を測定する • 入力は Matrix Market のデータを用いる http://math.nist.gov/MatrixMarket/matrices.html – 形式を変換したものを用意しています http://researchmap.jp/mub2jpgrp-1587/#_1587 • ファイルから行列を読み込み,その2乗を計算しファイルに出力する – ./a.out (入力) (出力) • 実行時間は time コマンドで測る – time ./a.out bcspwr01.txt bcspwr01-2.txt 28 THE UNIVERSITY OF TOKYO • 実行時間のグラフを作る • 行列のデータ構造や乗算アルゴリズムは複数用いてそれらの違いを調べる • それぞれのデータ構造,アルゴリズムの概略も説明する • 締切:6/27(金) 正午 • 宛先:[email protected] 29 THE UNIVERSITY OF TOKYO 前回の補足 • 汎用的なヒープの実装として,2つ紹介した – 方法1: ハッシュ関数のときと同じように,外部の比較関数を使う – 方法2: マクロを使う • 実装例は以下のページからダウンロードできます – http://researchmap.jp/mu84xo6ez-1587/#_1587 30 THE UNIVERSITY OF TOKYO 方法1:外部の関数を用いる方法 • ヒープの構造体は heap.h で次のように定義する typedef struct heapdata_ heapdata; typedef struct heap_ { int max_size; int size; heapdata *A; // void *A; としても良い } HEAP; • 外部関数として,次のものを使う // A[i] > A[j] なら1を返す外部関数 int heap_greater(HEAP *H, int i, int j); // A[i] と A[j] を入れ替える外部関数 void heap_swap(HEAP *H, int i, int j); 31 THE UNIVERSITY OF TOKYO • 配列の中身は整数,実数,構造体など様々でサイズも違うので, heap.c 内でデータの移動などは実現しにくい • HEAPの構造体では,配列 A のポインタを格納しているだけで, heap.c では配列の中身には触れないことにする • heap_extract_max() や heap_insert() は heapdata を入出力にしていた ので変更する – extract_max は最小値を配列の最後に移動するだけ – insert は,挿入する値を配列の最後に書き込んでから呼び出す 32 THE UNIVERSITY OF TOKYO 方法2: マクロを使う • heap.h の最初に,データの宣言と比較方法を書く typedef struct { int x; double y; } heapdata; #define GREATER(a, b) ((a).x > (b).x) • それ以外の部分はデータの型に依存せずに書ける – (ただし型を変えるときは再コンパイルが必要) • 方法1,2 でも,1つのソース内で複数種類のヒープを用いることはできない 33 THE UNIVERSITY OF TOKYO 方法3: C++のテンプレート的手法 • ヒープに格納するデータごとに異なる構造体,プログラムを用意する typedef struct { int max_size; int size; int *A; } heap_int; heap_int *heap_build_int(int n, int *A, int max_size); typedef struct { int max_size; int size; string *A; } heap_string; heap_string *heap_build_string(int n, string *A, int max_size); • 毎回書くのは大変なのでマクロを活用する 34 THE UNIVERSITY OF TOKYO • ヒープを使いたいソースファイルに次のように書く #define TYPE int #define ___(s) s ## _int #include "heap.h" • heap.h には次のように書く typedef struct { int max_size; int size; TYPE *A; } ___(HEAP); • すると,heap.h に次のように書いたのと同じになる typedef struct { int max_size; int size; int *A; } heap_int; 35 THE UNIVERSITY OF TOKYO • ヒープの処理をする関数自体を heap.h 内に書く (プロトタイプ宣言では無く) static TYPE ___(heap_extract_max)(___(HEAP) *H) { TYPE MAX, *A; A = H->A; if (H->size < 1) { printf("heap_extract_max: ERROR∖n"); exit(1); } MAX = A[1]; A[1] = A[H->size]; H->size = H->size - 1; ___(heap_heapify)(H,1); return MAX; } 36 THE UNIVERSITY OF TOKYO
© Copyright 2024 ExpyDoc