
- •1. Алгоритми та обчислювальна складність
- •1.1. Основи структури даних і алгоритми
- •1.1.2. Поняття абстрактного типу даних. Абстрактні типи даних: стеки, списки, вектори, словники, множини, мультимножини, черги, черги з пріоритетами
- •1.1.3. Кортежі, множини, словники, одно- та двобічнозв'язні списки. Реалізація абстрактних типів даних з оцінюванням складності операцій
- •1.1.4. Базові алгоритми та їх складність: пошук, сортування (прості сортування вибором, вставками, обмінами та удосконалені сортування деревом, сортування Шелла, швидке сортування)
- •1.2. Стратегії розроблення алгоритмів
- •1.2.1. Стратегія «розділяй та володарюй» та приклади застосування.
- •1.2.2. Стратегія балансування та приклади застосування.
- •1.2.3. Динамічне програмування та приклади застосування.
- •1.2.4. Оцінювання складності алгоритму під час застосування кожної стратегії
- •1.3. Моделі обчислень
- •1.3.1. Імперативний та декларативний підходи до програмування
- •1.3.2. Розв'язні, напіврозв'язні та нерозв'язні проблеми. Проблема зупинки
1.2.3. Динамічне програмування та приклади застосування.
Динамічне програмування — розділ математики, який присвячено теорії та методам розв'язання багатокрокових задач оптимального управління.
У динамічному програмуванні, для керованого процесу серед множини усіх допустимих управлінь шукають оптимальне, у сенсі деякого критерію, тобто таке, яке призводить до екстремального (найбільшого або найменшого) значення цільової функції — деякої числової характеристики процесу. Під багатоступеневістю розуміють або багатоступеневу структуру процесу, або розподілення управління на ряд послідовних етапів (ступенів, кроків), що відповідають, як правило, різним моментам часу. Таким чином, в назві «Динамічне програмування» під «програмуванням» розуміють «ухвалення рішень», «планування», а слово «динамічне» вказує на суттєве значення часу та порядку виконання операцій в процесах і методах, що розглядаються.
Методи динамічного програмування є складовою частиною методів, які використовуються при дослідженні операцій, і використовуються як у задачах оптимального планування, так і при розв'язанні різних технічних проблем (наприклад, у задачах визначення оптимальних розмірів ступенів багатоступеневих ракет, у задачах оптимального проектування прокладення доріг та ін.)
Методи динамічного програмування використовуються не лише в дискретних, але і в неперервних керованих процесах, наприклад, в таких процесах, коли в кожен момент певного проміжку часу необхідно ухвалювати рішення. Динамічне програмування також дало новий підхід до задач варіаційного числення.
Хоча метод динамічного програмування суттєво спрощує вихідні задачі, та безпосереднє його використання, як правило, пов'язане з громіздкими обчисленнями. Для подолання цих труднощів розробляються наближені методи динамічного програмування.
Динамічне програмування є водночас і методом математичної оптимізації і методом комп'ютерного програмування. В обох контекстах воно використовує підхід спрощення пошуку розв'язку складної задачі, розбиттям її на простіші підзадачі, часто методом рекурсії. Хоча деякі задачі не можуть бути розв'язані таким чином, рішення, які охоплюють кілька точок у часі дійсно часто розбиваються рекурсивно на підзадачі. Белман називав це принципом оптимальності. Подібно до цього, в комп'ютерних науках про проблему, яка може бути розбита на підзадачі рекурсивно, говорять що вона має оптимальну підструктуру.
Якщо підзадачі можуть бути вкладеними рекурсивно всередині більших задач, так що методи динамічного програмування можуть бути застосовні, то існує залежність між розв'язком загальної задачі, і розв'язком підзадач .[4] В методах оптимізації це відношення виражається рівнянням Беллмана.
Якщо ви коли-небудь стикалися із завданнями, де потрібно визначити найкраще рішення на основі певного набору обмежень, то ви, ймовірно, знаєте, що існує для цього метод динамічного програмування. Це досить потужний апарат, і за його допомогою можна розв’язувати задачі різної складності, від пошуку найбільшої загальної підпослідовності до визначення найвигіднішої комбінації товарів для рюкзака. Якщо ви хочете навчитися застосовувати цей метод, то вам необхідно розуміти, як працює апарат динамічного програмування.
Метод динамічного програмування (ДП) є одним з основних інструментів оптимізації та розв’язання задач в інформатиці, економіці, біології та інших галузях. Простими словами, динамічне програмування – це метод розв’язання задач, що ґрунтується на розбитті складної задачі на безліч дрібніших;
Важливо враховувати такі моменти:
Для ефективного застосування цього підходу необхідно запам’ятовувати рішення підзадач.
Підзадачі мають загальну структуру, що дає змогу використовувати однорідний спосіб їх розв’язання, замість того, щоб кожну вирішувати окремо різними алгоритмами.
За допомогою ДП ефективно розв’язують задачі з оптимізації, наприклад, якщо потрібно знайти найбільше або найменше значення функції. ДП також активно застосовується в задачах планування, де потрібно визначити оптимальну послідовність дій.
Давайте розберемося детальніше в основних поняттях методу.
Одне з головних понять – це оптимальна підструктура. Що ж це таке? У методі динамічного програмування ми розв’язуємо задачу, розбиваючи її на більш дрібні. Оптимальна підструктура означає, що найкраще рішення ми можемо отримати, якщо знаємо оптимальні рішення кожної з підзадач.
Інше важливе поняття – підзадачі, що перекриваються. Тут ми можемо зіткнутися з ситуацією, коли різні підзадачі мають спільні частини. У такому разі кажуть, що в нас є підзадачі, які перекриваються. Щоб не розв’язувати одну й ту саму задачу безліч разів, ми зберігаємо результати підзадач у пам’яті та перевикористовуємо їх під час розв’язання більших завдань. Це допомагає значно прискорити процес розв’язання.
Наприклад, нехай у нас є задача про знаходження найбільшої загальної підпослідовності двох рядків. Ми можемо розв’язувати її методом динамічного програмування, розбивши її на більш дрібні підзадачі – знаходження загальної підпослідовності для підрядків кожного з рядків. У цьому разі ми стикаємося з підзадачами, що перекриваються, – загальна підпослідовність для деяких підрядків уже може бути розрахована раніше і збережена в пам’яті. Таким чином, ми можемо уникнути повторних обчислень і розв’язати задачу більш ефективно.
ДП – це методологія розв’язання задач, яка являє собою не просто формулу або алгоритм, це скоріше роздуми про те, як розв’язати задачу.
Цей підхід вимагає розбиття задачі на дрібніші, простіші підзадачі (із вхідними даними меншого розміру, як-от менша кількість, масив меншого розміру або менша кількість параметрів, які можна налаштувати).
Розв’язки дрібніших підзадач можуть бути використані для розв’язання великої вихідної задачі. Прикладом може слугувати обчислення чисел Фібоначчі.
При цьому важливо ефективно використовувати розв’язки підзадач, наприклад, запам’ятовуючи їх, а також використовувати однорідний спосіб розв’язання для всіх підзадач, якщо вони мають спільну структуру.
Алгоритм методу динамічного програмування складається з кількох кроків:
Визначення структури оптимізаційної задачі. Необхідно визначити, які параметри задачі є змінними, які є константами, які обмеження існують на змінні.
Формулювання рекурсивної формули. Необхідно виразити розв’язання задачі через розв’язання дрібніших підзадач. Рекурсивна формула має бути коректною і мати властивість оптимальної підструктури.
Створення таблиці для зберігання результатів підзадач. Необхідно створити таблицю, де в кожній комірці зберігатиметься оптимальне розв’язання для відповідної підзадачі.
Заповнення таблиці. Необхідно заповнити таблицю, починаючи з найменших підзадач і поступово переходячи до більших. Під час заповнення таблиці використовується рекурсивна формула.
Отримання розв’язку вихідної задачі. Розв’язок вихідної задачі міститься в останній комірці таблиці.
Алгоритм методу ДП може бути застосований до широкого спектра задач, включно із задачами знаходження найкоротшого шляху в графі, оптимального розкладу робіт, знаходження максимального потоку в мережі та інші. Він має низку переваг, але також має недоліки і вимагає досить великого обсягу пам’яті для зберігання результатів підзадач.
Одним із найвідоміших прикладів використання методу динамічного програмування може бути розрахунок чисел Фібоначчі. Для обчислення будь-якого числа в послідовності нам необхідно спочатку обчислити два попередніх числа і скласти їх. Таким чином, для обчислення кожного наступного числа ми використовуємо результати попередніх обчислень. Хоча для обчислення чисел Фібоначчі є і замкнута формула.
Нехай у вас є набір чисел, і вам потрібно знайти максимальну суму, яку можна отримати, вибравши деякі з них, за умови, що вибрані числа не повинні стояти поруч одне з одним. Наприклад, для набору чисел [1, 2, 3, 1] максимальна сума дорівнює 4 (виберіть числа 1 і 3).
Це може здатися складним, але такий підхід дає змогу розв’язувати задачі в оптимальний спосіб і часто використовується в алгоритмах оброблення зображень, оброблення природної мови, ігрових додатках та інших галузях.
Ще одним прикладом використання методу ДП може бути розрахунок вартості рюкзака;
Припустімо, у вас є рюкзак і набір предметів із певними вагами та вартостями. Вам потрібно заповнити рюкзак таким чином, щоб максимізувати вартість обраних предметів, не перевищуючи місткість рюкзака.
Для розв’язання цієї задачі ми можемо використовувати таблицю заповнення. У кожній комірці таблиці зберігатиметься максимальна вартість, яку ми можемо отримати, використовуючи тільки перші i предметів і маючи рюкзак місткості не більше j.
Формула для заповнення таблиці матиме такий вигляд:
f[i][j] = max(f[i-1][j], f[i-1][j-w[i]] + v[i])
де f[i][j] – максимальна вартість, яку можна отримати серед перших i предметів, використовуючи рюкзак місткості не більше ніж j, w[i] і v[i] – вага і вартість i-го предмета.
Суть цієї формули полягає в тому, що для кожного предмета ми можемо розв’язати дві підзадачі: додати його до рюкзака чи не додавати. Якщо ми не додаємо предмет, то максимальна вартість дорівнюватиме максимальній вартості серед перших i-1 предметів і рюкзака місткості j. Якщо ми додаємо предмет, то максимальна вартість дорівнюватиме сумі вартості цього предмета і максимальної вартості серед перших i-1 предметів і рюкзака місткості j-вагу[i].
У результаті заповнення таблиці, відповіддю на вихідну задачу буде максимальна вартість серед перших n предметів і рюкзака місткості не більше W, де n – кількість предметів, W – місткість рюкзака.
Як і будь-який інший алгоритм, метод він має свої переваги та недоліки. Розглянемо їх детальніше.
Переваги:
Ефективність: ДП може бути дуже ефективним для розв’язання задач, які можна розбити на підзадачі, і де оптимальне розв’язання кожної підзадачі може бути обчислено тільки один раз.
Гнучкість: може бути застосований до широкого спектра завдань (знаходження найкоротшого шляху в графі, завдання оптимального розкладу і багато інших).
Точність: на відміну від алгоритмів, які приймають локально оптимальні рішення, ДП може гарантувати глобально оптимальне рішення.
Недоліки:
Висока складність: ДП може мати високу обчислювальну складність, особливо якщо вихідна задача дуже складна і може бути розбита на безліч підзадач.
Потреба у великому обсязі пам’яті: необхідно зберігати в пам’яті значення для всіх підзадач, що може призвести до великого споживання пам’яті.
Необхідність певної структури задачі: ДП може бути неефективним або незастосовним, якщо вихідну задачу не розбити на підзадачі або якщо їхні розв’язки неможливо ефективно комбінувати для отримання розв’язку вихідної.
Динамічне програмування. В процесі породження дерева рекурсивних викликів можливе повторення підзадач з одними і тими ж даними. Якщо запам'ятовувати результат їх виконання, то ефективність алгоритму може бути значно збільшена. Вивчення рекурсії нерозривно пов'язане з вивченням рекурсивно визначуваних структур даних, званих деревами (trees). Дерева використовуються як для спрощення розуміння і аналізу рекурсивних програм, так і в якості явних структур даних. У свою чергу, рекурсивні програми використовуються для побудови дерев. Глобальний зв'язок між ними (і рекурентними стосунками) використовується при аналізі алгоритмів. Багато алгоритмів використовують два рекурсивні виклики, кожен з яких працює приблизно з половиною вхідних даних. Така рекурсивна схема, по- видимому є найбільш важливим випадком добре відомого методу "розділяй і володарюй" (divide and conquer) розробки алгоритмів. В якості прикладу розглянемо завдання відшукування максимального з N елементів, збережених в масиві a, . ., a[N] з елементами типу Item. Це завдання легко може бути вирішене за один прохід масиву.