Система неперетинних множин (англ. disjoint-set-union або DSU, також використовують назви англ. union–find data structure, англ. merge–find set) — структура даних, яка дозволяє відстежувати множину елементів, розбиту на неперетинні підмножини. При цьому кожній підмножині призначається її представник — елемент цієї підмножини.
Структура даних надає такі можливості. Спочатку є кілька елементів, кожен з яких знаходиться в окремій (своїй власній) множині. За одну операцію можна об'єднати дві будь-які множини, а також можна запитати, в якій множині зараз знаходиться зазначений елемент. У класичному варіанті вводиться ще одна операція — створення нового елемента, який поміщається в окрему множину — синґлетон.
Таким чином, базовий інтерфейс даної структури даних складається всього з трьох операцій:
- MakeSet — додання нового елемента; розміщення його в нову множину, що складається з одного нього;
- Union — об'єднання двох зазначених множин;
- Find — повернення значення, в якій множині знаходиться зазначений елемент. Насправді при цьому повертається один з елементів множини званий представником (англ. representative) або лідером (leader). Цей представник вибирається в кожній множині самою структурою даних (і може змінюватися з плином часу). Наприклад, якщо виклик для якихось двох елементів повернув одне і те ж значення, то це означає, що ці елементи знаходяться в одній і тій же множині, а в іншому випадку — в різних множинах.
Описувана нижче структура даних дозволяє робити кожну з цих операцій майже за в середньому (більш докладно про асимптотику див. нижче після опису алгоритму).
Також в одному з підрозділів статті описаний альтернативний варіант реалізації DSU, що дозволяє домогтися асимптотики в середньому на один запит.
Побудова ефективної структури даних
Визначимося спочатку, в якому вигляді ми будемо зберігати всю інформацію.
Множину елементів ми будемо зберігати у вигляді дерев: одне дерево відповідає одній множині. Корінь дерева — це представник (лідер) множини.
При реалізації це означає, що ми заводимо масив, в якому для кожного елемента зберігаємо посилання на його предка в дереві. Для коренів дерев будемо вважати, що їх предок — вони самі (тобто посилання зациклюється в цьому місці).
Наївна реалізація
Ми вже можемо написати першу реалізацію системи неперетинних множин. Вона буде досить неефективною, але потім ми покращимо її за допомогою двох прийомів, отримавши в результаті майже константний час роботи.
Отже, вся інформація про множини елементів зберігається у нас за допомогою масиву parent.
- Щоб створити новий елемент (операція make_set(v)), ми просто створюємо дерево з коренем у вершині, зазначаючи, що її предок — це вона сама.
- Щоб об'єднати дві множини (операція union_set(a, b)), ми спочатку знайдемо лідерів першої і другої множини. Якщо лідери збіглися, то нічого не робимо — це означає, що множини і так вже були об'єднані. В іншому випадку можна просто вказати, що предок першої вершини дорівнює другій (або навпаки) — тим самим приєднавши одне дерево до іншого.
- Реалізація операції пошуку лідера (find_set(v)) проста: ми піднімаємося по предкам від вершини, поки не дійдемо до кореня. Цю операцію зручніше реалізувати рекурсивно (особливо це буде зручно пізніше, у зв'язку з доданими оптимізаціями).
void make_set (int v) { parent[v] = v; } int find_set (int v) { if (v == parent[v]) return v; return find_set (parent[v]); } void union_sets (int a, int b) { a = find_set (a); b = find_set (b); if (a != b) parent[b] = a; }
Утім, така реалізація системи неперетинних множин дуже неефективна. Легко побудувати приклад, коли після кількох об'єднань множин вийде ситуація, що множина — це дерево, звиродніле в довгий ланцюжок. У результаті кожен виклик буде працювати на такому тесті за час порядку глибини дерева, тобто за .
Це дуже далеко від тієї асимптотики, яку ми збиралися отримати (константний час роботи). Тому розглянемо дві оптимізації, які дозволять (навіть застосовані окремо) значно прискорити роботу.
Евристика стиснення шляху
Ця евристика призначена для прискорення роботи find_set().
Вона полягає в тому, що коли після виклику ми знайдемо шуканого лідера множини, то запам'ятаємо, що у вершині v і всіх пройдених по шляху вершин — саме цей лідер. Найпростіше це зробити, перенаправивши їх parent[] на цю вершину.
Таким чином, у масиву предків parent сенс дещо змінюється: тепер це стислий масив предків, тобто для кожної вершини там може зберігатися не безпосередній предок, а предок предка, предок предка предка, і т. д.
З іншого боку, зрозуміло, що не можна зробити, щоб ці покажчики parrent завжди вказували на лідера: інакше при виконанні операції довелося б оновлювати лідерів у елементів.
Таким чином, до масиву слід підходити саме як до масиву предків, можливо, частково стиснутого.
Нова реалізація операції має такий вигляд:
int find_set (int v) { if (v == parent[v]) return v; return parent[v] = find_set (parent[v]);
Така проста реалізація робить все, що задумувалося: спочатку шляхом рекурсивних викликів знаходиться лідера множини, а потім, в процесі розкрутки стека, цей лідер присвоюється parent посиланнями для всіх пройдених елементів.
Реалізувати цю операцію можна і не рекурсивно, але тоді доведеться здійснювати два проходи по дереву: перший знайде шуканого лідера, другий — проставить його всім вершин шляху. Втім, на практиці нерекурсивна реалізація не дає істотного виграшу.
Евристика об'єднання за рангом
Розглянемо іншу евристику, яка сама по собі здатна прискорити час роботи алгоритму, а в поєднанні з евристикою стиснення шляхів і зовсім здатна досягти практично константного часу роботи на один запит в середньому.
Ця евристика полягає в невеликій зміні роботи union_set: якщо в наївній реалізації те, яке дерево буде приєднано до якого, визначається випадково, то тепер ми будемо це робити на основі рангів.
Є два варіанти рангової евристики: в одному варіанті рангом дерева називається кількість вершин в ньому, в іншому — глибина дерева (точніше, верхня межа на глибину дерева, оскільки при одночасному застосуванні евристики стиснення шляхів реальна глибина дерева може зменшуватися).
В обох варіантах суть евристики одна й та ж: при виконанні union_set будемо приєднувати дерево з меншим рангом до дерева з великим рангом.
void make_set (int v) { parent[v] = v; size[v] = 1; } void union_sets (int a, int b) { a = find_set (a); b = find_set (b); if (a != b) { if (size[a] < size[b]) swap (a, b); parent[b] = a; size[a] += size[b]; }
Наведемо реалізацію рангової евристики на основі глибини дерев:
void make_set (int v) { parent[v] = v; rank[v] = 0; } void union_sets (int a, int b) { a = find_set (a); b = find_set (b); if (a != b) { if (rank[a] < rank[b]) swap (a, b); parent[b] = a; if (rank[a] == rank[b]) ++rank[a]; } }
Обидва варіанти рангової евристики є еквівалентними з точки зору асимптотики, тому на практиці можна застосовувати будь-яку з них.
Об'єднання евристик: стиснення шляху плюс рангова евристика
Як вже згадувалося вище, спільне застосування цих евристик дає особливо гарний результат, у підсумку досягаючи практично константного часу роботи.
Доведення асимптотики тут не наводиться через їх об'ємність (див., наприклад, Кормен, Лейзерсон, Ривест, Штайн «Вступ до алгоритмів»). Вперше це доведення було проведено Тарджаном (1975 р.).
Остаточний результат такий: при спільному застосуванні евристик стиснення шляху та об'єднання за рангом час роботи на один запит виходить в середньому, де — обернена функція Акермана, яка зростає дуже повільно, настільки повільно, що для всіх розумних обмежень вона не перевершує 4 (приблизно для n <= 10600).
Саме тому про асимптотику роботи системи неперетинних множин доречно говорити «майже константний час роботи».
Наведемо тут підсумкову реалізацію системи неперетинних множин, що реалізує обидві вказані евристики (використовується рангова евристика щодо глибин дерев):
void make_set (int v) { parent[v] = v; rank[v] = 0; } int find_set (int v) { if (v == parent[v]) return v; return parent[v] = find_set (parent[v]); } void union_sets (int a, int b) { a = find_set (a); b = find_set (b); if (a != b) { if (rank[a] < rank[b]) swap (a, b); parent[b] = a; if (rank[a] == rank[b]) ++rank[a]; } }
Застосування в задачах і різні поліпшення
Структури даних неперетинних множин моделюють розбиття множини. Як відомо, розбиття множини можна задати за допомогою задання на ній відношення еквівалентності. І, навпаки, кожному відношенню еквівалентності заданому на деякій множині відповідає розбиття цієї множини. Це означає, що структура даних «система неперетинних множин» широко використовується. Наприклад, можна відстежувати компоненти зв’язаності неорієнтованого графа[⇨]. Алгоритм Union–Find використовується у високопродуктивних реалізаціях уніфікації.
У цьому розділі розглянуто кілька застосувань структури даних «система неперетинних множин», як тривіальних, так і з використанням деяких поліпшень структури даних.
Підтримка компонент зв'язності графа
Це одна з очевидних програм структури даних «система неперетинних множин», яка, вочевидь, і стимулювала вивчення цієї структури.
Формально задачу можна сформулювати таким чином: спочатку заданий порожній граф, поступово в цей граф можуть додаватися вершини і неорієнтовані ребра, а також надходять запити — «чи в однакових компонентах зв'язності лежать вершини і?».
Безпосередньо застосовуючи тут описану вище структуру даних, ми отримуємо рішення, яке обробляє один запит на додавання вершини / ребра або запит на перевірку двох вершин — за майже константний час у середньому.
Враховуючи, що практично таке саме завдання ставиться при використанні алгоритму Крускала знаходження мінімального кістякового дерева, ми відразу ж отримуємо поліпшену версію цього алгоритму, що працює практично за лінійний час.
Іноді на практиці зустрічається інвертована версія цього завдання: спочатку є граф з якимись вершинами і ребрами, і надходять запити на видалення ребер. Якщо завдання дане в офлайн, тобто ми заздалегідь можемо дізнатися всі запити, то вирішувати це завдання можна таким чином: перевернемо завдання задом наперед: будемо вважати, що у нас є порожній граф, в який можуть додаватися ребра (спочатку додамо ребро останнього запиту, потім передостаннього, і т. д.). Тим самим у результаті інвертування цього завдання ми прийшли до звичайної задачі, рішення якої описувалося вище.
Пошук компонент зв'язності на зображенні
Одне з очевидних застосувань DSU полягає у вирішенні такого завдання: є зображення пікселів; спочатку все зображення біле, але потім на ньому малюється декілька чорних крапок. Потрібно визначити розмір кожної «білої» компоненти зв'язності на підсумковому зображенні.
Для рішення ми просто перебираємо всі білі клітини зображення, для кожної клітини перебираємо її чотирьох сусідів, і якщо сусід теж білий — то викликаємо від цих двох вершин. Таким чином, у нас буде DSU з вершинами, відповідними пікселям зображення. Отримані в результаті дерева DSU — і є шукані компоненти зв'язності.
Підтримка додаткової інформації для кожної множини
«Система неперетинних множин» дозволяє легко зберігати будь-яку додаткову інформацію, що відноситься до множини.
Простий приклад — це розміри множин: як їх зберігати, було сказано при описі рангової евристики (інформація там записувалася для поточного лідера множини).
Таким чином, разом з лідером кожної множини можна зберігати будь-яку додаткову необхідну в конкретному завданні інформацію.
Втім, таке рішення за допомогою системи неперетинних множин не має жодних переваг перед рішенням з допомогою обходу в глибину.
Застосування DSU для стиснення «стрибків» по відрізку. Завдання про зафарбування підвідрізків в офлайні
Одне з поширених застосувань DSU полягає в тому, що якщо є набір елементів, і з кожного елемента виходить по одному ребру, тож ми можемо швидко (за майже константний час) знаходити кінцеву точку, в яку ми потрапимо, якщо будемо рухатися вздовж ребер із заданої початкової точки.
Наочним прикладом цього застосування є завдання про фарбування підвідрізків: є відрізок довжини L, кожна клітинка якого (тобто кожен шматочок довжини 1) має нульовий колір. Надходять запити виду — (l, r,c) перефарбувати відрізок [l;r] в колір c. Потрібно знайти підсумковий колір кожної клітинки. Запити вважаються відомими заздалегідь, тобто завдання — в офлайні.
Для рішення ми можемо завести DSU-структуру, яка для кожної клітини буде зберігати посилання на найближчу справа непофарбовану клітинку. Таким чином, спочатку кожна клітинка вказує на саму себе, а після фарбування першого підвідрізка — клітинка перед початком підвідрізка буде вказувати на клітинку після кінця підвідрізка.
Тепер, щоб вирішити завдання, розглядаємо запити перефарбовування у зворотному порядку: від останнього до першого. Для виконання запиту кожного разу з допомогою нашого DSU знаходимо найлівішу непофарбовану клітинку всередині відрізка, перефарбовуєм її, і перекидаємо покажчик з неї на наступну справа порожню клітинку.
Таким чином, тут фактично використовуємо DSU з евристикою стиснення шляхів, але без рангової евристики (тому нам важливо, хто стане лідером після об'єднання). Отже, підсумкова асимптотика складе на запит (утім, з маленькою у порівнянні з іншими структурами даних константою).
Реалізація:
void init() { for (int i=0; i<L; ++i) make_set (i); } void process_query (int l, int r, int c) { for (int v=l; ; ) { v = find_set (v); if (v >= r) break; answer[v] = c; parent[v] = v+1; } }
Утім, можна реалізувати це рішення з ранговою евристикою: будемо зберігати для кожної множини в деякому масиві end[], де множина закінчується (тобто саму праву точку). Тоді можна буде об'єднувати дві множини в одну за їх ранговою евристикою, проставляючи потім отриману нову праву межу.
Підтримка відстаней до лідера
Іноді в конкретних програмах системи неперетинних множин з'являється вимога підтримувати відстань до лідера (тобто довжину шляху в ребрах у дереві від поточної вершини до кореня дерева).
Якби не було евристики стиснення шляхів, то ніяких складнощів не виникало б — відстань до кореня просто дорівнювало б числу рекурсивних викликів, які зробила функція find_set.
Проте в результаті стиснення шляхів кілька ребер шляху могли стиснутися в одне ребро. Таким чином, разом з кожною вершиною доведеться зберігати додаткову інформацію: довжину поточного ребра з вершини в предка.
При реалізації зручно уявляти, що масив parrent[] і функція find_set тепер повертають не одне число, а пару чисел: вершину-лідера і відстань до неї:
void make_set (int v) { parent[v] = make_pair (v, 0); rank[v] = 0; } pair<int,int> find_set (int v) { if (v != parent[v].first) { int len = parent[v].second; parent[v] = find_set (parent[v].first); parent[v].second += len; } return parent[v]; } void union_sets (int a, int b) { a = find_set (a) .first; b = find_set (b) .first; if (a != b) { if (rank[a] < rank[b]) swap (a, b); parent[b] = make_pair (a, 1); if (rank[a] == rank[b]) ++rank[a]; } }
Підтримка парності довжини шляху і завдання про перевірку двочасткового графа в онлайн
За аналогією з довжиною шляху до лідера, так само можна підтримувати парність довжини шляху до нього. Чому ж це застосування було виділено в окремий пункт?
Справа в тому, що зазвичай вимога зберігання парності шляху спливає у зв'язку з наступною задачею: спочатку дан порожній граф, в нього можуть додаватися ребра, а також надходити запити виду «чи є компонента зв'язності, що містить дану вершину, двочастковою?».
Для вирішення цього завдання ми можемо завести систему неперетинних множин для зберігання компонент зв'язності, і зберігати парність довжини шляху кожної вершини до її лідера. Тим самим, ми можемо швидко перевіряти, чи призведе додавання зазначеного ребра до порушення двочасткового графа чи ні: а саме, якщо кінці ребра лежать в одній і тій же компоненті зв'язності, і при цьому мають однакові парності довжини шляху до лідера, то додавання цього ребра призведе до утворення циклу непарної довжини і перетворенню поточної компоненти в недвудольну.
Головна складність, з якою ми стикаємося при цьому, — це те, що ми повинні акуратно, з урахуванням парності, проводити об'єднання двох дерев у функції union_sets.
Якщо ми додаємо ребро (a, b), що пов'язує дві компоненти зв'язності в один, то при приєднанні одного дерева до іншого ми повинні вказати йому таку парність, щоб в результаті у вершин a і b виходили б різні парності довжини шляху.
Виведемо формулу, за якою повинна виходити ця парність, що виставляється лідеру однієї множини при приєднанні його до лідера іншої. Позначимо через x парність довжини шляху від вершини a до лідера її множини, через y — парність довжини шляху від b вершини до лідера її множини, а через t — шукану парність, яку ми повинні поставити приєднуваному лідеру. Якщо множина з вершиною a приєднується до множини з вершиною b, стаючи піддеревом, то після приєднання у вершини b її парність не зміниться і залишиться рівною y, а у вершини a парність стане рівною (символом тут позначена операція XOR (симетрична різниця)). Нам потрібно, щоб ці дві парності розрізнялися, тобто їх XOR дорівнював одиниці. Тобто отримуємо рівняння на t:
вирішуючи яке, знаходимо:
Таким чином, незалежно від того, яка множина приєднується до якої, треба використовувати зазначену формулу для задання парності ребра, проведеного з одного лідера до іншого.
Наведемо реалізацію DSU з підтримкою парності. Як і в попередньому пункті, з метою зручності ми використовуємо пари для зберігання предків і результату операції find_set. Крім того, для кожної множини ми зберігаємо в масиві bipartite[], чи є воно все ще двочастковим чи ні.
void make_set (int v) { parent[v] = make_pair (v, 0); rank[v] = 0; bipartite[v] = true; } pair<int,int> find_set (int v) { if (v != parent[v].first) { int parity = parent[v].second; parent[v] = find_set (parent[v].first); parent[v].second ^= parity; } return parent[v]; } void add_edge (int a, int b) { pair<int,int> pa = find_set (a); a = pa.first; int x = pa.second; pair<int,int> pb = find_set (b); b = pb.first; int y = pb.second; if (a == b) { if (x == y) bipartite[a] = false; } else { if (rank[a] < rank[b]) swap (a, b); parent[b] = make_pair (a, x ^ y ^ 1); if (rank[a] == rank[b]) ++rank[a]; } } bool is_bipartite (int v) { return bipartite[ find_set(v) .first ];
Алгоритм знаходження RMQ (мінімуму на відрізку) в офлайні
Формально завдання ставиться так: потрібно реалізувати структуру даних, яка підтримує два види запитів: додавання вказаного числа і пошук і витяг поточного мінімального числа . Будемо вважати, що кожне число додається рівно один раз.
Крім того, вважаємо, що вся послідовність запитів відома нам заздалегідь, тобто завдання — в офлайні.
Ідея рішення така. Замість того, щоб по черзі відповідати на кожен запит, переберемо число , і визначимо, відповіддю на який запит це число повинне бути. Для цього нам треба знайти перший без відповіді запит, що йде після додавання цього числа — легко зрозуміти, що це і є той запит, відповіддю на який є число.
Таким чином, тут виходить ідея, схожа на завдання про фарбування відрізків.
Можна отримати рішення за в середньому на запит, якщо ми відмовимося від рангової евристики і будемо просто зберігати в кожному елементі посилання на найближчий справа запит , і використовувати стиснення шляхів для підтримки цих посилань після об'єднань.
Також можна отримати рішення і за , якщо ми будемо використовувати рангову евристику і будемо зберігати в кожній множині номер позиції, де вона закінчується (те, що в попередньому варіанті рішення досягалося автоматично за рахунок того, що посилання завжди йшли тільки вправо, — тепер треба буде зберігати явно).
Історія
У той час як ідеї, використані в системі неперетинних множин були давно знайомі, Роберт Тарджан був першим, хто довів верхню межу (і обмежену версію нижньої межі) у термінах зворотної функції Аккермана, у 1975 році. До цього найкращою межею на час роботи за операцію був, доведений Гопкрофтом і Ульманом, , повторний логарифм n, інша повільно зростаюча функція (але зростаюча не так повільно, як зворотня функція Аккермана).
Тарджан і [en] також розробили однопрохідний алгоритм пошуку, який є більш ефективним на практиці, зберігаючи ту ж складність у гіршому випадку.
У 2007 році Сільвен Коншон і Жан-Крістоф Фільят розробили варіант напів[en] для системи неперетинних множин, що дозволив ефективно зберегти попередні версії структури, та було доведено його правильність за допомогою засобу доведення теорем Coq.
Див. також
- Розбиття множини
- [en]
- [en]
Посилання
- Knight, Kevin (1989). Unification: A multidisciplinary survey (PDF). ACM Computing Surveys. 21: 93—124. doi:10.1145/62029.62030. S2CID 14619034.
- Efficiency of a Good But Not Linear Set Union Algorithm. Journal of the ACM. 22 (2): 215—225. doi:10.1145/321879.321884.
{{}}
: Cite має пусті невідомі параметри:|1=
,|2=
,|3=
та|4=
() - Set Merging Algorithms. SIAM Journal on Computing. 2 (4): 294—303. doi:10.1137/0202024.
{{}}
: Cite має пустий невідомий параметр:|1=
() - Worst-case analysis of set union algorithms, Journal of the ACM, 31 (2), 1984: 245—281, doi:10.1145/62.2160
{{}}
: Cite має пусті невідомі параметри:|1=
та|2=
() - A Persistent Union-Find Data Structure, ACM SIGPLAN Workshop on ML, Freiburg, Germany
{{}}
:|first=
з пропущеним|last=
(); Cite має пусті невідомі параметри:|1=
та|2=
()
Джерела
- Томас Кормен; Чарльз Лейзерсон, Рональд Рівест, Кліфорд Стайн (2009) [1990]. 10. Елементарні структури даних. Вступ до алгоритмів (вид. 3rd). MIT Press і McGraw-Hill. ISBN .
- C++ implementation, частина Boost C++ бібліотек
- A Java implementation with an application to color image segmentation, Statistical Region Merging (SRM), IEEE Trans. Pattern Anal. Mach. Intell. 26(11): 1452–1458 (2004)
- Java applet: A Graphical Union–Find Implementation, від Rory L. P. McGuire
- Wait-free Parallel Algorithms for the Union–Find Problem, стаття 1994 року від Richard J. Anderson і Heather Woll описує розпаралелену версію операцій Union-Find, які не потребують блокування
- Реалізація на пайтоні
- Візуальне пояснення і C# код
- https://www.geeksforgeeks.org/disjoint-set-data-structures/
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет
Sistema neperetinnih mnozhin angl disjoint set union abo DSU takozh vikoristovuyut nazvi angl union find data structure angl merge find set struktura danih yaka dozvolyaye vidstezhuvati mnozhinu elementiv rozbitu na neperetinni pidmnozhini Pri comu kozhnij pidmnozhini priznachayetsya yiyi predstavnik element ciyeyi pidmnozhini Dodani 8 elementiv Pislya dekilkoh operacij ob yednannya deyaki mnozhini zgrupovani Struktura danih nadaye taki mozhlivosti Spochatku ye kilka elementiv kozhen z yakih znahoditsya v okremij svoyij vlasnij mnozhini Za odnu operaciyu mozhna ob yednati dvi bud yaki mnozhini a takozh mozhna zapitati v yakij mnozhini zaraz znahoditsya zaznachenij element U klasichnomu varianti vvoditsya she odna operaciya stvorennya novogo elementa yakij pomishayetsya v okremu mnozhinu singleton Takim chinom bazovij interfejs danoyi strukturi danih skladayetsya vsogo z troh operacij MakeSet dodannya novogo elementa rozmishennya jogo v novu mnozhinu sho skladayetsya z odnogo nogo Union ob yednannya dvoh zaznachenih mnozhin Find povernennya znachennya v yakij mnozhini znahoditsya zaznachenij element Naspravdi pri comu povertayetsya odin z elementiv mnozhini zvanij predstavnikom angl representative abo liderom leader Cej predstavnik vibirayetsya v kozhnij mnozhini samoyu strukturoyu danih i mozhe zminyuvatisya z plinom chasu Napriklad yaksho viklik dlya yakihos dvoh elementiv povernuv odne i te zh znachennya to ce oznachaye sho ci elementi znahodyatsya v odnij i tij zhe mnozhini a v inshomu vipadku v riznih mnozhinah Opisuvana nizhche struktura danih dozvolyaye robiti kozhnu z cih operacij majzhe za O 1 displaystyle O 1 v serednomu bilsh dokladno pro asimptotiku div nizhche pislya opisu algoritmu Takozh v odnomu z pidrozdiliv statti opisanij alternativnij variant realizaciyi DSU sho dozvolyaye domogtisya asimptotiki O log N displaystyle O log N v serednomu na odin zapit Pobudova efektivnoyi strukturi danihViznachimosya spochatku v yakomu viglyadi mi budemo zberigati vsyu informaciyu Mnozhinu elementiv mi budemo zberigati u viglyadi derev odne derevo vidpovidaye odnij mnozhini Korin dereva ce predstavnik lider mnozhini Pri realizaciyi ce oznachaye sho mi zavodimo masiv v yakomu dlya kozhnogo elementa zberigayemo posilannya na jogo predka v derevi Dlya koreniv derev budemo vvazhati sho yih predok voni sami tobto posilannya zaciklyuyetsya v comu misci Nayivna realizaciya Mi vzhe mozhemo napisati pershu realizaciyu sistemi neperetinnih mnozhin Vona bude dosit neefektivnoyu ale potim mi pokrashimo yiyi za dopomogoyu dvoh prijomiv otrimavshi v rezultati majzhe konstantnij chas roboti Otzhe vsya informaciya pro mnozhini elementiv zberigayetsya u nas za dopomogoyu masivu parent Shob stvoriti novij element operaciya make set v mi prosto stvoryuyemo derevo z korenem u vershini zaznachayuchi sho yiyi predok ce vona sama Shob ob yednati dvi mnozhini operaciya union set a b mi spochatku znajdemo lideriv pershoyi i drugoyi mnozhini Yaksho lideri zbiglisya to nichogo ne robimo ce oznachaye sho mnozhini i tak vzhe buli ob yednani V inshomu vipadku mozhna prosto vkazati sho predok pershoyi vershini dorivnyuye drugij abo navpaki tim samim priyednavshi odne derevo do inshogo Realizaciya operaciyi poshuku lidera find set v prosta mi pidnimayemosya po predkam vid vershini poki ne dijdemo do korenya Cyu operaciyu zruchnishe realizuvati rekursivno osoblivo ce bude zruchno piznishe u zv yazku z dodanimi optimizaciyami void make set int v parent v v int find set int v if v parent v return v return find set parent v void union sets int a int b a find set a b find set b if a b parent b a Utim taka realizaciya sistemi neperetinnih mnozhin duzhe neefektivna Legko pobuduvati priklad koli pislya kilkoh ob yednan mnozhin vijde situaciya sho mnozhina ce derevo zvirodnile v dovgij lancyuzhok U rezultati kozhen viklik bude pracyuvati na takomu testi za chas poryadku glibini dereva tobto za O n displaystyle O n Ce duzhe daleko vid tiyeyi asimptotiki yaku mi zbiralisya otrimati konstantnij chas roboti Tomu rozglyanemo dvi optimizaciyi yaki dozvolyat navit zastosovani okremo znachno priskoriti robotu Evristika stisnennya shlyahu Cya evristika priznachena dlya priskorennya roboti find set Vona polyagaye v tomu sho koli pislya vikliku mi znajdemo shukanogo lidera mnozhini to zapam yatayemo sho u vershini v i vsih projdenih po shlyahu vershin same cej lider Najprostishe ce zrobiti perenapravivshi yih parent na cyu vershinu Takim chinom u masivu predkiv parent sens desho zminyuyetsya teper ce stislij masiv predkiv tobto dlya kozhnoyi vershini tam mozhe zberigatisya ne bezposerednij predok a predok predka predok predka predka i t d Z inshogo boku zrozumilo sho ne mozhna zrobiti shob ci pokazhchiki parrent zavzhdi vkazuvali na lidera inakshe pri vikonanni operaciyi dovelosya b onovlyuvati lideriv u elementiv Takim chinom do masivu slid pidhoditi same yak do masivu predkiv mozhlivo chastkovo stisnutogo Nova realizaciya operaciyi maye takij viglyad int find set int v if v parent v return v return parent v find set parent v Taka prosta realizaciya robit vse sho zadumuvalosya spochatku shlyahom rekursivnih viklikiv znahoditsya lidera mnozhini a potim v procesi rozkrutki steka cej lider prisvoyuyetsya parent posilannyami dlya vsih projdenih elementiv Realizuvati cyu operaciyu mozhna i ne rekursivno ale todi dovedetsya zdijsnyuvati dva prohodi po derevu pershij znajde shukanogo lidera drugij prostavit jogo vsim vershin shlyahu Vtim na praktici nerekursivna realizaciya ne daye istotnogo vigrashu Evristika ob yednannya za rangom Rozglyanemo inshu evristiku yaka sama po sobi zdatna priskoriti chas roboti algoritmu a v poyednanni z evristikoyu stisnennya shlyahiv i zovsim zdatna dosyagti praktichno konstantnogo chasu roboti na odin zapit v serednomu Cya evristika polyagaye v nevelikij zmini roboti union set yaksho v nayivnij realizaciyi te yake derevo bude priyednano do yakogo viznachayetsya vipadkovo to teper mi budemo ce robiti na osnovi rangiv Ye dva varianti rangovoyi evristiki v odnomu varianti rangom dereva nazivayetsya kilkist vershin v nomu v inshomu glibina dereva tochnishe verhnya mezha na glibinu dereva oskilki pri odnochasnomu zastosuvanni evristiki stisnennya shlyahiv realna glibina dereva mozhe zmenshuvatisya V oboh variantah sut evristiki odna j ta zh pri vikonanni union set budemo priyednuvati derevo z menshim rangom do dereva z velikim rangom void make set int v parent v v size v 1 void union sets int a int b a find set a b find set b if a b if size a lt size b swap a b parent b a size a size b Navedemo realizaciyu rangovoyi evristiki na osnovi glibini derev void make set int v parent v v rank v 0 void union sets int a int b a find set a b find set b if a b if rank a lt rank b swap a b parent b a if rank a rank b rank a Obidva varianti rangovoyi evristiki ye ekvivalentnimi z tochki zoru asimptotiki tomu na praktici mozhna zastosovuvati bud yaku z nih Ob yednannya evristik stisnennya shlyahu plyus rangova evristika Yak vzhe zgaduvalosya vishe spilne zastosuvannya cih evristik daye osoblivo garnij rezultat u pidsumku dosyagayuchi praktichno konstantnogo chasu roboti Dovedennya asimptotiki tut ne navoditsya cherez yih ob yemnist div napriklad Kormen Lejzerson Rivest Shtajn Vstup do algoritmiv Vpershe ce dovedennya bulo provedeno Tardzhanom 1975 r Ostatochnij rezultat takij pri spilnomu zastosuvanni evristik stisnennya shlyahu ta ob yednannya za rangom chas roboti na odin zapit vihodit O A n displaystyle O A n v serednomu de A n displaystyle A n obernena funkciya Akermana yaka zrostaye duzhe povilno nastilki povilno sho dlya vsih rozumnih obmezhen vona ne perevershuye 4 priblizno dlya n lt 10600 Same tomu pro asimptotiku roboti sistemi neperetinnih mnozhin dorechno govoriti majzhe konstantnij chas roboti Navedemo tut pidsumkovu realizaciyu sistemi neperetinnih mnozhin sho realizuye obidvi vkazani evristiki vikoristovuyetsya rangova evristika shodo glibin derev void make set int v parent v v rank v 0 int find set int v if v parent v return v return parent v find set parent v void union sets int a int b a find set a b find set b if a b if rank a lt rank b swap a b parent b a if rank a rank b rank a Zastosuvannya v zadachah i rizni polipshennyaStrukturi danih neperetinnih mnozhin modelyuyut rozbittya mnozhini Yak vidomo rozbittya mnozhini mozhna zadati za dopomogoyu zadannya na nij vidnoshennya ekvivalentnosti I navpaki kozhnomu vidnoshennyu ekvivalentnosti zadanomu na deyakij mnozhini vidpovidaye rozbittya ciyeyi mnozhini Ce oznachaye sho struktura danih sistema neperetinnih mnozhin shiroko vikoristovuyetsya Napriklad mozhna vidstezhuvati komponenti zv yazanosti neoriyentovanogo grafa Algoritm Union Find vikoristovuyetsya u visokoproduktivnih realizaciyah unifikaciyi U comu rozdili rozglyanuto kilka zastosuvan strukturi danih sistema neperetinnih mnozhin yak trivialnih tak i z vikoristannyam deyakih polipshen strukturi danih Pidtrimka komponent zv yaznosti grafa Ce odna z ochevidnih program strukturi danih sistema neperetinnih mnozhin yaka vochevid i stimulyuvala vivchennya ciyeyi strukturi Formalno zadachu mozhna sformulyuvati takim chinom spochatku zadanij porozhnij graf postupovo v cej graf mozhut dodavatisya vershini i neoriyentovani rebra a takozh nadhodyat zapiti chi v odnakovih komponentah zv yaznosti lezhat vershini i Bezposeredno zastosovuyuchi tut opisanu vishe strukturu danih mi otrimuyemo rishennya yake obroblyaye odin zapit na dodavannya vershini rebra abo zapit na perevirku dvoh vershin za majzhe konstantnij chas u serednomu Vrahovuyuchi sho praktichno take same zavdannya stavitsya pri vikoristanni algoritmu Kruskala znahodzhennya minimalnogo kistyakovogo dereva mi vidrazu zh otrimuyemo polipshenu versiyu cogo algoritmu sho pracyuye praktichno za linijnij chas Inodi na praktici zustrichayetsya invertovana versiya cogo zavdannya spochatku ye graf z yakimis vershinami i rebrami i nadhodyat zapiti na vidalennya reber Yaksho zavdannya dane v oflajn tobto mi zazdalegid mozhemo diznatisya vsi zapiti to virishuvati ce zavdannya mozhna takim chinom perevernemo zavdannya zadom napered budemo vvazhati sho u nas ye porozhnij graf v yakij mozhut dodavatisya rebra spochatku dodamo rebro ostannogo zapitu potim peredostannogo i t d Tim samim u rezultati invertuvannya cogo zavdannya mi prijshli do zvichajnoyi zadachi rishennya yakoyi opisuvalosya vishe Poshuk komponent zv yaznosti na zobrazhenni Div takozh Komponenta zv yaznosti grafa Odne z ochevidnih zastosuvan DSU polyagaye u virishenni takogo zavdannya ye zobrazhennya pikseliv spochatku vse zobrazhennya bile ale potim na nomu malyuyetsya dekilka chornih krapok Potribno viznachiti rozmir kozhnoyi biloyi komponenti zv yaznosti na pidsumkovomu zobrazhenni Dlya rishennya mi prosto perebirayemo vsi bili klitini zobrazhennya dlya kozhnoyi klitini perebirayemo yiyi chotiroh susidiv i yaksho susid tezh bilij to viklikayemo vid cih dvoh vershin Takim chinom u nas bude DSU z vershinami vidpovidnimi pikselyam zobrazhennya Otrimani v rezultati dereva DSU i ye shukani komponenti zv yaznosti Pidtrimka dodatkovoyi informaciyi dlya kozhnoyi mnozhini Sistema neperetinnih mnozhin dozvolyaye legko zberigati bud yaku dodatkovu informaciyu sho vidnositsya do mnozhini Prostij priklad ce rozmiri mnozhin yak yih zberigati bulo skazano pri opisi rangovoyi evristiki informaciya tam zapisuvalasya dlya potochnogo lidera mnozhini Takim chinom razom z liderom kozhnoyi mnozhini mozhna zberigati bud yaku dodatkovu neobhidnu v konkretnomu zavdanni informaciyu Vtim take rishennya za dopomogoyu sistemi neperetinnih mnozhin ne maye zhodnih perevag pered rishennyam z dopomogoyu obhodu v glibinu Zastosuvannya DSU dlya stisnennya stribkiv po vidrizku Zavdannya pro zafarbuvannya pidvidrizkiv v oflajni Odne z poshirenih zastosuvan DSU polyagaye v tomu sho yaksho ye nabir elementiv i z kozhnogo elementa vihodit po odnomu rebru tozh mi mozhemo shvidko za majzhe konstantnij chas znahoditi kincevu tochku v yaku mi potrapimo yaksho budemo ruhatisya vzdovzh reber iz zadanoyi pochatkovoyi tochki Naochnim prikladom cogo zastosuvannya ye zavdannya pro farbuvannya pidvidrizkiv ye vidrizok dovzhini L kozhna klitinka yakogo tobto kozhen shmatochok dovzhini 1 maye nulovij kolir Nadhodyat zapiti vidu l r c perefarbuvati vidrizok l r v kolir c Potribno znajti pidsumkovij kolir kozhnoyi klitinki Zapiti vvazhayutsya vidomimi zazdalegid tobto zavdannya v oflajni Dlya rishennya mi mozhemo zavesti DSU strukturu yaka dlya kozhnoyi klitini bude zberigati posilannya na najblizhchu sprava nepofarbovanu klitinku Takim chinom spochatku kozhna klitinka vkazuye na samu sebe a pislya farbuvannya pershogo pidvidrizka klitinka pered pochatkom pidvidrizka bude vkazuvati na klitinku pislya kincya pidvidrizka Teper shob virishiti zavdannya rozglyadayemo zapiti perefarbovuvannya u zvorotnomu poryadku vid ostannogo do pershogo Dlya vikonannya zapitu kozhnogo razu z dopomogoyu nashogo DSU znahodimo najlivishu nepofarbovanu klitinku vseredini vidrizka perefarbovuyem yiyi i perekidayemo pokazhchik z neyi na nastupnu sprava porozhnyu klitinku Takim chinom tut faktichno vikoristovuyemo DSU z evristikoyu stisnennya shlyahiv ale bez rangovoyi evristiki tomu nam vazhlivo hto stane liderom pislya ob yednannya Otzhe pidsumkova asimptotika sklade O l o g N displaystyle O logN na zapit utim z malenkoyu u porivnyanni z inshimi strukturami danih konstantoyu Realizaciya void init for int i 0 i lt L i make set i void process query int l int r int c for int v l v find set v if v gt r break answer v c parent v v 1 Utim mozhna realizuvati ce rishennya z rangovoyu evristikoyu budemo zberigati dlya kozhnoyi mnozhini v deyakomu masivi end de mnozhina zakinchuyetsya tobto samu pravu tochku Todi mozhna bude ob yednuvati dvi mnozhini v odnu za yih rangovoyu evristikoyu prostavlyayuchi potim otrimanu novu pravu mezhu Pidtrimka vidstanej do lideraInodi v konkretnih programah sistemi neperetinnih mnozhin z yavlyayetsya vimoga pidtrimuvati vidstan do lidera tobto dovzhinu shlyahu v rebrah u derevi vid potochnoyi vershini do korenya dereva Yakbi ne bulo evristiki stisnennya shlyahiv to niyakih skladnoshiv ne vinikalo b vidstan do korenya prosto dorivnyuvalo b chislu rekursivnih viklikiv yaki zrobila funkciya find set Prote v rezultati stisnennya shlyahiv kilka reber shlyahu mogli stisnutisya v odne rebro Takim chinom razom z kozhnoyu vershinoyu dovedetsya zberigati dodatkovu informaciyu dovzhinu potochnogo rebra z vershini v predka Pri realizaciyi zruchno uyavlyati sho masiv parrent i funkciya find set teper povertayut ne odne chislo a paru chisel vershinu lidera i vidstan do neyi void make set int v parent v make pair v 0 rank v 0 pair lt int int gt find set int v if v parent v first int len parent v second parent v find set parent v first parent v second len return parent v void union sets int a int b a find set a first b find set b first if a b if rank a lt rank b swap a b parent b make pair a 1 if rank a rank b rank a Pidtrimka parnosti dovzhini shlyahu i zavdannya pro perevirku dvochastkovogo grafa v onlajn Za analogiyeyu z dovzhinoyu shlyahu do lidera tak samo mozhna pidtrimuvati parnist dovzhini shlyahu do nogo Chomu zh ce zastosuvannya bulo vidileno v okremij punkt Sprava v tomu sho zazvichaj vimoga zberigannya parnosti shlyahu splivaye u zv yazku z nastupnoyu zadacheyu spochatku dan porozhnij graf v nogo mozhut dodavatisya rebra a takozh nadhoditi zapiti vidu chi ye komponenta zv yaznosti sho mistit danu vershinu dvochastkovoyu Dlya virishennya cogo zavdannya mi mozhemo zavesti sistemu neperetinnih mnozhin dlya zberigannya komponent zv yaznosti i zberigati parnist dovzhini shlyahu kozhnoyi vershini do yiyi lidera Tim samim mi mozhemo shvidko pereviryati chi prizvede dodavannya zaznachenogo rebra do porushennya dvochastkovogo grafa chi ni a same yaksho kinci rebra lezhat v odnij i tij zhe komponenti zv yaznosti i pri comu mayut odnakovi parnosti dovzhini shlyahu do lidera to dodavannya cogo rebra prizvede do utvorennya ciklu neparnoyi dovzhini i peretvorennyu potochnoyi komponenti v nedvudolnu Golovna skladnist z yakoyu mi stikayemosya pri comu ce te sho mi povinni akuratno z urahuvannyam parnosti provoditi ob yednannya dvoh derev u funkciyi union sets Yaksho mi dodayemo rebro a b sho pov yazuye dvi komponenti zv yaznosti v odin to pri priyednanni odnogo dereva do inshogo mi povinni vkazati jomu taku parnist shob v rezultati u vershin a i b vihodili b rizni parnosti dovzhini shlyahu Vivedemo formulu za yakoyu povinna vihoditi cya parnist sho vistavlyayetsya lideru odniyeyi mnozhini pri priyednanni jogo do lidera inshoyi Poznachimo cherez x parnist dovzhini shlyahu vid vershini a do lidera yiyi mnozhini cherez y parnist dovzhini shlyahu vid b vershini do lidera yiyi mnozhini a cherez t shukanu parnist yaku mi povinni postaviti priyednuvanomu lideru Yaksho mnozhina z vershinoyu a priyednuyetsya do mnozhini z vershinoyu b stayuchi pidderevom to pislya priyednannya u vershini b yiyi parnist ne zminitsya i zalishitsya rivnoyu y a u vershini a parnist stane rivnoyu a b displaystyle a oplus b simvolom displaystyle oplus tut poznachena operaciya XOR simetrichna riznicya Nam potribno shob ci dvi parnosti rozriznyalisya tobto yih XOR dorivnyuvav odinici Tobto otrimuyemo rivnyannya na t x y t displaystyle x oplus y oplus t virishuyuchi yake znahodimo t x y 1 displaystyle t x oplus y oplus 1 Takim chinom nezalezhno vid togo yaka mnozhina priyednuyetsya do yakoyi treba vikoristovuvati zaznachenu formulu dlya zadannya parnosti rebra provedenogo z odnogo lidera do inshogo Navedemo realizaciyu DSU z pidtrimkoyu parnosti Yak i v poperednomu punkti z metoyu zruchnosti mi vikoristovuyemo pari dlya zberigannya predkiv i rezultatu operaciyi find set Krim togo dlya kozhnoyi mnozhini mi zberigayemo v masivi bipartite chi ye vono vse she dvochastkovim chi ni void make set int v parent v make pair v 0 rank v 0 bipartite v true pair lt int int gt find set int v if v parent v first int parity parent v second parent v find set parent v first parent v second parity return parent v void add edge int a int b pair lt int int gt pa find set a a pa first int x pa second pair lt int int gt pb find set b b pb first int y pb second if a b if x y bipartite a false else if rank a lt rank b swap a b parent b make pair a x y 1 if rank a rank b rank a bool is bipartite int v return bipartite find set v first Algoritm znahodzhennya RMQ minimumu na vidrizku v oflajni Formalno zavdannya stavitsya tak potribno realizuvati strukturu danih yaka pidtrimuye dva vidi zapitiv dodavannya vkazanogo chisla i n s e r t i i 1 n displaystyle insert i i 1 n i poshuk i vityag potochnogo minimalnogo chisla e x t r a c t m i n displaystyle extractmin Budemo vvazhati sho kozhne chislo dodayetsya rivno odin raz Krim togo vvazhayemo sho vsya poslidovnist zapitiv vidoma nam zazdalegid tobto zavdannya v oflajni Ideya rishennya taka Zamist togo shob po cherzi vidpovidati na kozhen zapit pereberemo chislo i 1 n displaystyle i 1 n i viznachimo vidpoviddyu na yakij zapit ce chislo povinne buti Dlya cogo nam treba znajti pershij bez vidpovidi zapit sho jde pislya dodavannya i n s e r t i displaystyle insert i cogo chisla legko zrozumiti sho ce i ye toj zapit vidpoviddyu na yakij ye chislo Takim chinom tut vihodit ideya shozha na zavdannya pro farbuvannya vidrizkiv Mozhna otrimati rishennya za v serednomu O log N displaystyle O log N na zapit yaksho mi vidmovimosya vid rangovoyi evristiki i budemo prosto zberigati v kozhnomu elementi posilannya na najblizhchij sprava zapit e x t r a c t m i n displaystyle extract min i vikoristovuvati stisnennya shlyahiv dlya pidtrimki cih posilan pislya ob yednan Takozh mozhna otrimati rishennya i za O a n displaystyle O a n yaksho mi budemo vikoristovuvati rangovu evristiku i budemo zberigati v kozhnij mnozhini nomer poziciyi de vona zakinchuyetsya te sho v poperednomu varianti rishennya dosyagalosya avtomatichno za rahunok togo sho posilannya zavzhdi jshli tilki vpravo teper treba bude zberigati yavno IstoriyaU toj chas yak ideyi vikoristani v sistemi neperetinnih mnozhin buli davno znajomi Robert Tardzhan buv pershim hto doviv verhnyu mezhu i obmezhenu versiyu nizhnoyi mezhi u terminah zvorotnoyi funkciyi Akkermana u 1975 roci Do cogo najkrashoyu mezheyu na chas roboti za operaciyu buv dovedenij Gopkroftom i Ulmanom O log n displaystyle O log n povtornij logarifm n insha povilno zrostayucha funkciya ale zrostayucha ne tak povilno yak zvorotnya funkciya Akkermana Tardzhan i en takozh rozrobili odnoprohidnij algoritm poshuku yakij ye bilsh efektivnim na praktici zberigayuchi tu zh skladnist u girshomu vipadku U 2007 roci Silven Konshon i Zhan Kristof Filyat rozrobili variant napiv en dlya sistemi neperetinnih mnozhin sho dozvoliv efektivno zberegti poperedni versiyi strukturi ta bulo dovedeno jogo pravilnist za dopomogoyu zasobu dovedennya teorem Coq Div takozhRozbittya mnozhini en en PosilannyaKnight Kevin 1989 Unification A multidisciplinary survey PDF ACM Computing Surveys 21 93 124 doi 10 1145 62029 62030 S2CID 14619034 Efficiency of a Good But Not Linear Set Union Algorithm Journal of the ACM 22 2 215 225 doi 10 1145 321879 321884 a href wiki D0 A8 D0 B0 D0 B1 D0 BB D0 BE D0 BD Cite journal title Shablon Cite journal cite journal a Cite maye pusti nevidomi parametri 1 2 3 ta 4 dovidka Set Merging Algorithms SIAM Journal on Computing 2 4 294 303 doi 10 1137 0202024 a href wiki D0 A8 D0 B0 D0 B1 D0 BB D0 BE D0 BD Cite journal title Shablon Cite journal cite journal a Cite maye pustij nevidomij parametr 1 dovidka Worst case analysis of set union algorithms Journal of the ACM 31 2 1984 245 281 doi 10 1145 62 2160 a href wiki D0 A8 D0 B0 D0 B1 D0 BB D0 BE D0 BD Citation title Shablon Citation citation a Cite maye pusti nevidomi parametri 1 ta 2 dovidka A Persistent Union Find Data Structure ACM SIGPLAN Workshop on ML Freiburg Germany a href wiki D0 A8 D0 B0 D0 B1 D0 BB D0 BE D0 BD Citation title Shablon Citation citation a first z propushenim last dovidka Cite maye pusti nevidomi parametri 1 ta 2 dovidka DzherelaTomas Kormen Charlz Lejzerson Ronald Rivest Kliford Stajn 2009 1990 10 Elementarni strukturi danih Vstup do algoritmiv vid 3rd MIT Press i McGraw Hill ISBN 0 262 03384 4 C implementation chastina Boost C bibliotek A Java implementation with an application to color image segmentation Statistical Region Merging SRM IEEE Trans Pattern Anal Mach Intell 26 11 1452 1458 2004 Java applet A Graphical Union Find Implementation vid Rory L P McGuire Wait free Parallel Algorithms for the Union Find Problem stattya 1994 roku vid Richard J Anderson i Heather Woll opisuye rozparalelenu versiyu operacij Union Find yaki ne potrebuyut blokuvannya Realizaciya na pajtoni Vizualne poyasnennya i C kod https www geeksforgeeks org disjoint set data structures