Vypracoval Michal Turek, cvičení Po 12:45-14:15 (Petr Fišer)
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 iteruje přes všechny kombinace věcí, v každém průchodu se vypočte jejich celková hmotnost a hodnota. Pokud je hmotnost nižší než nosnost batohu a zároveň jejich cena vyšší než dosud nejvyšší nalezená, označí se dané řešení jako řešení problému, které je po průchodu všemi kombinacemi vypsáno. (brute_force.cpp, Makefile)
Knapsack() { opt = 0; // Nulové optimální řešení for (i = 0; i < 2^n; i++) // Všechny kombinace { cur = {x1, x2, ..., xn} // Další kombinace if (hm(cur) <= M) // Vejde se do batohu? if (cena(cur) >= cena(opt)) // Dosud nejlepší? opt = cur; // Označí jako dosud nejlepší } print(opt); // Zobrazí výsledek }Po spouštění programu byly získány očekávané výsledky. Rychle rostoucí čas potřebný k výpočtu a jeho trend je zřejmý už při n = 25. Naměřené hodnoty jsou zaneseny do tabulky a rovněž do grafu, jedná se vždy o sumu časů všech padesáti datových sad.
n | čas [s] |
---|---|
4 | 0.000292 |
10 | 0.03996 |
15 | 1.6481 |
20 | 66.412 |
22 | 286.350 |
25 | 2583.32 |
Algoritmus má složitost O(2n), což je počet všech možných stavů, ve kterých se může batoh nacházet. Exponenciální doba trvání je jasná i z grafu. Jedinou výhodou tohoto algoritmu je, že vždy vypočítá optimální řešení.
Heuristika nejdříve seřadí věci sestupně podle poměru cena/hmotnost. V další fázi se postupně zkouší přidávat jednotlivé věci do batohu (ty s nejvyšším poměrem nejdříve). Poté, co se projdou všechny věci, je obnoveno jejich původní pořadí v datové struktuře a vypsán výsledek. (cost_weight.cpp, Makefile)
Knapsack() { 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ě? batoh.add(cur); // Vložit do batohu } print(batoh); // Zobrazí výsledek }
Heuristiky nezajišťují vždy stoprocentně správné vyřešení daného problému, nicméně přinášejí obrovské časové úspory. Aby byla doba trvání vůbec měřitelná, byl výpočet prováděn tisíckrát a získaná hodnota poté vydělena. V tabulce jsou taktéž uvedeny průměrné a maximální relativní odchylky od optima pro jednotlivá n. Je patrné, že pro malá n se heuristikou dopouštíme relativně velké chyby, s rostoucím n však tato chyba postupně klesá.
n | čas [ms] | Prům. rel. odchylka | Max. rel. odchylka |
---|---|---|---|
4 | 0.252 | 2.175 | 36.364 |
10 | 0.496 | 1.104 | 11.480 |
15 | 0.768 | 0.305 | 2.770 |
20 | 1.044 | 0.449 | 4.079 |
22 | 1.096 | 0.542 | 3.018 |
25 | 1.208 | 0.425 | 2.588 |
27 | 1.416 | 0.289 | 1.845 |
30 | 1.524 | 0.383 | 1.749 |
32 | 1.648 | 0.274 | 2.278 |
35 | 1.824 | 0.188 | 1.817 |
37 | 1.940 | 0.181 | 1.176 |
40 | 2.112 | 0.153 | 0.946 |
Pokud není uvažována složitost řazení, má tento algoritmus (heuristika) lineární složitost, ne vždy se však nalezne optimální řešení problému.
Oba programy byly napsány v jazyce C++ s použitím šablon ze standardní knihovny STL. Programy byl spouštěny na počítači s procesorem AMD Atlon XP 1800+ pod operačním systému Debian Etch GNU/Linux. Doba jejich trvání byla pro jednotlivé datové soubory měřena pomocí standardního programu time (položka user).