Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
О.О.П / ооп / 4_кол / К курсовой / Методи побудови алгоритмів та їх аналіз / Інформатика_1 (методи побудови алгоритмівта та їх аналіз).doc
Скачиваний:
0
Добавлен:
30.05.2020
Размер:
2.5 Mб
Скачать

Створення алгоритму

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

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

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

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

Усі алгоритми поділяються на два великі класи. Це алго­ритми з повторениями та рекурсивні алгоритми.

В основі алгоритмів з повторениями лежать операції перехо­ду та умовні вирази, аналіз яких і виконує відповідні переходи. Для аналізу таких алгоритмів треба оцінити кількість операцій усередині циклу та кількість повторень цих циклів.

Рекурсивні алгоритми поділяють велику задачу на підзадачі, до кожної з яких окремо знову застосовуються ці самі алго­ритми. Такі алгоритми ще відносять до алгоритмів, основою створення яких є ідея «розділяй і володарюй». Перевагою та­ких алгоритмів є невеликий, простий і зрозумілий запис. Аналіз рекурсивних алгоритмів вимагає підрахунку кількості опе­раций, необхідних для поділу задачі на підзадачі, виконання алгоритму в кожній із них та об'єднання отриманих резуль­тате для розв'язування задачі в цілому.

Отже, створення алгоритму - це один з етапів розв'язування поставленої задачі. Але він не відокремлений від усіх інших етапів, таких як:

  • визначення моделі задачі, що розв'язується;

  • запис алгоритму мовою програмування;

  • тестування програми на комп'ютері;

  • отримання розв'язку поставленої задачі шляхом виконан­ня завершеної програми.

Математична модель, вибір структури даних

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

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

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

Інколи вибір структури даних визначений у самій постановці задачі. Наприклад, якщо треба упорядкувати послідовність елементів, то зрозуміло, що структурою вхідної та вихідної інформації буде одновимірний масив. Якщо ж умова задачі передбачає обробку елементів деякої таблиці, то вибір структу­ри даних є однозначним: двовимірний масив. Прикладом такої задачі може бути визначення рейтингу учнів класу за семестр з інформатики.

Є і складніші структури даних. Про них ітиметься в пособ­нику далі. Знания всіх можливих структур даних значно роз-ширить коло задач, які ви зможете розв'язувати.

Пошук оптимального алгоритму розв'язування

Який алгоритм вважати оптимальним? Якими є критерії оптимальності алгоритму? Як визначити, чи даний алгоритм є оптимальним? Чи варто взагалі говорити про оптимальність алгоритму, який розробляється?

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

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

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

Тому в першу чергу при розробці алгоритму треба намагатися досягти найменшої кількості виконуваних ним операцій, записуючи алгоритм при цьому максимально компактно і зрозуміло та використовуючи мінімальну кількість додаткових змінних.

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

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

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

7

xv x2, xr Схема пошуку розв'язку цієї задачі зображена на малюнку 1.

Мал. 1

Ця схема представляв собою бінарне (двійкове) дерево, у вуз-лах якого розміщені пари елементів, що порівнюються на кожному кроці. Pyx гілками дерева від його кореня (найвищої вершини) визначає конкретну гілку, яка у свою чергу є одним із варіантів отримання розв'язку задачі. Ліва гілка кожної вершини відповідає виконанню зазначеної у вершині умови, а права - ні. Кожна гілка завершується вершиною, де вказаний порядок розміщення заданих елементів, що задовольняє розв'язок задачі. Кількість таких гілок визначає кількість можливих варіантів отримання розв'язків задачі. Максимальна довжина гілки дорівнює кількості ребер, якими треба пройти від кореня дерева до останньої вершини у цій гілці. Таке дерево називається деревом розв'язків. За допомогою дерева розв'язків можна визначити кількість операцій, необхідних для роз-в'язування поставленої задачі.

Дерево розв'язків можна побудувати для кожної задачі. На малюнку 1 ми бачимо, що в даній задачі є 6 можливих варіантів отримання результату. Також можна помітити, що не всі гілки дерева однакової довжини. Серед них є найкоротші, які дають розв'язок задачі при певних початкових даних. Інші гілки є довшими і також дають правильну відповідь при іншій послідовності початкових даних. Для нашого прикладу найменша гілка мае довжину 2, а найбільша - 3. Яке з цих значень обрати за оптимальну кількість операцій, необхідних для отри­мання правильного розв'язку нашої задачі?

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

