Demystifying dynaaminen ohjelmointi

Kuinka rakentaa & koodata dynaamisia ohjelmointialgoritmeja

Ehkä olet kuullut siitä valmistellessaan koodaushaastatteluja. Ehkä olet kamppaillut sen läpi algoritmien kurssilla. Ehkä yrität oppia koodaamaan itse, ja sinulle sanottiin jonnekin matkalla, että on tärkeää ymmärtää dynaaminen ohjelmointi. Dynaamisen ohjelmoinnin (DP) käyttäminen algoritmien kirjoittamiseen on yhtä välttämätöntä kuin sitä pelkään.

Ja kuka voi syyttää niitä, jotka kutistuvat siitä? Dynaaminen ohjelmointi vaikuttaa pelottavalta, koska se on huonosti opetettu. Monet opetusohjelmat keskittyvät lopputulokseen - algoritmin selittämiseen prosessin sijasta - algoritmin löytämiseen. Tämä kannustaa muistamaan, ei ymmärtämään.

Tänä vuonna algoritmitunnin aikana kokoonpanin yhdessä oman prosessini ratkaista ongelmia, jotka vaativat dynaamista ohjelmointia. Osa siitä tulee algoritmiprofessoristani (joille on paljon luottoa!) Ja osat omasta dynaamisten ohjelmointialgoritmien leikkaamisesta.

Mutta ennen kuin jaan prosessini, aloitetaan perusteista. Mikä on dynaaminen ohjelmointi joka tapauksessa?

Dynaaminen ohjelmointi määritetty

Dynaaminen ohjelmointi tarkoittaa optimointiongelman jakamista yksinkertaisemmiksi alaongelmiksi ja ratkaisun tallentamista jokaiselle alaongelmalle siten, että kukin alaongelma ratkaistaan ​​vain kerran.

Totta puhuen, tämä määritelmä ei välttämättä ole täysin järkevä, ennen kuin näet esimerkin osa-ongelmasta. Se on okei, se tulee esiin seuraavassa osiossa.

Toivon voivani kertoa, että DP on hyödyllinen tekniikka optimointiongelmiin, niihin ongelmiin, jotka etsivät maksimaalista tai vähimmäisratkaisua tietyillä rajoituksilla, koska se tarkastelee kaikkia mahdollisia alaongelmia eikä koskaan laske uudelleen ratkaisua mihinkään alaongelmaan. Tämä takaa oikeellisuuden ja tehokkuuden, mitä emme voi sanoa useimmista tekniikoista, joita käytetään algoritmien ratkaisemiseen tai arviointiin. Pelkästään tämä tekee DP: stä erityisen.

Kahdessa seuraavassa osassa selitän, mikä on osaongelma, ja motivoin sitten, miksi ratkaisujen - tekniikka, jota kutsutaan muistamiseksi - säilyttämisellä on merkitystä dynaamisessa ohjelmoinnissa.

Alaongelmat alaongelmat alaongelmat

Alaongelmat ovat alkuperäisen ongelman pienempiä versioita. Itse asiassa osa-ongelmat näyttävät usein alkuperäisen ongelman sanamuodon versiona. Jos ongelmat on muotoiltu oikein, alaongelmat perustuvat toisiinsa saadakseen ratkaisun alkuperäiseen ongelmaan.

Löydämme paremman kuvan tämän toiminnasta etsimällä alaongelma esimerkistä dynaamisesta ohjelmointiongelmasta.

Kuvittele, että olet palannut 1950-luvulla IBM-650-tietokoneella. Tiedät mitä tämä tarkoittaa - punchcards! Sinun tehtäväsi on miehelle tai naiselle IBM-650 päivässä. Sinulle annetaan luonnollinen numero n lyömäkorttia suoritettavaksi. Jokainen leimauskortti i on ajettava jollakin ennalta määrätyllä aloitusajalla s_i ja lopetettava ajo jollain ennalta määrätyllä lopetusajalla f_i. Vain yksi leimasin voi toimia IBM-650: llä kerralla. Jokaisella lävistyskortilla on myös siihen liittyvä arvo v_i sen perusteella, kuinka tärkeä se on yrityksellesi.

