Union-Find サイズの最適化
前のセクションの考え方に従い、下図のような Union-Find をunion(4,9)操作します。

マージ操作後の構造は次のようになります:

この構造では木の階層が比較的高いことがわかります。もし要素数が増えると、発生するコストは比較的大きくなります。この問題の解決はとても簡単で、具体的なポインタ操作を行う前に判断し、要素数の少ない集合のルートノードを要素数の多いルートノードに指し示すことで、より高い確率で階層の低い木を生成できます。
Union-Find を構築するときには、もう1つパラメータが必要です。sz配列、sz[i]表すのは、i「〜を根とする集合の要素数」。
// コンストラクタ
public UnionFind3(int count){
parent = new int[count];
sz = new int[count];
this.count = count;
// 初期化、各parent[i]は自分自身を指し、各要素が自分自身でひとつの集合を構成することを示します
for( int i = 0 ; i < count ; i ++ ){
parent[i] = i;
sz[i] = 1;
}
}
public UnionFind3(int count){
parent = new int[count];
sz = new int[count];
this.count = count;
// 初期化、各parent[i]は自分自身を指し、各要素が自分自身でひとつの集合を構成することを示します
for( int i = 0 ; i < count ; i ++ ){
parent[i] = i;
sz[i] = 1;
}
}
マージ操作を行うときは、2つの要素が属する木の要素数の違いに基づいてマージ方向を判断します。
public void unionElements(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if( pRoot == qRoot )
return;
if( sz[pRoot] < sz[qRoot] ){
parent[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
}
else{
parent[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
}
int pRoot = find(p);
int qRoot = find(q);
if( pRoot == qRoot )
return;
if( sz[pRoot] < sz[qRoot] ){
parent[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
}
else{
parent[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
}
最適化後、マージ結果は次のようになり、9は親ノード8を指します。

Java サンプルコード
ソースコードパッケージのダウンロード:Download
UnionFind3.java ファイルコード:
package サンプル.union;
/**
* Union-Findのサイズ最適化
*/
public class UnionFind3 {
// parent[i] は最初の要素が指す親ノードを表します
private int[] parent;
// sz[i] は i を根とする集合の要素数を表します
private int[] sz;
// データ数
private int count;
// コンストラクタ
public UnionFind3(int count){
parent = new int[count];
sz = new int[count];
this.count = count;
// 初期化、各parent[i]は自分自身を指し、各要素が自分自身でひとつの集合を構成することを示します
for( int i = 0 ; i < count ; i ++ ){
parent[i] = i;
sz[i] = 1;
}
}
// 検索プロセス、要素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;
// 2つの要素が属する木の要素数の違いに基づいて、マージ方向を判断します
// 要素数が少ない集合を、要素数が多い集合にマージします
if( sz[pRoot] < sz[qRoot] ){
parent[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
}
else{
parent[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
}
}
/**
* Union-Findのサイズ最適化
*/
public class UnionFind3 {
// parent[i] は最初の要素が指す親ノードを表します
private int[] parent;
// sz[i] は i を根とする集合の要素数を表します
private int[] sz;
// データ数
private int count;
// コンストラクタ
public UnionFind3(int count){
parent = new int[count];
sz = new int[count];
this.count = count;
// 初期化、各parent[i]は自分自身を指し、各要素が自分自身でひとつの集合を構成することを示します
for( int i = 0 ; i < count ; i ++ ){
parent[i] = i;
sz[i] = 1;
}
}
// 検索プロセス、要素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;
// 2つの要素が属する木の要素数の違いに基づいて、マージ方向を判断します
// 要素数が少ない集合を、要素数が多い集合にマージします
if( sz[pRoot] < sz[qRoot] ){
parent[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
}
else{
parent[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
}
}