Давайте підійдемо до визначення оптимально! кількості виконуваних алгоритмом дій не лише за допомогою аналізу дерева розв'язків, а й з точки зору математичних міркувань.

На кінцях кожної з шести гілок розміщені всі можливі варіанти розташування заданих трьох вхідних елементів. Вияв-ляється, що можлива кількість переставлень для N елементів обраховується за формулою k - N\. Це можна пояснити так. Треба розмістити N різних елементів на N місцях. Для першого елемента є N варіантів розміщення, для другого залишається уже (N - 1) місце, для третьего - (N - 2) і т. д. Для останнього N-ro елемента залишиться одне місце. Для одержання загаль-ної кількості варіантів необхідно всі ці числа перемножити: N- (N - 1) • (N - 2) • ... • 1 = iVI. Це пояснюється тим, що, наприклад, для всіх варіантів розміщення першого елемента необхідно перебрати всі варіанти розміщення другого, а для всіх цих варіантів - розміщення третьего і т. д. Для нашої конкретно!' задачі одержимо 3! = 6.

А скільки рівнів мае бути у дереві розв'язку? Будемо нуме-рувати рівні, починаючи з 0. 3 малюнка 1 видно, що на кож­ному рівні кількість вершин подвоюється: на нульовому рівні -1 вершина, на першому — 2, на другому - 4, на третьому - 4. За логікою на третьому рівні мало б бути 8 вершин, але для нашої задачі їх усього 4, оскільки не всі вершини попереднього рівня мають своїх нащадків. Напрошується висновок, що на К-му рівні кількість вершин дорівнює 2К~1. А для всієї кількості елементів N треба знайти таке найменше L, для якого вико-нуеться умова N\ < 2L~l.

У математиці існує функція, яка визначає степінь у вказано-го числа х. Називається така функція логарифмічною і позна-чається log. Якщо ввести позначення k = ху, то можна сказати, що, знаючи число k, за допомогою логарифма можна дізнатися, до якого степеня треба піднести число х, щоб отримати k. Ма-тематично це записується так: у = \ogxk.

Використовуючи нові терміни, перейдемо до нашої задачі. Оскільки з нерівності N\ < 2L'X треба знайти L, то прологариф-муємо обидві и частини за основою 2. Таким чином, у правій частині нерівності ми отримаємо (L - 1), а у лівій - вираз log2M:

log^KL-l.

Як спростити вираз log2iV!? Нагадаємо його тлумачення: це степінь, до якого треба піднести 2, щоб отримати значения N1. Оскільки Nl'N- (N-l) ■ (N-2) ■... ■ 2 • 1, то нас цікавитиме сте-пінь, до якого треба піднести окремо числа N, (N-l), (N-2),..., 2, 1. Тому можна записати:

9

log2M = log2(N ■ (N-l) (N-2) ■ ... • 1) =

N

«= log^ + log2(W - 1) + \og2(N - 2) + ... + log2l = ]>>g2 i.

1=1 Скориставшись математичними формулами сумування, які не будемо тлумачити у рамках даного посібника, запишемо:

£log2l~N • log^ - 1,5 ~N • log2N.

i=l

Отриманий запис N ■ log2N слід розуміти так: оптимальна кількість операцій, необхідних для упорядкування заданої послідовності з N елементів, не повинна перевищувати добутку такого степеня числа 2, що в результаті дає N, та самого числа N. Зрозуміло, що log2N значно менше за саме число JV, а тому N • \og2N значно менше за N2.

Надалі в оцінках ефективності роботи алгоритмів будемо використовувати логарифмічну функцію за основою 2 і позна-чатимемо ЇЇ log2x.

Таким чином, ми визначили, якою повинна бути оцінка оптимальності алгоритму упорядкування будь-якої послідов-ності з Л^ елементів. Тепер знаємо, що будь-який алгоритм сортування, який дає правильний результат за N • log.^N опера­щи, є найкращим і його можна вважати оптимальним. Алго-ритми, які визначають результат задачі за меншу кількість операцій, необхідно детально дослідити. Можливо, за основу такого алгоритму було взято щось інше, ніж порівняння на кожному кроці двох елементів, як у нашому випадку.

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

Узагальнення та аналіз екстремальних ситуацій