Ongelma: IBM-650: n vastuuhenkilönä on määritettävä optimaalinen leimauskorttien aikataulu, joka maksimoi kaikkien suoritettujen leimauskorttien kokonaisarvon.

Koska käsittelen tätä esimerkkiä yksityiskohtaisesti läpi koko tämän artikkelin, teen vain teille sen alaongelmaa toistaiseksi:

Alaongelma: Lävistyskorttien i - n enimmäisarvoaikataulu siten, että leimauskortit on lajiteltu aloitusajan perusteella.

Huomaa, kuinka osa-ongelma hajottaa alkuperäisen ongelman komponenteiksi, jotka muodostavat ratkaisun. Alaongelman avulla löydät enimmäisarvon aikataulu lyönkkikorteille n-1 - n ja sitten leimauskorteille n-2 - n ja niin edelleen. Löytämällä ratkaisut jokaiselle alaongelmalle, voit sitten puuttua alkuperäiseen ongelmaan itse: lyöntikorttien 1 - n enimmäisarvoaikataulu. Koska osa-ongelma näyttää alkuperäiseltä ongelmalta, osa-ongelmia voidaan käyttää alkuperäisen ongelman ratkaisemiseen.

Dynaamisessa ohjelmoinnissa sinun on muistettava tai tallennettava jokaisen alaongelman ratkaisemisen jälkeen. Selvitetään syy seuraavasta osasta.

Motivoiva muistaminen Fibonacci-numeroilla

Mitä tekisit, kun sinulle kehotetaan ottamaan käyttöön algoritmi, joka laskee Fibonacci-arvon tietylle numerolle. Useimmat tuntemani ihmiset valitsevat rekursiivisen algoritmin, joka näyttää noin tältä Pythonista:

def fibonacciVal (n):
  jos n == 0:
    palauta 0
  elif n == 1:
    palauta 1
  else:
    paluu fibonacciVal (n-1) + fibonacciVal (n-2)

Tämä algoritmi saavuttaa tarkoituksensa, mutta kustannuksin. Katsotaanpa esimerkiksi, mitä tämän algoritmin on laskettava ratkaistaksesi n = 5 (lyhennettynä F (5)):

F (5)
                    / \
                   / \
                  / \
               F (4) F (3)
            / \ / \
          F (3) F (2) F (2) F (1)
         / \ / \ / \
       F (2) F (1) F (1) F (0) F (1) F (0)
       / \
     F (1) F (0)

Yllä oleva puu edustaa kutakin laskentaa, joka on suoritettava, jotta saadaan Fibonacci-arvo n = 5: lle. Huomaa, kuinka n = 2: n alitehtävä ratkaistaan ​​kolme kertaa. Suhteellisen pienelle esimerkille (n = 5), se on paljon toistuvaa ja hukkaan laskentaa!

Entä jos sen sijaan, että laskettaisiin Fibonacci-arvo n = 2: lle kolme kertaa, loimme algoritmin, joka laskee sen kerran, tallentaa sen arvon ja käyttää tallennettua Fibonacci-arvoa jokaisesta seuraavasta n = 2 -tapahtumasta? Juuri juuri muistaminen tekee.

Tämän mielestä olen kirjoittanut dynaamisen ohjelmointiratkaisun Fibonacci-arvoongelmaan:

def fibonacciVal (n):
  muistio = [0] * (n + 1)
  muistio [0], muistio [1] = 0, 1
  i: lle alueella (2, n + 1):
    muistio [i] = muistio [i-1] + muistio [i-2]
  palautusmuistio [n]

