Тема № 8. ОСТОВІ ДЕРЕВА МІНІМАЛЬНОЇ ВАГИ Багато речей нам незрозумілі не тому, що наші поняття слабкі; але тому, що ці речі не входять у коло наших понять. ( Козьма Прудков )
8. 1. Мінімальні покриваючі дерева • Нехай дані n контактів на друкованій платі, які ми хочемо з'єднати. Для цього досить використовувати n ‑ 1 проводів, кожний з яких з'єднує два контакти. При цьому ми звичайно прагнемо зробити сумарну довжину проводів якнайменше. • Спрощуючи ситуацію, можна сформулювати задачу так. Нехай є зв'язний неорієнтований граф G = (V, Е), у якому V — множина контактів, а E — множина їх можливих попарних з'єднань. Для кожного ребра графа (u, v) задана вага w(u, v) (довжина проведення, необхідного для з'єднання u і v). Завдання полягає в знаходженні підмножини Т Е, що зв'язує всі вершини, для якого сумарна вага • w(T) = Σ w(u, v) мінімальна.
8. 1. Мінімальні покриваючі дерева • Така підмножина Т буде деревом (оскільки не має циклів: у будь-якому циклі один із проводів можна видалити, не порушуючи зв'язності). Зв'язний ациклічний підграфа G, що є деревом і містить всі його вершини, називають покриваючим деревом (spanning tree) цього графа. (Іноді використовують термін "остове дерево"; для стислості ми будемо говорити просто "остов". ) • Далі ми розглянемо задачу про мінімальне покриваюче дерево ( minimum-spanning-tree problem). Тут слово "мінімальне" означає дерево, що має мінімально можливу вагу.
8. 1. Мінімальні покриваючі дерева
8. 1. Мінімальні покриваючі дерева На кожному ребрі графа зазначена вага. Виділено ребра мінімального покриваючого дерева. Таке дерево не єдине: заміняючи ребро (b, с) ребром (а, h), одержуємо інше дерево тої ж ваги 37. Розглянемо два способи рішення задачі про мінімальне покриваюче дерево: алгоритми Крускала й Прима. Кожний з них легко реалізувати за часом роботи O(E log 2 V), використовуючи звичайні двійкові купи. Застосувавши фібоначчієві купи, можна скоротити час роботи алгоритму Прима до O(E+V log 2 V) (що менше Е log 2 V, якщо |V| багато менше |Е|). Обидва алгоритми використовують пародигму "жадібної" стратегії: на кожному кроці вибирається "локально найкращий" варіант. Не для всіх задач такий вибір приведе до оптимального рішення, але для задачі про покриваюче дерево це так. Тут буде описана загальна схема алгоритму побудови мінімального кістяка (додавання ребер одного за іншим). Надалі будуть зазначені дві конкретних реалізації загальної схеми.
8. 2. Побудова мінімального остова • Нехай даний зв'язний неорієнтований граф G = (V, Е) і вагарня функцією w: Е R. Будемо шукати мінімальне покриваюче дерево (остов), виходячи з жадібної стратегії. • Загальна схема алгоритму буде така. Шуканий кістяк будується поступово: до спочатку порожньої множини А на кожному кроці додається одне ребро. Множина А завжди є підмножиною деякого мінімального кістяка. Ребро (u, v), що додається на черговому кроці, вибирається так, щоб не порушити цієї властивості: А {(u, v)} теж повинне бути підмножиною мінімального кістяка. Ми називаємо таке ребро безпечним ребром (safe edge) для А.
8. 2. Побудова мінімального остова • • • Generic-MST(G, w) 1 А←Ø 2 while A не є кістяком 3 do знайти безпечне ребро (u, v) для А 4 А ← A U{(u, v)} 5 return A
8. 2. Побудова мінімального остова
8. 2. Побудова мінімального остова • (а) Вершини множини S зображені чорними, його доповнення VS - білим. Ребра, що перетинають розріз, з'єднують білі вершини із чорними. Єдине легке ребро, що перетинає розріз - ребро (d, с). Множина А складається із сірих ребер. Розріз (s, V S) погоджений з А (жодне ребро з А не перетинає розріз). • (б) Вершини множини S зображені ліворуч, вершини V S - праворуч. Ребро перетинає розріз, якщо воно перетинає вертикальну пряму. • По визначенню безпечного ребра властивість "А є підмножиною деякого мінімального кістяка" (для порожньої множини ця властивість, мабуть, виконано) залишається істиним для будь-якого числа ітерацій циклу, так що в рядку 5 алгоритм видає мінімальний кістяк. Звичайно, головне питання в тім, як шукати безпечне ребро в рядку 3. Таке ребро існує (якщо А є підмножиною мінімального кістяка, те будь-яке ребро цього кістяка, що не входить в А, є безпечним). Помітимо, що множина А не може містити циклів (оскільки є частиною мінімального кістяка). Ребро, що додається тому в рядку 4, з'єднує різні компоненти графа Ga = (V, A), і з кожною ітерацією циклу число компонент зменшується на 1. Спочатку кожна вершина являє собою окремий компонент; наприкінці весь кістяк — один компонент, так що цикл повторюється |V| — 1 раз.
8. 2. Побудова мінімального остова • Приведемо правило відшукання безпечних ребер (теорема 8. 1). Почнемо з визначень. Розрізом (cut) (S, V S) неорієнтованого графа G = (V, Е) називається розбивка множини його вершин на дві підмножини. Говорять, що ребро (u, v) Є Е перетинає (crosses) розріз (S, V S), якщо один з його кінців лежить в S, а іншої — в V S. Розріз погоджений із множиною ребер A (respects the set А), якщо жодне ребро з А не перетинає цей розріз. Серед ребер, що перетинають розріз, виділяють ребра найменшої ваги , називаючи їхніми легкими (light edges).
8. 2. Побудова мінімального остова • Теорема 8. 1. Нехай G = (V, Е) — зв'язний неорієнтований граф, на множині вершин якого визначена дійсна функція w. Нехай А — множина ребер, що є підмножиною деякого мінімального кістяка графа G. Нехай (S, V S) — розріз графа G, погоджений з А, а (u, v) — легке ребро для цього розрізу. Тоді ребро (u, v) є безпечним для А. • Наслідок. Нехай G = (V, Е) — зв'язний неорієнтований граф і на множині Е визначена дійсна функція w. Нехай А — множина ребер графа, що є підмножиною деякого мінімального кістяка. Розглянемо ліс Ga = (V, A). Нехай дерево С — один зі зв'язних компонентів лісу Ga. Розглянемо всі ребра графа, що з'єднують вершини із С з вершинами не із С, і візьмемо серед них ребро найменшої ваги. Тоді це ребро безпечне для А. Очевидно: розріз (С, V C) погоджений з A, а ребро (u, v) — легке ребро для цього розрізу.
8. 3. Опис алгоритмів знаходження min покриваючого дерева • Обидва цих алгоритми додержуються описаної схеми, але по-різному вибирають безпечне ребро. В алгоритмі Крускала множина ребер А являє собою ліс, що складається з декількох зв'язних компонентів (дерев). Додається ребро мінімальної ваги серед всіх ребер, кінці яких лежать у різних компонентах. В алгоритмі Прима множина А являє собою єдине дерево, Безпечне ребро, що додається до А, вибирається як ребро найменшої ваги, що з'єднує це вже побудоване дерево з деякою новою вершиною.
Алгоритм Крускала • У будь-який момент роботи алгоритму Крускала множина А обрані ребра (частина майбутнього кістяка) не містить циклів. Серед всіх ребер, що з'єднують вершини з різних компонентів, береться ребро найменшої ваги. Треба перевірити, що воно є безпечним. • Нехай (u, v) — таке ребро, що з'єднує вершини з компонентів С 1 і C 2. Це ребро є легким ребром для розрізу (С 1, V C 1), і можна скористатися теоремою 4 (або її наслідком). • Реалізація алгоритму Крускала використовує структури даних для непересічних множин. Елементами множин є вершини графа. Нагадаємо, що Find-Set(u) повертає представника множини, що містить елемент u. Дві вершини u і v належать одній множині (компоненту), якщо Find-Set(u) = Find-Set(v). Об'єднання дерев виконується процедурою Union. (Строго говорячи, процедурам Find-Set і Union повинні передаватися покажчики на u і v )
Алгоритм Крускала • • • MST-Kruskal(G, w) 1 A←Ø 2 for кожні вершини v Є V[G] 3 do Make-Set(v) 4 упорядкувати ребра Е по вагам 5 for (u, v) Є E (у порядку зростання ваги) 6 do if Find-Set(u) ≠Find-Set(v) 7 then A : = A U{(u, v)} 8 Union(u, v) 9 return A
Алгоритм Крускала На Рис 8. 4 показано роботу алгоритму. Спочатку (рядка 1 -3) множина А порожня, і є |V| дерев, кожне з яких містить по одній вершині. У рядку 4 ребра з Е впорядковуються по вазі. У циклі (рядка 5 -8) ми перевіряємо, чи лежать кінці ребра в одному дереві. Якщо так, то ребро не можна додати до лісу (не створюючи циклу), і воно відкидається. Якщо ні, то ребро додається до А (рядок 7), і два з'єднаних їм дерева поєднуються в одне (рядок 8). Підрахуємо час роботи алгоритму Крускала. Будемо вважати, що для зберігання непересічних множин використовується метод з об'єднанням за рангом і стиском шляхів — найшвидший з відомих. Ініціалізація забирає час O(V), упорядкування ребер у рядку 4 — O(E log 2 E). Далі виробляється O(Е) операцій, у сукупності занимающих час О(Еα(Е, V)). (основний час іде на сортування).
Алгоритм Крускала
Алгоритм Прима. • Як показано на Рис 8. 5, формування дерева починається з довільної кореневої вершини r. На кожному кроці додається ребро найменшої ваги серед ребер з'єднуючі вершини цього дерева з вершинами не з дерева. По наслідку такі ребра є безпечними для А, так що в результаті виходить мінімальний кістяк. • При реалізації важливо швидко вибирати легке ребро. Алгоритм одержує на вхід зв'язний граф G і корінь r мінімального покриваючого дерева. У ході алгоритму всі вершини, що ще не потрапили в дерево, зберігаються в черзі із пріоритетами. Пріоритет вершини v визначається значенням key[u], що дорівнює мінімальній вазі ребер, що з'єднують v з вершинами дерева А. (Якщо таких ребер ні, думаємо key[V] = ∞). Поле π [v] для вершин дерева вказує на батька, а для вершини v Є Q указує на вершину дерева, у яку веде ребро ваги key[v] (одне з таких ребер, якщо їх трохи).
Алгоритм Прима. • • • MST-Prim(G, W, r) 1 Q ← V[G] 2 for для кожної вершини u Є Q 3 do key[u] ←∞ 4 key[r] ← 0 5π [r] ← nil 6 while Q≠Ø 7 do u ← Extract-Min(Q) 8 for для кожної вершини v Є Adj[u] 9 do if v Є Q і w(u, v)<key[v] 10 then π[v] ← u 11 key(v) ← w(u, v)
Алгоритм Прима.
Алгоритм Прима. • Ребра, що входять у дерево А. виділені сірим; вершини дерева - чорним. На кожному кроці до А додається ребро, що перетинає розріз між деревом і його доповненням. Наприклад, на другому кроці можна було б додати кожне з ребер (b, с) і (а, h). • Ha мал. 8. 5 показана робота алгоритму Прима. Після виконання рядків 1 -5 і першого проходу циклу в рядках 6 ‑ 11 дерево складається з єдиної вершини r, всі інші вершини перебувають у черзі, і значення key[v] для них дорівнює вазі ребра з r в v або ∞ , якщо такого ребра немає (у першому випадку π[v] = r). Таким чином, виконаний описаний вище інваріант (дерево є частина деякого кістяка, для вершин дерева поле вказує на батька, а для інших вершин на "найближчу" вершину дерева — вага ребра до її зберігається в key[v].
Алгоритм Прима. Час роботи алгоритму Прима залежить від того, як реалізована черга Q. Час ініціалізації в рядках 1 -4 O(V). Далі цикл виконується |V| раз, і кожна операція Extract-Min забирає час O(log 2 V), усього O(V log 2 V). Цикл for у рядках 8 -11 виконується в цілому О(Е) раз, оскільки сума ступенів вершин графа дорівнює 2 |Е|. Перевірку приналежності в рядку 9 усередині циклу for можна реалізувати за час O(1). Таким чином, усього одержуємо O(V log 2 V + E log 2 V) = O(E log 2 V) — та ж сама оцінка, що була для алгоритму Крускала