🔗 Система неперетинних множин — стиснення шляху
Об'єднуйте елементи в неперетинні множини та знаходьте їхні корені майже за сталий час. Об'єднання за рангом і стиснення шляху сплющують ліс, даючи оцінку оберненої функції Аккермана α(n).
Про Union-Find (неперетинні множини)
Структура даних Union-Find, також звана системою неперетинних множин (Disjoint Set Union, DSU), ефективно підтримує розбиття n елементів на неперетинні множини та підтримує дві операції: Union (об'єднати дві множини) і Find (визначити, якій множині належить елемент). З двома оптимізаціями — об'єднанням за рангом (або розміром) і стисненням шляху — обидві операції досягають амортизованої часової складності O(α(n)) на виклик, де α — надзвичайно повільно зростаюча обернена функція Аккермана. Для всіх практичних n α(n) ≤ 4, що робить Union-Find практично сталим за часом.
Симуляція дозволяє додавати ребра до графа по одному й спостерігати, як зв'язні компоненти об'єднуються в реальному часі. Ви можете вмикати й вимикати стиснення шляху, щоб порівняти висоти дерев, спостерігати, як об'єднання за рангом тримає дерева неглибокими, і рахувати загальну кількість оновлень вказівників, потрібних для обробки послідовності операцій Union.
Часті запитання
Що робить стиснення шляху?
Під час операції Find стиснення шляху змушує кожен вузол на шляху від елемента x до кореня вказувати безпосередньо на корінь. Це сплющує дерево, тож майбутні виклики Find для тих самих елементів виконуються за O(1). Без стиснення шляху, але з об'єднанням за рангом, Find має складність O(log n); разом вони досягають амортизованого O(α(n)), що довели Тар'ян і ван Леувен 1984 року.
Що таке об'єднання за рангом (об'єднання за розміром)?
Об'єднання за рангом завжди приєднує корінь нижчого дерева під корінь вищого дерева, тримаючи максимальну висоту дерева на рівні O(log n) навіть без стиснення шляху. Об'єднання за розміром — варіант, що відстежує кількість елементів, а не висоту; обидва досягають однакової асимптотичної межі. Без жодної з цих евристик послідовність n операцій Union може створити ланцюг висотою n, погіршуючи Find до O(n).
Що таке обернена функція Аккермана і чому вона важлива?
Функція Аккермана A(k, k) зростає швидше за будь-яку примітивно-рекурсивну функцію; тому її обернена функція α(n) надзвичайно мала. Для n = 2^65536 α(n) = 5. Це означає, що для будь-якого розміру вхідних даних, що трапляється на практиці, амортизована вартість Union і Find становить менше 5 операцій — практично сталу. Цю межу довів Тар'ян 1975 року.
Як Union-Find використовується в алгоритмі МОД Крускала?
Алгоритм Крускала будує мінімальне остовне дерево, сортуючи ребра за вагою та додаючи кожне ребро, якщо воно з'єднує два різні компоненти (виявляється за допомогою Find), а тоді об'єднує ці компоненти (Union). З Union-Find кожна з O(E) перевірок ребер коштує O(α(V)), даючи загальну складність O(E log E), домінує сортування.
Чи може Union-Find виявляти цикли в графі?
Так. Перед додаванням ребра (u, v) до графа викличте Find(u) і Find(v). Якщо вони повертають однаковий корінь, u і v уже в одному компоненті, і додавання ребра створить цикл. Саме так алгоритм Крускала уникає циклів. Перевірка коштує O(α(n)) амортизовано, що набагато дешевше за перевірку циклу через DFS на всьому графі.
Які є інші застосування Union-Find?
Union-Find використовується в запитах про мережеву зв'язність, сегментації зображень (об'єднання пікселів однієї ділянки), симуляції перколяції (для вивчення фазових переходів у фізиці), реалізації компіляторів (об'єднання класів еквівалентності) та в онлайн-алгоритмах динамічної зв'язності. Це також ключовий примітив в аналізі соціальних мереж для обчислення зв'язних компонентів.
Чи існує версія Union-Find, що підтримує розділення множин?
Ні — стандартний Union-Find підтримує лише об'єднання, але не розділення. Це фундаментальне обмеження: ефективні операції «зв'язати» й «знайти» разом із «розрізати» (розділенням) вимагають складніших структур, таких як link-cut дерева (також розроблені Тар'яном), що підтримують усі три операції за амортизований час O(log n).
Яка різниця між об'єднанням за рангом і об'єднанням за розміром?
Об'єднання за рангом відстежує верхню межу висоти кожного дерева. Об'єднання за розміром відстежує точну кількість вузлів. Обидва тримають дерева неглибокими й досягають однакової амортизованої межі O(α(n)) зі стисненням шляху. Об'єднання за розміром трохи легше реалізувати правильно (ранг може стати завищеним після стиснення шляху), але обидва є стандартними. Більшість реалізацій у спортивному програмуванні використовують об'єднання за розміром.
Як Union-Find розв'язує задачу перколяції?
У перколяції сітка n×n ділянок відкривається випадково; питання полягає в тому, чи з'єднує шлях з відкритих ділянок верхній рядок із нижнім. Union-Find використовується з двома віртуальними вузлами (верхнім і нижнім), з'єднаними з усіма відкритими ділянками верхнього й нижнього рядків відповідно. З'єднання від верху до низу (однаковий корінь) сигналізує про перколяцію. Симуляція Монте-Карло показує, що поріг становить приблизно 0,593 для квадратної сітки.
Часті запитання
Що таке структура даних union-find?
Union-find, також звана системою неперетинних множин (disjoint set union, DSU), підтримує колекцію множин, що не перетинаються. Вона підтримує дві основні операції: find, яка повертає представника (корінь) множини, що містить елемент, і union, яка об'єднує дві множини, що містять два елементи. Два елементи належать до однієї множини саме тоді, коли вони мають однаковий корінь.
Як union-find представлена внутрішньо?
Кожна множина зберігається як укорінене дерево всередині одного масиву parent[]. Кожен елемент вказує на свого батька, а корінь вказує сам на себе. Тому вся структура є лісом дерев, по одному дереву на кожну неперетинну множину. Щоб перевірити зв'язність, ви піднімаєте кожен елемент до його кореня і порівнюєте два корені.
Що робить об'єднання за рангом?
Об'єднання за рангом приєднує коротше дерево під корінь вищого дерева, тримаючи дерева неглибокими. Ранг — це верхня межа висоти дерева. Коли два корені мають однаковий ранг, один стає дитиною іншого, а ранг кореня, що залишився, збільшується на одиницю. Це не дає лісу вироджуватися в довгий ланцюг.
Що таке стиснення шляху?
Стиснення шляху застосовується під час find: після знаходження кореня кожен відвіданий по дорозі вузол перенаправляється безпосередньо на цей корінь. Це сплющує дерево, тож майбутні запити для цих вузлів виконуються майже миттєво. Симуляція анімує це, підсвічуючи пройдений шлях, а тоді перемальовуючи зі стисненими вказівниками.
Чому union-find майже O(1) на операцію?
З об'єднанням за рангом і стисненням шляху разом послідовність m операцій на n елементах виконується за O(m·α(n)) часу, де α — обернена функція Аккермана. α(n) зростає настільки повільно, що вона менша за 5 для будь-якого практичного n, тож кожна операція фактично стала, хоча строго не є O(1).
Що таке обернена функція Аккермана α(n)?
Функція Аккермана зростає астрономічно швидко, тому її обернена функція α(n) зростає астрономічно повільно. Для будь-якого розміру вхідних даних, що може вміститися в спостережуваному Всесвіті, α(n) не перевищує 4. Саме тому амортизовану вартість union-find з обома оптимізаціями на практиці вважають практично сталою.
Як union-find рахує зв'язні компоненти?
Кількість неперетинних множин дорівнює кількості коренів у лісі. Починаємо з n одноелементних множин, тобто n компонентів. Кожне успішне об'єднання двох різних множин зменшує кількість компонентів рівно на один. Це робить union-find ефективним способом відстежувати зв'язність графа при додаванні ребер.
Як union-find використовується в алгоритмі МОД Крускала?
Алгоритм мінімального остовного дерева Крускала сортує ребра за вагою й додає кожне ребро, лише якщо його кінці належать різним множинам, що перевіряється за допомогою find. Додавання ребра виконує union. Union-find робить це виявлення циклів майже сталим за часом, тому Крускал виконується за O(E log E), що домінується сортуванням.
Чи може union-find допомогти генерувати лабіринти?
Так. Рандомізована версія алгоритму Крускала будує ідеальні лабіринти: почніть із того, що кожна клітинка — власна множина, а стіни скрізь, тоді повторно зносьте випадкову стіну, лише якщо дві клітинки, які вона розділяє, належать різним множинам, об'єднуючи їх. Це гарантує повністю зв'язний лабіринт без петель.
Що станеться, якщо об'єднати два елементи, які вже в одній множині?
Якщо find(a) і find(b) повертають однаковий корінь, елементи вже з'єднані, тож union нічого структурно не робить, а кількість компонентів не змінюється. Надійна реалізація виявляє це заздалегідь і пропускає оновлення рангу, уникаючи зайвої роботи, зберігаючи ліс коректним.