Система непересекающихся множеств

Система непересекающихся множеств (англ. disjoint-set, или union–find data structure) — структура данных, которая позволяет администрировать множество элементов, разбитое на непересекающиеся подмножества. При этом каждому подмножеству назначается его представитель — элемент этого подмножества. Абстрактная структура данных определяется множеством трёх операций: .

Применяется для хранения компонент связности в графах, в частности, алгоритму Краскала необходима подобная структура данных для эффективной реализации.

Определение[править | править код]

Пусть конечное множество, разбитое на непересекающиеся подмножества (классы) :

.

Каждому подмножеству назначается представитель . Соответствующая система непересекающихся множеств поддерживает следующие операции:

  • : создаёт для элемента новое подмножество. Назначает этот же элемент представителем созданного подмножества.
  • : объединяет оба подмножества, принадлежащие представителям и , и назначает представителем нового подмножества.
  • : определяет для подмножество, к которому принадлежит элемент, и возвращает его представителя.

Алгоритмическая реализация[править | править код]

Тривиальная реализация сохраняет принадлежность элементов из и представителей в индексном массиве. На практике же чаще используются множества деревьев. Это позволяет существенно сократить время, необходимое для операции Find. При этом представитель записывается в корень дерева, а остальные элементы класса в узлы под ним.

  • : вешает корень более низкого дерева под корень более высокого дерева. Если при этом становится потомком , оба узла меняются местами.
  • : проходит путь от до корня дерева и возвращает его (корень в данном случае является представителем).

Эвристики[править | править код]

Для ускорения операций Union и Find могут быть использованы эвристики Union-By-Size, Union-By-Height, Random-Union и сжатие путей.

В эвристике Union-By-Size во время операции корень меньшего дерева вешается под корень большего дерева. Благодаря этому подходу сохраняется балансировка дерева. Глубина каждого поддерева не может превысить величину . При использовании этой эвристики время операции Find в худшем случае увеличивается с до . Для эффективной реализации предлагается сохранять в корне количество узлов в дереве.

Эвристика Union-By-Height аналогична Union-By-Size, но использует высоту дерева вместо размера.

В эвристике Random-Union используется тот факт, что можно не тратить дополнительные памяти на сохранение количества узлов в дереве: достаточно выбирать корень случайным образом — такое решение даёт на случайных запросах скорость, вполне сравнимую с другими реализациями. Тем не менее, если имеется много запросов вида «объединить большое множество с маленьким», данная эвристика улучшает матожидание (то есть среднее время работы) всего в два раза, поэтому использовать её без эвристики сжатия путей не рекомендуется.

Эвристика сжатия путей используется, чтобы ускорить операцию . При каждом новом поиске все элементы, находящиеся на пути от корня до искомого элемента, вешаются под корень дерева. В этом случае операция Find будет работать в среднем , где  — функция, обратная функции Аккермана. Это позволяет значительно ускорить работу, так как для всех применяемых на практике значений принимает значение, меньшее 5.

Пример реализации[править | править код]

Реализация на C++:

const int MAXN = 1000;  int p[MAXN], rank[MAXN];  void MakeSet(int x)  {     p[x] = x;     rank[x] = 0; }  int Find(int x)  {     return ( x == p[x] ? x : p[x] = Find(p[x]) ); }  void Union(int x, int y)  {     if ( (x = Find(x)) == (y = Find(y)) )         return; 	     if ( rank[x] <  rank[y] )         p[x] = y;     else {         p[y] = x;         if ( rank[x] == rank[y] )             ++rank[x];     } } 

Реализация на Free Pascal:

const MAX_N = 1000;  var Parent , Rank : array [ 1 .. MAX_N ] of LongInt;  procedure swap ( var x , y : LongInt );   var tmp : LongInt; begin   tmp := x;    x := y;    y := tmp; end;  procedure MakeSet ( x : LongInt ) ; begin   Parent[x] := x;   Rank[x] := 0; end;  function Find ( x : LongInt ) : LongInt; begin   if ( Parent[x] <> x ) then     Parent[x] := Find ( Parent[x] );   Exit ( Parent[x] ); end;  procedure Union ( x , y : LongInt ); begin   x := Find ( x );   y := Find ( y );   if ( x = y ) then exit();   if ( Rank[x] < Rank[y] ) then swap ( x , y );      Parent[y] := x;   if ( Rank[x] = Rank[y] ) then     inc ( Rank[x] ); end; 

См. также[править | править код]

Литература[править | править код]

  • Galler, Bernard A., and Michael J. Fisher. «An improved equivalence algorithm.» // Communications of the ACM, 7.5 (1964): 301—303. (англ.)
  • Tarjan, Robert E., and Jan Van Leeuwen. «Worst-case analysis of set union algorithms.» // Journal of the ACM 31.2 (1984): 245—281. (англ.)
  • Томас Кормен и др. Алгоритмы: построение и анализ = Introduction to Algorithms. — 2-е изд. — М.: «Вильямс», 2006. — С. 1296. — ISBN 0-07-013151-1.

Ссылки[править | править код]