Huomaa, kuinka palautusarvon ratkaisu tulee muistilohkojen muistiosta [], jonka it-silmukka täyttää toistuvasti. "Ieratiivisesti" tarkoitan, että muistio [2] lasketaan ja tallennetaan ennen muistion [3], muistion [4],… ja muistion [n]. Koska muistio [] täytetään tässä järjestyksessä, ratkaisu jokaiselle alaongelmalle (n = 3) voidaan ratkaista sen aiempien alaongelmien (n = 2 ja n = 1) ratkaisuilla, koska nämä arvot oli jo tallennettu muistio [] aikaisemmin.

Muistaminen ei tarkoita uudelleenlaskentaa, mikä tekee tehokkaamman algoritmin. Siten muistaminen varmistaa, että dynaaminen ohjelmointi on tehokasta, mutta oikean alaongelman valitseminen takaa, että dynaaminen ohjelma käy läpi kaikki mahdollisuudet löytääkseen parhaan.

Nyt kun olemme käsitelleet muistamista ja alaongelmia, on aika oppia dynaaminen ohjelmointiprosessi. Solki sisään.

Dynaaminen ohjelmointiprosessi

Vaihe 1: Tunnista alaongelma sanoin.

Ohjelmoijat kääntyvät liian usein koodin kirjoittamiseen ennen kuin ajattelevat kriittisesti käsillä olevaa ongelmaa. Ei hyvä. Yksi strategia aivojen polttamiseksi ennen näppäimistön koskettamista on sanojen, englanninkielisten tai muunlaisten käyttäminen kuvaamaan alkuperäisessä ongelmassa tunnistasi alaongelmaa.

Jos ratkaisee ongelmaa, joka vaatii dynaamista ohjelmointia, Tartu paperille ja mieti tietoja, joita tarvitset tämän ongelman ratkaisemiseksi. Kirjoita alaongelma tätä mielessä.

Esimerkiksi, leikkauskorttiongelmassa sanoin, että alaongelma voidaan kirjoittaa ”lyöntikorttien i-n enimmäisarvon aikataulusta niin, että leimauskortit lajitellaan alkamisajankohdan mukaan”. Löysin tämän alaongelman tajuamalla, että Jotta voimme määrittää leimauskorttien 1 - n enimmäisarvojen aikataulun siten, että leimaamot lajitellaan alkamisajan perusteella, minun olisi löydettävä vastaus seuraaviin alaongelmiin:

  • Lävistyskorttien n-1 - n enimmäisarvoaikataulu siten, että leimauskortit on lajiteltu aloitusajan perusteella
  • Lävistyskorttien n-2 - n enimmäisarvoaikataulu siten, että leimauskortit on lajiteltu aloitusajan perusteella
  • Lävistyskorttien n-3 - n enimmäisarvoaikataulu siten, että leimauskortit on lajiteltu aloitusajan perusteella
  • (Jne)
  • Lävistyskorttien 2 - n enimmäisarvoaikataulu siten, että leimauskortit on lajiteltu aloitusajan perusteella

Jos pystyt tunnistamaan alaongelman, joka perustuu aiempiin alaongelmiin tämän ongelman ratkaisemiseksi, olet oikealla tiellä.

Vaihe 2: Kirjoita alitehtävä toistuvaksi matemaattiseksi päätökseksi.

Kun olet tunnistanut alaongelman sanoin, on aika kirjoittaa se matemaattisesti. Miksi? No, löytämäsi matemaattinen toistuminen tai toistuva päätös on lopulta se mitä laitat koodiin. Lisäksi alaongelman kirjoittaminen matemaattisesti vetoaa alaongelmaan sanoin vaiheesta 1. Jos on vaikea koodata alaongelmaa vaiheesta 1 matematiikassa, se voi olla väärä alaongelma!

