rev4: Дуже красномовний коментар користувача Sammaron зазначив, що, можливо, ця відповідь раніше плутала зверху вниз і знизу вгору. Хоча спочатку ця відповідь (rev3) та інші відповіді говорили, що "знизу вгору - це запам'ятовування" ("припустимо, підпроблеми"), це може бути зворотним (тобто "зверху вниз" може бути "припустити підпрограми" і " знизу вгору "може бути" скласти підпроблеми "). Раніше я читав, як запам'ятовування є різним видом динамічного програмування на відміну від підтипу динамічного програмування. Я цитував цю точку зору, незважаючи на те, що не підписався на неї. Я переписав цю відповідь як агностик термінології, поки в літературі не знайдуться відповідні посилання. Я також перетворив цю відповідь у вікі спільноти. Будь ласка, віддайте перевагу академічним джерелам. Список посилань:} {Література: 5 }
Резюме
Динамічне програмування - це впорядкування обчислень таким чином, щоб уникнути перерахунку повторюваних робіт. У вас є основна проблема (корінь вашого дерева підпроблем) та підпроблеми (subtrees). Підпрограми, як правило, повторюються та перетинаються .
Наприклад, розгляньте свій улюблений приклад Fibonnaci. Це повне дерево підпроблем, якби ми виконували наївний рекурсивний виклик:
TOP of the tree
fib(4)
fib(3)...................... + fib(2)
fib(2)......... + fib(1) fib(1)........... + fib(0)
fib(1) + fib(0) fib(1) fib(1) fib(0)
fib(1) fib(0)
BOTTOM of the tree
(У деяких інших рідкісних проблемах це дерево може бути нескінченним у деяких гілках, що представляє собою нерозрив час. Таким чином, вам може знадобитися стратегія / алгоритм, щоб вирішити, які підпрограми розкривати.)
Пам'ятка, табуляція
Існують щонайменше дві основні методи динамічного програмування, які не є взаємовиключними:
Пам'ятка - Це підхід, який випускаєте з лайсез-файру: ви припускаєте, що ви вже обчислили всі підпрограми і не знаєте, який оптимальний порядок оцінювання. Як правило, ви виконували б рекурсивний дзвінок (або якийсь ітеративний еквівалент) з кореня, або сподіваєтесь, що ви наблизитесь до оптимального порядку оцінки, або отримаєте доказ того, що допоможете досягти оптимального порядку оцінки. Ви б переконалися, що рекурсивний виклик ніколи не перераховує підпроблему, оскільки ви кешуєте результати, і, отже, дублікати під дерев не будуть перераховані.
- Приклад: Якщо ви обчислюєте послідовність Фібоначчі
fib(100)
, ви просто зателефонували б цьому, і він би зателефонував fib(100)=fib(99)+fib(98)
, який би зателефонував fib(99)=fib(98)+fib(97)
, ... і т.д. ..., який закликав би fib(2)=fib(1)+fib(0)=1+0=1
. Тоді воно остаточно вирішиться fib(3)=fib(2)+fib(1)
, але його не потрібно перераховувати fib(2)
, тому що ми кешували це.
- Це починається у верхній частині дерева і оцінює підпрограми від листя / підтрубки назад до кореня.
Таблиця - Динамічне програмування також можна розглядати як алгоритм «заповнення таблиці» (хоча зазвичай багатовимірний, ця «таблиця» може мати неевклідову геометрію в дуже рідкісних випадках *). Це як запам'ятовування, але більш активне і передбачає ще один додатковий крок: Ви повинні заздалегідь вибрати точний порядок, у якому ви будете робити свої обчислення. Це не повинно означати, що порядок повинен бути статичним, але у вас набагато більше гнучкості, ніж запам'ятовування.
- Приклад: Якщо ви виконуєте Fibonacci, ви можете вибрати для обчислення числа в такому порядку:
fib(2)
, fib(3)
, fib(4)
... кешування кожне значення , так що ви можете вирахувати такі з них легше. Ви також можете вважати це заповненням таблиці (інша форма кешування).
- Я особисто багато не чую слова «табуляція», але це дуже пристойний термін. Дехто вважає це "динамічним програмуванням".
- Перш ніж запустити алгоритм, програміст розглядає ціле дерево, після чого пише алгоритм для оцінки підпроблем у певному порядку до кореня, як правило, заповнюючи таблицю.
- * виноска: Іноді "стіл" не є прямокутною таблицею з сіткою, як такою, як таке. Це може мати складнішу структуру, наприклад дерево, або структуру, характерну для проблемного домену (наприклад, міста, що знаходяться в межах відстані на карті), або навіть графічну діаграму, яка, не маючи сітки, не має наприклад, структура підключення вгору-вліво-вправо тощо. Наприклад, user3290797 зв'язав приклад динамічного програмування пошуку максимального незалежного набору в дереві , що відповідає заповненню пробілів у дереві.
(Як правило, в парадигмі "динамічного програмування" я б сказав, що програміст розглядає ціле дерево, тодіпише алгоритм, який реалізує стратегію оцінки підпроблем, яка може оптимізувати будь-які властивості, які ви хочете (зазвичай це комбінація часової складності та просторової складності). Ваша стратегія повинна починатися десь із якоїсь конкретної підпроблеми і, можливо, може адаптуватися на основі результатів цих оцінок. У загальному розумінні "динамічного програмування" ви можете спробувати кешувати ці підпрограми, і, загалом, спробувати уникати повторного перегляду підпроблем з тонким відмінністю, можливо, це стосується графіків у різних структурах даних. Дуже часто такі структури даних лежать в основі, як масиви або таблиці. Рішення підпроблем можна викинути, якщо вони вже не потрібні.)
[Раніше ця відповідь робила заяву про термінологію зверху вниз та знизу вгору; чітко існують два основні підходи, що називаються "Мемоїзація та Таблиця", які можуть бути в поєднанні з цими термінами (хоча і не повністю). Загальним терміном, який більшість людей використовують, все ще є "Динамічне програмування", і деякі люди кажуть "Пам'ять", щоб посилатися на саме цей підтип "Динамічне програмування". Ця відповідь відмовляється сказати, що знаходиться зверху вниз і знизу вгору, поки громада не зможе знайти належні посилання в наукових працях. Зрештою, важливо зрозуміти відмінність, а не термінологію.]
Плюси і мінуси
Простота кодування
Пам'ять дуже легко кодувати (ви, як правило, * можете написати анотацію "мемоалізатора" або функцію обгортки, яка автоматично робить це за вас), і це має бути вашим першим напрямком підходу. Недоліком таблиць є те, що вам потрібно придумати замовлення.
* (це насправді просто, якщо ви пишете функцію самостійно та / або кодуєте нечистою / нефункціональною мовою програмування ... наприклад, якщо хтось уже написав попередньо складену fib
функцію, вона обов'язково робить рекурсивні дзвінки до себе, і ви не можете магічно запам'ятати функцію, не забезпечивши, щоб ці рекурсивні дзвінки викликали вашу нову запам'ятовувану функцію (а не оригінальну невстановлену функцію))
Рекурсивність
Зауважте, що і зверху вниз, і знизу вгору можна реалізувати за допомогою рекурсії або ітеративного заповнення таблиці, хоча це може бути не природно.
Практичні проблеми
Якщо запам'ятовується, якщо дерево дуже глибоке (наприклад fib(10^6)
), у вас буде не вистачає простору стека, тому що кожне затримка обчислень повинна бути поставлена на стек, і у вас буде 10 ^ 6 з них.
Оптимальність
Будь-який підхід може не бути оптимальним за часом, якщо замовлення, яке ви (або намагаєтесь) відвідати підпрограми, не є оптимальним, зокрема, якщо існує декілька способів обчислення підпрограми (звичайно кешування вирішить це, але теоретично можливо, що кешування може не в деяких екзотичних випадках). Пам'ять зазвичай додасть вашої часової складності вашій просторовій складності (наприклад, за допомогою табуляції у вас є більше свободи відкидати обчислення, як-от використання табуляції за допомогою Fib дозволяє використовувати простір O (1), але запам'ятовування за допомогою Fib використовує O (N) простір укладання).
Розширені оптимізації
Якщо ви також робите надзвичайно складні проблеми, у вас, можливо, не залишається іншого вибору, як робити табуляцію (або, принаймні, брати більш активну роль у керуванні запам'ятовуванням, куди ви хочете). Крім того, якщо ви потрапляєте в ситуацію, коли оптимізація є абсолютно критичною, і ви повинні оптимізувати її, таблична таблиця дозволить вам зробити оптимізацію, запам'ятовування якої інакше не дозволило б вам зробити це здоровим способом. На мою скромну думку, у звичайній інженерії програмного забезпечення жоден із цих двох випадків ніколи не з'являється, тому я просто використовував би запам'ятовування ("функція, яка кешує свої відповіді"), якщо щось (наприклад, простір стека) не потребує табуляції ... хоча технічно уникнути вибуху стека ви можете 1) збільшити обмеження розміру стека на мовах, які це дозволяють, або 2) з'їсти постійний фактор додаткової роботи для віртуалізації стека (ick),
Більш складні приклади
Тут ми перераховуємо приклади, що представляють особливий інтерес, які є не просто загальними проблемами ДП, але цікаво розрізняють запам’ятовування та табуляцію. Наприклад, одна рецептура може бути набагато простішою, ніж інша, або може бути оптимізація, яка в основному вимагає складання таблиць:
- алгоритм обчислення відстані редагування [ 4 ], цікавий як нетривіальний приклад двовимірного алгоритму заповнення таблиці