Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Зубенко, Омельчук - Програмування. Поглиблений курс

.pdf
Скачиваний:
58
Добавлен:
07.03.2016
Размер:
5 Мб
Скачать
☆

 

 

 

 

 

 

 

Розділ IV. АЛГОРИТМИ

 

1

 

2

 

3

 

4

 

 

 

 

 

 

 

1

0

 

0

 

1

 

1

 

 

2

1

 

0

 

0

 

1

 

 

3

0

 

0

 

0

 

1

 

 

4

0

 

0

 

0

 

1

 

 

Для орієнтованого графа сума довжин усіх списків суміжних вер- шин дорівнює загальній кількості ребер: ребру (u, v) відповідає еле-

мент v списку Adj[u].

Зображення орієнтованого зваженого графа обома способами:

 

 

1

3

 

2

2

1

3

 

1

 

5

 

 

 

 

4

4

Граф 3

Списки суміжних вершин зручні для подання зважених графів, в яких кожному ребру відповідає деяка вага (напр., число).

Зображення графа 3 у вигляді списку суміжних вершин:

 

 

 

4

1

 

 

3

 

2

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2 1 3 4 1

3 4 5

4 4 4

За допомогою матриці суміжності також зручно зображувати оріє- нтовані графи, при цьому вагу w ребра (u,v ) можна зберігати на пе- ретині рядка u та стовпчика v .

571

ПРОГРАМУВАННЯ

Зображення графа 3 за допомогою матриці суміжності:

 

1

2

3

4

1

Nul

nul

2

1

2

3

nul

nul

1

3

Nul

nul

nul

5

4

Nul

nul

nul

4

Для відсутніх ребер можна записати значення nul (іноді пишуть 0 або ∞ ).

Отже, для невеликих графів, коли місця в пам'яті вистачає, матри- ця суміжності досить зручна. Якщо не потрібно зберігати вагу, то її елементи є бітами та їх можна розміщувати по кілька в одному ма- шинному слові, що дає помітну економію пам'яті. Якщо ж потрібно зберігати великий зважений граф, то краще застосовувати списки суміжних вершин.

4.6.2. ПОШУК ШЛЯХІВ У ГРАФАХ

Для пошуку тих чи інших шляхів у графах застосовуються різні ал- горитми. Розглянемо лише деякі з них:

пошук у глибину;

пошук у ширину;

алгоритм Дейкстри для пошуку найкоротшого шляху від даної вершини до інших.

Іноді дуже складно оцінити ефективність методу пошуку. Для нас найважливішими є два критерії: 1) як швидко при пошуку знайдено ро- зв'язок (шлях у графі); 2) наскільки задовільним є знайдений розв'язок.

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

Швидкість пошуку визначається як довжиною шляху розв'язку, так і кількістю вершин, через які фактично доводиться пройти в процесі пошуку шляху. Варто пам'ятати, що при поверненні з тупика зусилля виявляються витраченими даремно. Тому необхідно виробити таку стратегію пошуку, завдяки якій до мінімуму зводиться можли- вість тупикових ситуацій.

Необхідно розуміти різницю між гарним і оптимальним розв'язка- ми. Щоб знайти оптимальний розв'язок, може знадобитися вичерп- ний пошук для підтвердження того, що розв'язок – найкращий. Для

572

Розділ IV. АЛГОРИТМИ

гарного розв'язку достатньо знайти шлях, що задовольняє набір об- межень; при цьому неважливо, чи є ще кращий розв'язок.

Методи пошуку, описані далі, не в усіх ситуаціях працюють одна- ково ефективно. Тому важко сказати, чи завжди один метод кращий за інший. Проте в середньому деякі методи будуть прийнятнішими ніж інші. Іноді сама постановка задачі підказує кращий метод.

4.6.3.ПОШУК У ШИРИНУ

Єодним із базових алгоритмів пошуку. Нехай задано граф G = (V ,E )

іфіксовану початкову вершину s . Алгоритм пошуку в ширину перера- ховує всі досяжні із s вершини в порядку зростання відстані від s . Ві- дстанню вважається довжина найкоротшого шляху (кількість ребер чи сума вагових коефіцієнтів для зваженого графа). Під час пошуку шляху

вграфі виділяється дерево пошуку в ширину з коренем s . Воно містить

усі досяжні із s вершини й тільки їх. Алгоритм може застосовуватись як для орієнтованих, так і для неорієнтованих графів.

Назва пояснюється тим, що в процесі пошуку ми йдемо в ширину, а не в глибину (спочатку переглядаємо сусідні вершини, потім їхніх сусідів і т. д.). Для наочності поділятимемо вершини графа на білі, сірі й чорні. Спочатку вони всі білі, а в процесі роботи алгоритму біла вершина може стати сірою, сіра – чорною, але не навпаки. Зустрівши нову вершину, алгоритм пошуку фарбує її в сірий чи чорний відпові-

дно до правила: якщо (u,v ) E та u – чорна, то v – чорна або сіра.