Kysyn itseltäni kaksi kysymystä joka kerta yrittäessään löytää toistumisen:

  • Minkä päätöksen teen jokaisessa vaiheessa?
  • Jos algoritmi on vaiheessa i, mitä tietoja sen tarvitsisi päättääkseen mitä tehdä vaiheessa i + 1? (Ja joskus: Jos algoritmi on vaiheessa i, mitä tietoja sen tarvitsi päättääkseen mitä tehdä vaiheessa i-1?)

Palataan takaisin punchcard -ongelmaan ja esitetään nämä kysymykset.

Minkä päätöksen teen jokaisessa vaiheessa? Oletetaan, että rei'ityskortit on lajiteltu aloitusajan perusteella, kuten aiemmin mainittiin. Jokaiselle rei'ityskortille, joka on yhteensopiva tähän mennessä aikataulun kanssa (sen alkamisaika on sen hetken käynnissä olevan leimautumisajan viimeisen ajan jälkeen), algoritmin on valittava kahden vaihtoehdon välillä: suorittaa tai olla käyttämättä leimauskorttia.

Tämä dynaaminen ohjelma valitsee kahden vaihtoehdon välillä jokaisessa vaiheessa, aivan kuten rakas ystävämme Hamlet!

Jos algoritmi on vaiheessa i, mitä tietoja sen tarvitsisi päättääkseen mitä tehdä vaiheessa i + 1? Jotta voidaan päättää kahden vaihtoehdon välillä, algoritmin on tiedettävä seuraava yhteensopiva lävistyskortti järjestyksessä. Seuraava yhteensopiva rei'ityskortti tietylle leimauskortille p on leimakortti q sellainen, että s_q (ennalta määrätty aloitusaika leimakortille q) tapahtuu f_p: n (rei'ityskortin p ennalta määrätty viimeisaika) jälkeen ja ero s_q: n ja f_p: n välillä on minimoitu. Jos hylätään matemaatikko-puhetta, seuraava yhteensopiva punchcard on aikaisin aloitusaika sen jälkeen, kun nykyinen punchcard on päättynyt ajoon.

Jos algoritmi on vaiheessa i, mitä tietoja sen tarvitsi päättääkseen mitä tehdä vaiheessa i-1? Algoritmin on tiedettävä tulevista päätöksistä: niistä, jotka on tehty leimauskorteille i - n, jotta voidaan päättää suorittaa tai olla käyttämättä leimauskortti i-1.

Nyt kun olemme vastanneet näihin kysymyksiin, olet ehkä aloittanut mielessäsi toistuvan matemaattisen päätöksenteon. Jos ei, niin se on myös hyvä, toistojen kirjoittaminen on helpompaa, kun altistat dynaamisempia ohjelmointiongelmia.

Ilman enempää, tässä toistuu:

OPT (i) = max (v_i + OPT (seuraava [i]), OPT (i + 1))

Tämä matemaattinen toisto vaatii selitystä, etenkin niille, jotka eivät ole kirjoittaneet sitä aikaisemmin. Käytän OPT (i) edustamaan lyöntikorttien i - n enimmäisarvoaikataulua siten, että leimauskortit lajitellaan alkamisajankohdan mukaan. Kuulostaa tutulta, eikö? OPT (•) on alaongelma vaiheesta 1 alkaen.

OPT (i): n arvon määrittämiseksi harkitsemme kahta vaihtoehtoa ja haluamme käyttää maksimaalista näistä vaihtoehdoista tavoitteemme saavuttamiseksi: kaikkien leimauskorttien maksimiarvoaikataulua. Kun olemme valinneet vaihtoehdon, joka antaa suurimman tuloksen vaiheessa i, muistamme sen arvon OPT (i): nä.

Kaksi vaihtoehtoa - suorittaa tai estää lyödä korttia i - esitetään matemaattisesti seuraavasti:

v_i + OPT (seuraava [i])