У попередніх пояснениях використовувалося поняття «опе-рація». Зупинимося детальніше на тому, які дії алгоритму вважати операціями і які треба враховувати при обчисленні оцінки нашого алгоритму.

Традиційно вважається, що значущими є операції двох ти-пів: порівняння та арифметичні операції.

Усі операції порівняння вважаються еквівалентними за ча­сом виконання. Найчастіше ці операції використовуються в операторах умовного переходу та повторенні.

Арифметичні операції, у свою чергу, діляться на дві групи:

  • додавання, віднімання, збільшення та зменшення лічиль-ника;

  • множення, ділення, отримання залишку по модулю.

10

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

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

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

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

Найгіршим випадком для наведених прикладів є пошук еле­мента, який розміщений у заданій послідовності на останньому місці або його зовсім там немає, та упорядкування за зростан-ням вхідної послідовності, яка упорядкована за спаданиям. Аналіз найгіршого випадку дає верхні оцінки для часу роботи досліджуваного алгоритму. Для задачі пошуку заданого еле­мента в послідовності треба буде зробити N порівнянь, а для за­дач! упорядкування — N2.

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

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

11

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

1

для всіх варіантів вхідних даних і вважається рівною —, де

М М кількість груп. У напіій задачі ймовірність потрапляння

1 кожного варіанта у свою групу дорівнює —.

N У загальному випадку вхідні дані однієї групи мають однако-

вий час виконання tt, оскільки саме за цією умовою ми поділили їх на групи, тому середній час роботи обчислюється за формулою:

1 м

Мы Шдставимо в наведену формулу дані нашої задачі. Умовно вважатимемо, що час виконання кожного варіанта вхідних да­них дорівнює кількості виконуваних операцій: для першого випадку - 1 операція, для другого - 2 і т. д. Тому сума кількос-ті операцій, виконуваних для кожного варіанта вхідних даних, виглядае як 1 + 2 + 3 + ... + (N - 1) + N. Загальна формула для визначення кількості виконуваних операцій для середнього ви­падку нашої задачі виглядае так:

N

Значения арифметичного виразу 2*' = 1+2 + 3 + ... +

+ (N - 1) + N можна замінити компактною формулою. Для цього розв'яжемо таку задачу.

