Table of Contents

Úloha 3 - Řešení problému batohu dynamickým programováním, metodou větví a hranic a aproximativním algoritmem

Zadání

Popis problému

Řešený problém se shoduje se zadáním v úkolu 1, tedy:

Je dán batoh s kapacitou W a monožina předmětů N, kde každý předmět má svou váhu wi a cenu vi. Ú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í

Naprogramoval jsem řešení zadanéúlohy popsanými metodami. Výsledky měření jsem shrnul do tabulek a grafů a v závěru shrnul nabyté poznatky.

Metoda větví a hranic

Metoda vychází z původního řešení hrubou silou. Úkolem je omezit průchod stavovým prostorem pouze na ty větve, které mají smysl, tedy takové, o kterých můžeme s jistotou říci, že nevedou k optimálnímu řešení. Lze provádět ořezávání dvou typů:

Složitost zůstává stejná jako u řešení hrubou silou (O(2n)), metoda by však měla být výrazně rychlejší. Nalezne vždy optimální řešení.

    private Solution solveBaB(Instance instance, Solution solution, int index) {
 
        if (index < 0) {
            return solution;
        }
 
        if (instance.items[index].weight > solution.remCapacity) {
            return solveBaB(instance, solution, index - 1);
        }
        if (instance.remValues[index] + solution.value < instance.bestSolution.value) {
            return solution;
        }
 
        Solution s1 = solution;
        Solution s2 = solution.clone();
 
        s2.value += instance.items[index].value;
        s2.remCapacity -= instance.items[index].weight;
        s2.taken[index] = 1;
 
        s1.remValue -= instance.items[index].value;  // BaB
        s2.remValue -= instance.items[index].value;  // BaB
 
        s1 = solveBaB(instance, s1, index - 1);
        s2 = solveBaB(instance, s2, index - 1);
 
        solution = s1.value < s2.value ? s2 : s1;
        instance.bestSolution = solution;           // BaB
        return solution;
    }

Dynamické programování

U dynamického programování dekomponujeme problém na jeho podproblémy. Zvolil jsem variantu dekompozice podle kapacity batohu. Řešení každého vyřešeného podproblému je opět nutné ukládat do “globálního” dvourozměrného pole, které uchovává dosažené hodnoty pro jednotlivé dvojice W (kapacita), i (počet předmětů). V případě, že je podproblém již vyřešen, vezme se tato hodnota z tohoto pole namísto opětovného počítání. Algoritmus se skládá z obou fází - dopřednou i zpětnou. Nepředpočítáváme tedy řešení pro podproblémy, které nejsou pro hledané řešení potřeba.

Složitost tohoto algoritmu je pseudopolynomiální, závisí totiž i na jiném parametru než je počet prvků.

    private Solution solveDynamic(Instance instance, Solution solution, int index, int weight) {
 
        if (weight < 0) {
            Solution s = new Solution(instance);
            s.value = Integer.MIN_VALUE;
            return s;
        }
        if (index < 0) {
            return new Solution(instance);
        }
 
        // return already known
        if (instance.solMatrix[index][weight] != null){
            return instance.solMatrix[index][weight].clone();   // pomocné pole
        }
 
        Solution s1 = solution;
        Solution s2 = solution.clone();
 
        s1 = solveDynamic(instance, s1, index - 1, weight );
        s2 = solveDynamic(instance, s2, index - 1, weight - instance.items[index].weight);
 
        s2.value += instance.items[index].value;
        s2.remCapacity -= instance.items[index].weight;
        s2.taken[index] = 1;
 
        Solution result  = s1.value < s2.value ? s2 : s1;
        instance.solMatrix[index][weight] = result.clone();  // pomocné pole
        return result;
    }

Aproximativní algoritmus (FPTAS)

Aproximativní algoritmus je založen na předchozím dynamickém, tedy na verzi s dekompozicí podle vah. Pomocí FPTAS lze řešit problém se stanovenou přesností a zvyšování přesnosti zvyšuje složitost ne více než polynomiálně. Aproximativní algoritmes se tedy liší se tím, že před samotným spuštěním dynamického algoritmu “zaokrouhlí” kapacitu batohu i jednotlivé hmotnosti vkládaných předmětů. Toho dosáhneme zanedbáním nejnižších bitů těchto hodnot o zvolenou hodnotu.

        ...
        instance.capacity = instance.capacity >> instance.bits;
        for (int i = 0; i < instance.itemCount; i++) {
            instance.items[i].weight = instance.items[i].weight >> instance.bits;
        }
        instance.solMatrix = new Solution[instance.itemCount][instance.capacity+1];   // pomocné pole
 
        Algorithm algorithm = new Dynamic();
        Solution result = algorithm.run(instance, solution);
 
        return result;