Tämä lauseke edustaa päätöstä käyttää punchcard i: tä. Se lisää arvon, joka saadaan käyräkortin i suorittamisesta OPT: iin (seuraava [i]), missä seuraava [i] edustaa seuraavaa yhteensopivaa lyötykorttia seuraavan lyötykortin i jälkeen. OPT (seuraava [i]) antaa enimmäisarvojen aikataulun seuraaville [i] - n: n lyönkkikorteille siten, että leimauskortit on lajiteltu aloitusajan perusteella. Kun nämä kaksi arvoa lisätään yhteen, saadaan lyöntikorttien i - n maksimiarvoaikataulu siten, että leimauskortit lajitellaan alkamisajankohdan mukaan, jos lyötykortti i suoritetaan.

OPT (i + 1)

Päinvastoin, tämä lauseke edustaa päätöstä olla käyttämättä lyömäkorttia i. Jos leimauskorttia i ei käytetä, sen arvoa ei saada. OPT (i + 1) antaa enimmäisarvon aikataulu lyöntikorteille i + 1 - n siten, että leimauskortit lajitellaan alkamisajan perusteella. Joten, OPT (i + 1) antaa suurimman aikataulun leimauskorteille i - n siten, että leimauskortit lajitellaan aloitusajan perusteella, jos leimauskorttia i ei käytetä.

Tällä tavalla punchcard-ongelmien kussakin vaiheessa tehty päätös koodataan matemaattisesti heijastamaan vaiheen 1 alaongelmaa.

Vaihe 3: Ratkaise alkuperäinen ongelma vaiheiden 1 ja 2 avulla.

Vaiheessa 1 kirjoitimme muistiin lyömäkorttiongelman alaongelman sanoin. Vaiheessa 2 kirjoitimme toistuvan matemaattisen päätöksen, joka vastaa näitä alaongelmia. Kuinka voimme ratkaista alkuperäisen ongelman näillä tiedoilla?

OPT (1)

Se on niin yksinkertaista. Koska vaiheessa 1 löytämämme alaongelma on lyöntikorttien i - n enimmäisarvoaikataulu siten, että leimauskortit lajitellaan alkamisajan perusteella, voimme kirjoittaa ratkaisun alkuperäiseen ongelmaan maksimiarvojen aikatauluna lyöntikortteille 1 - n. siten, että leimaamot on lajiteltu aloitusajan perusteella. Koska vaiheet 1 ja 2 kulkevat käsi kädessä, alkuperäinen ongelma voidaan myös kirjoittaa nimellä OPT (1).

Vaihe 4: Määritä muistiryhmän mitat ja suunta, johon se tulisi täyttää.

Löysitkö vaiheen 3 petollisesti yksinkertaiseksi? Se varmasti näyttää siltä. Saatat ajatella, kuinka OPT (1) voi olla ratkaisu dynaamiseen ohjelmaan, jos se luottaa OPT (2), OPT (seuraava [1]) ja niin edelleen?

Olet oikein huomatessasi, että OPT (1) luottaa ratkaisuun OPT (2). Tämä seuraa suoraan vaiheesta 2:

OPT (1) = max (v_1 + OPT (seuraava [1]), OPT (2))

Mutta tämä ei ole murskaava kysymys. Mieti takaisin Fibonacci-muistamisesimerkkiin. Fibonacci-arvon löytämiseksi n = 5: lle algoritmi perustuu siihen tosiseikkaan, että Fibonacci-arvot arvoille n = 4, n = 3, n = 2, n = 1 ja n = 0 oli jo muisteltu. Jos täytämme muistiotaulukkomme oikeassa järjestyksessä, OPT (1): n luottaminen muihin alaongelmiin ei ole suuri asia.

Kuinka voimme tunnistaa oikean suunnan muistiotaulukon täyttämiseen? Koska meistä OPT (1) on tiedossa, että OPT (1) luottaa ratkaisuihin optioihin OPT (2) ja OPT (seuraava [1]), ja että leimauskorttien 2 ja seuraavien [1] aloitusajat ovat rei'ityskortin 1 jälkeen lajittelun vuoksi, voi päätellä, että meidän on täytettävä muistiotaulukko OPT (n) - OPT (1).