Різниця між сірими й чорними вершинами використовується алгори- тмом для керування порядком обходу: сірі вершини – це ті, по яких ще потрібно рухатися, а всі сусіди чорних уже переглянуті. Отже, тільки сірі вершини можуть мати суміжні невиявлені вершини. Спо- чатку дерево пошуку складається тільки з початкової вершини s . При виявленні алгоритмом нової вершини v , суміжної з раніше знай-

деною вершиною u , вершина v разом із ребром (u,v ) додається до

дерева пошуку, стаючи нащадком вершини u , а u стає батьком v . Дерево пошуку в ширину зручно зберігати в черзі (див. вправу 16, б). Кожна вершина знаходиться тільки один раз, тому двох батьків у неї бути не може. Оцінюючи складність цього алгоритму, зауважимо, що вершини тільки темніють. Час обходу суміжних вершин до даної ве-

ршини становить O(E ), де E – кількість ребер, а обчислювальна

573

ПРОГРАМУВАННЯ

складність алгоритму пошуку в ширину – O(V + E ), де V – кількість

вершин у графі.

Проілюструємо порядок обходу дерева в ширину, згідно з яким пронумеровано вершини:

1

2

3

4

5

6

7

8

9

4.6.4. ПОШУК У ГЛИБИНУ

Стратегія пошуку в глибину: іти вглиб, поки є вихідні ребра, що не пройдені; повертатися й шукати інший шлях, коли таких ребер не- має. Якщо після цього залишаються незнайдені вершини, то вибрати одну з них і повторювати процес доти, доки вони є.

Знайшовши вперше вершину v , суміжну з u , записуємо в поле π[v] значення u . Утворюється дерево чи кілька дерев, якщо пошук

відбувається з кількох вершин. Як і при пошуку в ширину, алгоритм пошуку в глибину використовує кольори вершин. Спочатку всі вер- шини – білі. При виявленні нової вершини вона фарбується в сірий колір, а після обробки – у чорний. Крім цього, пошук у глибину ста-

вить на вершинах мітки часу. Кожна вершина має дві мітки: d [v] показує, коли вершина виявлена, f [v] – коли оброблена. Мітки вико-

ристовуються в багатьох алгоритмах на графах. Під час виконання алгоритму обробка кожної вершини відбувається рівно один раз.

Складність роботи алгоритму становить O(V + E ), де V – кількість

574

Розділ IV. АЛГОРИТМИ

вершин у графі, а E – кількість ребер. Дерево пошуку в глибину

зручно зберігати в стеку (див. вправу 16, а).

Проілюструємо порядок обходу дерева в глибину, згідно з яким пронумеровано вершини:

1

2

5

7

3

4

6

8

9

4.6.5. АЛГОРИТМ ДЕЙКСТРИ

Даний алгоритм запропонував Е. Дейкстра для знаходження най- коротшої відстані від заданої вершини до зваженого орієнтованого

графа G = (V ,E ) з початковою вершиною s V , в якому вага всіх ре- бер невід'ємна: (u, v ) E : w (u, v )≥ 0 .

Кожній вершині з V зіставимо мітку – мінімальну відому відстань від вершини до s . На початку роботи алгоритму мітка вершини s ініціалізується значенням 0 , мітки інших вершин – нескінченності. Це означає, що відстані від s до інших вершин поки невідомі. Усі вершини графа позначаються як невідвідані.

Якщо всі вершини переглянуті, то алгоритм завершується. Інакше зі ще не переглянутих вершин вибирається вершина u , яка має міні- мальну мітку. Розглянемо всі можливі маршрути, в яких u є передос- таннім пунктом, а для кожної сусідньої з u вершини – нову довжину шляху, що дорівнює сумі поточної мітки u й довжини ребра, яке з'єд- нує u із сусідом. Якщо отримана довжина менша за мітку сусіда, то замінимо останню на цю довжину. Розглянувши всіх сусідів, позна-

575

ПРОГРАМУВАННЯ

чимо вершину u як переглянуту й повторимо крок. Робота алгоритму завершується, коли всі вершини переглянуті.

Приклад 4.35. Алгоритм Дейкстри.

Нехай потрібно знайти відстань від вершини 1 до всіх інших для графа:

 

 

1

3

 

2

2

1

3

 

1

 

5

 

 

 

 

4

Початкова ініціалізація:

 

 

0

 

 

 

1

 

3

 

2

 

∞ 2

1

3

∞

 

 

 

1

 

5

 

 

 

 

 

 

4

 

∞

Крок 1. Мінімальну мітку має вершина 1. Її сусідами є вершини 3 та 4. Позначимо довжини шляхів до вершин через першу. Довжина шляху у вершини 3 чи 4 через вершину 1 дорівнює сумі найкоротшої відстані до вершини 1 і довжини ребра, що йде з 1 у 3 чи 4, відповід- но, тобто 0 + 2 = 2 для вершини 3 (0 + 1 = 1 – для 4). Це менше поточ- них міток вершин 3, 4, тому нова мітка третьої вершини дорівнює 2, а четвертої – 1:

