お久しぶりです。まぁじです。
- まえおき
- 実行環境
- 自力実装したUnionFind法
- IntDisjointSetsを使ったUnionFind法
- 自力実装よりIntDisjointSetsの方が早い事例(2023/7/12追記)
- 参考資料
まえおき
今回はDataStructures.jlにあるDisjointSetsについてです。 競技プログラマーならUnion-Find法の方が聞き覚えがあると思います。
日本語でいうと「素集合データ構造」で、木構造を用いて
グループの併合 (union / unite)
それぞれのデータが同じグループに存在するかの判定 (find)
が高速にできます。
詳しいアルゴリズムや応用はこのスライドやこのページを読むとわかりやすいと思います。
実行環境
Juliaのインストールは既に済んでいるものとします。
ちなみに使ってるエディタはVSCodeで、Julia Extensionを入れて使ってます。
julia> versioninfo() Julia Version 1.5.3 Commit 788b2c77c1 (2020-11-09 13:37 UTC) Platform Info: OS: Windows (x86_64-w64-mingw32) CPU: Intel(R) Core(TM) i3-8130U CPU @ 2.20GHz WORD_SIZE: 64 LIBM: libopenlibm LLVM: libLLVM-9.0.1 (ORCJIT, skylake)
自力実装したUnionFind法
あまり複雑なアルゴリズムではないので、20行ほどで書けます。
名前 | 説明 |
---|---|
par | 各要素の親を表す(初期値は自分が親)。 |
siz | 各要素を親とするグループの大きさを表す(初期値は1)。 |
root(x) | xの親を探す。 |
unite!(x,y) | xのノードとyのノードを結合。小さい方が親となる。 |
issame(x,y) | xのノードとyのノードの親が同じかを返す。 |
※ちなみに、慣例上Juliaにおいて破壊的処理は!
を付けることになっているので、結合処理はunite!
になってます。
あとはこれをmain関数内にペタリして処理をするだけです。
par=Vector{Int}([i for i=1:n]) siz=ones(Int,n) function root(x::Int) index=par[x] while par[index]!=index index=par[index] end return index end function unite!(x::Int,y::Int) rx,ry=root(x),root(y) if rx==ry return 0 elseif rx>ry rx,ry=ry,rx end par[ry]=rx siz[rx]+=siz[ry] end function issame(x::Int,y::Int) isequal(root(x),root(y)) end
例:ACL Beginner Contest 「C - Connect Cities」
(Submission #20614886 - ACL Beginner Contest)
自力実装の利点・欠点
DataStructures.jlを使うよりもパッケージの読み込み時間がないので早い
このコード内ですべて完結しているのでバグの原因を見つけやすい
コードが比較的長くなる
サイズMのUnion-Find木を作らなければならないところを、コピペミスしてサイズNでWA
(実体験)
IntDisjointSetsを使ったUnionFind法
DataStructures.jlのインストール
DataStructures.jlがインストールされていない場合、先にPkgを開いてインストールしてください。
コマンド入力時に]
を押すとPkgのコマンドを入力できます。
(@v1.5) Pkg> add "DataStructures"
(Int)DisjointSetsの説明
DataStructures.jl内にはDisjointSetsとIntDisjointSetsが存在します。
DisjointSetsはIntDisjointSetsのラッパーであり、辞書を用いてマッピングしているのでChar型とか数字以外でもUnionFindができるらしいです(必要か?)。
※d=DisjointSets(1:N)
と宣言することで同様に使えます。
名前 | 説明 |
---|---|
IntDisjointSets(N) | 要素数NのUnionFind木を宣言。 |
union!(d,x,y) | xのノードとyのノードを結合。xが親になる。 |
root_union!(d,x,y) | xのノードとyのノードのそれぞれ親を結合。 |
find_root!(d,x) | xのノードの親を返す。 |
in_same_set(d,x,y) | xのノードとyのノードが同じグループにあるかを返す。 |
d.ngroups | 木構造内のグループ数を返す。 |
num_groups(d) | おそらくd.ngroupsと同様。 |
# プログラムの例 using DataStructures uf=IntDisjointSets(5) union!(uf,2,3) @show uf root_union!(uf,1,3) #ここをunion!(uf,1,3)にすると1の親が2になる @show uf @show find_root!(uf,3) @show in_same_set(uf,1,4) @show uf.ngroups @show num_groups(uf)
# 出力。@showに各行が対応 uf = IntDisjointSets{Int64}([1, 2, 2, 4, 5], [0, 1, 0, 0, 0], 4) uf = IntDisjointSets{Int64}([1, 2, 1, 4, 5], [1, 1, 0, 0, 0], 3) find_root!(uf, 3) = 1 in_same_set(uf, 1, 4) = false uf.ngroups = 3 num_groups(uf) = 3
IntDisjointSetsを用いた提出例
https://atcoder.jp/contests/abl/submissions/20614444
DisjointSetsを用いた提出例(こちらではngroupsは使えないっぽいです)
https://atcoder.jp/contests/abl/submissions/20614009
IntDisjointSetsを用いる利点・欠点
コードが割と短く簡潔に書くことができる
効率よく書かれている(はず)のでコード自体は早い
root_union!やnum_groupsなど様々な種類の関数があるため使いやすい
DataStructures.jlをインポートする時間がかかるのでAtCoderでは少し不利
自力実装よりIntDisjointSetsの方が早い事例(2023/7/12追記)
- ABC229-E (E - Graph Destruction)
i=1,2,…,N について、頂点 i まで消した時にグラフはいくつの連結成分に分かれていますか? という問題。逆から見てUnion-Findの典型。
自力実装の提出(TLE): https://atcoder.jp/contests/abc229/submissions/43509722
IntDisjointSetsを使った提出(AC, 943ms): https://atcoder.jp/contests/abc229/submissions/43510042
IntDisjoinSetsを使おう(戒め)
参考資料
https://juliacollections.github.io/DataStructures.jl/latest/disjoint_set/
Union find(素集合データ構造)
競プロ覚書:Union-Find まとめ - pyてよn日記
https://ei1333.github.io/algorithm/union-find.html
素集合データ構造 - Wikipedia