Що таке динамічне програмування?


33

Вибачте заздалегідь, якщо це питання звучить нерозумно ...

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

  1. висловити проблему як рецидиви;
  2. реалізувати відношення рецидивів або через запам'ятовування, або за допомогою підходу знизу вгору.

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

Отже, що особливого в динамічному програмуванні? Що це дає вам, окрім розпливчастого методу підходу до певного виду проблем?


11
Історичний фактоїд (цей коментар вам не допоможе, але Беллман насправді є хорошим лідером, якщо ви хочете отримати важку теорію щодо динамічного програмування): коли Беллман придумав те, що зараз відомо як динамічне програмування, він назвав ідею "динамічним програмуванням "тому що чисто теоретична робота тоді не летіла б із його роботодавцем, тому йому було потрібно щось більш чудове слово, яке не можна було б використовувати в пейоративній формі .
Г. Бах

3
Наскільки мені відомо, ви саме ці два моменти згадуєте. Він стає особливим, коли дозволяє уникнути експоненціального вибуху через перекриття підпрограм. Це все. Ах, до речі, мій професор вважає за краще "алгоритмічну парадигму" перед "розпливчастим методом".
Гендрік Ян

"Динамічне програмування", здається, є головним словом (воно з тих пір втратило свій голос). Це не означає, що це, звичайно, не корисно.
користувач253751

3
Не вартий відповіді, але для мене динамічне програмування, безумовно, "це те, що ви використовуєте, коли намагаєтеся вирішити проблему рекурсивно, але в кінцевому підсумку ви витрачаєте час, переглядаючи ті самі підпрограми знову і знову".
варення

@hobbs: Саме так, але майстерність полягає у пошуку того початкового способу витрачати час;)
j_random_hacker

Відповіді:


27

Динамічне програмування дає вам можливість подумати про розробку алгоритму. Це часто дуже корисно.

Методи запам'ятовування та знизу вгору дають вам правило / метод перетворення відношень повторення в код. Пам'ять - це досить проста ідея, але найкращі ідеї часто є!