Kuinka määritetämme tämän muistiryhmän mitat? Tässä temppu: taulukon mitat ovat yhtä suuret kuin niiden muuttujien lukumäärä ja koko, joihin OPT (•) perustuu. Lävistyskortin ongelmassa meillä on OPT (i), mikä tarkoittaa, että OPT (•) luottaa vain muuttujaan i, joka edustaa leimauskortin numeroa. Tämä viittaa siihen, että muistiryhmämme on yksiulotteinen ja että sen koko on n, koska leikkauspisteitä on yhteensä n.

Jos tiedämme, että n = 5, muistilistomme voi näyttää tältä:

muistio = [OPT (1), OPT (2), OPT (3), OPT (4), OPT (5)]

Koska monet ohjelmointikielet alkavat indeksoida taulukot 0: lla, voi olla helpompaa luoda tämä muistiryhmä siten, että sen indeksit kohdistuvat leimauskortinumeroihin:

muistio = [0, OPT (1), OPT (2), OPT (3), OPT (4), OPT (5)]

Vaihe 5: Kooda se!

Dynaamisen ohjelman koodaamiseksi kootamme vaiheet 2–4. Ainoa uusi tieto, jota tarvitset dynaamisen ohjelman kirjoittamiseen, on kantaesimerkki, jonka löydät etsimällä algoritmiisi.

Dynaaminen ohjelma leimauskorttiongelmaan näyttää tältä:

def punchcardSchedule (n, arvot, seuraava):
 # Alusta muistiluku - Vaihe 4
  muistio = [0] * (n + 1)
  
 # Aseta peruskotelo
  muistio [n] = arvot [n]
  
 # Luo muistiontaulukko n: stä yhteen - vaihe 2
  i: lle alueella (n-1, 0, -1):
    muistio [i] = max (v_i + muistio [seuraava [i]], muistio [i + 1])
 
 # Palauta ratkaisu alkuperäiseen ongelmaan OPT (1) - Vaihe 3
  palautusilmoitus [1]

Onnittelut ensimmäisen dynaamisen ohjelman kirjoittamisesta! Nyt kun olet märkä jalat, käydään läpi erityyppisen dynaamisen ohjelman.

Valinnan paradoksi: Useita vaihtoehtoja DP-esimerkki

Ei liity DP: hen, mutta tarkka kuvaus siitä, kuinka monen vaihtoehdon päätösten riippaaminen voi olla.

Vaikka edellisessä dynaamisessa ohjelmointiesimerkissä oli kahden vaihtoehdon päätös - suorittaa leimauskortti tai olla käyttämättä sitä, jotkut ongelmat vaativat, että useita vaihtoehtoja on harkittava ennen päätöksen tekemistä kussakin vaiheessa.

Aika uudelle esimerkille.

Kuvittele, että myyt ystävyysrannekkeita n asiakkaalle, ja tuotteen arvo kasvaa monotonisesti. Tämä tarkoittaa, että tuotteella on hinnat {p_1, ..., p_n} siten, että p_i ≤ p_j, jos asiakas j tulee asiakkaan i jälkeen. Näillä n asiakkaalla on arvoja {v_1,…, v_n}. Annettu asiakas ostaa ystävyysrannekkeen hinnalla p_i vain ja jos p_i ≤ v_i; muuten kyseisestä asiakkaasta saatava tulo on 0. Oletetaan, että hinnat ovat luonnollisia lukuja.

Ongelma: Sinun on löydettävä hintajoukko, joka takaa sinulle suurimman mahdollisen tulon ystävyysrannekkeiden myynnistä.

