競技プログラミング練習会
2015 Normal
第4回
2015/05/08
長嶺英朗(ID:hnagamin)
●
プライオリティキュー
●Union-Find 木
●
ダイクストラ法
●
最小全域木問題
●
プリム法
●
クラスカル法
目次
プライオリティキュー
●
キューは以下のことができるデータ構造でした
思い出してください
● push
データを入れる
● pop
最後に入れたデータを取り出す
push
pop
●
プライオリティキューでは以下のことができます
● “Priority” = 優先度
プライオリティキュー
● push
データを入れる
● pop
今までに入れたデータのうち、
最も優先度の高いものを1つ
取り出す
10
1
8
push 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
2
3
10
20
60
15
30
頂点1 0: 20⇒
頂点1 1: 0⇒
頂点1 2: 35⇒
頂点1 3: 65⇒
単一始点最短経路問題の例
復習: ベルマンフォード法
●単一始点最短経路問題を解くアルゴリズム
●各頂点への暫定的な最短経路を保存しておいて、
各辺を使うことで最短経路を更新できるか調べる
●計算量 O(VE)
1
0
2
3
10
20
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 > d2
+ 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
2
3
10
50
10
15
30
S
T
コスト0
コスト10
ダイクストラ法のアイデア
● Sの頂点からTの頂点へ伸びる辺の集合E'をとる
1
0
2
3
10
50
10
15
30
S
T
コスト0
コスト10
ダイクストラ法のアイデア
● Sの頂点の最短距離は確定しているので、E'の各終
点(つまりTの幾つかの頂点)に対して暫定的な最短
距離が計算できる
1
0
2
3
10
50
10
15
30
S
T
コスト0
コスト10
コスト≦10+30
コスト≦0+50
ダイクストラ法のアイデア
●
ここで計算された暫定的な最短距離のうち最小の
ものをとると、これは実際最短距離である
1
0
2
3
10
50
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
2
3
10
50
10
15
30
S
T
コスト0
コスト10
コスト≦10+30
コスト≦0+50
ダイクストラ法のアイデア
● 以上から新たな頂点の最短距離が確定し、Sが拡
大する。このようにして1個ずつ最短距離を確定させ
ていく
1
0
2
3
10
50
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
A
1
2
3
4
5
6
7
8
9
プリム法の動作
● SからTに伸びる辺の最小
コストは2です
● よってこの辺と頂点Aを追
加します
D
F
C
B
E
A
1
2
3
4
5
6
7
8
9
プリム法の動作
● SからTに伸びる辺の最小
コストは1です
● よってこの辺と頂点Eを追
加します
D
F
C
B
E
A
1
2
3
4
5
6
7
8
9
プリム法の動作
● SからTに伸びる辺の最小
コストは5です
● よってこの辺と頂点Bを追
加します
D
F
C
B
E
A
1
2
3
4
5
6
7
8
9
プリム法の動作
● SからTに伸びる辺の最小
コストは3です
● よってこの辺と頂点Fを追
加します
D
F
C
B
E
A
1
2
3
4
5
6
7
8
9
プリム法の動作
● SからTに伸びる辺の最小
コストは3です
● よってこの辺と頂点Dを追
加します
●
これで最小全域木が完成
しました
D
F
C
B
E
A
1
2
3
4
5
6
7
8
9
プリム法の計算量
●
コストの小ささを優先度として辺を格納するプライ
オリティキューを利用するとO(E log E)となる
●
実装については蟻本を読め
クラスカル法
クラスカル法
●
最小全域木問題を解く
●
辺をコストが小さい順に見ていき、閉路ができない
限り追加していく
● O(E log E)
クラスカル法の動作
●
辺のコストが小さい順に
見ていきます
D
F
C
B
E
A
1
2
3
4
5
6
7
8
9
クラスカル法の動作
● コスト1の辺が最小です
● AとEは繋がっていないので、
この辺を使います
D
F
C
B
E
A
1
2
3
4
5
6
7
8
9
クラスカル法の動作
● コスト2の辺が最小です
● AとCは繋がっていないので、
この辺を使います
D
F
C
B
E
A
1
2
3
4
5
6
7
8
9
クラスカル法の動作
● コスト3の辺が最小です
● FとBは繋がっていないので、
この辺を使います
D
F
C
B
E
A
1
2
3
4
5
6
7
8
9
クラスカル法の動作
● コスト4の辺が最小です
● CとEは既に繋がっているので、
この辺は使いません
D
F
C
B
E
A
1
2
3
4
5
6
7
8
9
クラスカル法の動作
● コスト5の辺が最小です
● BとEは繋がっていないので、
この辺を使います
D
F
C
B
E
A
1
2
3
5
6
7
8
9
クラスカル法の動作
● コスト6の辺が最小です
● CとDは繋がっていないので、
この辺を使います
D
F
C
B
E
A
1
2
3
5
6
7
8
9
クラスカル法の動作
● 7,8,9の辺は全て使いません
●
これで最小全域木が完成します
D
F
C
B
E
A
1
2
3
5
6
クラスカル法の動作
●
このようなアルゴリズムで最小全域
木が求まることは、プリム法と同様
の形式で示すことができます
D
F
C
B
E
A
1
2
3
5
6
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)
α (2
2
2
65536
−3)=4

競技プログラミング練習会2015 Normal 第4回