X36PAA, Úloha #1 - Problém batohu

Vypracoval Michal Turek, cvičení Po 12:45-14:15 (Petr Fišer)


Zadání


Specifikace problému

Zadáno je

Cílem je

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.


Řešení hrubou silou

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í.


Řešení heuristikou podle poměru cena/váha

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.


Závěr

Každý další předmět, který se uvažuje při vkládání do batohu při řešení hrubou silou, extrémně zvyšuje výpočetní čas. Heuristika tuto dobu výrazně snižuje, nicméně ne vždy nalézá 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).