Union-Find木(ユニオンファインド木)とはグループ分けを管理するデータ構造です。Union-Find木を用いることで、グループ同士の併合(Union)、ある要素がどのグループに属しているかを見つける操作(Find)を木構造を利用することで効率的にできるようになります。

Union-Find木の実装

Union-Find木におけるFind(ファインド)

以下のような配列が与えられたとします。

0 2 9 3 2 5 6 7 3 3

これは0番目(一番左を0番と数える)の要素の親は0(つまり自分自身が親)、1番目は2番目が親で2番目の親は9番目であることを示しています。続けると9番目の親は3番目で3番目の親は3番目です。つまり1番目の根(Root)は3番目の要素であることになります。Union-Find木において、ある要素の根を求めることを一般的にFind(ファインド)といいます。

Union-Find木にいける併合(Union)

Union-Find木では同じ根を持つ要素を同じグループとみなして管理します。

最初は配列の初期値では、すべての要素においてそれ自身が根となっています。つまり配列は以下のようになっています。

0 1 2 3 4 5 …

このなかからふたつの要素(aとb)を取り出し、aの根とbの根が同じでないなら、bの根の親をaの根とする操作をおこないます。両者の根が同じならば何もしません。

上の操作のように、別の根を持つ、別のグループの要素たちの根を同じにして同一グループとする操作を一般的にユニオンと言います。

C#で実装する

C#で実装してみることにします。

実行結果

経路圧縮による動作改善

Findメソッドを少し改良して値を返すときに根を求める際にA[p]も更新しています。これによって次にpの根を求めるときに何回も再帰する必要がなくなり、処理速度を向上させることができます。これは経路圧縮と呼ばれています。

上記コードの実行結果は以下のようになります。

実行結果

ランクによる動作改善

配列arrに加えて、もう一つ長さnの配列ranksを定義して、[1, …, 1]で初期化します。整数 i が属する木の、根から末端までの最大の長さをランクと定義してranksに格納します。

a の根と b の根を併合(Union)するとき、両者の根のランクに差があり、低いほうの根の親を高いほうの根とする場合は、高いほうのランクがさらに高くなることはないので、ランクは変わりません。逆に両者のランクが同じ場合はbの根の親をa の根とすることによって、aの根のランクが必ず1増えることになります。

Findメソッドは上と同じです。

実行結果

応用例

連結判定や閉路検出にも使えます。ルートがひとつしか存在しない場合はグラフは連結しているといえます。またグラフに辺を追加するときふたつの頂点のルートが同じであれば辺をつなぐことで閉路ができてしまうことに気づくことができます。

最小全域木

各辺に重み(コスト)がある場合、すべての頂点が辺によって連結していて、最小のコストで構成される全域木を最小全域木といいます。

クラスカル法とは最小全域木問題を解くアルゴリズムの一つです。辺の重みが小さい順にソートし、辺を追加したときに閉路ができないならば追加し、閉路ができる場合はとばして次の辺を考えます。

実行結果