Union-Find クイックユニオン
一組のデータに対して、Union-Find は主に2つの操作をサポートします。
-
union(p,q) - p と q の2つの要素を接続します。
-
find(p)- p 要素がどの集合にあるかを検索します。
-
isConnected(p,q)- p と q の2つの要素が接続されているかどうかを確認します。
前のセクションでは、id配列の形式で Union-Find を表現しました。実際の操作における検索の時間計算量はO(1)、しかし接続の効率は高くありません。
このセクションでは、別の方法で Union-Find を実装します。各要素を1つのノードと見なし、自分の親ノードを指すようにします。ルートノードは自分自身を指します。下図のように、ノード3はノード2を指しています。これは3と2が接続されていることを表します。ノード2自体がルートノードなので、自分自身を指します。

同じく配列で Union-Find を表現しますが、次の一組の要素はparent現在の要素が指す親ノードを表します。各要素は自分自身を指し、すべて独立しています。


ここで、以下の操作を行うとunion(4,3)、要素4を要素3に向けます:

配列もそれに応じて変更されます:

2つの要素が接続されているかどうかを判断するには、ルートノードが同じかどうかを判断するだけです。
下図のように、ノード4とノード9のルートノードはどちらも8なので、それらは接続されています。

2つの要素を接続するには、それらの対応するルートノードを見つけ、ルートノードを接続するだけで、それらは接続されたノードになります。
上の図の6と4を接続したい場合は、6のルートノード5を4のルートノード8に向けるだけで済みます。

このような親ノードを指す木構造を構築するには、配列を使用して親ノードを指す木を構築します。parent[i] は i 要素が指す親ノードを表します。
private int[] parent;
private int count; // データ数
...
検索プロセス:要素 p に対応する集合番号を検索します。自分の親ノードをルートノードに到達するまで繰り返し調べます。ルートノードの特徴は parent[p] == p です。計算量は O(h) で、h は木の高さです。
private int find(int p){
assert( p >= 0 && p < count );
while( p != parent[p] )
p = parent[p];
return p;
}
...
要素 p と要素 q が属する集合を結合します。それぞれの要素のルートノードを検索し、一方のルートノードをもう一方のルートノードに向けさせると、2つの集合が結合されます。この操作の時間計算量は O(h) で、h は木の高さです。
int pRoot = find(p);
int qRoot = find(q);
if( pRoot == qRoot )
return;
parent[pRoot] = qRoot;
}
Java サンプルコード
ソースコードパッケージのダウンロード:Download
UnionFind2.java ファイルのコード:
/**
* 第二版 UnionFind
*/
public class UnionFind2 {
// 私たちの第二版 Union-Find。配列を使用して親ノードを指す木を構築する
// parent[i] は i 番目の要素が指す親ノードを表す
private int[] parent;
private int count; // データ数
// コンストラクタ
public UnionFind2(int count){
parent = new int[count];
this.count = count;
// 初期化。各 parent[i] は自分自身を指し、各要素が自分自身で一つの集合を形成することを表す
for( int i = 0 ; i < count ; i ++ )
parent[i] = i;
}
// 検索プロセス。要素 p に対応する集合番号を検索する
// O(h) の計算量。h は木の高さ
private int find(int p){
assert( p >= 0 && p < count );
// 自分の親ノードをルートノードに到達するまで繰り返し調べる
// ルートノードの特徴: parent[p] == p
while( p != parent[p] )
p = parent[p];
return p;
}
// 要素 p と要素 q が同じ集合に属するかどうかを確認する
// O(h) の計算量。h は木の高さ
public boolean isConnected( int p , int q ){
return find(p) == find(q);
}
// 要素 p と要素 q が属する集合を結合する
// O(h) の計算量。h は木の高さ
public void unionElements(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if( pRoot == qRoot )
return;
parent[pRoot] = qRoot;
}
}