Mieti hetki miettiä, miten voisit ratkaista tämän ongelman, ennen kuin katsot ratkaisuani vaiheisiin 1 ja 2.

Vaihe 1: Tunnista alaongelma sanoin.

Alaongelma: Enimmäistuotot, jotka saadaan asiakkailta i kautta n siten, että asiakkaan i-1 hintaksi asetettiin q.

Löysin tämän alaongelman tajuamalla, että asiakkaiden 1 - n enimmäistuoton määrittämiseksi minun on löydettävä vastaus seuraaviin alaongelmiin:

  • Asiakkailta n-1 - n saatava enimmäistuotto siten, että asiakkaan n-2 hintaksi asetettiin q.
  • Asiakkailta n-2 - n saatava enimmäistuotto siten, että asiakkaan n-3 hintaksi asetettiin q.
  • (Jne)

Huomaa, että lisäsin toisen muuttujan q alaongelmaan. Tein tämän, koska jokaisen alaongelman ratkaisemiseksi minun on tiedettävä hinta, jonka olen asettanut asiakkaalle ennen kyseistä alaongelmaa. Muuttuja q varmistaa hintajoukon monotonisen luonteen ja muuttuja i seuraa nykyistä asiakasta.

Vaihe 2: Kirjoita alitehtävä toistuvaksi matemaattiseksi päätökseksi.

Kysyn itseltäni kaksi kysymystä joka kerta yrittäessään löytää toistumisen:

  • Minkä päätöksen teen jokaisessa vaiheessa?
  • Jos algoritmi on vaiheessa i, mitä tietoja sen tarvitsisi päättääkseen mitä tehdä vaiheessa i + 1? (Ja joskus: Jos algoritmi on vaiheessa i, mitä tietoja sen tarvitsisi päättääkseen mitä tehdä vaiheessa i-1?)

Palataan takaisin ystävyysrannekkeen ongelmaan ja esitetään nämä kysymykset.

Minkä päätöksen teen jokaisessa vaiheessa? Päätin millä hinnalla myyn ystävyysrannekkeen nykyiselle asiakkaalle. Koska hintojen on oltava luonnollisia lukuja, tiedän, että minun pitäisi asettaa hinta asiakkaalleni i välillä q - asiakkaalle i-1 asetettu hinta - v_i - enimmäishinta, jolla asiakas ostaa ystävyysrannekkeen.

Jos algoritmi on vaiheessa i, mitä tietoja sen tarvitsisi päättääkseen mitä tehdä vaiheessa i + 1? Algoritmini on tiedettävä asiakkaalle i asetettu hinta ja asiakkaan i + 1 arvo, jotta voin päättää, millä luonnollisella numerolla asetetaan hinta asiakkaalle i + 1.

Tämän tiedon avulla voin matemaattisesti kirjoittaa uusiutumisen:

OPT (i, q) = max ~ ([tuotot (v_i, a) + OPT (i + 1, a)])
sellainen, että max ~ löytää maksimin kaikista a: n välillä alueella q ≤ a ≤ v_i

Jälleen kerran, tämä matemaattinen toistuminen vaatii selitystä. Koska asiakkaan i-1 hinta on q, asiakkaan i osalta hinta a joko pysyy kokonaislukuna q tai muuttuu yhdeksi kokonaisluvuksi välillä q + 1 ja v_i. Kokonaistuottojen löytämiseksi lisäämme asiakkaan i tuotot enimmäistuottoihin, jotka saadaan asiakkailta i + 1 - n siten, että asiakkaan i hintaksi asetettiin a.

Toisin sanoen kokonaistulojen maksimoimiseksi algoritmin on löydettävä asiakkaalle i optimaalinen hinta tarkistamalla kaikki mahdolliset hinnat välillä q ja v_i. Jos v_i ≤ q, niin hinnan a on pysyttävä q: ssa.

Entä muut vaiheet?

