Stručně popíši implementaci užitých algoritmů, pro podrobnější vysvětlení viz komentáře ve zdrojových souborech (hlavně před metodou solve).
Jádrem tohoto řešení je rozložení problému na menší s využitím faktu, že je-li řešení problému s k věcmi a nosností N optimální a obsahuje k-tou věc, pak optimální řešení problému s prvními k-1 věcmi a nosností (N - váha k) je toto řešení po vyjmutí k-té věci. Výhoda DP spočívá v tom, že si pamatujeme co jsme již počítali a nepočítáme to znovu. K tomu mi slouží pole bestcosts[w], w = 0 ... nosnost, které v k-tém průchodu hlavní smyčkou obsahuje nejlepší možné řešení pro prvých k věcí a nosnost batohu rovnou w. Zde je tedy srdce algoritmu:
for (int i = 0; i < nr_items; i++){
for (int w = max_load; w > 0; --w){
if (items[i]->weight < w) {
// bestcosts[w] = MAXIMUM of
// 1. best solution for k < i first items and capacity w, without i-th item,
// 2. best solution for k = i first items and capacity w, including i-th item
// (which is based on the best solution for k < i and capacity (w - i-th item's weight))
bestcosts[w] = max( bestcosts[w], bestcosts[w - items[i]->weight] + items[i]->cost );
}// if
}// for all weights
}// for all items
solution_cost = bestcosts[max_load];
Algoritmus je založen na řešení hrubou
silou z první úlohy. Rozdíl je v tom, že některé větve nejsou
prozkoumávány, resp. metoda descend skončí už když se dostane do
situace, že rozvíjená větev nemůže být lepší než dosud dosažené řešení
(a neschází ji celou), a metoda mount obdobně pomíjí neperspektivní řešení.
Jádro změn leží ve funkcích (viz dynamic_progr.hh.html)
do_notExclude(int item) pro mount a lessThanBest(int item, int preceding_cost)
pro descend. Obě staví na porovnání ceny nejlepšího dosud dosaženého řešení
a horní meze, čili největší potenciální ceně rozvíjeného řešení,
která se - při zvažování i-té věci - spočte jako součet cen věcí <
i které v řešení jsou a součet cen všech věcí > i [případně + cena
i-té věci].
Očividně takto určená horní mez někdy značně nadhodnocuje možnosti rozvíjeného
řešení. To je cena za to, že jsem dal přednost jednoduchosti před
kvalitou. Podstatně lepší by bylo užít Kolesarův algoritmus, ale s tím
jsem se bohužel seznámil příliš pozdě.
Tento algoritmus je téměř totožný s heuristikou z první úlohy, jenom bylo přidáno porovnání s řešením obsahujícím pouze nejcennější věc a vybrání toho lepšího.
Značení: DP = Dynamické Programování, BB = metoda řezů a větví, HR = Heuristika
Cena řešení | ||||
---|---|---|---|---|
Věcí | DP | BB | HR | Optimální |
4 | 352 | 352 | 347 | 352 |
10 | 1125 | 1125 | 1110 | 1125 |
15 | 1814 | 1814 | 1808 | 1814 |
20 | 2335 | 2335 | 2321 | 2335 |
22 | 2501 | 2501 | 2488 | 2501 |
25 | 2954 | 2954 | 2941 | 2954 |
27 | 3143 | 3143 | 3122 | 3143 |
30 | 3581 | 3581 | 3563 | 3581 |
32 | 3694 | 3694 | 3678 | 3694 |
35 | 4081 | 4081 | 4067 | 4081 |
37 | 4222 | 4222 | 4205 | 4222 |
40 | 4628 | 4628 | 4612 | 4628 |
Dynamické programování a metoda větví a řezů vždy (nejen v tomto případě) najdou optimální řešení. Heuristika dává i neoptimální řešení, ale v drtivé většině případů se rozdíl od optima drží pod 1% a nikdy nepřekročí 2% (viz úkol 1 pro [horší verzi heuristiky]). Přestože naměřené výsledky mají jistou vypovídací hodnotu, nelze je zobecňovat. Připomeňme jenom, že tato heuristika má teoreticky prokázanou úspěšnost pouze > 50% (tzn. je zaručeno, že dosáhne přinejmenším poloviny ceny optima).
Počet kroků | |||
---|---|---|---|
Věcí | DP | BB | HR |
4 | 400 | 12 | 4 |
10 | 1000 | 146 | 10 |
15 | 3000 | 178 | 15 |
20 | 5000 | 3491 | 20 |
22 | 5500 | 12755 | 22 |
25 | 7500 | 33225 | 25 |
27 | 9450 | 38631 | 27 |
30 | 12000 | 184291 | 30 |
32 | 14400 | 263077 | 32 |
35 | 17500 | 677279 | 35 |
37 | 20350 | 721623 | 37 |
40 | 24000 | 8576258 | 40 |
Z hlediska počtu kroků je zdaleka nejúspěšnější heuristika, vzhledem k tomu, že počet kroků je roven počtu věcí. Docela rozumné je ještě dynamické programování s O(počet věcí*nosnost). Zato metoda řezů a větví roste opravdu značně rychle, i když zpočátku je lepší než dynamické programování; kdyby používala lepší heuristiku, byly by výsledky přijatelnější, ovšem na asymptotické složitosti by se nejspíš příliš nezměnilo.
Následující údaje jsou pouze orientační, vzhledem k tomu, že čas běhu programu závisí na kvalitě překladače a rychlosti počítače. Slouží k ilustraci problému růstu počtu kroků.
Doba výpočtu | |||
---|---|---|---|
Věcí | DP | BB | HR |
4 | 28 | 18 | 19 |
10 | 48 | 17 | 27 |
15 | 114 | 32 | 31 |
20 | 181 | 70 | 36 |
22 | 194 | 168 | 38 |
25 | 260 | 432 | 40 |
27 | 316 | 505 | 44 |
30 | 399 | 2061 | 40 |
32 | 475 | 2946 | 48 |
35 | 567 | 7653 | 52 |
37 | 661 | 7672 | 55 |
40 | 770 | 93762 | 59 |
Z dosud řečeného plyne, že jako vítěz se pro naše vstupní data a implementaci jeví heuristika. Při praktické aplikaci je však zapotřebí pečlivě zvážit, zda je důležitější cena řešení, rychlost (největší pro heuristiku) nebo něco mezi, a to s ohledem na vnější podmínky jako dostupné výpočetní vybavení, čas a charakteristika instance.
Soubor | Popis |
---|---|
common.hh.html | rodičovská třída všech algoritmů |
dynamic_progr.hh.html dynamic_progr.cc.html |
dynamické programování |
branch_bound.hh.html branch_bound.cc.html |
metoda větví a hranic |
heuristic2.hh.html heuristic2.cc.html |
heuristika |
ksack_in.hh.html ksack_in.cc.html |
zpracování vstupního souboru dat |
main.cc.html | hlavní program, rozhraní mezi uživatelem a algoritmem |