Офлайновий алгоритм Тарджана для пошуку найменшого спільного предка — Вікіпедія
Офлайновий алгоритм Тарджана для пошуку найменшого спільного предка — алгоритм для знаходження найменшого спільного предка пари вузлів у дереві. Він названий на честь Роберта Андре Тарджана, який відкрив цей алгоритм у 1979 році. Алгоритм Тарджана не є алгоритмом реального часу, тобто, він вимагає, щоб усі пари вузлів, для яких потрібно знайти найменший спільний предок, були вказані заздалегідь.
Формальне визначення завдання[ред. | ред. код]
Дано дерево з вершинами і дано запитів виду (,), потрібно для кожного запиту (,) знайти найменшого спільного предка, тобто, таку вершину , яка найбільш віддалена від кореня дерева і при цьому є предком для обох вершин та .
Алгоритм[ред. | ред. код]
Опис[ред. | ред. код]
Основою для алгоритму є структура даних «система неперетинних множин», яка і була винайдена Тарджаном.
Алгоритм фактично являє собою обхід у глибину із кореня дерева, в процесі якого поступово знаходяться відповіді на запити. А саме, відповідь на запит знаходиться, коли обхід у глибину обробляє вершину , а вершина вже була відвідана, або навпаки.
Підвісимо наше дерево за будь-яку вершину, і запустимо обхід у глибину з неї. Нехай обхід дерева у глибину знаходиться в деякій вершині . Помістимо її в окремий клас в структурі неперетинних множин, . Як завжди, в обході у глибину, перебираємо усі вихідні ребра . Для кожного такого запускаємо обхід у глибину із цієї вершини, а потім додаємо цю вершину разом з її піддеревом в клас вершини . Це реалізується операціями структури даних «система неперетинних множин», присвоюванням для представника множини (так як після об'єднання представник класу міг змінитися). Після обробки всіх ребер ми перебираємо всі запити виду , і якщо вершина була позначена як відвідана обходом у глибину, то відповіддю на цей запит буде вершина . Очевидно, що для кожного запиту ця умова (що одна вершина запиту оброблюється обходом у глибину, а друга була відвідана раніше) виповниться рівно один раз.
Псевдокод[ред. | ред. код]
Псевдокод нижче визначає найменший спільний предок для кожної пари із , задано корінь дерева у якому діти вузла зберігаються у множині . Для цього алгоритму, множина повинна бути вказана заздалегідь. В процедурі використовуються MakeSet, Find та Union функції системи неперетинних множин. розміщує елемент в нову множину, що складається з одного нього, повертає представника множини, у якій міститься , створює нову множину, яка є об'єднанням множин, які містять і .
function TarjanOLCA(u) is MakeSet(u) u.ancestor := u for each v in u.children do TarjanOLCA(v) Union(u, v) Find(u).ancestor := u u.color := black for each v such that {u, v} in P do if v.color == black then print "Tarjan's Lowest Common Ancestor of " + u + " and " + v + " is " + Find(v).ancestor + "."
Нижче наведено оптимізовані версії функцій MakeSet , Union і Find(використано евристику стиснення шляху та евристику об'єднання за рангом(в наведеному нижче псевдокоді рангову евристику реалізовано на основі глибини дерев)).
function MakeSet(x) is x.parent := x x.rank := 1 function Union(x, y) is xRoot := Find(x) yRoot := Find(y) if xRoot.rank > yRoot.rank then yRoot.parent := xRoot else if xRoot.rank < yRoot.rank then xRoot.parent := yRoot else if xRoot.rank == yRoot.rank then yRoot.parent := xRoot xRoot.rank := xRoot.rank + 1 function Find(x) is if x.parent != x then x.parent := Find(x.parent) return x.parent
Приклад реалізації мовою С++[ред. | ред. код]
#include <iostream> #include <vector> using namespace std; const int N = 100001; // N - максимальна кількість вершин у дереві vector < int > g[N], q[N]; int ancestor[N], parent[N], r[N]; bool visited[N]; void MakeSet(int x) { parent[x] = x; r[x] = 1; } int FindSet(int x ) { if (x == parent[x]) return x; return parent[x] = FindSet(parent[x]); } void Union(int x, int y) { int xRoot = FindSet(x), int yRoot = FindSet(y); if (r[xRoot] < r[yRoot]) swap(xRoot, yRoot); parent[yRoot] = xRoot; r[xRoot] += r[yRoot]; } void TarjanLCA(int v , int p) { MakeSet(v); ancestor[v] = v; for (int i = 0; i < g[v].size(); i++) if (g[v][i] != p ) { TarjanLCA(g[v][i] , v); Union(g[v][i], v); ancestor[FindSet(v)] = v; } visited[v] = true; for (int i = 0; i < q[v].size(); i++) if (visited[q[v][i]]) cout << "Tarjan's Lowest Common Ancestor of " << v << " and " << q[v][i] << " is " << ancestor[FindSet(q[v][i])] << '/n'; } int main() { // Тут зчитуємо структуру графу та запити (звідки-небудь, наприклад, з файлу). TarjanLCA(1 , -1); //вважаємо , що коренем дерева є вершина під номером 1 }
Оцінка складності алгоритму[ред. | ред. код]
Оцінка складності алгоритму складається із декількох частин.
- Обхід у глибину виконується за .
- Операції по об'єднанню множин, які в сумі працюють за , де — обернена функція Акермана, яка зростає дуже повільно, настільки повільно, що для всіх розумних обмежень вона не перевершує 4 (приблизно для ). Саме тому про асимптотику роботи системи неперетинних множин доречно говорити «майже константний час роботи» — . Кожний запит буде оброблений рівно один раз, тому можна вважати, що всі запити обробляються сумарно за .
- Для кожного запиту перевірка умови і визначення результату для всіх розумних виконується за .
Отже, складність алгоритму — , що при достатньо великих складає на один запит.
Література[ред. | ред. код]
- Наименьший общий предок. Нахождение за O(1) в оффлайн (алгоритм Тарьяна)(рос.)
- Алгоритм Тарьяна поиска LCA за O(1) в оффлайн (рос.)
- Tarjan's off-line lowest common ancestors algorithm (англ.)
- Gabow, H. N.; Tarjan, R. E. (1983), A linear-time algorithm for a special case of disjoint set union, Proceedings of the 15th ACM Symposium on Theory of Computing (STOC), с. 246—251, doi:10.1145/800061.808753.
- Tarjan, R. E. (1979), Applications of path compression on balanced trees, Journal of the ACM, 26 (4): 690—715, doi:10.1145/322154.322161.
Див. також[ред. | ред. код]
|