Нехай треба знайти х, де х = 1 + 2 + 3 + ... + (N - 1) + N. До-дамо до даного виразу ще такий самий, але доданки розмістимо у зворотному порядку. Відповідно шукана сума збільшиться вдвічі: 2x-[l + 2 + 3 + ... + (N-l) + N\ + [N + (N-l) + ... + 3 + + 2 + 1]. Згрупуємо доданки у двох квадратних дужках: до пер­шого доданка першої дужки додамо перший доданок другої і т. д. Отримаємо: 2х - [1 + N] + [2 + {N - 1)] + ... + [N + 1] = - N(N + 1). Тепер уже нескладно знайти невідоме:

N(N + 1)

Отже, можемо визначити середній час роботи алгоритму:

1 N(N + 1) N + 1 N пс N

5 '- = = — + 0,5 = —.

N 2 2 2 2

Часом виконання пошуку заданого елемента в будь-якій вхід-

•* N

нш послідовності в середньому випадку можна вважати —,

2

оскільки для великих значень N числом 0,5 можна знехтувати.

12

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

Оцінка та аналіз ефективності алгоритму

Давайте спочатку домовимося, які саме алгоритми будемо порівнювати і як це робитимемо.

Порівнювати можна лише алгоритми, призначені для роз-в'язування однієї і тієї самої задачі. Абсолютно недоречно порівнювати алгоритм пошуку заданої величини у відомій послідовності елементів з алгоритмом її упорядкування. А от порівнювати між собою різні алгоритми сортування послідов-ностей - зовсім інша справа. Це вже логічно.

Тепер треба визначитися з питаниям: що вважати критеріями ефективності виконання алгоритму? На думку приходить досить природна відповідь: час виконання алгоритму. Але насправді це досить суб'єктивна оцінка, оскільки вона пропорційна тех-нічним характеристикам комп'ютера, на якому виконусться алгоритм, а саме тактовій частоті процесора. Ще одним факто­ром, який впливає на час виконання алгоритму, є якість ком-пілятора, що оптимізує виконуваний код алгоритму. У разі перенесения одного і того самого алгоритму з «повільного» комп'ютера на більш «швидкий» мало що зміниться в самому алгоритмі: від цього він не стане ані кращим, ані гіршим.

Що ж тоді є визначальним у ефективності виконання алго­ритму? Це не реальна кількість секунд або інших інтервалів часу, а приблизна кількість операцій, що виконусться даним алгоритмом. Саме цей фактор будемо вважати «часом», який визначає ефективність алгоритму, що, відповідно, породжу-ється його складністю.

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

Перейдемо до детального обговорення питания щодо визна-чення ефективності виконання алгоритму.

13

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

Для невеликих N, наприклад N ■ 10, кількість таких опера-цій можна порахувати точно. Але чи є в цьому потреба? Відмін-ність між алгоритмом, який виконує N + 5 операцій, і тим, що виконує N + 250 операцііі для великих N, наприклад N = 100 000, стає непомітною! Тому можна зробити висновок: якщо формула N + С для обчислення кількості виконуваних операцій алго­ритму містить константу С, нею можна знехтувати.

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

Наприклад, нехай два алгоритми А та В розв'язують одну й ту саму задачу та дають можливість отримати очікуваний результат. Причому алгоритм А - за N переглядів масиву з N елементів, а алгоритм В - за N2 переглядів тих саме N еле-ментів. Нехай відомо, що алгоритм А вимагає значно більшої кількості операцій, ніж алгоритм В (умовно А » В). Для прикладу вважатимемо, що алгоритм А виконує задачу за 100 операцій, а алгоритм В - за 10. Надалі порівнюватимемо їх щодо швидкості росту загальної кількості операцій.

При невеликих значениях N, наприклад N = 5, алгоритм В може бути ефективнішим за алгоритм А: для отримання ре­зультату алгоритмуА необхідно виконати 500 операцій, а алго­ритму В - 250. Якщо збільшувати N, то рано чи пізно алгоритм А програє алгоритмові В за часом роботи. Якщо взяти N = 100, то алгоритм А вимагатиме виконання 10 000 операцій, а алго­ритм В -N2" 100 000.

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

Як визначати порядок росту часу роботи алгоритму і як його позначати? Для цього найкраще розглянути конкретний

14

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

Номер

Дія

Час

ВИКО-

Кількість

кроку

нання

повторень

1

for І := 2 to N

ci

N

2

dox:=A[i];

C2

N-1

3

визначення мюця k(1<k<i-1), де мае стояти A[j] у відсортованій частині А[1 .. і - 1];

C3

N-1

4

forj := ktoi - 1

c4

2 + 3 + ... + N

5

doA[j + 1]:=A[j];

C5

1 +2 + 3 + .. . + (N- 1)

6

A[k] :■ x

C6

N-1

Пояснимо, як визначити кількість повторювань на кожному кроці алгоритму.

Призначенням кроку 1 є організація циклу, що виконується (N - 1) разів. Але оскільки виконання оператора повторения передбачає і перевірку завершения роботи циклу, то кількість таких дій збільшується на 1. Тобто загальна кількість повто­рень кроку 1 складає N.

Кроки 2, 3 та 6 виконуються у циклі, визначеному кроком 1, (N -1) разів.

На окрему увагу заслуговують кроки 4 і 5. У найгіршому ви-падку, коли всі елементи вхідного масиву повинні бути пере­ставлен! на перше місце, тобто буде виконана максимальна кількість переміщень для кожного елемента масиву. Перегляд елементів масиву починається з другого елемента, і цикл кроку 4 виглядатиме так: forj := 1 to 1. Це означає, що даний крок ви-конається двічі: один раз сам цикл, другий - перевірка на його завершения. Для другого елемента масиву буде виконуватися цикл forj := 1 to 2, і кількість виконуваних ним дій складатиме 3. Для останнього iV-ro елемента цикл for j := 1 to N - 1 вимагати-ме N звернень до кроку 4. Таким чином, загальна кількість ви­конання кроку 4 складатиме 2 + 3 + ... + N. Ми вже раніше виводили формулу для обчислення суми 1 + 2 + 3 + ... + N=

(1 + N)N z • Скориставшись тим самим алгоритмом для нашого

(2 + iV)(iV-l) випадку, отримаемо .

15

Використовуючи попередні міркування, можна стверджува-

ти, що крок 5 у найгіршому випадку виконається за 1 + 2 + 3 +

+ ... + (N - 1) разів. Обчислити загальну кількість виконан-

N(N-1) ня кроку 5 можна за формулою .

В

Таким чином, загальний час виконання нашого алгоритму складає:

T(N) = clN + c2(N-l) + cs(N-l) + c^2 + N'^N~1' +

N(N-l)

Яким чином визначити порядок росту часу роботи цього ал­горитму? Розглянемо його виконання у таких трьох випадках:

  • найкращому: елементи вхідного масиву утворюють упо-рядковану за зростанням послідовність;

  • найгіршому: елементи вхідного масиву утворюють упо-рядковану за спаданиям послідовність;

  • середньому: елементи вхідного масиву утворюють послі-довність, згенеровану випадковим чином.

У першому випадку треба буде лише переглянути всі еле­менти масиву і залишити їх на своїх місцях, тобто кроки 4—5 не виконаються жодного разу. Загальний час виконання алго­ритму в такому випадку становитиме:

T(N) = ctN + c2{N - 1) + c3{N - 1) + ce(N - 1) -= (с, + c2 + c3 + c6)N - (c2 + c, + ce).

Зважаючи на те, що при обчисленні кількості виконуваних алгоритмом операцій константами можна знехтувати, то мож­на зробити висновок, що для нашого алгоритму вона станови­тиме — N.

У другому випадку треба переставити місцями всі елементи масиву. Тому обчислення слід вести за формулою:

T(N) = c1N + c2(N-l) + c3{N-l) + c4± £ '-+

+ c5N{N2'1Kc6(N-l) = clN + c2(N-l) + c3(N-l) + N2+N-2 N2-N ,„ ,. (с. c.VTl

+ c< 2— ♦v-j^^-i)-^*]» +

