- •1.1. Визначення інформації
- •1.2. Визначення алгоритму
- •1.3. Виконавці алгоритмів
- •1.4. Способи описання алгоритмів
- •1.5. Властивості алгоритмів
- •1.6. Поняття обчислювальної складності
- •1.7. Класи алгоритмів
- •1.7.1. Експоненційні алгоритми та перебір
- •1.7.2. Алгоритм із поверненнями назад
- •1.7.3. Машини Тьюринґа
- •Будь-який алгоритм може бути реалізований відповідною машиною Тьюринґа.
- •Усе, що реалізовано в одній з цих конструкцій, можна зробити і в інших.
- •1.7.4. Рекурсія та її використання
- •1.7.5. Теза Чорча. Алгоритмічно нерозв’язні проблеми
- •2.1. Поняття структури даних
- •2.2. Рівні описуваннявання даних
- •2.3. Класифікація структур даних у програмах користувача й у пам’яті комп’ютера
- •2.4. Основні види складених типів даних
- •2.4. Структури даних у пам’яті комп’ютера
- •2.4.1. Структури даних в оперативній пам’яті
- •2.4.2. Сд у зовнішній пам’яті
- •3.1. Поняття структури даних типу «масив»
- •3.2. Набір допустимих операцій для сд типу «масив»
- •3.3. Дескриптор сд типу «масив»
- •3.4. Ефективність масивів
- •3.5. Зберігання багатовимірних масивів
- •3.6. Сд типу «множина»
- •3.7. Сд типу «запис (прямий декартовий добуток)»
- •3.8. Сд типу «таблиця»
- •3.9. Сд типу «стек»
- •3.9.1. Дескриптор сд типу «стек»
- •3.9.2. Області застосування сд типу «стек»
- •3.10. Сд типу «черга»
- •3.11. Сд типу «дек»
1.6. Поняття обчислювальної складності
Основними мірами обчислювальної складності алгоритмів є:
часова складність, яка характеризує час, необхідний для виконання алгоритму на певній машині; цей час, як правило, визначається кількістю операцій, які треба виконати для реалізації алгоритму;
ємнісна складність, яка характеризує об’єм пам’яті, необхідний для виконання алгоритму.
Часова та ємнісна складність тісно пов’язані між собою. Обидві є функціями від розміру вхідних даних. Надалі обмежимося тільки аналізом часової складності.
Складність алгоритму описуванняється функцією f(n), де n – розмір вхідних даних. Важливе теоретичне і практичне значення має класифікація алгоритмів, яка бере до увагу швидкість зростання цієї функції.
1.7. Класи алгоритмів
Основною оцінкою
функції складності алгоритму f(n)
є оцінка
.
Кажуть, що f(n) = (g(n)), якщо при g > 0 при n > 0 існують додатні с1, с2, n0, такі, що
при n > n0.
Тобто, можна знайти такі с1 та
c2, що при достатньо великих n
функція f(n) знаходитиметься між
та
.
У такому випадку функція g(n) є асимптотично точною оцінкою функції f(n), оскільки за визнченням функція f(n) не відрізняється від функції g(n) з точністю до постійного множника.
Виділяють такі основні класи алгоритмів:
логарифмічні: f(n) = (log2n);
лінійні: f(n) = (n); якщо n=1, то отримуємо константні алгоритми;
поліноміальні: f(n) = (nm); тут m – натуральне число, більше від одиниці; при m = 1 алгоритм є лінійним;
експоненційні: f(n) = (an); a – натуральне число, більше від одиниці. Експоненційні алгоритми часто пов’язані з перебором різних варіантів розв’язку.
Для однієї й тієї ж задачі можуть існувати алгоритми різної складності. Часто буває і так, що повільніший алгоритм працює завжди, а швидший – лише за певних умов.
Будемо називати часовою складністю задачі часову складність найефективнішого алгоритму для її розв’язання.
Алгоритми без циклів і рекурсивних викликів мають константну складність. Якщо немає рекурсії та циклів, всі керуючі структури можуть бути зведені до структур константної складності. Отже, і весь алгоритм також характеризується константною складністю.
Визначення складності алгоритму в основному зводиться до аналізу циклів і рекурсивних викликів.
Приклад 1.7. Розглянемо алгоритм опрацювання елементів масиву. Нехай таким опрацюванням буде пошук заданого елемента.
For i:=1 to N do
Begin
If a[i]=k then
...
End;
Складність цього алгоритму (N), оскільки тіло циклу виконується N разів, і складність тіла циклу рівна (1).
Якщо один цикл вкладений у інший і обидва цикли залежать від величини однієї і тієї ж змінної, то вся конструкція характеризується квадратичною складністю.
For i:=1 to N do
For j:=1 toN do
Begin
...
End;
Складність цієї програми (N2).
Але, якщо заздалегідь відомо, що послідовність упорядкована за зростанням або за спаданням, можна застосувати інший алгоритм – алгоритм половинного ділення. Послідовність ділиться на дві рівні частини. Оскільки послідовність упорядкована, можна визначити, в якій частині міститься потрібний елемент. Після цього процедура повторюється: потрібна частина знову ділиться навпіл і т.д. Цей алгоритм є логарифмічним.
Дамо тепер визначення складності класів задач P і NP. Клас P складається зs задач, для яких існують поліноміальні алгоритми розв’язання. Клас NP складають задачі, для яких існують поліноміальні алгоритми перевірки правильності рішення (точніше, якщо є розв’язок задачі, то існує деяка підказка, яка дозволяє за поліноміальний час отримати цю відповідь). Неформально кажучи, клас P складається зі задач, які можна швидко розв’язати, а класс NP – зі задач, розв’язок яких можна швидко перевірити.
Наведемо приклад задачі класу P. Треба визначити, чи є у масиві дійсних чисел A[1..n] елемент зі значенням не меншим, ніж k. Очевидний у цьому випадку алгоритм перебирає всі елементи масиву за час (n).
Прикладом задачі класу NP є задача комівояжера (є множина міст та відділей між ними, мандрівний торговець має відвідати усі міста, не заходячи у жодне двічі, з мінімальними витратами на дорору). Дійсно, якщо задано деякий маршрут завдовжки не більше ніж k, то за час (n) можна перевірити, що він дійсно має саме таку довжину, і тим самим переконатися у його існуванні. Для цього треба перебрати всі n переходів між містами, що містяться у маршруті, і додати їх довжини.
Очевидним є також
включення P
NP (для перевірки розв’язання
задачі класу P досить розв'язати
її поліноміальним алгоритмом).
Задача називається NP-повною, якщо вона належить класу NP і до неї за поліноміальний час можна звести будь-яку іншу задачу цього класу. Якщо якась NP-повна задача має поліноміальний алгоритм розв’язання, то всі NP-повні задачі можуть бути поліноміально розв'язані і, як наслідок, P=NP.
NP-повні задачі є найважчими у класі NP.
