差分

このページの2つのバージョン間の差分を表示します。

この比較画面へのリンク

両方とも前のリビジョン前のリビジョン
次のリビジョン
前のリビジョン
次のリビジョン両方とも次のリビジョン
programming:algorithm:data_structure:union_find_tree [2017/08/07] ikatakosprogramming_algorithm:data_structure:union_find_tree [2019/09/21] – [実装] ikatakos
行 1: 行 1:
 ======Union-Find木====== ======Union-Find木======
  
-  * 要素が$n$個ある +  * [[wpjp>素集合データ構造]] 
-  * 各要素は互いに素なグループに1つだけ属する + 
-    * 例: 生徒は必ず何かのクラブ活動に入り、かつ、兼部は認められていない +  * 要素が $n$ 個あり、各要素は互いに素なグループに1つだけ属する 
-  * グループは統合される可能性がある分割はされない+    * 例: 生徒は必ず何かのクラブ活動に入り、兼部は認められていない 
 +  * グループは統合される可能性があるが、分割はされない
  
 という前提で、 という前提で、
行 19: 行 20:
 =====アルゴリズム===== =====アルゴリズム=====
  
-  * 各グループの要素から、リーダーを1つ決める +  * 各グループの要素から、"代表"を1つ決める 
-  * リーダー以外の要素は、親となる要素を1つずつ持つ +  * 代表以外の要素は、""となる要素を1つずつ持つ 
-    * どの要素からでも、親の親の親……を辿っていけば自身のリーダーに着くようにする +    * どの要素からでも、親の親の親……を辿っていけば自身の代表に着くようにする 
-    * つまり、グループを、リーダーを根とした木構造で表現する+    * つまり、グループを、代表を根とした木構造で表現する
  
-  * ${\rm Union}(x, y)$は、一方のリーダーの親をもう一方のリーダー書き換える +<code> 
-  * ${\rm Find}(x, y)$は、xとyから親を辿って同じリーダーに行き着くか調べる+    1    4    8  ←代表 
 +  ↗↑    ↑ 
 + 2 5    6 
 + ↑     ↗  ↖ 
 + 3    7    9 
 +</code> 
 + 
 +  * ${\rm Union}(x, y)$は、一方の代表もう一方の代表につなぐ(親 
 +  * ${\rm Find}(x, y)$は、xとyから親を辿って同じ代表に行き着くか調べる
  
 ====高速化の工夫==== ====高速化の工夫====
  
-  * 木の階層を低く保たないと、リーダーを見つける処理に時間がかかる +  * 木の階層を低く保たないと、代表を見つける処理に時間がかかる 
-    * はやい例では2~4のどの要素からも、1回調べたらにたどり着ける +    * 以下の例、はやい例では2~4のどの要素からも、1回調べたら代表にたどり着ける 
-    * おそい例では、4からだと、4→3→2→1と3回辿らないとに着かない+    * おそい例では、4からだと、4→3→2→1と3回辿らないと代表に着かない
     * できるだけ、はやい例のような状態を保ちたい     * できるだけ、はやい例のような状態を保ちたい
 <code> <code>
行 44: 行 53:
 </code> </code>
  
-  * 経路圧縮 +===経路圧縮=== 
-    * 一度調べた経路上にある要素は、全て親をリーダーに書き換える +  * 一度調べた経路上にある要素は、全て親を代表に書き換える 
-      * おそい例において、4→3→2→1と調べて1がリーダーだとわかったら、2~4の親を1に書き換える +    * おそい例において、4→3→2→1と調べて1が代表だとわかったら、2~4の親を1に書き換える 
-  ランク + 
-    * 木の最大深さ(ランク)を記録しておく +===ランク=== 
-    * Union時、ランクの低い方のグループを、高い方のグループに吸収させる +  * 木の最大深さ(ランク)を記録しておく 
-      * 互いのランクが等しい場合を除き、ランクが高くならずに済む(等しい場合のみ1増える) +  * Union時、ランクの低い方のグループを、高い方のグループにつなぐ 
-    * ランクは深さでなく、要素数で考えることもある +    * 互いのランクが等しい場合を除き、ランクが高くならずに済む(等しい場合のみ1増える)
-      * 例: 要素数100,最大深さ2のグループと、要素数5,最大深さ4のグループ +
-      * たった5個の要素が深さ5になるより、100個の要素の深さが1ずつ増える方が効率悪くない?という考え方 +
-      * どちらがいいかは場合に依りけり+
  
 <code> <code>
行 69: 行 75:
                       |              |        8                       |              |        8
 </code> </code>
 +
 +ランクは深さでなく、要素数で考えることもある
 +
 +  * 例: 要素数100,最大深さ2のグループAと、要素数5,最大深さ4のグループB
 +    * AにBを繋ぐとBの5個の要素の深さが1ずつ増え、最大深さは5
 +    * BにAを繋ぐとAの100個の要素の深さが1ずつ増え、最大深さは4
 +    * 局所的に最大深さが深くなろうが、100個の要素の深さが1ずつ増える方が効率悪くない?という考え方か
 +  * A.V.Aho, John E. Hopcroft, Jeffrey D. Ulman, 『データ構造とアルゴリズム』, 培風館, 1987
  
 =====実装===== =====実装=====
 +
 +  * 要素数 $n$ の配列''**table**''を作り、-1で初期化する
 +    * ${\rm table}[x]$ が負ならば、$x$ は代表。絶対値はランクを示す
 +    * ${\rm table}[x]$ が非負ならば、$x$ は代表でない。値は $x$ の親を示す
 +    * 最初は、$n$個の要素が別々の$n$個のグループに所属している状態
 +  * ''_root''で代表を再帰的に探索する
 +    * ついでに経路圧縮を行う
  
 <sxh python> <sxh python>
行 116: 行 137:
     print(ins.find(0, 3))  # True     print(ins.find(0, 3))  # True
  
 +</sxh>
 +
 +統合時、どちらを根にするかの基準をグループの深さで無く「要素数」とした実装。unionの部分が少し変化。
 +
 +<sxh python>
 +
 +class UnionFind:
 +    def __init__(self, n):
 +        self.table = [-1] * n
 +
 +    def _root(self, x):
 +        if self.table[x] < 0:
 +            return x
 +        else:
 +            self.table[x] = self._root(self.table[x])
 +            return self.table[x]
 +
 +    def find(self, x, y):
 +        return self._root(x) == self._root(y)
 +
 +    def union(self, x, y):
 +        r1 = self._root(x)
 +        r2 = self._root(y)
 +        if r1 == r2:
 +            return
 +        d1 = self.table[r1]
 +        d2 = self.table[r2]
 +        if d1 <= d2:
 +            self.table[r2] = r1
 +            self.table[r1] += d2
 +        else:
 +            self.table[r1] = r2
 +            self.table[r2] += d1
 </sxh> </sxh>
programming_algorithm/data_structure/union_find_tree.txt · 最終更新: 2023/05/26 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0