Raw

Лекція 8. Алгоритми на графах

Огляд

Лекція 7 дала нам статичну мову графів: вершини й ребра, степені вершин, орієнтовані та зважені графи, способи подання (списки й матриці суміжності), маршрути й шляхи, дерева та остовні дерева. Та лекція розповіла, що це за об’єкти; вона майже не торкалася питання, як із ними щось обчислювати. Ця лекція — про дії над графами: ми беремо зважений граф і ставимо три природні обчислювальні задачі, кожна з яких виникає в реальній практиці — від маршрутизації пакетів у мережі до прокладання кабелю в цеху, — і будуємо для них алгоритми з псевдокодом, докладними покроковими прикладами й доведеннями коректності.

Три задачі організують розділ:

  1. Який найдешевший маршрут від однієї вершини до всіх інших? Це задача про найкоротший шлях; для графа з невід’ємними вагами її розв’язує алгоритм Дейкстри (§8.1).
  2. Яка найдешевша мережа зв’язків, що тримає все з’єднаним? Це задача про мінімальне остовне дерево (МОД); її розв’язують два жадібні алгоритми — Прима (§8.3) і Крускала (§8.4), обидва обґрунтовані однією красивою лемою — властивістю розрізу (§8.2).
  3. Скільки продукту можна пропустити мережею від джерела до споживача? Це задача про максимальний потік у транспортній мережі; її розв’язує алгоритм Форда–Фалкерсона (§8.5–8.6).

Наскрізна тема — жадібність. Кожен алгоритм цієї лекції жадібний: він будує відповідь крок за кроком і на кожному кроці остаточно робить вибір, найкращий саме зараз, не переглядаючи його згодом. Жадібні алгоритми привабливі простотою й швидкістю, але глибоке питання щоразу те саме: чи приводить локально найкращий вибір до глобально оптимального результату? Для найкоротших шляхів і остовних дерев відповідь — так, і ми це доведемо (через інваріант зафіксованої відстані для Дейкстри та через властивість розрізу для Прима й Крускала). Уміння відрізняти коли жадібність провабно безпечна — одна з головних ідей курсу.

Позначення та передумови

Ми спираємося на такі поняття з Лекції 7:

  • Зважений граф приписує кожному ребру дійсне число w(u,v)w(u,v)вагу, довжину, вартість чи пропускну здатність.
  • Список суміжності зберігає для кожної вершини vv перелік її сусідів разом із вагами ребер; він займає O(n+m)O(n+m) пам’яті й дає змогу переглянути всі інцидентні вершині ребра за час, пропорційний її степеню. Це подання ми припускаємо всюди.
  • Остовне дерево зв’язного графа — підграф, що є деревом (зв’язним і без циклів) і містить усі вершини; воно має рівно n1n-1 ребро.

Ми пишемо n=Vn=|V| для кількості вершин і m=Em=|E| для кількості ребер. Усі приклади розділу проганяються на одному зважному графі GG (нижче), щоб результати різних алгоритмів можна було порівняти.


8.1 Задача про найкоротший шлях. Алгоритм Дейкстри

Постановка задачі

Нехай задано зважений граф і одну початкову вершину ss (джерело). Потрібно знайти найдешевший маршрут від ss до кожної іншої вершини. Це задача про найкоротші шляхи з однієї вершини (single-source shortest paths) — обчислювальне ядро GPS-навігації, маршрутизації пакетів (протокол OSPF виконує саме Дейкстру), аналізу затримок у мережах і безлічі задач планування.

Означення (відстань і найкоротший шлях). Відстанню δ(s,v)\delta(s,v) від джерела ss до вершини vv називають мінімальну сумарну вагу серед усіх шляхів з ss у vvδ(s,v)=\delta(s,v)=\infty, якщо vv недосяжна). Шлях, що досягає цього мінімуму, — найкоротший шлях.

Алгоритм Дейкстри (E. W. Dijkstra, 1959) знаходить δ(s,v)\delta(s,v) для всіх vv за умови, що всі ваги ребер невід’ємні. Він тримає для кожної вершини орієнтовну відстань dist[v]\text{dist}[v] — довжину найкращого знайденого досі маршруту до vv — і працює двома чергованими діями:

Означення (жадібний вибір і релаксація). На кожному кроці Дейкстра фіксує (робить позначеною, остаточною) ще незафіксовану вершину uu з найменшою поточною dist[u]\text{dist}[u] — це жадібний вибір найближчої вершини. Потім він виконує релаксацію кожного ребра (u,v)(u,v): якщо

dist[u]+w(u,v)<dist[v],\text{dist}[u] + w(u,v) < \text{dist}[v],

то знайдено коротший маршрут до vv через uu, тож покладають dist[v]dist[u]+w(u,v)\text{dist}[v] \leftarrow \text{dist}[u]+w(u,v) і запам’ятовують попередника prev[v]u\text{prev}[v]\leftarrow u (щоб потім відновити самий шлях).

Ключова властивість: щойно вершину вилучено (зафіксовано), її відстань остаточна й більше ніколи не змінюється — на цьому тримається доведення коректності.

Псевдокод

Дейкстра(G, w, s):
    для кожної вершини v графа G:
        dist[v] ← ∞                       # орієнтовна відстань
        prev[v] ← невизначено
    dist[s] ← 0
    Q ← черга з пріоритетом з усіх вершин, ключ = dist
    поки Q не порожня:
        u ← вилучити-мінімум(Q)            # найближча незафіксована вершина
        для кожного сусіда v вершини u:
            alt ← dist[u] + w(u, v)
            якщо alt < dist[v]:            # релаксація
                dist[v] ← alt
                prev[v] ← u
                зменшити-ключ(Q, v, alt)
    повернути dist, prev

Робочий граф

Розгляньмо зважений граф G=(V,E)G=(V,E) на семи вершинах V={A,B,C,D,E,F,G}V=\{A,B,C,D,E,F,G\} з дев’ятьма ребрами:

Ребро ww Ребро ww Ребро ww
ADA\text{–}D 5 DFD\text{–}F 6 CGC\text{–}G 7
ABA\text{–}B 9 BFB\text{–}F 4 EGE\text{–}G 10
DBD\text{–}B 2 BCB\text{–}C 8 FEF\text{–}E 3

Зважений граф G на семи вершинах A–G із дев'ятьма позначеними вагами ребер

Усі дев’ять ваг різні — це, як ми доведемо в §8.2, робить мінімальне остовне дерево єдиним, тож Прим і Крускал у §8.3–8.4 мусять дати той самий набір ребер.

Покроковий приклад: Дейкстра з джерела AA

Проженемо алгоритм із джерела AA. Кожен рядок таблиці показує масив орієнтовних відстаней після фіксації чергової вершини; стовпець «Позначено» називає щойно вилучену вершину (з її вже остаточною відстанню), а прочерк «-» означає «ще не досягнута» (\infty). Жирним позначено значення, яке щойно суворо покращилося релаксацією.

