====== Úkol 1 - problém batohu (knapsack problem) ====== * CTU Prague, MI-PAA, ZS 2011 * Autor: **Tomáš Borovička (borovto1)** **Zadání** * Naprogramujte řešení [[homeworks:knapsack:def|problému batohu]] hrubou silou. Na [[homeworks:knapsack:def|zkušebních datech]] pozorujte závislost výpočetního času na n. * Naprogramujte řešení problému batohu heuristikou podle poměru hmotnost/cena. Pozorujte: * závislost výpočetního času na n. Grafy jsou vítány (i pro exaktní metodu). * průměrnou a maximální *relativní chybu* (tj. zhoršení proti exaktní metodě) ===== Popis problému ===== Je dán batoh s kapacitou W a monožina předmětů N, kde každý předmět má svou váhu w_i a cenu v_i. Úkolem je najít takovou podmonožinu N, která maximalizuje cenu předmětů v batohu a nepřesáhne svou vahou kapacitu batohu W. Problém batohu je kombinatorický optimalizační problém spadající do třídy NP úplných problémů. ===== Řešení ===== Vytvořil jsem dvě řešení - hrubou silou a řešení s využitím jednoduché heuristiky. ==== Řešení hrubou silou (brute force) ==== Při řešení hrubou silou procházíme všechny možná řešení problému a hledáme optimální řešení. Algoritmus rekurzivně generuje všechny možné kombinace věcí, které můžeme vložit do batohu, aniž bychom ho přetížili, a hledá optimální řešení. Složitost tohoto algoritmu je 2^|N|. === Část zdrojového kódu - řešení hrubou silou === public Solution SolveBruteForce(Instance instance, Solution solution, int idx) { if (idx < 0) return solution; if(solution.ResCapacity < instance.Items[idx].Weight) return SolveBruteForce(instance, solution, idx-1); Solution s1 = solution; Solution s2 = solution.Clone(); s2.Value += instance.Items[idx].Value; s2.ResCapacity -= instance.Items[idx].Weight; s1 = SolveBruteForce(instance, s1, idx-1); s2 = SolveBruteForce(instance, s2, idx-1); solution = s1.Value > s2.Value ? s1 : s2; return solution; } ==== Řešení s využitím jednoduché heuristiky (cená/váha) ==== Další řešení je s využitím jednoduché heuristiky. Heuristika spočívá v seřazení věcí podle poměru cena/váha. Následně do batohu vkládáme věci s nejvyšším poměrem cena/váha, dokud batoh zcela nezaplníme. Výpočetní složitost je značně redukována na cenu řazení + O(|N|). === Část zdrojového kódu - řešení pomocí heuristiky === public Solution SolveWithHeuristic(Instance instance) { instance.SortItems(new ItemByValueWeightComparer()); var solution = new Solution(instance); for (int idx = solution.Count-1; idx >=0 ; idx--) { if (solution.ResCapacity < instance.Items[idx].Weight) continue; solution.Value += instance.Items[idx].Value; solution.ResCapacity -= instance.Items[idx].Weight; } ===== Výsledky měření ===== ==== Platforma a HW konfigurace ==== Windows Server 2008 EE x64, Intel Core2 Duo E8500@3.16GHz, 8 GB DDR2 ==== Způsob měření ==== Platforma .NET nabízí několik možností jak měřit čas vykonávání algoritmu: * **DateTime.UtcNow** * velmi rychlé volání, * špatná rozlišovací schopnost (10 milliseconds), * **Stopwatch** * pomalejší než UtcNow, * nespolehlivé chování na vícejádrových počítačích, nebo počítačích s pohyblivou frekvencí, * **Process.TotalProcessorTime** * měří pouze procesorový čas, * pokud problém zpracovává více jader, čas se sčítá, * výrazně pomalejší volání než u UtcNow. Nejvýhodnější pro měření výkonnosti algoritmu je bezpochyby měření pomocí //Process.TotalProcessorTime//, které měří pouze procesorový čas, který nás zajímá. Bohužel i při využití //Process.TotalProcessorTime// nebyla rozlišovací schopnost pro malé instance a heuristiku dostatečná. Bylo nutné opakovat volání algoritmu ve smyčce a výsledný čas podělit počtem opakování. Ilustrace použití v následujícíh částech zdrojového kódu. === Část zdrojového kódu - měření CPU času === TimeSpan begin = Process.GetCurrentProcess().TotalProcessorTime; ... TimeSpan end = Process.GetCurrentProcess().TotalProcessorTime; Console.WriteLine("Execution time: " + (end - begin).TotalMilliseconds + " ms."); === Část zdrojového kódu - měření CPU času pro malé instance a heuristiku === begin = Process.GetCurrentProcess().TotalProcessorTime; for (int i = 0; i < 100000; i++) { solutionH = solver.SolveWithHeuristic(instance.Value); } end = Process.GetCurrentProcess().TotalProcessorTime; ==== Čas výpočtu ==== Z tabulky a grafu 1 (v logaritmickém měřítku) je vidět exponenciálně rostoucí čas řešení hrubou silou. Naopak u řešení pomocí heuristiky je vidět lineární nárůst viz graf 2. Obrovský rozdíl ve výpočetním čase nám může přiblížit pohled na graf 3. Problémy s 32,35 a 37 předměty jsem řešil pouze pro dvě instance, jelikož jsou jejich nároky na čas příliš vysoké. ^N ^CPU time BF (ms)^CPU time H (ms)^ |4 |9.67E-04 |7.80E-04| |10 |4.90E-02 |1.97E-03| |15 |2.36E+00 |2.90E-03| |20 |6.74E+01 |4.05E-03| |22 |2.54E+02 |4.42E-03| |25 |2.14E+03 |4.90E-03| |27 |9.49E+03 |5.36E-03| |30 |7.40E+04 |5.74E-03| |32 |3.02E+05 |6.40E-03| |35 |2.42E+06 |7.18E-03| |37 |9.98E+06 |7.02E-03| |{{:school:fit:mipaa:zavislostcasuvypostunapostupredmetubf.png|}}| | **Graf 1** | |{{:school:fit:mipaa:zavislostcasuvypostunapostupredmetuh.png|}}| | **Graf 2** | | {{:school:fit:mipaa:zavislostcasuvypostunapostupredmetu.png|}} || | **Graf 3** || ==== Relativní a maximální chyba ==== Pro každou instanci jsem spočetl relativní chybu a udělal průměr této chyby přes počet předmětů v instanci a zároveň zaznamenal maximální chybu. Nakonec jsem spočetl celkovou průměrnou relativní odchylku. Data můžeme vidět v tabulce, relativní chybu v grafu 4 a maximální chybu v grafu 5. ^N ^ε (%)^ ε_max (%)^ |4 |4.78% |77.87%| |10 |1.30% |10.00%| |15 |0.64% |9.34%| |20 |0.85% |9.21%| |22 |0.58% |7.79%| |25 |0.45% |3.82%| |27 |0.77% |11.86%| |30 |0.46% |5.84%| |32 |0.78% |0.78%| |35 |0.39% |0.39%| |37 |1.37% |1.37%| ^avg ^1.12% ^ - ^ === Relativní chyba === Relativní chybu spočteme jako ε = ( C(OPT)-C(APX) ) / C(OPT), * kde C(OPT) je cena optima, * a C(APX) je cena přibližného řešení. Relativní chyba se pohybovala do 2%, i když průměrnou hodnotu ovlivnila jedna odlehlá hodnota pro instanci se 4 předměty, byl průměr na 1%, což je velmi dobrý výsledek. |{{:school:fit:mipaa:relativnichyba.png|Graf 4}}| | **Graf 4** | === Maximální chyba === Maximální chyba se pohybovala v rozmezí 0-10%, pouze jedna hodnota u instance s 4 předměty byla velmi vysoká a to 78%. To bylo nejspíš způsobeno netestováním vložení do batohu pouze nejdražšího předmětu. |{{:school:fit:mipaa:maximalnichyba.png|Graf 5}}| | **Graf 5** | ===== Závěr ===== Naměřené hodnoty naplnily očekávání a potvrdily, že řešení hrubou silou je pro svou výpočetní složitost u většího počtu předmětů nepoužitelné. Naopak řešení s využitím jednoduché heuristiky dává velmi dobré výsledky (průměrná relativní chyba 1,12%) při výrazně nižší výpočetní složitosti. Pokud je řešen optimalizační problém, je nutné znát cenu optimálního řešení a zhodnotit, zda není výhodnější využít heuristiky s výrazně nižší cenou, avšak s nejistotou nejlepšího optimálního řešení. ===== Source code ===== ===== References ===== * [[http://en.wikipedia.org/wiki/Knapsack_problem]] * [[http://rosettacode.org/wiki/Knapsack_problem/0-1]]