Динамічне програмування дає вам структурований спосіб продумати час роботи вашого алгоритму. Час виконання в основному визначається двома числами: кількістю підпроблем, які ви повинні вирішити, і часу, необхідного для вирішення кожної підпрограми. Це забезпечує зручний простий спосіб подумати над проблемою проектування алгоритму. Коли у вас є співвідношення рецидивів кандидата, ви можете подивитися на це і дуже швидко зрозуміти, яким може бути час роботи (наприклад, ви часто дуже швидко можете сказати, скільки буде підпроблем, що є нижньою межею на час роботи; якщо є багато експонентних підпроблем, які вам доведеться вирішити, то повтор, ймовірно, не буде хорошим підходом). Це також допомагає виключити декомпозиції кандидатських субпроблем. Наприклад, якщо у нас є рядок , визначаючи підпроблему префіксом S [ 1 .. i ] або суфіксом S [ j . . n ] або підряд S [ i . . j ] може бути розумним (кількість підпроблем є поліноміальним в n ), але визначення підпроблеми за допомогою послідовності S , ймовірно, не буде хорошим підходом (кількість підпроблем є експоненціальною в n ). Це дозволяє обрізати "простір пошуку" можливих повторень.S[1.н]S[1.i]S[j..н]S[i..j]нSн

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

  • Якщо вхід є натуральним числом , одним із варіантів способу визначення підпрограми є заміна n меншим цілим числом n ' (st 0 n n ).ннн'0nn

  • Якщо вхідним рядком є рядок , деякі можливі способи визначення підпроблеми включають: замінити S [ 1 .. n ] префіксом S [ 1 .. i ] ; замініть S [ 1 .. n ] суфіксом S [ j . . п ] ; замініть S [ 1 .. n ] на підрядку S [ i . . j ]S[1.н]S[1.н]S[1.i]S[1.н]S[j..н]S[1.н]S[i..j]. (Тут підпроблема визначається вибором .)i,j

  • Якщо вхід є списком , зробіть те саме, що ви зробили для рядка.

  • Якщо вхід - це дерево , то одним із варіантів способу визначення підпрограми є заміна T будь-яким піддіревом T (тобто, вибір вузла x і заміна T на піддерево, укорінене в x ; підпроблема визначається вибором x ).TTTxTxx

  • Якщо вхід - пара , то рекурсивно подивіться на тип x та тип y, щоб визначити спосіб вибору підпроблеми для кожного. Іншими словами, одним із можливих способів визначення підпроблеми є заміна ( x , y ) на ( x , y ), де x є підпроблемою для x, а y є підпроблемою для y . (Ви також можете розглянути підпроблеми форми ( x , y(x,y)xy(x,y)(x,y)xxyy Або ( x , y ) .)(x,y)(x,y)

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

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


Отже, ви підтверджуєте, що динамічне програмування не передбачає конкретних «процедур», які слід дотримуватися. Це просто "спосіб думати", як ви сказали. Зауважте, що я не сперечаюся з тим, що DP марний (навпаки!), Я просто намагаюся зрозуміти, чи є щось, чого я пропускаю, або якщо я повинен просто більше практикувати.
ей-ей,

@heyhey, ну так ... і ні. Дивіться мою переглянуту відповідь для більш детальної розробки. Це не срібна куля, але вона дає певні напів конкретні процедури, які часто корисні (не гарантуються, але часто виявляються корисними).
DW

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

"якщо є багато експонентних підпроблем, які вам доведеться вирішити, то повтор, ймовірно, не буде хорошим підходом". Для багатьох проблем не існує відомого багаточленного алгоритму часу. Чому це має бути критерієм використання DP?
Chiel ten Brinke

@Chiel, це не критерій використання DP. Якщо у вас є проблеми, коли ви були б задоволені експозиційними алгоритмами часу, тоді ви можете проігнорувати це конкретне батьківське зауваження. Це лише приклад, щоб спробувати проілюструвати загальний пункт, який я висловлював - не те, що слід сприймати занадто серйозно або інтерпретувати як жорстке правило.
DW

9

Ваше розуміння динамічного програмування правильне ( afaik ), і ваше питання виправдане.

Я думаю, що додатковий проектний простір, який ми отримуємо від типу повторень, які ми називаємо «динамічним програмуванням», найкраще можна побачити порівняно з іншими схемами рекурсивних підходів.

Зробимо вигляд, що наші входи - це масиви заради висвітлення понять.A[1..n]

  1. Індуктивний підхід

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

    f(A)=g(f(A[1..nc]),A)

    з функція / алгоритм, який переводить рішення.g

    Приклад: Пошук суперзірок у лінійному часі

  2. Розділіть і завоюйте

    Розділіть вхід на декілька менших частин, вирішіть проблему для кожної і об'єднайте. Схематично (на дві частини),

    .f(A)=g(f(A[1..c]),f(A[c+1..n]),A)

    Приклади: Злиття / Квіксорт, Найкоротша попарна відстань у площині

  3. Динамічне програмування

    Розгляньте всі способи розподілу проблеми на менші проблеми та виберіть найкращий. Схематично (на дві частини),

    .f(A)=best{g(f(A[1..c]),f(A[c+1..n]))|1cn1}

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

    Важлива сторона: динамічне програмування - це не груба сила ! Застосування на кожному кроці значно скорочує простір пошуку.best

У певному сенсі ви знаєте, що все менше статично рухається зверху вниз, і вам доведеться динамічно приймати все більше і більше рішень.

Урок з вивчення динамічного програмування полягає в тому, що добре спробувати всі можливі розділи (ну, це потрібно для правильності), оскільки це все ще може бути ефективним за допомогою запам'ятовування.


"Обрізане динамічне програмування" (коли воно застосовується) доводить, що спробувати всі можливості НЕ потрібно для правильності.
Ben Voigt

@BenVoigt Звичайно. Я залишався свідомо розпливчастим щодо того, що означає "всі шляхи до поділу"; ви хочете, щоб виключати якомога більше, звичайно! (Однак, навіть якщо ви спробуєте всі способи розділення, ви не отримуєте грубої сили, оскільки ви коли-небудь досліджуєте комбінації оптимальних рішень підпроблем, тоді як брутальна сила досліджує всі комбінації всіх рішень.)
Рафаель


5

Динамічне програмування дозволяє торгувати пам'яттю на час обчислення. Розглянемо класичний приклад Фібоначчі.

Фібоначчі визначається повторенням . Якщо ви вирішите скористатися цією рекурсією, ви закінчите виконувати виклики O ( 2 n ) на F i b ( ) , оскільки дерево рекурсії - це бінарне дерево висотою n .Fib(n)=Fib(n1)+Fib(n2)O(2n)Fib()n

Замість цього ви хочете обчислити , потім скористайтеся цим, щоб знайти F i b ( 3 ) , використовуйте це, щоб знайти F i b ( 4 ) і т. Д. Це вимагає лише часу O ( n ) .Fib(2)Fib(3)Fib(4)O(n)

DP також надає нам основні прийоми перекладу відношення рецидиву в рішення знизу вгору, але вони відносно прості (і, як правило, передбачають використання розмірної матриці або кордону такої матриці, де m - кількість параметрів у відношення рецидиву). Вони добре пояснені в будь-якому тексті про ДП.mm


1
Ви говорите лише про частину запам'ятовування, яка пропускає суть питання.
Рафаель

1
"Динамічне програмування дозволяє торгувати пам'яттю на час обчислень" - це не те, що я чув, роблячи недогра, і це прекрасний спосіб поглянути на цю тему. Це інтуїтивна відповідь з лаконічним прикладом.
trueshot

@trueshot: За винятком того, що іноді динамічне програмування (і особливо "Обрізане динамічне програмування") здатне зменшити як час, так і простір.
Ben Voigt

@Бен я не казав, що це торгівля один на один. Можна також обрізати дерево рецидиву. Я вважаю, що я відповів на запитання, яке було: "Що нас отримує?" Це дає нам швидші алгоритми, торгуючи простором на час. Я погоджуюсь, що прийнята відповідь є більш ретельною, але це також справедливо.
Кітціл

2

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

kAn2nO(n2)f(i,)i

f(i,)=j<i such thatA[j]<A[i]f(j,1)
f(i,1)=1 for all i=1n

O(n2k)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.