Крок Позначено AA BB CC DD EE FF GG
0 (старт) 0 - - - - - -
1 A(0)A\,(0) 0 9 - 5 - - -
2 D(5)D\,(5) 0 7 - 5 - 11 -
3 B(7)B\,(7) 0 7 15 5 - 11 -
4 F(11)F\,(11) 0 7 15 5 14 11 -
5 E(14)E\,(14) 0 7 15 5 14 11 24
6 C(15)C\,(15) 0 7 15 5 14 11 22
7 G(22)G\,(22) 0 7 15 5 14 11 22

Читаємо кроки уважно:

  • Крок 1. Фіксуємо AA (відстань 00); релаксуємо його ребра: B=9B=9 (через ABA\text{–}B) і D=5D=5 (через ADA\text{–}D).
  • Крок 2. Найменша орієнтовна відстань — це D=5D=5; фіксуємо DD і релаксуємо його ребра. Ребро DBD\text{–}B дає 5+2=7<95+2=7<9, тож BB покращується з 99 до 77 (маршрут ADBA\to D\to B дешевший за пряме ребро ABA\to B). Ребро DFD\text{–}F дає F=5+6=11F=5+6=11.
  • Крок 3. Фіксуємо B=7B=7; релаксуємо BCB\text{–}C: 7+8=157+8=15, тож C=15C=15. Ребро BFB\text{–}F дає 7+4=117+4=11 — це не менше за наявні 1111, тож FF не змінюється.
  • Крок 4. Фіксуємо F=11F=11; релаксуємо FEF\text{–}E: 11+3=1411+3=14, тож E=14E=14.
  • Крок 5. Фіксуємо E=14E=14; релаксуємо EGE\text{–}G: 14+10=2414+10=24, тож G=24G=24 (перше досягнення GG).
  • Крок 6. Фіксуємо C=15C=15; релаксуємо CGC\text{–}G: 15+7=22<2415+7=22<24, тож GG покращується з 2424 до 2222 — довший маршрут через EE відкинуто на користь маршруту через CC.
  • Крок 7. Фіксуємо G=22G=22; покращувати більше нічого. Готово.

Остаточні відстані від AA:

A=0,D=5,B=7,F=11,E=14,C=15,G=22,A=0,\quad D=5,\quad B=7,\quad F=11,\quad E=14,\quad C=15,\quad G=22,

а порядок фіксації вершин — рівно A(0)D(5)B(7)F(11)E(14)C(15)G(22)A(0)\to D(5)\to B(7)\to F(11)\to E(14)\to C(15)\to G(22): вершини стають позначеними в порядку зростання їхньої остаточної відстані. Зверніть увагу на дві релаксації, що покращили вже досягнуте значення (B ⁣:97B\colon 9\to 7 і G ⁣:2422G\colon 24\to 22) — це і є суть релаксації.

Зауваження (позначені та непозначені вершини). Слайдова подача цієї самої задачі показує лише позначені (остаточні) мітки, що з’являються по одній: спочатку A=0A{=}0, потім додається D=5D{=}5, далі B=7B{=}7, F=11F{=}11, E=14E{=}14, C=15C{=}15 і нарешті G=22G{=}22. Це — головна діагональ нашої таблиці (стовпець «Позначено»). Повна таблиця багатша: вона показує ще й орієнтовні мітки непозначених вершин, які змінюються дорогою.

Відновлення шляху

Щоб відновити самий найкоротший шлях, ідуть за вказівниками prev\text{prev} у зворотному напрямку. Для GG:

prev[G]=C, prev[C]=B, prev[B]=D, prev[D]=A,\text{prev}[G]=C,\ \text{prev}[C]=B,\ \text{prev}[B]=D,\ \text{prev}[D]=A,

тобто найкоротший шлях ADBCGA\to D\to B\to C\to G довжини 5+2+8+7=225+2+8+7=22. Так само відновлюємо найкоротші шляхи до всіх вершин:

Вершина Найкоротший шлях від AA Довжина
DD ADA\to D 55
BB ADBA\to D\to B 5+2=75+2=7
FF ADFA\to D\to F 5+6=115+6=11
EE ADFEA\to D\to F\to E 5+6+3=145+6+3=14
CC ADBCA\to D\to B\to C 5+2+8=155+2+8=15
GG ADBCGA\to D\to B\to C\to G 5+2+8+7=225+2+8+7=22

Разом усі вказівники prev\text{prev} утворюють дерево найкоротших шляхів з коренем AA:

{AD, DB, DF, FE, BC, CG}.\{A\text{–}D,\ D\text{–}B,\ D\text{–}F,\ F\text{–}E,\ B\text{–}C,\ C\text{–}G\}.

Зауважте: дерево найкоротших шляхів (звідси) — це не те саме, що мінімальне остовне дерево (§8.3): перше мінімізує відстані від фіксованого кореня, друге — сумарну вагу всіх ребер. На нашому графі вони справді різні (порівняйте з рисунком МОД у §8.3).

Дерево найкоротших шляхів від A (помаранчеві ребра); біля кожної вершини — її відстань, найдальша вершина G на відстані 22

Коректність (коротко)

Ідея коректності — інваріант зафіксованої відстані: щойно вершину вилучено з черги, її мітка вже дорівнює справжній відстані.

Теорема 8.1 (коректність Дейкстри). Якщо всі ваги невід’ємні, то в мить вилучення вершини uu з QQ виконано dist[u]=δ(s,u)\text{dist}[u]=\delta(s,u). Отже, усі остаточні відстані правильні.

Доведення (від супротивного). Спершу зауважимо два прості факти. (А) У будь-який момент dist[v]δ(s,v)\text{dist}[v]\ge\delta(s,v), і якщо dist[v]\text{dist}[v] скінченна, то це довжина якогось реального шляху svs\to v (релаксація лише подовжує реальний шлях на одне ребро). (Б) Кожен префікс найкоротшого шляху сам є найкоротшим шляхом до своєї кінцевої вершини.

Припустимо, що теорема хибна, і нехай uuперша вилучена вершина, для якої dist[u]δ(s,u)\text{dist}[u]\ne\delta(s,u); за фактом (А) тоді dist[u]>δ(s,u)\text{dist}[u]>\delta(s,u). Візьмімо найкоротший шлях PP з ss у uu і нехай SS — множина вже зафіксованих вершин перед вилученням uu. Шлях PP починається в SS (бо sSs\in S) і закінчується поза SS (бо uSu\notin S); нехай yy — перша вершина PP поза SS, а xx — її попередник на PP (отже xSx\in S). Оскільки xx зафіксовано раніше й (за вибором uu) правильно, у мить фіксації xx ми релаксували ребро (x,y)(x,y), тож dist[y]dist[x]+w(x,y)=δ(s,x)+w(x,y)=δ(s,y)\text{dist}[y]\le\text{dist}[x]+w(x,y)=\delta(s,x)+w(x,y)=\delta(s,y) (остання рівність — факт (Б)). Разом із (А): dist[y]=δ(s,y)\text{dist}[y]=\delta(s,y). Тепер відрізок PP від yy до uu має невід’ємну вагу (єдине місце, де потрібна невід’ємність!), тому δ(s,y)δ(s,u)\delta(s,y)\le\delta(s,u). Складаємо ланцюжок:

