Zadání
Podle zadání jsem navrhnul a implementoval problém přelévání vody. Jednak tedy algoritmem, který řeší problém hrubou silou a za druhé heuristikou.
Řešení hrubou silou spočívá v prohledávání stavového prostoru do šířky (BFS).
Algoritmus expanduje aktualně zpracovávaný stav do všech stavů, které z něho mohou vzniknout. Tyto stavy přidá do kolekce (množiny). Do kolekce není možné vložit dva stejné stavy, tím je zabráněno vytváření cyklů při prohledávání. Pokud algoritmus najde stav, který je shodný s požadovaným stavem, ukončuje výpočet a vrací nalezený stav. Z nalezeného stavu se rekonstruje cesta, kterou vznikl. Prohledáváním do šířky je zajištěno nalezení nejkratší cesty.
// solves the pouring water problem private State solve(Instance instance) { // parameter deteremines the chosen algorithm instance.states = new StateQueue(StateQueue.Method.COMB); instance.states.add(instance.initialState); StateQueue states = instance.states; while (states.getCount() > 0) { State state = states.getFirst(); // solution found if (state.equals(instance.finalState)) { return state; } State newState; // expand State for (int i = 0; i < instance.itemCount; i++) { byte cap = instance.capacities[i]; // fill bucket if ((newState = state.fill(i, cap)) != null) { states.add(newState); } // empty bucket if ((newState = state.empty(i)) != null) { states.add(newState); } // pour bucket to some other for (int j = 0; j < instance.itemCount; j++) { if (j == i) { continue; } if ((newState = state.pour2(i, instance.capacities, j)) != null) { states.add(newState); } } } } // no solution return null; }
Při návrhu jsem se snažil nalézt takové pořadí zpracovávání procházených stavů, aby celkový počet navšívených stavů byl co nejmenší. Ostatní charakteristiky průchodu zůstala nezměněna. Jednotlivá řešení se tedy lišila pouze v použité implementaci rozhraní Comparator.
Heuristika spočívá v prioritním zpracovávání stavů, které mají nejvíce kbelíků naplněných na požadovaný objem. Tyto stavy mají zřejmě vyšší pravděpodobnost, že budou blíže konečnému stavu, než ostatní. Efekt této heuristiky by se měl zvyšovat s počtem kbelíků.
public class TotalFinalCmp implements Comparator<State>{ public int compare(State t1, State t2) { int count1 = 0; int count2 = 0; for (int i = 0; i < t1.instance.itemCount; i++) { count1 += (t1.buckets[i] == t1.instance.finalState.buckets[i]) ? 1 : 0; count2 += (t2.buckets[i] == t2.instance.finalState.buckets[i]) ? 1 : 0; } if (count1 < count2) { return 1; } else if (count1 > count2){ return -1; } return 0; } }
Heuristika spočívá v prioritním zpracovávání stavů, které mají nejmenší rozdíl v součtu rozdílů aktuálních objemů kbelíků oproti požadovanému objemu. Tedy pro každý kbelík spočítáme rozdíl objemů: | Vaktuální - Vpožadovaný | a takto spočtené hodnoty sečteme.
public class SumDiffCmp implements Comparator<State>{ public int compare(State t1, State t2) { int sum1 = 0; int sum2 = 0; for (int i = 0; i < t1.instance.itemCount; i++) { sum1 += Math.abs(t1.buckets[i] - t1.instance.finalState.buckets[i]); sum2 += Math.abs(t2.buckets[i] - t2.instance.finalState.buckets[i]); } if (sum1 < sum2) { return -1; } else if (sum1 > sum2){ return 1; } return 0; } }
Další možností je heuristiky zkombinovat. A to následujícím způsobem - podle heuristiky 2 (SumDiff) spočítám součet rozdílů od požadovaného stavu a od tohoto součtu odečtu konstantu vynásobenou počtem kbelíků, které již mají požadovaný objem. Konstantu volím v závislosti na celkovém objemu kbelíků a jejich počtu. Např. tedy průměrný objem kbelíku vynásobený nějakou konstantou. Tedy porovnávací hodnoty pro jednotlivé stavy byly vypoítány takto:
porovnávaná_hodnota = součet_rozdílů_od_požadovaného_stavu - konst * průměrný_objem_kbelíku * počet_hotových_kbelíků
Lépe je výpočet vidět na kódu Comparatoru:
public class CombCmp implements Comparator<State>{ public int compare(State t1, State t2) { int sum1 = 0; int sum2 = 0; int count1 = 0; int count2 = 0; int value1 = 0; int value2 = 0; for (int i = 0; i < t1.instance.itemCount; i++) { count1 += (t1.buckets[i] == t1.instance.finalState.buckets[i]) ? 1 : 0; count2 += (t2.buckets[i] == t2.instance.finalState.buckets[i]) ? 1 : 0; sum1 += Math.abs(t1.buckets[i] - t1.instance.finalState.buckets[i]); sum2 += Math.abs(t2.buckets[i] - t2.instance.finalState.buckets[i]); } int multiplier = (int) Math.floor(t1.instance.getSumCapacities() / (double)t1.instance.itemCount); value1 = sum1 - (int)Math.floor(1.8 * multiplier * count1); value2 = sum2 - (int)Math.floor(1.8 * multiplier * count2); if (value1 < value2) { return -1; } else if (value1 > value2){ return 1; } return 0; } }
V tabulce a grafech jsou znázorněny naměřené hodnoty, a to pedevším:
Výsledné hodnoty z heuristik jsem porovnal s BFS. Pro všechny dané instance i heuristiky jsem spočítal následující hodnoty:
Tyto hodnoty jsem dále zprůměroval (poslední řádek tabulky) přes všechny instance. Z těchto hodnot lze vyčíst, že úspora času (spotřebovaných stavů) byla pro všechny 3 heuristiky enormní, počet prošlých stavů byl roven mezi 1-10% původního BFS řešení. Tato úspora však byla vykoupena poměrně velkými odstupy v délce cesty nalezeného řešení, které bylo o 100-200% delší než u BFS, tedy u nejkratšího možného řešení.
Nejlépe ze všech heuristik vyšla heuristika 1 (TotalFinal). Dosáhla jak nejelpšího výsledku úspory času, tak i nejlepších řešení. Bohužel, kombinování 2 přístupů v heuristice 3 (Combination) se neukázala jako příliš dobrá, ačkoliv v některých instancích heuristiku 1 ve výsledcích předčila.
BFS | TotalFinal | SumDiff | Combination | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ID | n | states | lpath | states | lpath | sBFS/sH | ε(lpath) | states | lpath | sBFS/sH | ε(lpath) | states | lpath | sBFS/sH | ε(lpath) | ||||||||||||||||
11 | 5 | 8992 | 10 | 125 | 19 | 1.39% | 90% | 67 | 20 | 0.75% | 100% | 107 | 14 | 1.19% | 40% | ||||||||||||||||
12 | 5 | 8055 | 8 | 141 | 12 | 1.75% | 50% | 545 | 21 | 6.77% | 163% | 281 | 18 | 3.49% | 125% | ||||||||||||||||
13 | 5 | 8063 | 8 | 27 | 10 | 0.33% | 25% | 12 | 8 | 0.15% | 0% | 18 | 13 | 0.22% | 63% | ||||||||||||||||
14 | 5 | 179 | 3 | 6 | 4 | 3.35% | 33% | 6 | 4 | 3.35% | 33% | 5 | 4 | 2.79% | 33% | ||||||||||||||||
21 | 5 | 49350 | 16 | 385 | 42 | 0.78% | 163% | 6884 | 64 | 13.95% | 300% | 744 | 52 | 1.51% | 225% | ||||||||||||||||
22 | 5 | 40020 | 12 | 170 | 41 | 0.42% | 242% | 9164 | 45 | 22.90% | 275% | 671 | 61 | 1.68% | 408% | ||||||||||||||||
23 | 5 | 31387 | 11 | 307 | 30 | 0.98% | 173% | 6176 | 58 | 19.68% | 427% | 550 | 27 | 1.75% | 145% | ||||||||||||||||
24 | 5 | 790 | 5 | 15 | 8 | 1.90% | 60% | 449 | 12 | 56.84% | 140% | 55 | 12 | 6.96% | 140% | ||||||||||||||||
25 | 5 | 4722 | 7 | 104 | 19 | 2.20% | 171% | 626 | 24 | 13.26% | 243% | 97 | 38 | 2.05% | 443% | ||||||||||||||||
31 | 5 | 59199 | 14 | 153 | 28 | 0.26% | 100% | 1368 | 43 | 2.31% | 207% | 337 | 38 | 0.57% | 171% | ||||||||||||||||
32 | 5 | 58703 | 12 | 265 | 37 | 0.45% | 208% | 410 | 39 | 0.70% | 225% | 106 | 30 | 0.18% | 150% | ||||||||||||||||
33 | 5 | 43770 | 10 | 93 | 25 | 0.21% | 150% | 279 | 32 | 0.64% | 220% | 66 | 33 | 0.15% | 230% | ||||||||||||||||
34 | 5 | 1008 | 5 | 32 | 11 | 3.17% | 120% | 52 | 12 | 5.16% | 140% | 23 | 8 | 2.28% | 60% | ||||||||||||||||
35 | 5 | 7459 | 7 | 60 | 13 | 0.80% | 86% | 119 | 10 | 1.60% | 43% | 31 | 10 | 0.42% | 43% | ||||||||||||||||
36 | 5 | 27532 | 9 | 57 | 16 | 0.21% | 78% | 1415 | 37 | 5.14% | 311% | 144 | 29 | 0.52% | 222% | ||||||||||||||||
AVG | - | - | - | - | - | 1.21% | 117% | - | - | 10.21% | 188% | - | - | 1.72% | 167% |
Experiment ukázal, že využití heuristik může významně snížit počet stavů prošlých ve stavovém prostoru a významně tak zkrátit výpočetní čas. Toto zlepšení je však vykoupeno podstatně horší kvalitou výsledného řešení. To by v případě existenční úlohy (typu najdi nějaké řešení) vůbec nevadilo, v případě optimalizační (najdi co možná nejlepší řešení) už ano. Nejlepších výsledků dosáhla heuristika, která prioritně zpracovávala stavy s co nejvyšším počtem již hotových kbelíků, tedy takových, které již mají požadovaný stav. Druhá navržená heuristika zohledňující co možná nejbližší objem kbelíků požadovanému stavu nebyla tak účinná. Kombinování heuristik také nepřineslo očekávané zlepšení.