LINUX-BG Adres : http://www.linux-bg.org |
Vuvedenie v dinamichnata optimizatsiia |
Ot: vesso Publikuvana na: 30-08-2005 Adres na statiiata: http://www.linux-bg.org/cgi-bin/y/index.pl?page=article&id=devs&key=374961042 |
Vuvedenie v dinamichnata optimizatsiia Zadacha: Imame opredelen broi moneti ot razlichni denominatsii. Imame i suma koiato triabva da platim. Da se napishe funktsiia koiato da izchisli minimalniia broi moneti neobhodimi za plashtane na sumata. Ako ne mozhem da formirame sumata s nalichnite moneti funktsiiata triabva da vurne chislo po-goliamo ot broia na monetite kato indikatsiia za greshka. Purvi opit: Oslaniame se na instinktite si. Znaem che kolkoto poveche edri moneti polzvame tolkova po-maluk broi moneti shte sa neobhodimi. Zatova vzimame nai-goliamata moneta (koiato vse pak e po-malka ot sumata), namaliavame sumata sus stoinostta na monetata i izvikvame sushtata funktsiia s namalena suma no i s edna moneta po-malko. Kogato sumata stigne do nula vrushtame 0 moneti. Kogato broia moneti stigne do 0 vrushtame 1 (greshka). Na piton funktsiiata izglezhda taka: def coins1(purse, amount): if amount == 0: return 0 if amount < 0 or len(purse) == 0: return len(purse) + 1 purse.sort() for index in range(len(purse) - 1, -1, -1): if purse[index] <= amount: new_purse = purse[:index] + purse[index+1:] new_amount = amount - purse[index] return 1 + coins1(new_purse, new_amount) return len(purse) + 1 Testvame naburzo: print coins1([50], 20), ' -> >1' print coins1([50], 50), ' -> 1' print coins1([10, 20, 50], 20), ' -> 1' print coins1([5, 5, 10, 10, 10, 20, 50], 25), ' -> 2' print coins1([5, 5, 10, 10, 10, 50], 25), ' -> 3' print coins1([5, 5, 10, 20, 20, 20, 50, 50], 60), ' -> 2' Neshtata izglezhdat dobre ... dokato ne probvame: print coins1([2, 2, 2, 5], 6), ' -> 3' Ochakvame rezultat 3 (3 moneti po 2 st) no funktsiiata vrushta 5, t.e. ne mozhe da napravi sumata ot zadadenite moneti. A ne mozhe zashtoto vednuzh reshila da polzva monetata ot 5 st niama kak da napravi ostatuka ot 1 st. Tozi algoritum mozhe da se uslozhni taka che ako ne mozhe da napravi new_amount da opita s po-malka moneta - togava vinagi shte uspiava i izvikvaneto coins1([2, 2, 2, 5], 6) shte vrushta korektna stoinost. Obache izvikvane coins1([1, 1, 1, 1, 1, 1, 5, 20, 20, 20, 50], 60) shte vrushta 7 (50 + 5 + 1 + 1 + 1 + 1 + 1) vmesto korektnoto 3 (20 + 20 + 20). Opisaniiat algoritum v opit 1 e alchen (greedy) zashtoto vinagi zapochva ot nai-goliamoto kandidat-reshenie. Tezi algoritmi sa lesni za pisane i rabotiat burzo no ne vinagi rabotiat korektno. Vtori opit: Qvno nastoiavaneto da polzvame nai-goliamata moneta ne vinagi e udachno. Zatova za vsiaka moneta shte opitame kakvo shte stane (1) ako monetata uchastva i (2) ako monetata ne uchastva v sumata. Sled tova shte vzemem po-dobroto reshenie. Otnovo se oformia rekursivna funktsiia: def coins2(purse, amount): if amount == 0: return 0 if amount < 0 or len(purse) == 0: return len(purse) + 1 without = coins2(purse[1:], amount) if without >= len(purse): without = len(purse) + 1 with = coins2(purse[1:], amount - purse[0]) if with >= len(purse): with = len(purse) + 1 return min([without, with + 1]) Puskame gornite testove i vsichko vurvi dobre, vklyuchitelno i tezi primeri koito zatrudniavaha purvata funktsiia ... dokato ne opitame: print coins2([1, 1, 1, 1, 1, 1, 2, 2, 5, 5, 5, 10, 10, 10 ,10, 20, 20, 20, 20, 50, 50, 50], 275), ' -> 12' Funktsiiata raboti viarno no nepriemlivo dulgo vreme. Prichinata e che ako ima 1 moneta funktsiiata che obraboti 2 sluchaia (2^1) - ediniia sus a drugiia bez monetata. Ako ima 2 moneti funktsiiata shte obraboti 4 sluchaia (2^2) i suotvetno ako ima N moneti funktsiiata shte triabva da obraboti 2^N sluchaia. Ili zapisano v t.nar. O notatsiia slozhnostta na funktsiiata e O(2^N). Takava funktsiia, dori napisana na asembler, shte raboti godini nared da smetne suma ot stotina moneti. Treti opit: reshenie s dinamichna optimizatsiia. Imeto na poniatieto "dinamichna optimizatsiia" idva ot povedenieto na programite koito dinamichno izgrazhdat tablitsi s mezhdinni rezultati. Polzvaiki tezi mezhdinni rezultati lesno i burzo se stiga do krainiia rezultat. Eto i funktsiiata: def coins3(purse, amount): big = len(purse) + 1 current_row = [0] for amount_idx in range(amount): current_row.append(big) all_rows = [current_row] for coin in purse: prev_row = current_row current_row = [] for amount_idx in range(amount + 1): if amount_idx < coin: current_row.append(prev_row[amount_idx]) else: without = prev_row[amount_idx] with = 1 + prev_row[amount_idx - coin] current_row.append(min([with, without])) all_rows.append(current_row) return current_row[amount] Obiasnenie: Neka izvikame funktsiiata s coins3([2, 2, 2, 2, 5, 5, 10], 11). Funktsiiata si izbira proizvolen kod za "nevuzmozhna kombinatsiia", po-goliam ot broia na monetite. Sled tova si suzdava edin spisuk (ednomeren masiv) sus slednoto sudurzhanie: [0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8] Spisukut ima tolkova elementa kolkoto e sumata koiato ni triabva (11) plyus edno (za suma 0). Smisulut na taka polucheniiat red e che s nikakvi moneti (zashtoto oshte nikakvi moneti ne sa obraboteni) mozhem da postignem suma 0 sus 0 moneti (purviia element) i ne mozhem da napravim nikakvi drugi sumi (8 = kod za nevalidna suma). Indeksut v tozi spisuk e sumata koiato iskame da napravim, a stoinostite - broia moneti neobhodimi za postiganeto na sumata. Sled tova za vsiaka moneta si suzdavame nov spisuk, v koito: - Ako indeksut (sumata) e po-maluk ot monetata prosto kopirame stoinostite ot predishniia red. - v protiven sluchai sravniavame broia moneti ot sushtiia indeks na predishniia red (t.e. broia moneti bez tekushtata) s edno plyus broia moneti ot predishniia red, neobhodim za postigane na suma namalena sus stoinostta na tekushtata moneta. Dobaviame po-malkata ot tezi stoinosti kum tekushtiia red. Sled obrabotvane na purvata moneta (2 st) spisukut izglezhda taka: [0, 8, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8], Smisulut na tova e che s 0 moneti mozhem da postignem suma 0, s 1 moneta mozhem da postignem suma 2 (indeks 2) a ostanalite sumi ne mogat da budat napraveni s 1 moneta ot 2 st. Kogato programata zavurshi spisukut all_rows sudurzha slednoto: [ [0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], [0, 8, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8], [0, 8, 1, 8, 2, 8, 8, 8, 8, 8, 8, 8], [0, 8, 1, 8, 2, 8, 3, 8, 8, 8, 8, 8], [0, 8, 1, 8, 2, 8, 3, 8, 4, 8, 8, 8], [0, 8, 1, 8, 2, 1, 3, 2, 4, 3, 8, 4], [0, 8, 1, 8, 2, 1, 3, 2, 4, 3, 2, 4], [0, 8, 1, 8, 2, 1, 3, 2, 4, 3, 1, 4]] Posledniiat red sudurzha broia moneti neobhodimi za suma ravna na indeksa v reda. Mozhem da postignem: - s 0 moneti: suma 0 - s 1 moneta: suma 2, 5 i 10 st (s takiva moneti razpolagame) - s 2 moneti: sumi 4 i 7 st. - s 3 moneti: sumi 6 i 9 st. - s 4 moneti: sumi 8 i 11 st. - ne mozhem da napravim sumi 1 i 3 st. Otgovorut na nashata zadacha e v posledniia red, poslednata kolona. Paradoks e che namerihme resheniiata na vsichki sumi po-malki ot tursenata, pri tova - pone v niakoi sluchai - po-efektivno otkolkoto namiraneto samo na edno reshenie ot vtoriia ni opit. Po-vnimatelnite mozhe bi sa zabeliazali che kum spisukut all_rows samo se dobavia no ot nego ne se chete. Programata mozhe da raboti chudesno i bez nego. Takuv spisuk e neobhodim kogato iskame da vidim koi moneti uchastvat v reshenieto. Praviloto e: 1. Trugvame ot dolniia desen ugul i ot poslednata moneta 2. Sravniavame chisloto nad tekushtoto: 2.1. Ako e sushtoto znachi tekushtata moneta ne uchastva v rezultata. Zapazvame sushtiia indeks. 2.2. Ako e razlichno znachi tekushtata moneta uchastva v rezultata. Namaliavame indeksa sus stoinostta na tekushtata moneta. 3. Minavame na po-gorniia red i na predishnata moneta. 4. Ako sme stignali do 0 sme gotovi, v protiven sluchai otivame na t. 2. Zadachi za uprazhnenie: 1. Prepravete funktsiiata coins3 taka che da izpolzva samo edin spisuk (ednomeren masiv) 2. Pesho ima da dava na Gosho dadena suma. Znaem kakvi moneti imat i Pesho i Gosho. Da se napishe programa koiato da izchisli minimalniia broi moneti smeniavashti sobstvenika si taka che Pesho da se izdulzhi. Znaem che e dopustimo Gosho da vrushta resto. Funktsiia: def exer2(pesho_coins, gosho_coins, amount): Izvikvane: print exer2([10, 10, 20, 50], [5, 10, 20], 40) rezultat: 2 - Pesho dava edna moneta ot 50 i poluchava resto edna moneta ot 10 3. Mozhem da nosim maksimum X kg. Ima N predmeta s teglo Gi kg i stoinost Vi. Kakva e maksimalnata stoinost na predmetite koito mozhem da vzemem. Funktsiia: def exer3(MaxWeight, Weights, Values): Izvikvane: print exer3(50, [24, 25, 45], [5, 6, 10]) rezultat: 11 - purviia i vtoriia predmet (Podskazka: Google "knapsack problem") 24 Avgust 2005 Veselin Kostadinov Redaktsiia ot 31 Avgust 2005: Vmesto termina "dinamichna optimizatsiia" statiiata polzvashe "dinamichno programirane" (bukvalen prevod ot angliiskiia termin "dynamic programming"). Blagodaria na d.vasilev. << Kraina tsel: FreeBSD | Programirane pod Linuks. Vuvedenie za nachinaeshti. >> |
Avtorite na saita, kakto i tehnite sutrudnitsi zapazvat avtorskite prava vurhu sobstvenite si materiali publikuvani tuk,
no te sa copyleft t.e. mogat svobodno da budat kopirani i razprostraniavani s iziskvaneto izrichno da se upomenava imeto na avtora,
kakto i da se publikuva na vidno miasto, che te sa vzeti ot originalniia im URL-adres na tozi survur (http://www.linux-bg.org). Avtorskite prava na prevodnite materiali prinadlezhat na tehnite avtori. Ako s publikuvaneto tuk na niakakuv material nevolno sa narusheni nechii prava - sled konstatiraneto na tozi fakt materialut shte bude svalen.
All trademarks, logos and copyrights mentioned on this site are the property of their respective owners.
|