dist[y]=δ(s,y)δ(s,u)<dist[u].\text{dist}[y]=\delta(s,y)\le\delta(s,u)<\text{dist}[u].

Але yy і uu обидві були в QQ, і алгоритм обрав саме uu як вершину з найменшою міткою, тобто dist[u]dist[y]\text{dist}[u]\le\text{dist}[y] — суперечність. Отже, такої uu немає. \blacksquare

Типова помилка (від’ємні ваги). Для графа з від’ємною вагою Дейкстра може дати неправильний результат: вона фіксує вершину назавжди й не переглядає її, а від’ємне ребро згодом здатне відкрити коротший маршрут. Для таких графів застосовують алгоритм Беллмана–Форда (релаксує всі ребра n1n-1 разів за O(nm)O(nm) і виявляє цикли від’ємної ваги). Умова невід’ємності ваг для Дейкстри — не дрібниця, а суттєва передумова.

Складність

Час залежить винятково від черги з пріоритетом. Вилучень-мінімуму рівно nn, а релаксацій (зменшень ключа) — не більш ніж mm. З двійковою купою обидві операції коштують O(logn)O(\log n), тож увесь алгоритм — O((n+m)logn)O((n+m)\log n). На щільних графах вигіднішим буває невпорядкований масив: O(n2)O(n^2).

Зауваження (ліниве видалення). Багато реалізацій узагалі не мають операції «зменшити-ключ»: під час релаксації вони просто додають у купу нову пару (dist,v)(\text{dist}, v), а під час вилучення відкидають застарілі пари, чий ключ більший за поточну dist[v]\text{dist}[v]. Купа може містити до mm записів, але logm2logn\log m\le 2\log n, тож оцінка O((n+m)logn)O((n+m)\log n) зберігається. Це трохи більше пам’яті в обмін на суттєво простіший код — так робить більшість стандартних бібліотек.


8.2 Мінімальне остовне дерево. Властивість розрізу

Задача

Означення (мінімальне остовне дерево). Мінімальне остовне дерево (МОД, англ. minimum spanning tree) зв’язного зваженого графа — це остовне дерево найменшої сумарної ваги ребер.

МОД відповідає на запитання «яка найдешевша сукупність зв’язків тримає всю мережу з’єднаною?» — прокладання кабелю, доріг, трубопроводів; воно ж лежить в основі деяких алгоритмів кластеризації. Обидва алгоритми, що ми розглянемо (Прим і Крускал), жадібні, і обидва обґрунтовуються однією лемою. Щоб її сформулювати, потрібен словник розрізів.

Означення (розріз, перетинне ребро, легке ребро). Розрізом називають розбиття вершин на дві непорожні частини (S, VS)(S,\ V\setminus S). Ребро перетинає розріз, якщо один його кінець лежить у SS, а другий — у VSV\setminus S. Перетинне ребро найменшої ваги називають легким ребром розрізу. Кажуть, що набір ребер AA узгоджений із розрізом, якщо жодне ребро AA не перетинає цей розріз.

Розріз (S, V∖S) із S = {A, D}; перетинні ребра A–B, D–B, D–F; легке ребро розрізу — D–B вагою 2

Властивість розрізу

Теорема 8.2 (властивість розрізу). Нехай набір ребер AA міститься в якомусь МОД графа GG. Нехай (S,VS)(S,V\setminus S) — будь-який розріз, узгоджений з AA, і нехай ee — легке ребро цього розрізу. Тоді ребро ee безпечне для AA, тобто A{e}A\cup\{e\} також міститься в якомусь МОД.

