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

  • MI-PAA, ZS 2011/12
  • Autor: Jaroslav Fibichr (fibicjar)

Zadání

  • Naprogramujte řešení problému batohu:
    1. metodou větví a hranic (B&B) tak, aby omezujícím faktorem byla hodnota optimalizačního kritéria. Tj. použijte ořezávání shora (překročení kapacity batohu) i zdola (stávající řešení nemůže být lepší než nejlepší dosud nalezené)
    2. metodou dynamického programování (dekompozice podle vah nebo podle cen)
    3. modifikujte tento program tak, aby pracoval s omezenou přesností zobrazení vah nebo cen (podle zvoleného přístupu k dynamickému programování) - aproximativní algoritmus.

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ů:

  • Shora - do batohu nepřidáváme věci, pokud byla překročena jeho kapacita
  • Zdola - do batohu nepřidáváme věci, pokud součet hodnot všech zbývajících věcí, které jsme ještě nezkusili přidat, a dosavadní ceny batohu je menší než doposud nejlepší nalezená celková cena. Tento přístup vyžaduje uchovávání “globální” nejlepší dosažené hodnoty ceny batohu a její případná aktualizace.

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

  • Intel Core2 Duo E7200@2.5GHz, 4 GB DDR2
  • Windows 7 64-bit

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ů:

  • snižuje výpočetní čas
  • zvyšuje relativní chyba

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:

  • v případě požadavku na optimálnost řešení je nejvhodnější algoritmus dynamického programování, ten dosahoval nejnižších časů výpočtu
  • pokud lze požadavek optimálnosti opustit, lze pomocí algoritmu FPTAS dosahovat ještě výrazně lepších časů, ovšem za cenu snižování přesnosti řešení

Zdrojový kód

school/fit/miric/jaramipaa/uloha3.txt · Last modified: 2018-06-21 19:48 (external edit)
CC Attribution-Noncommercial-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0