Vypracoval Michal Turek, cvičení Po 12:45-14:15 (Petr Fišer)
Zpráva by měla obsahovat:
Najít takovou podmnožinu množiny věcí, která se ještě vejde do batohu, a zároveň je její cena nejvyšší možná. Řečeno formálněji: Najít množinu X = {x1, x2, ..., xn}, xi = {0, 1}, tak, aby platilo:
(tj. batoh nebyl přetížen) a aby výraz
nabýval maximální hodnoty pro všechny takové množiny.
Algoritmus rekurzivně prochází všechny zadané věci, v každém zanoření se rozhoduje, zda určitá věc bude ve výsledném množině, či ne. Ořezávání se provádí podle dvou jednoduchých pravidel. 1) Rekurze se v dané větvi ukončí, když přidání aktuální věci přetíží batoh. 2) Do batohu se fiktivně přidají všechny zbývající/neprověřené věci a pokud je celková cena tohoto fiktivního řešení menší než cena dosud nejlepšího nalezeného, tak se rekurze v dané větvi opět ukončí. (branch_bound.cpp, Makefile)
Knapsack(index, cost, weight, things) { // End of recursion? if(index >= n) return; // Actual thing won't be there if(cost + remaining_costs > currently_the_best) SolveKnapsack(index + 1, cost, weight, things); // Actual thing will be there if(weight + actual_thing.weight <= M) { if(cost + remaining_costs > currently_the_best) { // Update the best solution if(cost + actual_thing.cost > currently_the_best) { currently_the_best = cost + actual_thing.cost; things[index] = true; resolution_things = things; } things[index] = true; SolveKnapsack(index + 1, cost + actual_thing.cost, weight + actual_thing.weight, things); } } } Knapsack(0, 0, 0, 0);
Algoritmus si v pseudopolynomiálním čase vygeneruje dvourozměrnou tabulku s výsledky řešení jednotlivých podproblémů a poté pouze zobrazí výsledek na požadovaném indexu tabulky. (dynprog.cpp, Makefile)
void Knapsack(ID, n, M, data) { ZeroFirstRow(table); ZeroFirstColumn(table); // Calculate table items for(i = 1; i <= M; i++) { for(j = 1; j <= n; j++) { if(cur.weight > i) { table(actual).cost = table(left).cost; table(actual).weight = table(left).weight; table(actual).things = table(left).things; } else { if(table(left).cost > table(down).cost + cur.cost) { table(actual).cost = table(left).cost; table(actual).weight = table(left).weight; table(actual).things = table(left).things; } else { table(actual).cost = table(down).cost + cur.cost; table(actual).weight = table(down).weight + cur.weight; table(actual).things = table(down).things; table(actual).things[j-1] = true; } } } } DisplaySolution(table[n, M]); }
Výsledek původní heuristiky podle poměru cena/váha z první úlohy je srovnán s řešením, kdy obsahem batohu bude pouze nejcennější věc. Jako výsledek se vezme to řešení, které vychází lépe. (cost_weight_maxval.cpp, Makefile)
Knapsack(ID, n, M, data) { T = {t1, t2, ..., tn}; // Seznam věcí sort(T); // Řazení podle cena/hmotnost sestupně ret = 0; // Nulování řešení for(i = 0; i < n; i++) // Všechny věci { if(hm(cur) + hm(ret) <= M) // Vejde se ještě? knapsack.add(cur); // Vložit do batohu } thing = FindMostValuableThing(); // Nejhodnotnější věc if(thing.cost > knapsack.sumOfCosts()) // Je dražší než původní řešení? { knapsack.removeAllThings(); // Odstraní všechny věci knapsack.add(thing); // Vloží pouze nejcennější věc } print(knapsack); // Zobrazí výsledek }
Následující tabulka obsahuje dobu trvání spuštění programu vždy pro padesát testovacích instancí problému batohu, které jsou uvedené na webu předmětu.
čas [s] | |||
---|---|---|---|
n | Hrubá síla | B&B | Dyn. prog. |
4 | 0.000292 | 0.005 | 0.003 |
10 | 0.03996 | 0.008 | 0.004 |
15 | 1.6481 | 0.013 | 0.012 |
20 | 66.412 | 0.073 | 0.021 |
22 | 286.350 | 0.259 | 0.022 |
25 | 2583.32 | 1.720 | 0.028 |
27 | - | 6.609 | 0.037 |
30 | - | 52.371 | 0.049 |
32 | - | 0.910 * | 0.051 |
35 | - | 2.339 * | 0.064 |
37 | - | 2.522 * | 0.069 |
40 | - | 18.719 * | 0.082 |
Ze souborů instancí označených hvězdičkou byla odstraněna konfigurace, která způsobovala, že se metoda B&B chovala téměř jako metoda hrubou silou, a tudíž nebylo možné získat výsledky během relativně krátkého času. V příkladu níže jsou hodnoty hmotností takové, že se batoh hned ze začátku nepřetíží, cena poslední věci způsobí, že prořezávání nezafunguje ani podle ceny. Z tohoto příkladu je vidět, že B&B může být výrazně závislá na složení vstupních dat problému a v nejhorším případě může dosahovat původní exponenciální složitosti.
9405 32 450 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 450 449
Porovnají-li se výstupy z programů a správné výsledky uváděné na webu předmětu v diffovacím nástroji (diff, Meld, Araxis Merge...), je vidět, že některé z uvedených instancí mají více optimálních řešení. Protože je výsledná cena všech věcí v batohu v obou případech stejná, nejedná se o chybu v programu.
Horní řádek: výsledky z webu předmětu Dolní řádek: výsledky získané metodou dynamického programování 9588 40 4805 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 0 0 1 1 1 1 0 1 1 1 0 0 1 9588 40 4805 1 0 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 0 0 1 1 1 1 0 0 1 1 1 0 1 9596 40 4244 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 1 0 1 1 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 0 1 9596 40 4244 1 1 1 1 1 1 1 1 0 1 1 1 1 1 0 0 1 1 1 1 0 1 1 0 1 0 1 0 1 1 1 1 0 1 1 1 0 1 1 1
Všechny programy byly napsány v jazyce C++ s použitím šablon ze standardní knihovny STL. Programy byly spouštěny na počítači s procesorem Intel Celeron 1,7 GHz pod operačním systémem Debian Sarge GNU/Linux. Doba jejich trvání byla pro jednotlivé datové soubory (tedy vždy 50 různých instancí problému) měřena pomocí standardního programu time (položka user).
Srovná-li se heuristika z první úlohy s kombinovanou heuristiou, že vidět, že porovnání výsledků a zvolení lepší hodnoty, opravdu pomáhá. U malých instací je to více zřetelné než u velkých. Nevyžadují-li se naprosto přesné výsledky je implementovaná heuristika vhodným řešením. Odchylka od optimálního řešení je poté maximálně 50% (důkaz na stránkách předmětu).
Pokud je nutné přesné řešení, vychází nejlépe dynamické programování. To ale může být na druhou stranu limitováno velikostí dostupné paměti použité pro cachování mezivýsledků.
U většiny instancí problému se bude velice dobře chovat i metoda větví a hranic. Doba hledání řešení je však závislá i na vlastních hodnotách v zadání problému a za určitých okolností může dokonce narůst na původní exponenciální složitost metody hrubé síly.