Post on 06-Aug-2015
競技プログラミング練習会2015 Normal
第4回
2015/05/08長嶺英朗(ID:hnagamin)
●プライオリティキュー●Union-Find 木●ダイクストラ法●最小全域木問題
●プリム法●クラスカル法
目次
プライオリティキュー
● キューは以下のことができるデータ構造でした
思い出してください
●pushデータを入れる
●pop最後に入れたデータを取り出す
push
pop
● プライオリティキューでは以下のことができます● “Priority” = 優先度
プライオリティキュー
●pushデータを入れる
●pop今までに入れたデータのうち、最も優先度の高いものを1つ取り出す
10
1
8push pop
8
2
42
23
42
プライオリティキュー
● 先の2操作のほかに追加の操作をサポートするものもあります– 最も小さい要素の参照(peek)
– ある要素の優先度を下げる(decrease)
– 2つのプライオリティキューを併合する(meld)
実装
● いろいろあります– 赤黒木(平衡二分探索木) push: O(log n) pop: O(log n)
– 二分ヒープ push: O(log n) pop: O(log n)
– フィボナッチヒープ push: O(1) pop: O(log n)
– Leftist Heap push: O(log n) pop: O(log n)● 各データ構造に長所・短所があるので目的に応じ
て使い分けてください– (時間の都合上、長所・短所については説明しません)
C++におけるプライオリティキュー
● 朗報です● C++の標準ライブラリはプライオリティキューをサ
ポートしています
#include<queue>using namespace std;
priority_queue<int> q;// 入れた要素が大きい順に出てくる
C++におけるプライオリティキュー
● 朗報です● C++の標準ライブラリはプライオリティキューをサ
ポートしています
#include<queue>#include<vector>using namespace std;
priority_queue<int, vector<int>, greater<int> > q;// 入れた要素が小さい順に出てくる
ダイクストラ法
復習: 最短経路問題
●頂点間の距離を求める問題●全点対最短経路問題…全ての頂点間の距離
●単一始点最短経路問題…ある点から各点への距離
1
0
23
1020
60
15
30頂点1 0: 20⇒頂点1 1: 0⇒頂点1 2: 35⇒頂点1 3: 65⇒
単一始点最短経路問題の例
復習: ベルマンフォード法
●単一始点最短経路問題を解くアルゴリズム
●各頂点への暫定的な最短経路を保存しておいて、各辺を使うことで最短経路を更新できるか調べる
●計算量 O(VE)1
0
23
1020
60
15
30
頂点i 0 1 2 3
距離di 20 0 35 65
辺のリスト(0,1,10), (0,2,15), (1,0, 20), (1,2,60), (2,3,30)
d3 = 90 > d
2 + 30 = 65
(更新する)
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=GRL_1_A
GRL_1: Single Source Shortest Path
● 単一始点最短経路問題● 0 (≦ 各辺のコスト) 10≦ 4 ● V 10≦ 5
● E 5 × 10≦ 5
● ベルマンフォード法では解けない– VE > 1010
ダイクストラ法
● 単一始点最短経路問題を解く● 負のコストの辺が存在しないとき使える● 計算量O(E log V)
– 辺の探索方法によって変わる
ダイクストラ法のアイデア
● 負の辺が無いことを仮定しています
ダイクストラ法のアイデア
● 最短距離が確定した頂点の集合Sと確定していない頂点の集合Tを考える
1
0
23
1050
10
15
30
S
T
コスト0
コスト10
ダイクストラ法のアイデア
● Sの頂点からTの頂点へ伸びる辺の集合E'をとる
1
0
23
1050
10
15
30
S
T
コスト0
コスト10
ダイクストラ法のアイデア
● Sの頂点の最短距離は確定しているので、E'の各終点(つまりTの幾つかの頂点)に対して暫定的な最短距離が計算できる
1
0
23
1050
10
15
30
S
T
コスト0
コスト10コスト≦10+30
コスト≦0+50
ダイクストラ法のアイデア
● ここで計算された暫定的な最短距離のうち最小のものをとると、これは実際最短距離である
1
0
23
1050
10
15
30
S
T
コスト0
コスト10コスト≦10+30
コスト≦0+50
ダイクストラ法のアイデア
● ここで計算された暫定的な最短距離のうち最小のものをとると、これは実際最短距離である
3
S
T
仮にこのようなより短い経路が存在したと仮定すると、
コスト≦10+30
ダイクストラ法のアイデア
● ここで計算された暫定的な最短距離のうち最小のものをとると、これは実際最短距離である
3
S
T
この経路のコストは非負なので、
コスト≦10+30
ダイクストラ法のアイデア
● ここで計算された暫定的な最短距離のうち最小のものをとると、これは実際最短距離である
3
S
T
この頂点の最短距離は40より小さくないといけない。これは最小のものをとったと言う仮定に矛盾する。
よってそのような経路は存在しない。コスト≦10+30
ダイクストラ法のアイデア
● ここで計算された暫定的な最短距離のうち最小のものをとると、これは実際最短距離である
1
0
23
1050
10
15
30
S
T
コスト0
コスト10コスト≦10+30
コスト≦0+50
ダイクストラ法のアイデア
● 以上から新たな頂点の最短距離が確定し、Sが拡大する。このようにして1個ずつ最短距離を確定させていく
1
0
23
1050
10
15
30
S
T
コスト0
コスト10
ダイクストラ法
● 暫定的最短距離が最小の頂点を探すときに「頂点を格納するプライオリティキュー」を使うと計算量を落とすことができる
● push回数はO(E)● キューの要素数はO(V)→挿入コストはO(log V)● 全体の計算量はO(E log V)● 実装については蟻本を読め(完)
最小全域木問題
木
● 以下、無向グラフのみを考えます● 連結かつ閉路のないグラフを木といいます
– 連結…どの2頂点間にも道がある– 閉路…始点と頂点が同じ道
木
木じゃない木じゃない
最小全域木問題
● 辺に重みがある連結グラフが与えられる● この中の辺のいくつかを使って、与えられたグラフ
を木にしたい● 使用する辺の重みの合計を最小化せよ
問題例
● いくつかの街が存在する● 街の間に道路を作ってどの街からどの街へも行け
るようにしたい● 道路を作るための費用が予めわかっている● 必要な道路作成費用を最小化せよ
街 ⇔ 頂点道路 ⇔ 辺費用 ⇔ 辺の重み(コスト)
最小全域木問題を解くアルゴリズム
● 今日は2種類のアルゴリズムを紹介します– プリム法– クラスカル法
プリム法
プリム法
● 最小全域木問題を解く● 部分木を広げていくことで最小全域木を得る● 計算量O(E log E)
– E: 辺の本数, V: 頂点の数
– 辺の探索方法によって変わる
プリム法のアイデア
● 用語を定義します● グラフのある部分グラフSが木になっていて、かつS
を部分木として含む最小全域木が存在するとき、Sは「素敵な部分木である」ということにします
S
プリム法のアイデア
● いま、素敵な部分木Sがあるとします
● このときSを使ってより大きい素敵な部分木S'を作ることができます
● 作り方を示します
S
プリム法のアイデア
● Sの頂点以外の頂点の集合をTとする
S
T
プリム法のアイデア
● Sの頂点とTの頂点を結ぶ辺の集合E'をとる
S
T細い辺がE'の要素
プリム法のアイデア
● E'の要素のうちコストが最小の辺eをとる
S
T
e
プリム法のアイデア
● このとき、下のような部分木S'を考えると、S'は素敵な部分木である
S'
プリム法のアイデア
● このとき、下のような部分木S'を考えると、S'は素敵な部分木である
S e
仮定よりSを含む最小全域木が存在しますいま、eを含まない最小全域木が何かひとつ存在したとします
T
プリム法のアイデア
● このとき、下のような部分木S'を考えると、S'は素敵な部分木である
S e
このとき、この部分木にはSとTをつなぐ辺が必ず存在しますこれをe'とします
T
プリム法のアイデア
● このとき、下のような部分木S'を考えると、S'は素敵な部分木である
S e
ここにeを加えてみますすると、閉路が1つできます
T
e'
プリム法のアイデア
● このとき、下のような部分木S'を考えると、S'は素敵な部分木である
S e
e'を消去するとこれはまた木になります
T
プリム法のアイデア
● このとき、下のような部分木S'を考えると、S'は素敵な部分木である
S e
すると、e'のコストは絶対にeのコスト以上なので、これはまた最小全域木になります従って、S'を部分木に持つ最小全域木が少なくともひとつあることになります
T
プリム法のアイデア
● こうして素敵な部分木Sを使ってより大きい素敵な部分木S'を作ることができました
● これを繰り返すことでグラフの頂点全体を使った素敵な部分木ができます。これが最小全域木です。
S'
プリム法の動作
1.頂点を1個適当に取って、頂点が1つの素敵な部分木Sを作る (これは明らかに素敵な部分木ですよね)
2.SからTに伸びる辺のうちコストが最小の辺を1つ選び、その辺およびその辺に繋がっている頂点をSに追加する
3.全ての頂点を覆うまで2を繰り返す
プリム法の動作
● 頂点としてCを選びます– どれを選んでも良い
● S = {C}
T = {A,B,D,E,F}
D
F
C
B
E
A1
2
3
4
56
7
8
9
プリム法の動作
● SからTに伸びる辺の最小コストは2です
● よってこの辺と頂点Aを追加します
D
F
C
B
E
A1
2
3
4
56
7
8
9
プリム法の動作
● SからTに伸びる辺の最小コストは1です
● よってこの辺と頂点Eを追加します
D
F
C
B
E
A1
2
3
4
56
7
8
9
プリム法の動作
● SからTに伸びる辺の最小コストは5です
● よってこの辺と頂点Bを追加します
D
F
C
B
E
A1
2
3
4
56
7
8
9
プリム法の動作
● SからTに伸びる辺の最小コストは3です
● よってこの辺と頂点Fを追加します
D
F
C
B
E
A1
2
3
4
56
7
8
9
プリム法の動作
● SからTに伸びる辺の最小コストは3です
● よってこの辺と頂点Dを追加します
● これで最小全域木が完成しました
D
F
C
B
E
A1
2
3
4
56
7
8
9
プリム法の計算量
● コストの小ささを優先度として辺を格納するプライオリティキューを利用するとO(E log E)となる
● 実装については蟻本を読め
クラスカル法
クラスカル法
● 最小全域木問題を解く● 辺をコストが小さい順に見ていき、閉路ができない限り追加していく
● O(E log E)
クラスカル法の動作
● 辺のコストが小さい順に見ていきます
D
F
C
B
E
A1
2
3
4
56
7
8
9
クラスカル法の動作
● コスト1の辺が最小です
● AとEは繋がっていないので、この辺を使います
D
F
C
B
E
A1
2
3
4
56
7
8
9
クラスカル法の動作
● コスト2の辺が最小です
● AとCは繋がっていないので、この辺を使います
D
F
C
B
E
A1
2
3
4
56
7
8
9
クラスカル法の動作
● コスト3の辺が最小です
● FとBは繋がっていないので、この辺を使います
D
F
C
B
E
A1
2
3
4
56
7
8
9
クラスカル法の動作
● コスト4の辺が最小です
● CとEは既に繋がっているので、この辺は使いません
D
F
C
B
E
A1
2
3
4
56
7
8
9
クラスカル法の動作
● コスト5の辺が最小です
● BとEは繋がっていないので、この辺を使います
D
F
C
B
E
A1
2
3
56
7
8
9
クラスカル法の動作
● コスト6の辺が最小です
● CとDは繋がっていないので、この辺を使います
D
F
C
B
E
A1
2
3
56
7
8
9
クラスカル法の動作
● 7,8,9の辺は全て使いません● これで最小全域木が完成します
D
F
C
B
E
A1
2
3
56
クラスカル法の動作
● このようなアルゴリズムで最小全域木が求まることは、プリム法と同様の形式で示すことができます
D
F
C
B
E
A1
2
3
56
7
8
9
e
クラスカル法の計算量
● Union-Find 木というデータ構造を使うと、繋がってるか否かの判断を効率的に行うことができる– 「繋がっている」「繋がっていない」を「同じ集合に属す」
「属さない」とみる
// ここでUnion-Find 木の話をする
クラスカル法の計算量
● 辺のソートにO(E log E)かかる● 各辺について頂点が繋がっているか判定するのに
O(α(E))かかる
– 全体でO(E・α(E))
– α(n)は逆アッカーマン関数
● よって全体の計算量は
O(E log E + E α(E)) = O(E log E)
α (222 65536
−3)=4