576

Розділ IV. АЛГОРИТМИ

 

 

 

0

 

 

 

 

1

 

 

3

 

2

 

∞

2

1

3

2

 

 

 

 

 

1

5

 

4

1

Усі сусіди вершини 1 перевірені. Поточна мінімальна відстань до вершини 1 вважається остаточною (те, що це дійсно так, уперше до- вів Дейкстра). Позначимо її як переглянуту:

 

 

 

0

 

 

 

 

1

 

 

3

 

2

 

∞

2

1

3

2

 

 

 

 

 

1

5

 

 

4

1

Крок повторюється – беремо вершину з мінімальною міткою (4) і переглядаємо шляхи з неї. Таких шляхів у даному випадку немає. Ви- креслюємо її як переглянуту:

 

 

 

0

 

 

 

 

1

 

 

3

 

2

 

∞

2

1

3

2

 

 

 

 

 

1

5

 

 

4

 

1

577

ПРОГРАМУВАННЯ

Знову беремо вершину з мінімальною міткою (3) і переглядаємо шля- хи з неї. Є шлях у вершину 4, але ця вершина вже викреслена зі списку. Отже, з вершини 3 шляхів немає. Викреслюємо її як переглянуту:

 

 

 

0

 

 

 

 

1

 

 

3

 

2

 

∞

2

1

3

2

 

 

 

 

1

 

5

 

 

 

 

 

4

1

На наступному кроці знову шукаємо вершину з мінімальною міт- кою (2), але її мітка ∞, тобто вона недосяжна з вершини 1, тому не може зменшити мітки інших вершин. Викреслюємо її з міткою ∞. Ал- горитм завершений, оскільки всі вершини розглянуті ■

Складність алгоритму Дейкстри залежить від способів знаходження ве- ршини v , збереження множини невідвіданих вершин і відновлення мі- ток. Позначимо через n кількість вершин, а через m – ребер у графі G .

У найпростішому випадку, коли для пошуку вершини з мінімаль- ним d [v] проглядається вся множина вершин, а для збереження ве-

личин d – масив, час роботи алгоритму становить O(V 2 + E ). Осно-

вний цикл виконується близько V разів, кожного разу на знахо- дження мінімуму витрачається приблизно E операцій, плюс кіль-

кість змін міток, що не перевищує кількості ребер у вихідному графі. Для розріджених графів невідвідані вершини можна зберігати у

двійковій купі, а як ключ використовувати значення d [i]. Швидкість роботи такої реалізації O(V log V + E log V ).

Якщо для зберігання невідвіданих вершин використати фібоначчі- єві купи, в яких час додавання елемента O(1), то час роботи алгорит-

му становитиме O(V log V + E ).

578

Розділ IV. АЛГОРИТМИ

4.6.6. ЗАСТОСУВАННЯ АЛГОРИТМІВ ПОШУКУ

Повернемося до задачі, описаної на початку підрозділу. Нехай ав- тобуси компанії ABC за розкладом здійснюють такі рейси:

Рейс

Відстань, км

Київ – Львів

544

Львів – Чернігів

701

Київ – Чернігів

151

Київ – Вінниця

266

Вінниця – Донецьк

812

Вінниця – Сімферополь

801

Вінниця – Львів

369

Чернігів – Житомир

297

Чернігів – Одеса

634

Одеса – Сімферополь

473

Донецьк – Сімферополь

571

Завдання полягає в тому, щоб написати кілька варіантів програм, які будуть вибирати маршрути поїздок і порівнювати їх.

Інформацію про рейси компанії ABC природно подати у вигляді зваженого орієнтованого графа, де вагою є відстань (рис. 4.7). Для спрощення зображення не будемо прописувати вагу на ребрах.

Чернігів

Львів

Київ

Житомир

Вінниця

Донецьк

Одеса

Сімферополь

Рис. 4.7

Для побудови програм застосуємо технологію UML-проектування, описану в підрозд. 2.7.3. Програма, призначена для вибору маршру- ту, має надавати користувачу можливість введення пунктів виїзду й прибуття, а також відображати знайдений маршрут.

Діаграма прецедентів для розроблюваної програми:

579

ПРОГРАМУВАННЯ

Користувач

<<include>> <<include>>

Задати пункт виїзду

Знайти маршрут Переглянути маршрут

<<include >>

Задати пункт прибуття

У програмі, призначеній для вибору маршруту, використовується база даних з інформацією про рейси компанії ABC. Кожен запис бази містить відомості про місто – пункт відправлення й місто – пункт прибуття, про відстань між ними, а також прапорець, що допомагає при пошуку з поверненням.

Діаграма класів програми:

CStack

m_sFrom : char[20]

m_sTo : char[20] m_iDist : int

0..n CStack() bt_stack

1

1

CFind

setup()

route()

assertFligh...

push()

pop()

isFlight()

find()

match() setEmpty()

fligth CFl

m_cSkip : char 0..n

CFl()

580