+ f с, + c2 +c3 +|-_|. + Ce W -(e, +C, +c< +c,\

Ми отримали квадратичний вираз для обчислення часу ви­конання алгоритму в найгіршому випадку. Оскільки при вели-

16

них значениях N можна вважати, що N значно менше за Nz i,

як відомо, константами можна знехтувати, то кількість обчис-

лювальних дій - N2.

N Для останнього випадку зсув треба буде виконати для — еле-

2

ментів масиву (кроки 4 і 5), але при цьому все одно перегляну-

ти всі елементи масиву. Визначити час можна за формулою: T(N) = clN + c2{N-l) + ca{N-l) + ct(2 + N^N~1K

+k+c2 + c3+^--|--ce W-

С4

Це доводить, що i в середньому випадку час квадратично за-лежить від N, тобто кількість обчислювальних дій « N2.

Отже, бачимо, що час роботи в найгіршому і найкращому випадках дуже відрізняється один від одного. Але найчастіше нас цікавитиме саме час роботи в найгіршому випадку. Чому саме так? Спробуємо відповісти на це запитання таким чином: на це є три причини.

  1. Знаючи час роботи алгоритму в найгіршому випадку, ми можемо гарантувати виконання алгоритму за обчислений час при будь-якому наборі вхідних даних.

  2. На практиці «погані» вхідні дані зустрічаються частіше, ніж «гарні».

  3. У середньому випадку час роботи алгоритму може бути дуже близьким до часу роботи в найгіршому випадку.

Як позначати отриману характеристику ефективності робо­ти алгоритму? Аналіз часу роботи алгоритму упорядкування за зростанням N елементів одновимірного масиву методом вклю­чения базувався на декількох спрощеннях. У формулах, виве-дених для обчислення кількості операцій, виконуваних алго­ритмом, ми знехтували членами менших порядків (лінійними) та коефіцієнтом при N2. Робимо висновок, що швидкість росту часу роботи алгоритму є квадратичною. Це записується так: 0(N2).

Зрозуміло, що алгоритм, для якого час виконання визна-чається як 0(N2), мае переваги перед алгоритмом, час виконан­ня якого 0(N3). Чим більшою буде кількість вхідних даних, тим відчутнішими будуть ці переваги.

Заслуговуе на увагу й аналіз алгоритмів типу «розділяй і во-лодарюй». Підрахунок кроків таких алгоритмів не дуже прос-тий. Він залежить від рекурсивних викликів, від підготовчих та завершальних дій.

17

Як приклад розглянемо рекурсивный алгоритм обчислення значения факторіала:

  1. Factorial(N);

  2. if N = 1

  1. then Factorial := 1

  2. else Factorial := N * Factorial(N - 1);

Введемо такі позначення: a - кількість підзадач, на які поді-ляється наша задача; b - коефіцієнт, який визначає, у скільки ра-зів підзадача менша за задачу; T(N/b) - час виконання підзада-чі; D(N) - час, необхідний для поділу задачі на підзадачі; C(N) -час, необхідний на об'єднання отриманих розв'язків підзадач у кінцевий результат. Таким чином, формула для обчислення за-гального часу виконання рекурсивного алгоритму буде такою:

0(N) = a!7i— + D(N) + C{N).

Який висновок можна зробити з наведено!' формули? А він такий: використовувати рекурсивні алгоритмы є сенс лише тоді, коли накладні витрати D(N) та C(N) значно менші, ніж час виконання самого алгоритму. А це може статися лише за великих значень N. Для малих N краще застосовувати прямі методи розв'язування задачі.

Повернемося до задачі і спробуємо оцінити час виконання рекурсивно! версії Гї розв'язування. Оскільки граничним зна­чениям N є 1, то гілка then виконається лише один раз. Тому можна вважати, що в тілі цієї підпрограми фактично весь час виконується гілка else, а саме: Factorial := N * * Factorial(N - 1). У цьому арифметичному виразі використовується лише одна операція множення значения змінної N на результат виконан­ня функції, що не залежить від інтервалу, на якому обчис-люється значения факторіала [l..iV], [1..JV - 1], ..., [1..1]. Тому

можна вважати, що 71— =1.

Що ж являе собою операція віднімання, яка використовуєть-ся при виклику функції? Саме вона необхідна для поділу задачі на підзадачі. Таких підзадач буде N, оскільки саме стільки звер-нень буде до функції Factorial. Тобто в нашому випадку а = N.

Поділ нашої загальної задачі на підзадачі полягає в тому, що на кожному наступному кроці довжина інтервалу [l..iV] змен-шується на 1: [1..N], [1..N - 1], ..., [1..1]. А це означав, що поділів буде N і витрати часу на них складатимуть відповідно D(N) та C(N).

Отже, можна визначитися з формулою для обчислення за-гального часу виконання рекурсивного алгоритму Factorial(N):

0(N) = N-1+ D(N) + C(N).

18

Отриманий результат показує, що для даної задачі краще використовувати звичайний алгоритм накопичення добутку, який використовуе час на виконання лише N операцій множен-ня, оскільки часові витрати на рекурсивний виклик функції D(N) та «збирання» результатів ЇЇ виконання C(N) збільшують загальний час роботи алгоритму втричі.

А.

Запитання для самоконтролю

  1. На які класи поділяються всі алгоритми? За якою ознакою відбу-вається такий поділ?

  2. У чому полягає важливість вибору моделі для розв'язування поставлено! задачі?

  3. У чому полягає важливість вибору структури даних для роз­в'язування поставлено! задачі?

  4. Якими можуть бути критері! оптимальності алгоритму?

  5. Яким критеріям при розробці алгоритму задачі надається пріоритет, а якими можна знехтувати?

  6. Що називається деревом розв'язків задачі? Наведіть власний приклад.

  7. Як за допомогою дерева розв'язків задачі можна визначити оцінку оптимальності алгоритму?

  8. Які дії, виконувані алгоритмами, традиційно вважаються опера-ціями?

  9. Яку роль при виконанні алгоритму відіграють вхідні дані?

  1. Які вхідні дані вважаються найкращим випадком? Наведіть влас­ний приклад обчислення часу роботи алгоритму в найкращому випадку.

  2. Які вхідні дані вважаються найгіршим випадком? Наведіть влас­ний приклад обчислення часу роботи алгоритму в найгіршому випадку.

  3. Які вхідні дані вважаються середнім випадком? Наведіть влас­ний приклад обчислення часу роботи алгоритму в середньому випадку.

  4. Які алгоритми слід порівнювати при визначенні оцінки їх ефек-тивності?

  5. Що вважається критеріями ефективності виконання алгоритму? Чим можна знехтувати при обчисленні ефективності виконання алгоритму?

  6. Що називається порядком росту часу роботи алгоритму? Наве-діть приклад обчислення порядку росту часу роботи алгоритму.

  7. Як позначасться порядок росту часу роботи алгоритму?

  8. Як обчислюсться порядок росту часу роботи алгоритмів типу «розділяй і володарюй»? Наведіть власний приклад.

19