Доведення (обмінний аргумент). Нехай TT — МОД, що містить AA. Якщо eTe\in T — усе доведено. Тож припустимо e=(u,v)Te=(u,v)\notin T, де uSu\in S, vVSv\in V\setminus S. Додавання ee до дерева TT утворює рівно один цикл C\mathcal C (у дереві між uu і vv уже є єдиний шлях, а ребро ee замикає його в цикл). Обходячи C\mathcal C, ми стартуємо в uSu\in S, ребром ee переходимо у vVSv\in V\setminus S і зрештою повертаємось у uu; що кожен перетин розрізу змінює бік, а закінчити треба там, де почали, — цикл перетинає розріз парну кількість разів. Один перетин — це саме ee; отже, є ще принаймні один перетин, ребром eee'\ne e. Це ee' не належить AA (бо AA узгоджений із розрізом, а ee' його перетинає). Виконаймо обмін: покладемо T=(T{e}){e}T'=(T\setminus\{e'\})\cup\{e\}. Вилучення ребра ee', що лежить на циклі, зберігає зв’язність; TT' має ті самі n1n-1 ребро, тож TT' — остовне дерево. Його вага

w(T)=w(T)w(e)+w(e).w(T')=w(T)-w(e')+w(e).

Оскільки ee — легке (мінімальне) перетинне ребро, а ee' теж перетинає, то w(e)w(e)w(e)\le w(e'), звідки w(T)w(T)w(T')\le w(T). Але TT — МОД, тож w(T)w(T)w(T')\ge w(T); отже, w(T)=w(T)w(T')=w(T) і TT' — теж МОД. При цьому ATA\subseteq T' (ми вилучили eAe'\notin A й додали ee) і eTe\in T', тобто A{e}TA\cup\{e\}\subseteq T': ребро ee безпечне. \blacksquare

Наслідок 8.3 (єдиність МОД). Якщо всі ваги ребер різні, то МОД єдине. Справді, за різних ваг нерівність w(e)w(e)w(e)\le w(e') у доведенні стає суворою для легкого ребра, тож кожен розріз має рівно одне легке ребро, і воно належить кожному МОД — це однозначно визначає дерево. \blacksquare

Наш граф GG має різні ваги, отже, його МОД єдине, і Прим із Крускалом мусять повернути однаковий набір ребер, а не лише однакову вагу.


8.3 Алгоритм Прима

Ідея та псевдокод

Алгоритм Прима (V. Jarník 1930; R. Prim 1957) вирощує одне дерево, розпочинаючи з довільної стартової вершини. Він тримає множину вершин дерева й на кожному кроці додає найдешевше ребро, що з’єднує дерево з вершиною поза ним. Кожна вершина vv поза деревом зберігає key[v]\text{key}[v] — вагу найдешевшого відомого ребра, що сполучає її з поточним деревом, а черга з пріоритетом видає глобальний мінімум.

Прим(G, w, s):
    для кожної вершини v:
        key[v] ← ∞               # вага найдешевшого ребра з v до дерева
        parent[v] ← невизначено
        inMST[v] ← хиба
    key[s] ← 0
    Q ← черга з пріоритетом з усіх вершин, ключ = key
    поки Q не порожня:
        u ← вилучити-мінімум(Q)
        inMST[u] ← істина
        для кожного сусіда v вершини u:
            якщо (не inMST[v]) і w(u, v) < key[v]:
                key[v] ← w(u, v)
                parent[v] ← u
                зменшити-ключ(Q, v, key[v])
    повернути { (v, parent[v]) : v ≠ s }      # ребра МОД

Покроковий приклад: Прим із вершини AA

Проженемо Прим на GG від AA. Кожен рядок показує масив key[]\text{key}[\,] після чергової фіксації; знак «\bullet» означає «вершина вже в дереві», жирне — щойно покращений ключ. Додане ребро — це (parent[u],u)(\text{parent}[u],u) для вилученої вершини uu.

Крок Вилучено Додане ребро AA BB CC DD EE FF GG
0 0 \infty \infty \infty \infty \infty \infty
1 AA \bullet 9 \infty 5 \infty \infty \infty
2 D(5)D\,(5) ADA\text{–}D \bullet 2 \infty \bullet \infty 6 \infty
3 B(2)B\,(2) DBD\text{–}B \bullet \bullet 8 \bullet \infty 4 \infty
4 F(4)F\,(4) BFB\text{–}F \bullet \bullet 8 \bullet 3 \bullet \infty
5 E(3)E\,(3) FEF\text{–}E \bullet \bullet 8 \bullet \bullet \bullet 10
6 C(8)C\,(8) BCB\text{–}C \bullet \bullet \bullet \bullet \bullet \bullet 7
7 G(7)G\,(7) CGC\text{–}G \bullet \bullet \bullet \bullet \bullet \bullet \bullet

Розповідь:

  • Крок 1. Зі старту AA ключі сусідів B,DB,D стають 9,59,5.
  • Крок 2. Вилучаємо DD (ключ 55 — найменший), додаємо ребро ADA\text{–}D. Релаксація ребер DD опускає key[B]\text{key}[B] з 99 до 22 (ребро DBD\text{–}B) і задає key[F]=6\text{key}[F]=6.
  • Крок 3. Вилучаємо BB (ключ 22), додаємо DBD\text{–}B. Ребро BFB\text{–}F опускає key[F]\text{key}[F] з 66 до 44; ребро BCB\text{–}C задає key[C]=8\text{key}[C]=8.
  • Крок 4. Вилучаємо FF (ключ 44), додаємо BFB\text{–}F. Ребро FEF\text{–}E задає key[E]=3\text{key}[E]=3.
  • Крок 5. Вилучаємо EE (ключ 33), додаємо FEF\text{–}E. Ребро EGE\text{–}G задає key[G]=10\text{key}[G]=10.
  • Крок 6. Вилучаємо CC (ключ 88), додаємо BCB\text{–}C. Ребро CGC\text{–}G опускає key[G]\text{key}[G] з 1010 до 77.
  • Крок 7. Вилучаємо GG (ключ 77), додаємо CGC\text{–}G. Дерево завершене.

Прим, крок 2: дерево {A–D, D–B} з двома помаранчевими ребрами

Прим, крок 4: дерево {A–D, D–B, B–F, F–E} з чотирма помаранчевими ребрами

Ребра МОД: {AD, DB, BF, FE, BC, CG}\{A\text{–}D,\ D\text{–}B,\ B\text{–}F,\ F\text{–}E,\ B\text{–}C,\ C\text{–}G\}; сумарна вага 5+2+4+3+8+7=295+2+4+3+8+7=\mathbf{29}. Шість ребер на сім вершин — коректне остовне дерево. Зверніть увагу: Прим додавав ваги в порядку 5,2,4,3,8,75,2,4,3,8,7 — він щоразу бере найдешевше ребро на межі свого дерева, а це загалом не найдешевше з усіх ребер, що лишилися (пор. Крускал у §8.4).

Мінімальне остовне дерево графа G (єдине): помаранчеві ребра дерева, вага 29

Коректність

Теорема 8.4. Алгоритм Прима повертає мінімальне остовне дерево.

Доведення. Нехай AA — множина вже дібраних ребер, а SS — множина вершин дерева. Доводимо індукцією за кроками інваріант: AA міститься в якомусь МОД. База: A=A=\varnothing міститься в будь-якому МОД. Крок: нехай AA у якомусь МОД. Кожне ребро AA сполучає дві вершини дерева, тож лежить усередині SS, отже, AA узгоджений із розрізом (S,VS)(S,V\setminus S). Наступне ребро, яке додає Прим, — за побудовою мінімальне ребро від SS до VSV\setminus S, тобто легке ребро цього розрізу. За властивістю розрізу (Теорема 8.2) воно безпечне, отже, A{e}A\cup\{e\} теж у якомусь МОД — інваріант зберігається. Коли QQ спорожніє, S=VS=V і AA має n1n-1 ребро, що утворюють остовне дерево, яке міститься в МОД, — а це і є те МОД. \blacksquare

Складність

Структурно Прим — це Дейкстра з іншим ключем («найдешевше ребро в дерево» замість «відстань від джерела»), тож і час той самий: O((n+m)logn)O((n+m)\log n) з двійковою купою або O(n2)O(n^2) з масивом (краще для щільних графів).

Типова помилка. Прим вимагає зв’язного графа; на незв’язному він побудує лише дерево компоненти стартової вершини. Також релаксувати треба лише до вершин, які ще не в дереві.


8.4 Алгоритм Крускала

Ідея та псевдокод

Алгоритм Крускала (J. Kruskal, 1956) підходить до МОД з боку ребер. Він сортує всі ребра за зростанням ваги й переглядає їх по черзі, додаючи ребро до зростаючого лісу щоразу, коли воно не утворює циклу — тобто коли його кінці лежать у різних компонентах. Алгоритм зупиняється, коли додано n1n-1 ребро.

Крускал(G, w):
    A ← ∅                              # набір ребер МОД
    для кожної вершини v: MakeSet(v)
    відсортувати всі ребра за зростанням ваги
    для кожного ребра (u, v) у відсортованому порядку:
        якщо Find(u) ≠ Find(v):        # кінці в різних компонентах
            A ← A ∪ { (u, v) }
            Union(u, v)
            якщо |A| = n − 1: зупинитися
    повернути A

Система неперетинних множин (union–find)

Єдина нетривіальна операція — швидко перевіряти «в одній компоненті?» й оновлювати компоненти після злиття. Це робота системи неперетинних множин (union–find), що підтримує розбиття вершин на неперетинні множини — поточні компоненти лісу — трьома операціями:

  • MakeSet(x) — покласти xx в окрему одноелементну множину;
  • Find(x) — повернути канонічного представника (корінь) множини xx; дві вершини в одній множині тоді й лише тоді, коли їхні представники збігаються;
  • Union(x, y) — злити множини, що містять xx і yy.

Кожну множину зберігають як кореневе дерево через масив parent\text{parent} (корінь указує сам на себе); Find іде вказівниками до кореня. Дві оптимізації тримають дерева пласкими: об’єднання за рангом (менше дерево підвішують під більше) і стиснення шляхів (під час Find кожну пройдену вершину перепідвішують просто до кореня). Разом вони дають амортизований час O(α(n))O(\alpha(n)) на операцію, де α\alpha — обернена функція Аккермана, що зростає так повільно, що α(n)4\alpha(n)\le 4 для будь-якого мислимого nn — практично стала.

Покроковий приклад: Крускал на GG

Відсортовані ребра графа GG:

DB(2), FE(3), BF(4), AD(5), DF(6), CG(7), BC(8), AB(9), EG(10).D\text{–}B\,(2),\ F\text{–}E\,(3),\ B\text{–}F\,(4),\ A\text{–}D\,(5),\ D\text{–}F\,(6),\ C\text{–}G\,(7),\ B\text{–}C\,(8),\ A\text{–}B\,(9),\ E\text{–}G\,(10).

Ребро ww Find(u)\text{Find}(u) vs Find(v)\text{Find}(v) Цикл? Дія Компоненти після
DBD\text{–}B 2 DBD\ne B ні додати {DB},{A},{C},{E},{F},{G}\{DB\},\{A\},\{C\},\{E\},\{F\},\{G\}
FEF\text{–}E 3 FEF\ne E ні додати {DB},{FE},{A},{C},{G}\{DB\},\{FE\},\{A\},\{C\},\{G\}
BFB\text{–}F 4 BFB\ne F ні додати {DBFE},{A},{C},{G}\{DBFE\},\{A\},\{C\},\{G\}
ADA\text{–}D 5 ADA\ne D ні додати {ADBFE},{C},{G}\{ADBFE\},\{C\},\{G\}
DFD\text{–}F 6 D=FD=F так пропустити {ADBFE},{C},{G}\{ADBFE\},\{C\},\{G\}
CGC\text{–}G 7 CGC\ne G ні додати {ADBFE},{CG}\{ADBFE\},\{CG\}
BCB\text{–}C 8 BCB\ne C ні додати {ADBFECG}\{ADBFECG\} — усе
ABA\text{–}B 9 A=BA=B так пропустити (уже 66 ребер)
EGE\text{–}G 10 E=GE=G так пропустити

Ребра за зростанням ваги: додані до МОД (D–B, F–E, B–F, A–D, C–G, B–C) позначено галочкою, пропущені як цикл (D–F, A–B, E–G) — хрестиком

Ребра МОД: {DB, FE, BF, AD, CG, BC}\{D\text{–}B,\ F\text{–}E,\ B\text{–}F,\ A\text{–}D,\ C\text{–}G,\ B\text{–}C\}; сумарна вага 2+3+4+5+7+8=292+3+4+5+7+8=\mathbf{29}.

Це те саме дерево, що знайшов Прим (обидва дають {AD,DB,BF,FE,BC,CG}\{A\text{–}D, D\text{–}B, B\text{–}F, F\text{–}E, B\text{–}C, C\text{–}G\}, вага 2929) — як і гарантує Наслідок 8.3, адже ваги GG різні. Ребро DF(6)D\text{–}F\,(6) було пропущене, бо DD і FF уже лежали в одній компоненті {A,D,B,F,E}\{A,D,B,F,E\}: додавання замкнуло б цикл DBFDD\text{–}B\text{–}F\text{–}D, на якому DFD\text{–}F — найважче ребро.

Трасування union–find

Простежмо структуру неперетинних множин крізь об’єднання Крускала (у порядку доданих ребер), стартуючи з кожної вершини як окремого кореня з рангом 00.

Операція Дія (об’єднання за рангом) Корені після
Union(D,B) рівні ранги: parent[B]=D\text{parent}[B]{=}D, rank[D]=1\text{rank}[D]{=}1 D ⁣:{D,B}D\!:\{D,B\}
Union(F,E) рівні ранги: parent[E]=F\text{parent}[E]{=}F, rank[F]=1\text{rank}[F]{=}1 F ⁣:{F,E}F\!:\{F,E\}
Union(B,F) Find(B)=D, Find(F)=F\text{Find}(B){=}D,\ \text{Find}(F){=}F, рівні ранги 11: parent[F]=D\text{parent}[F]{=}D, rank[D]=2\text{rank}[D]{=}2 D ⁣:{D,B,F,E}D\!:\{D,B,F,E\}
Union(A,D) rank[A]=0<rank[D]=2\text{rank}[A]{=}0<\text{rank}[D]{=}2: parent[A]=D\text{parent}[A]{=}D D ⁣:{A,D,B,F,E}D\!:\{A,D,B,F,E\}
Union(C,G) рівні ранги: parent[G]=C\text{parent}[G]{=}C, rank[C]=1\text{rank}[C]{=}1 C ⁣:{C,G}C\!:\{C,G\}
Union(B,C) rank[C]=1<rank[D]=2\text{rank}[C]{=}1<\text{rank}[D]{=}2: parent[C]=D\text{parent}[C]{=}D D ⁣:{A,B,C,D,E,F,G}D\!:\{A,B,C,D,E,F,G\}

Після цих операцій виклик Find(E)\text{Find}(E) проходить EFDE\to F\to D і завдяки стисненню шляхів перепідвішує parent[E]D\text{parent}[E]\leftarrow D, тож наступний Find(E)\text{Find}(E) — це вже один крок. Саме так дві оптимізації тримають дерева пласкими.

Коректність

Теорема 8.5. Алгоритм Крускала повертає мінімальне остовне дерево.

Доведення. Знову доводимо інваріант «набір AA міститься в якомусь МОД» індукцією за додаваннями. Нехай Крускал збирається додати ребро e=(u,v)e=(u,v), бо Find(u)Find(v)\text{Find}(u)\ne\text{Find}(v). Нехай SS — компонента вершини uu в поточному лісі; візьмімо розріз (S,VS)(S,V\setminus S). Кожне ребро AA лежить усередині якоїсь компоненти, тож AA узгоджений із цим розрізом, а ee його перетинає. Ба більше, ee — легке ребро розрізу: якби якесь перетинне ребро ee' мало w(e)<w(e)w(e')<w(e), то Крускал розглянув би ee' раніше (порядок за зростанням ваги) і додав би його (його кінці й тоді були в різних компонентах, бо компоненти лише зливаються), тобто eAe'\in A — суперечність із тим, що AA узгоджений. За властивістю розрізу (Теорема 8.2) ee безпечне. Пропущені ребра нешкідливі: ребро пропускають лише тоді, коли його кінці вже з’єднані дешевшими ребрами, тож воно найважче на своєму циклі й не належить жодному МОД. \blacksquare

Складність

Домінує сортування mm ребер: O(mlogm)=O(mlogn)O(m\log m)=O(m\log n). Усі операції union–find разом коштують O(mα(n))O(m\,\alpha(n)) — практично лінійно й дешевше за сортування. Отже, Крускал працює за O(mlogn)O(m\log n).

Прим чи Крускал? Обидва дають те саме МОД. Прим вирощує одне зв’язне дерево від зерна, беручи найдешевше ребро на межі; його масивна версія O(n2)O(n^2) незалежна від mm, тож він добрий на щільних графах. Крускал розглядає ребра глобально від найдешевшого, склеюючи окремі фрагменти лісу; він добрий на розріджених графах і коли ребра вже відсортовані.

Історична довідка. Задачу про МОД першим поставив і розв’язав Отакар Боровка (1926), проєктуючи ефективну електромережу Моравії. Метод вирощування одного дерева описав Войтех Ярник (1930); згодом його незалежно перевідкрили Роберт Прим (1957) і Едсгер Дейкстра (1959). Той самий Дейкстра в короткій нотатці 1959 р. опублікував і свій алгоритм найкоротших шляхів, придуманий, за його спогадами, хвилин за двадцять «подумки» в амстердамській кав’ярні. Джозеф Крускал оприлюднив свій алгоритм 1956 р. Через таку потрійну історію алгоритм Прима іноді звуть алгоритмом Ярника–Прима.


8.5 Транспортні мережі

Досі ваги ребер означали довжину. Тепер вони означатимуть пропускну здатність — скільки продукту дуга здатна пропустити за одиницю часу.

Означення (транспортна мережа). Транспортна мережа — це орієнтований граф G=(V,E)G=(V,E) з двома виділеними вершинами: джерелом ss (звідки продукт витікає) і стоком tt (куди він стікає), у якому кожній дузі (u,v)(u,v) приписано невід’ємну пропускну здатність c(u,v)0c(u,v)\ge 0.

Означення (потік). Потоком називають функцію ff, що приписує кожній дузі число f(u,v)f(u,v) і задовольняє дві умови:

  • обмеження пропускної здатності: 0f(u,v)c(u,v)0\le f(u,v)\le c(u,v) для кожної дуги (крізь дугу не можна пропустити більше, ніж вона витримує);
  • збереження потоку: для кожної проміжної вершини vs,tv\ne s,t сума вхідного потоку дорівнює сумі вихідного:

    (u,v)Ef(u,v)  =  (v,w)Ef(v,w).\sum_{(u,v)\in E} f(u,v) \;=\; \sum_{(v,w)\in E} f(v,w).

Величиною потоку f|f| називають чистий потік, що витікає з джерела (він дорівнює чистому потоку, що втікає у стік):

f  =  (s,w)Ef(s,w)    (u,s)Ef(u,s).|f| \;=\; \sum_{(s,w)\in E} f(s,w) \;-\; \sum_{(u,s)\in E} f(u,s).

Дугу називають насиченою, якщо f(u,v)=c(u,v)f(u,v)=c(u,v) (заповнена вщерть), і порожньою, якщо f(u,v)=0f(u,v)=0.

Транспортна мережа: джерело s, стік t, проміжні вершини u, v; на кожній дузі — позначення «потік/пропускна здатність», спочатку 0/3

Означення (розріз мережі та його пропускна здатність). Розрізом мережі називають розбиття вершин (S,T)(S,T), у якому sSs\in S і tTt\in T. Пропускною здатністю розрізу c(S,T)c(S,T) називають суму пропускних здатностей дуг, що йдуть із SS у TT (лише в цьому напрямку):

c(S,T)=(u,v)EuS, vTc(u,v).c(S,T)=\sum_{\substack{(u,v)\in E \\ u\in S,\ v\in T}} c(u,v).

Фундаментальний зв’язок між потоками й розрізами — слабка двоїстість:

Твердження 8.6 (потік не перевищує розрізу). Для будь-якого потоку ff і будь-якого розрізу (S,T)(S,T) виконано fc(S,T)|f|\le c(S,T).

Ідея доведення. Увесь потік з ss у tt мусить хоч раз перетнути межу з SS у TT; сумарний потік крізь межу дорівнює f|f| (за збереженням у проміжних вершинах), а він обмежений сумою пропускних здатностей прямих дуг межі, тобто c(S,T)c(S,T). \blacksquare

Отже, величина будь-якого потоку \le пропускної здатності будь-якого розрізу; зокрема, максимальний потік \le мінімальний розріз. У §8.6 ми побачимо, що насправді тут завжди рівність.


8.6 Максимальний потік. Алгоритм Форда–Фалкерсона

Залишкова мережа та збільшуючий ланцюг

Як збільшувати потік? Наївна ідея «шукати ще не насичені шляхи від ss до tt і доливати по них» майже правильна, але недостатня: іноді, щоб дійти оптимуму, доводиться відкликати частину вже пущеного потоку. Це вловлює поняття залишкової мережі.

Означення (залишкова мережа). Для потоку ff залишкова мережа має на кожній дузі дві залишкові пропускні здатності:

  • за напрямком дуги (u,v)(u,v) можна додати ще c(u,v)f(u,v)c(u,v)-f(u,v) одиниць — це працює, поки дуга не насичена;
  • проти напрямку дуги (u,v)(u,v) можна «повернути» до f(u,v)f(u,v) одиниць (зменшивши потік) — це працює, поки дуга не порожня.

Означення (збільшуючий / чередуючий ланцюг). Збільшуючим ланцюгом називають маршрут із ss у tt у залишковій мережі. Його будують, чергуючи напрямки: дугу проходять

  • за напрямком — якщо вона не насичена (f<cf<c), або
  • проти напрямку — якщо вона не порожня (f>0f>0).

Пропускна здатність ланцюга Δ\Delta — це мінімальна залишкова пропускна здатність уздовж нього. Пустивши уздовж ланцюга ще Δ\Delta одиниць (додаючи Δ\Delta до прямих дуг і віднімаючи Δ\Delta від зворотних), дістаємо новий коректний потік, більший на Δ\Delta.

Псевдокод

Форд–Фалкерсон(G, c, s, t):
    для кожної дуги (u, v): f(u, v) ← 0
    поки в залишковій мережі існує збільшуючий ланцюг P від s до t:
        Δ ← мінімальна залишкова пропускна здатність уздовж P
        для кожної дуги ланцюга P:
            якщо дугу пройдено за напрямком:  f ← f + Δ
            якщо дугу пройдено проти напрямку: f ← f − Δ
    повернути f            # максимальний потік

За цілих пропускних здатностей кожна ітерація збільшує f|f| принаймні на 11, тож алгоритм завершується, і підсумковий потік теж цілий (теорема про цілочисловість).

Зауваження (вибір ланцюга). За ірраціональних пропускних здатностей і невдалого вибору ланцюгів метод може не завершитися взагалі. Тому на практиці збільшуючий ланцюг шукають пошуком у ширину (найкоротший за кількістю дуг) — це варіант Едмондса–Карпа, що гарантовано завершується за O(nm2)O(n\,m^2) незалежно від величин пропускних здатностей.

Теорема про максимальний потік і мінімальний розріз

Теорема 8.7 (Форд–Фалкерсон; max-flow min-cut). Для будь-якої транспортної мережі величина максимального потоку дорівнює пропускній здатності мінімального розрізу:

maxff  =  min(S,T)c(S,T).\max_f |f| \;=\; \min_{(S,T)} c(S,T).

Потік ff максимальний тоді й лише тоді, коли в залишковій мережі немає збільшуючого ланцюга.

Ідея доведення. Якщо збільшуючий ланцюг існує, потік не максимальний (його можна збільшити на Δ>0\Delta>0). Навпаки, якщо ланцюга немає, візьмімо за SS множину всіх вершин, досяжних із ss у залишковій мережі; тоді tSt\notin S, усі прямі дуги з SS у T=VST=V\setminus S насичені, а всі зворотні — порожні, тому f=c(S,T)|f|=c(S,T). За Твердженням 8.6 більший потік неможливий, тож ff максимальний, а розріз (S,T)(S,T) — мінімальний. \blacksquare

Покроковий приклад

Розгляньмо мережу з чотирма вершинами s,u,v,ts,u,v,t і п’ятьма дугами однакової пропускної здатності 33: sus\to u, svs\to v, uvu\to v, utu\to t, vtv\to t. Позначаємо стан дуги як «потік/пропускна здатність».

Ітерація 1. Нехай алгоритм спершу знайшов ланцюг suvts\to u\to v\to t — усі три дуги прямі й не насичені, залишок кожної 33. Пропускна здатність ланцюга Δ=min(3,3,3)=3\Delta=\min(3,3,3)=3. Пускаємо 33 одиниці: тепер sus\to u, uvu\to v, vtv\to t насичені (3/33/3), а svs\to v і utu\to t порожні (0/30/3). Величина потоку f=3|f|=3.

Ітерація 2. Прямого ненасиченого шляху з ss у tt вже немає: sus\to u насичена. Але в залишковій мережі є ланцюг, що чергує напрямки:

s за напрямком v проти напрямку u за напрямком t.s \xrightarrow{\ \text{за напрямком}\ } v \xrightarrow{\ \text{проти напрямку}\ } u \xrightarrow{\ \text{за напрямком}\ } t.

Тут дугу svs\to v проходимо за напрямком (вона порожня, залишок 33); дугу uvu\to v проходимо проти напрямку (вона не порожня, f=3f=3, тож можна повернути до 33); дугу utu\to t проходимо за напрямком (порожня, залишок 33). Пропускна здатність ланцюга Δ=min(3,3,3)=3\Delta=\min(3,3,3)=3. Збільшуємо потік: svs\to v стає 3/33/3, потік дуги uvu\to v зменшуємо на 333/33/3 до 0/30/3), utu\to t стає 3/33/3. Тепер f=6|f|=6.

Збільшуючий ланцюг s→v→u→t: дуги s–v та u–t пройдено за напрямком, а дугу u–v — проти напрямку (вона не порожня)

Ітерація 3. Обидві дуги з джерела (sus\to u, svs\to v) тепер насичені, тож з ss у залишковій мережі нема куди рушити — збільшуючого ланцюга немає. Алгоритм зупиняється.

Максимальний потік f=6|f|=6. Перевіримо теорему: розріз S={s}S=\{s\}, T={u,v,t}T=\{u,v,t\} має прямі дуги sus\to u і svs\to v пропускною здатністю 3+3=63+3=6. Отже, f=6=c(S,T)|f|=6=c(S,T) — потік максимальний, а цей розріз мінімальний, як і обіцяє Теорема 8.7.

Максимальний потік величиною 6; штрихова лінія — мінімальний розріз {s} проти {u, v, t} пропускною здатністю 6

Зауваження. Другу ітерацію можна було б уникнути, якби алгоритм від початку обрав «розумні» шляхи suts\to u\to t і svts\to v\to t. Сила залишкової мережі саме в тому, що вона виправляє невдалий ранній вибір: зворотна (чередуюча) дуга «відкликала» три одиниці невдало пущеного потоку uvu\to v і перенаправила їх правильно. Без зворотних дуг алгоритм застряг би на f=3|f|=3.


8.7 Порівняння алгоритмів

Усі чотири алгоритми жадібні, але їхні гарантії й ідеальні входи різні.

Алгоритм Задача Жадібний крок Структура даних Час
Дейкстра найкоротші шляхи з ss, w0w\ge 0 фіксувати найближчу, релаксувати ребра черга з пріоритетом O((n+m)logn)O((n+m)\log n)
Прим мінімальне остовне дерево додати найдешевше ребро межі дерева черга з пріоритетом O((n+m)logn)O((n+m)\log n)
Крускал мінімальне остовне дерево додати найдешевше безциклове ребро сортування + union–find O(mlogn)O(m\log n)
Форд–Фалкерсон максимальний потік s ⁣ ⁣ts\!\to\! t збільшити потік уздовж ланцюга залишкова мережа $O(m,

Кілька спостережень:

  • Дейкстра й Прим — структурні близнюки. Обидва в циклі «вилучити найдешевшу вершину, тоді релаксувати її ребра», відрізняючись лише змістом ключа: відстань від джерела (Дейкстра) чи вага найдешевшого ребра в дерево (Прим). Їхні профілі складності однакові.
  • Крускал — інший за природою: керований ребрами, потребує сортування й union–find; вигідний на розріджених графах.
  • Стежте за передумовами. Дейкстра вимагає невід’ємних ваг (інакше — Беллман–Форд); Прим і Крускал вимагають зв’язного графа (інакше будують остовний ліс); різні ваги роблять МОД єдиним. Форд–Фалкерсон із пошуком найкоротших збільшуючих ланцюгів (варіант Едмондса–Карпа) працює за O(nm2)O(n\,m^2) незалежно від величини потоку.

Застосування у видавництві та поліграфії

Алгоритми на графах безпосередньо обслуговують задачі фаху:

  • Маршрутизація й навігація. Дейкстра знаходить найдешевший маршрут у будь-якій зваженій мережі: від прокладання оптимального шляху доставки накладу між складами до маршрутизації даних у видавничій комп’ютерній мережі (протокол OSPF — це Дейкстра в дії).
  • Найдешевша мережа зв’язків (МОД). Прим і Крускал дають найдешевший набір ліній, що з’єднує всі вузли: прокладання кабелю локальної мережі між цехами друкарні, розведення живлення чи пневматики між машинами так, щоб сумарна довжина (вартість) була мінімальною.
  • Логістика й пропускна здатність (потоки). Транспортну мережу застосовують до планування завантаження: джерело — склад паперу, стік — відділ відвантаження, дуги — ділянки виробничої лінії з їхньою пропускною здатністю (аркушів за годину); максимальний потік показує, скільки продукції реально пропустить лінія, а мінімальний розріз вказує на «вузьке місце», яке варто розширити.
  • Планування завдань. Розподіл замовлень на друкарські машини й ділянки природно моделюється потоком у мережі «замовлення → машини → зміни».
  • Кластеризація зображень і кольорів. МОД слугує заготовкою для кластеризації: вилучивши найважчі ребра дерева, отримують групи схожих пікселів або кольорів (сегментація, побудова палітри).

Підсумок

  • Задача про найкоротші шляхи з однієї вершини для графа з невід’ємними вагами розв’язується алгоритмом Дейкстри: багаторазово фіксувати найближчу непозначену вершину (жадібний вибір) і релаксувати її ребра. На нашому графі від AA порядок фіксації — A(0),D(5),B(7),F(11),E(14),C(15),G(22)A(0), D(5), B(7), F(11), E(14), C(15), G(22); відстані правильні за інваріантом зафіксованої відстані (Теорема 8.1), який суттєво спирається на невід’ємність ваг. Час — O((n+m)logn)O((n+m)\log n) з двійковою купою.
  • Мінімальне остовне дерево керується властивістю розрізу (Теорема 8.2): легке ребро будь-якого розрізу, узгодженого з поточним набором, — безпечне. Різні ваги роблять МОД єдиним (Наслідок 8.3).
  • Алгоритм Прима вирощує одне дерево від старту, щоразу додаючи найдешевше ребро межі; алгоритм Крускала сортує ребра й додає кожне, що не утворює циклу, перевіряючи це системою неперетинних множин (union–find) за амортизований O(α(n))O(\alpha(n)). На нашому графі обидва дають те саме МОД {AD,DB,BF,FE,BC,CG}\{A\text{–}D, D\text{–}B, B\text{–}F, F\text{–}E, B\text{–}C, C\text{–}G\} вагою 2929; Крускал пропускає ребро DF(6)D\text{–}F\,(6) як таке, що замикає цикл.
  • Транспортна мережа — орієнтований граф із джерелом ss, стоком tt і пропускними здатностями дуг; потік підпорядкований обмеженню пропускної здатності й збереженню, а його величина обмежена пропускною здатністю будь-якого розрізу (Твердження 8.6).
  • Алгоритм Форда–Фалкерсона нарощує потік уздовж збільшуючих (чередуючих) ланцюгів у залишковій мережі — за напрямком дуги, якщо вона не насичена, і проти напрямку, якщо не порожня. За теоремою про максимальний потік і мінімальний розріз (Теорема 8.7) максимальний потік дорівнює мінімальному розрізу; у прикладі f=6=c({s},)|f|=6=c(\{s\},\cdot).

На цьому завершується модуль теорії графів. Наступна, Лекція 9, присвячена комбінаториці та теорії ймовірностей — методам підрахунку й оцінювання випадковості, корисним, зокрема, для контролю якості друку та вибіркових перевірок тиражу.

Вправи

Якщо не сказано інакше, користуйтеся робочим графом GG (вершини AAGG; ваги з таблиці у §8.1).

Для розігріву

  1. Проженіть Дейкстру на GG із джерела AA й випишіть найкоротші шляхи (не лише відстані) до FF, до CC і до GG. Яка вершина найдальша від AA?
  2. У трасуванні Дейкстри вершина BB спершу дістала мітку 99, а потім 77. Яка релаксація її покращила і чому пізніше ребро ABA\text{–}B не змінило dist[B]\text{dist}[B]?
  3. Перелічіть ребра МОД графа GG і його сумарну вагу. Чому Прим і Крускал тут зобов’язані дати однаковий набір ребер?
  4. У трасуванні Крускала ребро DF(6)D\text{–}F\,(6) було пропущене. Яке порівняння Find\text{Find} спричинило пропуск і який цикл замкнуло б це ребро?
  5. Дайте означення насиченої та порожньої дуги. У прикладі §8.6 після першої ітерації назвіть усі насичені й усі порожні дуги.

Стандартні

  1. Проженіть Дейкстру на GG із джерела GG (а не AA). Наведіть таблицю, що еволюціонує, і остаточні відстані до всіх вершин.
  2. Побудуйте повне трасування Прима на GG, стартуючи з вершини CC (а не AA). Чи збіглося отримане дерево з деревом зі старту AA? Поясніть, спираючись на Наслідок 8.3.
  3. Сформулюйте властивість розрізу й застосуйте її до першого кроку Прима зі старту AA: укажіть розріз (S,VS)(S,V\setminus S) і легке ребро, яке додається.
  4. Наведіть невеликий орієнтований граф із одним від’ємним ребром, на якому Дейкстра дає неправильну відстань; покажіть її результат і справжню відстань та назвіть алгоритм, яким слід скористатися.
  5. У мережі §8.6 знайдіть усі розрізи (S,T)(S,T) і їхні пропускні здатності. Який із них мінімальний? Звірте з величиною максимального потоку.

Підвищеної складності

  1. Доведіть властивість розрізу (Теорема 8.2) повністю, зокрема твердження, що цикл перетинає будь-який розріз парну кількість разів.
  2. Доведіть коректність Дейкстри (Теорема 8.1) і вкажіть єдиний крок, який потребує невід’ємності ваг. Що зламається за наявності від’ємного ребра?
  3. Доведіть, що якщо всі ваги ребер різні, то МОД єдине (Наслідок 8.3). Підказка: візьміть найлегше ребро в симетричній різниці двох різних МОД і застосуйте обмінний аргумент.
  4. Змініть у GG вагу ребра BCB\text{–}C з 88 на 11. Перерахуйте МОД (ребра й вагу) і скажіть, яке ребро залишає дерево та чому.
  5. Побудуйте мережу, у якій єдиний спосіб досягти максимального потоку — застосувати збільшуючий ланцюг зі зворотною дугою; поясніть, чому без зворотних дуг алгоритм застряг би, і знайдіть мінімальний розріз.

Lectures/ODM-L08.md · 67.6 KB · updated 2026-08-04 15:46