// Naive implementation of find
int find(int parent[], int i)
{
if (parent[i] == -1)
return i;
return find(parent, parent[i]);
}
// Naive implementation of union()
void Union(int parent[], int x, int y)
{
int xset = find(parent, x);
int yset = find(parent, y);
parent[xset] = yset;
}
上記union()
とfind()
はナイーブであり、最悪の場合の時間複雑度は線形です。サブセットを表現するために作成されたツリーは歪んでいる可能性があり、リンクされたリストのようになります。以下は、最悪のシナリオの例です。
Let there be 4 elements 0, 1, 2, 3
Initially all elements are single element subsets.
0 1 2 3
Do Union(0, 1)
1 2 3
/
0
Do Union(1, 2)
2 3
/
1
/
0
Do Union(2, 3)
3
/
2
/
1
/
0
上記の操作は最悪の場合O(Log n)
に最適化できます。アイデアは、より深いツリーのルートの下に常に小さな深さのツリーを付けることです。この技術は、ユニオンbyランクと呼ばれています。ランクという用語は高さの代わりに優先されます。これは、パス圧縮手法(以下で説明しました)を使用すると、ランクが常に高さに等しいとは限りません。
Let us see the above example with union by rank
Initially all elements are single element subsets.
0 1 2 3
Do Union(0, 1)
1 2 3
/
0
Do Union(1, 2)
1 3
/\
0 2
Do Union(2, 3)
1
/| \
0 2 3
素朴なメソッドの2番目の最適化は、パス圧縮です。考え方は、find()
が呼び出されたときにツリーを平坦にすることです。要素x
に対してfind()
が呼び出されると、ツリーのルートが返されます。 find()
オペレーションは、x
からトラップしてルートを検索します。パス圧縮の考え方は、発見されたルートを親としてx
にすることで、すべての中間ノードを再びトラバースする必要がなくなります。 x
がサブツリーのルートである場合、x
の下のすべてのノードからのパス(ルートへ)も圧縮されます。
Let the subset {0, 1, .. 9} be represented as below and find() is called
for element 3.
9
/ | \
4 5 6
/ \ /\
0 3 7 8
/\
1 2
When find() is called for 3, we traverse up and find 9 as representative
of this subset. With path compression, we also make 3 as child of 9 so
that when find() is called next time for 1, 2 or 3, the path to root is
reduced.
9
//\ \
4 5 6 3
/ /\ /\
0 7 8 1 2
2つの手法が互いに補完します。各操作の時間複雑度は、O(logn)
〜O(n)
よりもさらに小さくなります。実際、償却された時間の複雑さは効果的に小さくなります。
上記の最適化でコードを投稿していないのは、私が推測する割り当て部分だからです。それが役に立てば幸い!
多くの場合、ユニオン検索データ構造では、ルートに格納されたランクがあり、より小さいランクのルートをより高いランクにグラフトすることによってユニオンが実行され、長い狭いツリーができなくなります。あなたが話していることを示すコードの短い抽出を提供し、あなたがO(N^2)であると考える特定のケースを提供すれば、あなたの質問はより良いでしょう。それから具体的な答えがあります。 –