Výsledky měřění

Konfigurace HW a OS

Srovnání výpočetních časů

V následující tabulce a grafu jsou znázorněny výsledky měření jednotlivých algoritmů na daných počtech předmětů a instancí. Je zřetelné, že složitost jednotlivých algoritmů se se zvyšujícím počtem prvků v batohu se je jednoznačně určná uspořádáním:

BruteForce > Branch&Bound > Dynamic > FPTAS(2)

Jediný algoritmus, který nevrací zaručeně optimální řešení je FPTAS, tudíž nejlepší z implementovaných algoritmů, který dává optimální řešení je dynamické programování.

Pozn.: Měření pro velmi krátké časy (< 1 ms) bylo opakováno vícenásobně a následně průměrováno pro dosažení kvalitnějších výsledků.

nBruteForceBranchAndBoundDynamicFPTAS (2)
48,93E-048,33E-045,13E-035,29E-03
106,03E-021,50E-021,51E-015,36E-02
153,42E+005,26E-027,03E-011,98E-01
201,19E+021,86E+001,57E+004,20E-01
224,72E+021,02E+011,92E+005,21E-01
254,43E+035,80E+013,13E+008,25E-01
271,88E+041,44E+023,50E+009,98E-01
301,77E+057,68E+024,84E+001,22E+00
327,55E+051,45E+038,44E+001,80E+00
356,40E+063,06E+041,10E+012,57E+00
37 5,02E+049,17E+002,88E+00
40 6,42E+051,53E+013,77E+00

Závislost chyby a výpočetního času aproximativního algoritmu na zvolené přesnosti zobrazení

Podle očekávání se se zvyšujícím počtem zanedbaných bitů:

V tabulce je možné pozorovat výsledky měření pro různé hodnoty zanedbaných bitů. Algoritmus dynamického programování, ze kterého metoda vychází je rovna FPTAS(0). Ta vrací vždy optimální řešení, proto je relativní chyba pro jakýkoliv počet předmětů roven 0. Relativně vysoká míra relativních chyb je z velké části dána nízké “granularitě” vstupních dat. To platí především pro nízká N. V případě zanedbání 3 a více bitů relativní chyba téměř vždy překračuje 5%. Tím například výrazně překračuje chybovost i heuristiky implementované v úkolu č.1.

Z hodnot i grafu naměřených časů lze vyčíst, že čas klesá s počtem zanedbaných bitů exponenciálně. Například pro největší instance se 40 předměty dosáhla v průměru pod 4 ms.

Výpočetní čas (ms)

nDynamicFPTAS(1)FPTAS(2)FPTAS(3)FPTAS(4)FPTAS(5)
45,11E-036,07E-035,06E-034,60E-034,24E-033,53E-03
101,19E-017,44E-024,01E-022,29E-021,42E-029,93E-03
155,79E-012,93E-011,53E-017,35E-023,43E-021,66E-02
201,64E+008,04E-013,82E-011,86E-018,50E-023,47E-02
222,00E+001,03E+004,87E-012,33E-011,00E-014,12E-02
253,30E+001,71E+008,41E-014,01E-011,58E-016,14E-02
274,20E+002,25E+001,09E+005,20E-012,17E-017,72E-02
305,48E+002,90E+001,43E+006,88E-012,92E-019,89E-02
326,63E+003,43E+001,57E+006,59E-012,64E-011,02E-01
351,00E+015,24E+002,07E+008,96E-014,55E-011,56E-01
371,11E+015,87E+002,72E+001,31E+005,54E-011,84E-01
401,36E+017,08E+003,23E+001,59E+006,58E-012,23E-01

Relativní chyba

nDynamicFPTAS(1)FPTAS(2)FPTAS(3)FPTAS(4)FPTAS(5)
400,78%5,33%6,09%18,00%28,89%
1001,20%3,91%6,60%13,82%21,04%
1500,65%1,55%2,65%5,10%5,22%
2001,09%2,64%5,46%9,69%10,28%
2201,05%3,02%6,82%12,20%13,44%
2501,21%3,17%6,31%11,39%11,90%
2700,95%2,78%5,46%10,11%10,63%
3001,09%3,03%6,15%11,33%11,47%
3200,60%2,00%5,91%8,29%8,60%
3501,16%2,77%6,60%11,30%11,30%
3700,87%2,32%4,90%9,90%10,05%
4000,97%2,72%5,71%11,21%11,48%

Závěr

Naimplementoval jsem podle zadání různé metody řešení zadaného problému batohu. Z měření vyplynulo:

Zdrojový kód

u3_jaroslav_fibichr_2011.zip