Vaiheiden 1 ja 2 käsittely on vaikein osa dynaamista ohjelmointia. Harjoituksena ehdotan, että työskentelet vaiheiden 3, 4 ja 5 läpi yksin tarkistaaksesi ymmärryksesi.

Dynaamisten ohjelmien ajonaikainen analyysi

Nyt algoritmien kirjoittamisen hauskaa osaa varten: ajonaikainen analyysi. Käytän iso-O-merkintää koko tässä keskustelussa. Jos et vielä tunne big-O: ta, suosittelen, että luet sen täällä.

Yleensä dynaamisen ohjelman ajonaika koostuu seuraavista ominaisuuksista:

  • Esikäsittelyä
  • Kuinka monta kertaa for-silmukka käy
  • Kuinka paljon aikaa toistuminen kestää suorittaa yhdessä silmukan iterointiin
  • Jälkikäsittelyä

Kaiken kaikkiaan ajonaika on seuraavassa muodossa:

Esikäsittely + silmukka * toistuminen + jälkikäsittely

Suoritetaan suoritusaikaanalyysi lävistyskorttiongelmasta tutustuaksesi dynaamisten ohjelmien iso-O: hen. Tässä on lyöjäkorttiongelmien dynaaminen ohjelma:

def punchcardSchedule (n, arvot, seuraava):
 # Alusta muistiluku - Vaihe 4
  muistio = [0] * (n + 1)
  
 # Aseta peruskotelo
  muistio [n] = arvot [n]
  
 # Luo muistiontaulukko n: stä yhteen - vaihe 2
  i: lle alueella (n-1, 0, -1):
    muistio [i] = max (v_i + muistio [seuraava [i]], muistio [i + 1])
 
 # Palauta ratkaisu alkuperäiseen ongelmaan OPT (1) - Vaihe 3
  palautusilmoitus [1]

Jaotellaan sen ajoaika:

  • Esikäsittely: Tässä tarkoitetaan muistiryhmän rakentamista. Päällä).
  • Kuinka monta kertaa for-silmukka käy: O (n).
  • Kuinka paljon aikaa toistuminen kestää suorittamalla yhdessä silmukkatoistoon: Toistuminen vie jatkuvan ajan, koska se tekee päätöksen kahden vaihtoehdon välillä jokaisessa iteraatiossa. O (1).
  • Jälkikäsittely: Ei mitään täällä! O (1).

Rei'ityskortti-ongelma-dynaamisen ohjelman kokonaiskesto on O (n) O (n) * O (1) + O (1) tai yksinkertaistetussa muodossa O (n).

Teit sen!

No, siinä on - olet askeleen lähemmäksi tullaksesi dynaamiseksi ohjelmointitoiminnaksi!

Margaret Hamilton: yksi historian monista ohjelmointitoimista!

Viimeinen viisauden pala: jatka dynaamisen ohjelmoinnin harjoittelua. Riippumatta siitä, kuinka turhauttavaa nämä algoritmit voivat tuntua, toistuvasti kirjoittamalla dynaamisia ohjelmia tehdään aliongelmat ja toistuvat kohdat luonnollisemmin. Tässä on joukkoläheinen luettelo klassisista dynaamisen ohjelmoinnin ongelmista, joita voit kokeilla.

Joten mene ulos ja ota haastattelut, luokat ja elämäsi (tietysti) uuden löytämäsi dynaamisen ohjelmointitietosi kanssa!

Paljon kiitoksia Steven Bennettille, Claire Durandille ja Prithaj Nathille tämän viestin oikoluvasta. Kiitos professori Hartlinelle, joka herätti minua niin innoissani dynaamisesta ohjelmoinnista, että kirjoitin siitä pitkään.

Nautitko lukemasi? Levitä rakkautta pitämällä ja jakamalla tämä pala. Onko sinulla ajatuksia tai kysymyksiä? Ota minuun yhteyttä Twitterissä tai alla olevissa kommentteissa.