Problémy a algoritmy
Transkript
Problémy a algoritmy Semestrální práce Vladimír Macek, 5. ročník kombinované studium FEL ČVUT Obsah Úloha I. – Problém batohu 0/1.......................................................................................2 I.1 Zadání I.2 Základ problému I.3 Řešení hrubou silou I.4 Řešení heuristikou podle poměru cena/váha I.5 Závěr Úloha II. - Problém přelévání vody..................................................................................4 II.1 Zadání II.2 Řešení II.3 Výsledky a zdrojový kód II.4 Závěr Úloha III. - Problém batohu 0/1 dalšími metodami...............................................................8 III.1 Zadání III.2 Řešení metodou větví a hranic III.3 Řešení metodou dynamického programování III.4 Řešení heuristikou cena/váha s testem nejcennější věci III.5 Závěr Úloha IV. - Experimentální hodnocení.............................................................................11 IV.1 Počet navštívených stavů IV.2 Generátor instancí IV.2.1 Závislost: Maximální váha věci → počet navštívených stavů IV.2.2 Závislost: Maximální cena věci → počet navštívených stavů IV.2.3 Kapacita batohu / celková váha věcí → počet navštívených stavů IV.3 Závěr Úloha V. - Splnitelnost booleovské formule pomocí GA........................................................14 V.1 Zadání V.2 Řešení V.3 Vstupní data V.4 Příklad výsledku (V=C=1000, K=3) Vysvětlivky k výstupu V.5 Implementace Zdrojový kód V.6 Závěr Testující konfigurace: • AMD Athlon XP 2200+, 1800MHz, chipset nVidia nForce 2, 32bitová architektura • 3563.52 bogomips (kernel 2.6.8) • OS Linux Debian Sarge • překladač gcc 3.3.5 s optimalizací -O3 • interpretr jazyka Python 2.3.5 Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 1/20 Úloha I. – Problém batohu 0/1 I.1 Zadání • • Naprogramujte řešení 0/1 problému batohu hrubou silou. Na zkušebních datech pozorujte závislost výpočetního času na n. Naprogramujte řešení 0/1 problému batohu heuristikou podle poměru cena/váha. Pozorujte • závislost výpočetního času na n, • průměrné zhoršení proti exaktní metodě, • maximální chybu. Měření nechť je orientační - doba chodu jednotlivých testů nemusí překračovat několik málo minut. I.2 Základ problému • • • • Je dáno: celé číslo n (počet věcí), celé číslo M (kapacita batohu), konečná množina V = {v1, v2, ... ,vn} konečná množina C = {c1, c2, ... ,cn} (hmotnosti věcí), (ceny věcí). Nejznámější varianta je optimalizační. Pokud se mluví o „problému batohu“ bez bližšího určení, většinou se rozumí tato verze. Zkonstruujte množinu X= {x1, x2, ... ,xn}, kde každé xi je 0 nebo 1, tak, aby platilo: 1. v1.x1 + v2.x2 + ... + vn.xn <= M 2. c1.x1 + c2.x2 + ... + cn.xn bylo maximální (batoh nebyl přetížen), (cena věcí v batohu byla maximální). I.3 Řešení hrubou silou Řešení problémů hrubou silou klade nejnižší nároky na uvažování programátora. Stavový prostor úlohy se prohledává postupně a zde má velikost 2n, neboť je třeba otestovat všechny kombinace umístění nebo neumístění věcí do batohu. I v případě, kdy jsme naše řešení ochudili alespoň o prohledávání těch irelevantních stavů, které překročí kapacitu batohu, časová složitost rychle roste. Do hodnoty 25 včetně byla doba jedné instance T počítána jako padesátina z celé sady o 50 instancích. U vyšších četností se měřila pouze doba první instance ze zadaných dat. n T [ms] n T [ms] 4 <1 27 2495 10 <1 30 22098 15 2 32 100772 20 19 35 710108 22 74 37 příliš 25 602 40 příliš Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 2/20 I.4 Řešení heuristikou podle poměru cena/váha Předpokládáme, že věc, která má nejlepší poměr cena/váha a zároveň ji ještě batoh unese, bude s největší pravděpodobností součástí výsledného řešení. Výpočet řešení takto velmi zjednodušíme, avšak může být zatížen velkými chybami. Velikost této chyby není shora omezena a závisí na konkrétní instanci. Postup řešení: Věci budou seřazeny sestupně podle poměru cena/váha. Pak budou postupně vkládány do batohu až do naplnění jeho kapacity. Časovou složitost zde již z výsledků vypouštíme, protože rychlost výpočtu je závislá na optimálním řadicím algoritmu QuickSort a je velice krátká. n Optimálních řešení [%] Maximální chyba [%] Průměrné zhoršení [%] 4 72 68,37 7,19 10 46 15,75 2,93 15 60 5,64 0,80 20 40 6,12 1,27 22 30 8,75 1,43 25 32 5,42 1,26 27 36 3,87 0,91 30 16 3,33 0,96 32 28 3,61 0,69 35 28 3,82 0,95 37 32 2,20 0,61 40 24 2,57 0,62 I.5 Závěr Z uvedeného je zřejmé, že existují reálné problémy, které jsou a i nadále budou hrubou silou v podstatě neřešitelné. Pro jejich vyřešení je potřeba zvolit jinou cestu, například vhodnou heuristiku. To však vždy vnáší nejistotu správného řešení. Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 3/20 Úloha II. - Problém přelévání vody II.1 Zadání K dispozici je určitý počet kýblů o různých objemech, neomezený zdroj vody a nenasytný kanál. Kýble mají libovolný, známý počáteční obsah. Úkolem je dosáhnout ve všech kýblech předepsaného stavu vody. Možné úkony: • naplnění kýblu po okraj, • vylití obsahu kýblu do kanálu, • přelití vody z jednoho kýblu do druhého tak, aby druhý kýbl nepřetekl. Navrhněte a implementujte heuristiku řešící tento problém. Heuristiku otestujte na zadaných příkladech (viz zdrojový text). II.2 Řešení K řešení byla použita metoda prohledávání do šířky o třech typech hledání: 0. Neinformované (hrubou silou), 1. Informované s dvoustavovou heuristikou (+2 body za správně naplněný kýbl), 2. Informované s třístavovou heuristikou (jako předchozí, navíc +1 bod za vhodný objem v jiném než správném kýblu). Vybírání z fronty se dělo tak, že neinformovaný typ hledání odebírá první prvek, informované typy také, ale předtím si frontu seřadí sestupně podle ohodnocení stavů. Nově expandovaný stav nebyl vkládán do fronty v případě, že už byl někdy dříve expandován. Ke zjištění této skutečnosti bylo použito asociativní pole. Výpočet byl ukončen při prvním dosažení cílového stavu. Tato situace byla zjišťována nalezením stavu ohodnoceném dvojnásobkem počtu kýblu (= všechny kýble mají cílový objem, hodnocení 2). Implementace byla provedena v jazyce Python, a celý výpočet trval přibližně 3m20s. II.3 Výsledky a zdrojový kód Na následujících stranách. Vysvětlivky k výsledkům: SType – Typ hledání, Found – řešení nalezeno, Explored – počet zkoumaných stavů, Expanded – celkový počet expandovaných stavů ve frontě, Depth – hloubka řešení ve stavovém stromě, Time – čas řešení v milisekundách. II.4 Závěr Lze prohlásit některá tvrzení: • Neinformované hledání do šířky z principu vždy najde nejkratší cestu, • delší nalezená cesta informovaných metod stojí zlomek času, • třístavová heuristika nabízí obvykle lepší výsledky než dvoustavová (méně informovaná), • počet navštívených a expandovaných stavů se pro jednotlivé instance může dosti lišit, • heuristika nemusí poskytovat nejkratší cestu nebo vůbec selže, protože je příliš jednoduchá. Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 4/20 SType Found Explored Expanded Depth Time Problem: 2 1 0 {'volume': (14, 10, 6, 2, 8), 'state': [0, 0, 1, 0, 0], 'goal': (12, 6, 4, 1, 8)} True 46 598 18 41.1 True 123 1211 14 151.2 True 8992 8992 11 4265.1 Problem: 2 1 0 {'volume': (14, 10, 6, 2, 8), 'state': [0, 0, 1, 0, 0], 'goal': (14, 4, 5, 0, 4)} True 47 493 14 35.9 True 88 743 12 71.2 True 8055 8918 9 3787.3 Problem: 2 1 0 {'volume': (14, 10, 6, 2, 8), 'state': [0, 0, 1, 0, 0], 'goal': (12, 6, 6, 2, 4)} True 19 184 11 11.2 True 21 183 11 11.5 True 8063 8919 9 3803.9 Problem: 2 1 0 {'volume': (14, 10, 6, 2, 8), 'state': [0, 0, 1, 0, 0], 'goal': (0, 2, 1, 2, 8)} True 6 56 5 2.8 True 6 56 5 3.3 True 179 848 4 77.5 Problem: 2 1 0 {'volume': (15, 12, 8, 4, 6), 'state': [0, 0, 0, 0, 0], 'goal': (5, 5, 5, 0, 1)} True 685 4936 21 2093.4 True 554 4117 29 1319.2 True 49350 49350 17 30376.2 Problem: 2 1 0 {'volume': (15, 12, 8, 4, 6), 'state': [0, 0, 0, 0, 0], 'goal': (12, 1, 3, 4, 5)} True 233 2307 34 382.1 True 303 2685 34 536.1 True 40020 45082 13 20731.1 Problem: 2 1 0 {'volume': (15, 12, 8, 4, 6), 'state': [0, 0, 0, 0, 0], 'goal': (11, 1, 3, 4, 5)} True 254 2449 36 423.8 True 235 2091 21 362.0 True 31387 38912 12 16095.8 Problem: 2 1 0 {'volume': (15, 12, 8, 4, 6), 'state': [0, 0, 0, 0, 0], 'goal': (3, 12, 4, 0, 6)} True 20 189 12 11.4 True 17 133 9 8.4 True 790 2266 6 363.9 Problem: 2 1 0 {'volume': (15, 12, 8, 4, 6), 'state': [0, 0, 0, 0, 0], 'goal': (2, 0, 4, 3, 6)} True 36 406 16 25.1 True 127 1132 20 122.1 True 4722 9698 8 2212.9 Problem: 2 1 0 {'volume': (14, 10, 12, 3, 8), 'state': [0, 0, 0, 0, 0], 'goal': (13, 9, 12, 2, 7)} True 70 758 18 65.6 True 237 2017 18 336.6 True 59199 59202 15 31839.8 Problem: 2 1 0 {'volume': (14, 10, 12, 3, 8), 'state': [0, 0, 0, 0, 0], 'goal': (1, 5, 5, 3, 4)} True 290 3021 28 612.8 True 415 3834 27 1016.9 True 58703 59193 13 31486.7 Problem: 2 1 0 {'volume': (14, 10, 12, 3, 8), 'state': [0, 0, 0, 0, 0], 'goal': (0, 9, 6, 3, 1)} True 66 658 26 50.5 True 195 1682 26 237.3 True 43770 53898 11 26749.9 Problem: 2 1 0 {'volume': (14, 10, 12, 3, 8), 'state': [0, 0, 0, 0, 0], 'goal': (12, 0, 12, 0, 2)} True 17 141 9 8.7 True 23 190 9 11.9 True 1008 3209 6 469.1 Problem: 2 1 0 {'volume': (14, 10, 12, 3, 8), 'state': [0, 0, 0, 0, 0], 'goal': (7, 3, 7, 0, 0)} True 60 674 10 50.1 True 79 614 11 55.8 True 7459 15864 8 3626.6 Problem: 2 1 0 {'volume': (14, 10, 12, 3, 8), 'state': [0, 0, 0, 0, 0], 'goal': (7, 0, 7, 0, 7)} True 59 444 12 35.8 True 93 655 12 62.8 True 27532 40434 10 14502.9 Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 5/20 #!/usr/bin/python if self.verbose_level >= 3: print "Expanding: %s" % state import time # Instance vstupnich dat -- po radcich jednotlive problemy # volume - objem kbeliku # state - naplnenost jednotlivych kbeliku na pocatku # goal - cilovy stav jednotlivych kbeliku INPUT_DATA = (\ {'volume': (14,10,6,2,8), 'state': [0,0,1,0,0], 'goal': (12,6,4,1,8)}, {'volume': (14,10,6,2,8), 'state': [0,0,1,0,0], 'goal': (14,4,5,0,4)}, {'volume': (14,10,6,2,8), 'state': [0,0,1,0,0], 'goal': (12,6,6,2,4)}, {'volume': (14,10,6,2,8), 'state': [0,0,1,0,0], 'goal': (0,2,1,2,8)}, {'volume': (15,12,8,4,6), 'state': [0,0,0,0,0], 'goal': (5,5,5,0,1)}, {'volume': (15,12,8,4,6), 'state': [0,0,0,0,0], 'goal': (12,1,3,4,5)}, {'volume': (15,12,8,4,6), 'state': [0,0,0,0,0], 'goal': (11,1,3,4,5)}, {'volume': (15,12,8,4,6), 'state': [0,0,0,0,0], 'goal': (3,12,4,0,6)}, {'volume': (15,12,8,4,6), 'state': [0,0,0,0,0], 'goal': (2,0,4,3,6)}, {'volume': (14,10,12,3,8), 'state': [0,0,0,0,0], 'goal': (13,9,12,2,7)}, {'volume': (14,10,12,3,8), 'state': [0,0,0,0,0], 'goal': (1,5,5,3,4)}, {'volume': (14,10,12,3,8), 'state': [0,0,0,0,0], 'goal': (0,9,6,3,1)}, {'volume': (14,10,12,3,8), 'state': [0,0,0,0,0], 'goal': (12,0,12,0,2)}, {'volume': (14,10,12,3,8), 'state': [0,0,0,0,0], 'goal': (7,3,7,0,0)}, {'volume': (14,10,12,3,8), 'state': [0,0,0,0,0], 'goal': (7,0,7,0,7)} ) TYPE_UNINFORMED = 0 TYPE_HEURISTIC1 = 1 TYPE_HEURISTIC2 = 2 # Prohledavani bez heuristiky # Prohledavani s dvoustavovou heuristikou # Prohledavani s tristavovou heuristikou class BucketSolver: def __init__(self, problem_instance, search_type, verbose_level = 0): self.verbose_level = verbose_level self.search_type = search_type self.num = len(problem_instance['volume']) # Pocet kbeliku self.closed = {} # Jiz jednou expandovane stavy nepridavame self.queue = [] self.explored = 0 self.expanded = 0 problem_instance['depth'] = 0 # Hloubka 1. stavu self.push_state(problem_instance) # Pocatecni stav do fronty self.goal_rank = self.num * 2 # Hodnoceni ciloveho stavu if self.verbose_level >= 5: print "Queue dump:" for s in self.queue: print "\t%s" % s stopwatch = (time.time() - stopwatch) * 1000 if self.verbose_level: print "%-9d%-5s%10d%11d%8d%9.1f" % (self.search_type, found, \ self.explored, self.expanded, state['depth'], stopwatch) if self.verbose_level >= 2: print "State:", state def dequeue(self): """ Vybere z fronty kandidata. Neinformovana verze proste prvniho. Informovana si predtim frontu sestupne seradi podle ohodnoceni. """ if self.search_type != TYPE_UNINFORMED: self.queue.sort(lambda x, y: y['rank'] - x['rank']) return self.queue.pop(0) def expand(self, state): """ Pridame do fronty vsechny stavy, do kt. se lze dostat z aktualniho. """ for i in range(self.num): # i-ty kybl vyprazdneny emptied_state = state.copy() emptied_state['state'] = emptied_state['state'][:] emptied_state['state'][i] = 0 self.push_state(emptied_state) # i-ty kybl naplneny filled_state = state.copy() filled_state['state'] = filled_state['state'][:] filled_state['state'][i] = filled_state['volume'][i] self.push_state(filled_state) def search(self): stopwatch = time.time() while self.queue: state = self.dequeue() self.explored += 1 found = (state['rank'] == self.goal_rank) if found: break # Cil dle hodnoceni self.expand(state) Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 6/20 for j in range(self.num): if i != j: # i-ty kybl prelity do j-teho poured_state = state.copy() poured_state['state'] = poured_state['state'][:] # objem, ktery muzeme prelit volume = min(poured_state['state'][i], \ poured_state['volume'][j] - poured_state['state'][j]) if volume > 0: poured_state['state'][i] -= volume poured_state['state'][j] += volume self.push_state(poured_state) def solve(problems, verbose_level = 0): if verbose_level: print "SType Found Explored Expanded Depth Time" for problem in problems: if verbose_level: print "\nProblem: %s" % (problem) for search_type in (TYPE_HEURISTIC2, TYPE_HEURISTIC1, TYPE_UNINFORMED): bs = BucketSolver(problem, search_type, verbose_level) bs.search() del bs if __name__ == '__main__': solve(INPUT_DATA, verbose_level = 1) def push_state(self, state): """ Pridavame novy expandovany stav s vypoctenym ohodnocenim. Pokud tento stav uz nekdy byl pridavan, nepridavame ho podruhe. """ state_str = str(state['state']) if state_str not in self.closed: self.closed[state_str] = True state['rank'] = self.rank(state) self.expanded += 1 state['depth'] += 1 self.queue.append(state) def rank(self, state): """ Tristavova heuristicka funkce: 1 bod za to, je-li pozadovany objem v jakekoli nadobe 2 body za to, je-li pozadovany objem ve spravne nadobe Dvoustavova je bez hodnoceni "1 bod". """ points = 0 for i in range(self.num): goal = state['goal'][i] if state['state'][i] == goal: points += 2 elif (goal in state['state']) \ and (self.search_type == TYPE_HEURISTIC2): points += 1 return points Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 7/20 Úloha III. - Problém batohu 0/1 dalšími metodami III.1 Zadání • Naprogramujte řešení 0/1 problému batohu metodou větví a hranic tak, aby omezujícím faktorem byla hodnota optimalizačního kritéria. • Naprogramujte řešení 0/1 problému batohu nebo alespoň 0/1 exaktního problému batohu bez cen metodou dynamického programování. • Naprogramujte řešení 0/1 problému batohu heuristikou podle poměru cena/hmotnost s testem nejcennější věci. III.2 Řešení metodou větví a hranic Tento algoritmus, je téměř shodný řešení hrubou silou z odstavce I.3. Procházíme stavový prostor rekurzivně do hloubky. Rozdíl se značným dopadem na efektivitu je ten, že před přidáním stavu testujeme, zda podstrom, který se jím může začít, vůbec může vést na řešení lepší než to dosud nalezené. Jedno takové kritérium bylo přidáno už v odstavci I.3 (test překročení kapacity batohu). Druhé přidáme nyní: Budeme zkoumat, zda součet cen zbývajících věcí, které můžeme v dané chvíli přidat, může překročit průběžně vylepšovanou cenu. Metoda dosahuje na optimální řešení za vynechání značné části stavového prostoru. n T [ms] T/50 ø počet kroků 4 <1 <1 10 10 1 <1 180 15 1 <1 361 20 27 <1 6821 22 70 1 26562 25 104 2 74717 27 139 2 95417 30 658 13 451564 32 1176 23 755697 35 3096 61 2035667 37 3504 70 2286085 40 45270 905 25525694 Vidíme, že oproti metodě I.3 dosáhneme na největší z problémů (velikost 40) již v rozumnějším čase. Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 8/20 III.3 Řešení metodou dynamického programování Dynamické programování se vyznačuje tím, že úlohy dekomponuje na menší nezávislé a do pomocných datových struktur uchovává jejich výsledky. Výsledek původní úlohy se pak komponuje z mezivýsledků. V ideálním případě se nic nepočítá vícekrát. Nevýhodou je velká spotřeba paměti, která může při jistých úlohách metodu diskvalifikovat. Nepříjemné je, že spotřebovaná paměť je silně závislá na datech. Zde bylo použito dvourozměrné pole VAHY[cena, věc] pro ukládání mezisoučtů hmotností věcí řešené instance. Počáteční nastavení: VAHY[0, 0] ← 0, VAHY[c, 0] ← nekonečno (pro c > 0). Pole se pak plní podle tohoto předpisu: VAHY[c, v+1] ← min( VAHY(c, v), VAHY(c – cv) + vv ) 0 <= c <= celková cena, 0 <= v <= počet věcí – 1 cv je cena v-té věci, vv je váha v-té věci Pak stačí v poli odshora (najde nejvyšší cenu) vyhledat první stav, který vyhovuje omezující podmínce (hodnota v poli je menší nebo rovna kapacitě batohu). n T [ms] T/50 4 11 <1 10 87 1 15 112 2 20 212 4 22 258 5 25 336 6 27 386 7 30 499 9 32 544 10 35 672 13 37 716 14 40 879 17 Stejně jako předchozí algoritmus větví a mezí poskytuje i tento optimální řešení. Časová složitost je polynomiální, což vykupuje paměťovou náročnost. Doba běhu celého testu byla asi 5 sekund. Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 9/20 III.4 Řešení heuristikou cena/váha s testem nejcennější věci Metoda je pouze malým rozšířením heuristické metody z odstavce I.4. Po skončení se testuje, zda jediná nejcennější věc, byla-li by v batohu obsažena sama (a vejde-li se tam), neposkytne lepší (dražší výsledek). Zesložitění je lineární a algoritmus se nijak podstatně nezpomalí. Zato má šanci se přiblížit optimálnímu řešení. n Optimálních řešení [%] Maximální chyba [%] Průměrné zhoršení [%] 4 76 35,28 4,26 10 46 15,75 2,93 15 60 5,64 0,80 20 40 6,12 1,27 22 30 8,75 1,43 25 32 5,42 1,26 27 36 3,87 0,91 30 16 3,33 0,96 32 28 3,61 0,69 35 28 3,82 0,95 37 32 2,20 0,61 40 24 2,57 0,62 Test nejcennější věci se vyplatil pouze v nejmenším souboru, kdy čtyřikrát pomohl k optimálnímu výsledku. III.5 Závěr Hledáme-li optimální řešení problému v rozumném čase a máme dost operační paměti, nabízí se použití dynamického programování. Omezují-li nás paměťové nároky, je zde metoda větví a hranic. Jde-li nám o co nejrychlejší „nástin“ řešení, vybereme si některou z heuristických metod. Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 10/20 Úloha IV. - Experimentální hodnocení IV.1 Počet navštívených stavů Jedná se o důležitý údaj sloužící ke srovnání algoritmů. Je nezávislý na výpočetní síle počítače, což je jeho výhoda. Jako v předešlých tabulkách, jedná se i zde o aritmetické průměry, přepočteno z padesáti zadaných instancí na jednu. n Hrubá síla Větve a hranice Heuristika cena/váha Dynamické programování 4 9 10 2 2023 10 604 180 7 13526 15 31667 361 13 28188 20 946334 6821 15 50659 22 3414819 26562 16 61419 25 28816961 74717 18 81435 27 123921277 95417 20 92540 30 1002829997 451564 22 118089 32 mnoho 755697 24 129374 35 mnoho 2035667 26 156097 37 mnoho 2286085 27 169316 40 mnoho 25525694 29 203263 IV.2 Generátor instancí Následující část popisuje výsledky experimentů prováděných s parametrizovatelným generátorem instancí pro problém batohu. Byl zkoumán vliv maximální váhy věcí, maximální ceny věcí a vliv poměru váhy batohu vůči váze věcí. Poznámka: Metoda hrubou silou zahrnuje již dříve zmíněnou optimalizaci na maximální kapacitu. Parametry experimentů: Počet instancí 50 Počet předmětů 17 Kapacita batohu / celková váha věcí 0,7 Maximální váha věcí 100 Maximální cena věci 300 Exponent granularity 1 Poměr malých a velkých věcí 1 Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 11/20 IV.2.1 Závislost: Maximální váha věci → počet navštívených stavů n Hrubá síla Větve a hranice Heuristika cena/váha Dynamické programování 50 121005 2480 14 45328 100 120923 2400 14 43267 150 120916 2571 14 44214 200 121276 2839 14 41626 250 120956 2806 14 43938 300 121124 2213 13 42785 350 120861 2145 14 44461 400 120895 1956 14 42993 450 120391 2250 14 41938 500 121025 2722 14 42991 U žádné z metod není patrná prokazatelná závislost počtu navštívených stavů na velikosti instance. IV.2.2 Závislost: Maximální cena věci → počet navštívených stavů n Hrubá síla Větve a hranice Heuristika cena/váha Dynamické programování 50 120736 2420 13 7420 100 120797 2315 14 14928 150 121120 2505 14 22265 200 121190 2569 14 29730 250 120855 2244 14 35589 300 120583 2558 14 44186 350 120861 2530 14 50084 400 121080 2871 15 59244 450 121168 2530 14 63695 500 121110 2645 14 70830 Zde je patrná lineární závislost počtu navštívených stavů u poslední metody v tabulce. Fakt vychází z podstaty algoritmu založeném na dynamickém programování. Velikost jeho stavového prostoru je dána součinem počtu věcí a součtu jejich cen. Je zde přímá souvislost. Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 12/20 IV.2.3 Kapacita batohu / celková váha věcí → počet navštívených stavů Kapacita / váha Hrubá síla Větve a hranice Heuristika cena/váha Dynamické programování 0,1 155 572 5 44330 0,2 1980 3331 7 43994 0,3 10012 7279 8 43147 0,4 31232 10669 10 43698 0,5 65614 7960 11 41719 0,6 99239 4846 12 43332 0,7 120905 2689 14 43717 0,8 129144 955 15 43284 0,9 130922 354 16 43427 Zde je patrných závislostí několik. Skutečné řešení hrubou silou by nemělo být na zkoumaném poměru závislé. Je zřejmé, že vykázaná silná závislosti nemá příčinu v ničem jiném než v jediné optimalizaci této metody (stav není prozkoumáván, pokud by znamenal překročení maximální kapacity batohu). Metoda větví a hranic obsahuje stejnou optimalizaci, která se projevuje zejména pro nižší hodnoty poměru. Dále však obsahuje také další optimalizační metodu, díky níž se zbytečně neprocházejí stavy, které již nemohou vést k cenově lepšímu řešení než to dosud nejlepší nalezené. Závislost napovídá normální (Gaussovo) rozdělení se středem někde mezi hodnotami 0,4 a 0,5. Heuristická metoda vykazuje lineární závislost a dynamické programování se zdá být na poměru nezávislé. IV.3 Závěr I když jsme v průběhu experimentu měnili vždy pouze jeden parametr generátoru, bylo možné ilustrovat některé zajímavé závislosti, které vycházejí z použitých metod řešení. Zároveň je však na místě opatrnost, chceme-li dělat obecnější závěry, protože náš výběr nemusel být reprezentativní. Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 13/20 Úloha V. - Splnitelnost booleovské formule pomocí GA V.1 Zadání Je dána booleovská formule F proměnných X = (x1, x2, ... , xV) v konjunktivní normální formě (tj. součin součtů). Dále jsou dány celočíselné kladné váhy proměnných W = (w1, w2, ... , wV). Najděte ohodnocení Y = (y1, y2, ... , yV) proměnných x1, x2, ... , xV tak, aby F(Y) = 1 a součet vah proměnných, které jsou ohodnoceny jedničkou, byl maximální. Je přípustné omezit se na formule, z nichž každá disjunkce má právě K = 3 literály (problém 3-SAT). Problém řešte některou z pokročilých lokálních heuristik (simulované ochlazování, genetické algoritmy, tabu prohledávání). V.2 Řešení Genetické algoritmy (GA) patří do skupiny stochastických neúplných (nemusíme se dobrat k optimálnímu výsledku) metod, jejichž prohledávací schopnosti jsou posíleny modelováním dědičnosti a posilováním kladných vlastností. Vývoj řešení probíhá v tzv. generacích, z nichž každá nese množinu jedinců, jejíž četnost se nazývá velikost populace (pop_size). První řídicí parametr. Počet generací (dobu běhu celého algoritmu) omezujeme různými druhy podmínek, z nichž pouze jednou je nalezení optimálního řešení, na rozdíl od metod úplných. Míra vhodnosti každého jedince pro šlechtění je vyjádřena jediným vypočítaným číslem nazývaným zdatnost (fitness). K jeho určení se používá toliko jedinec v daném okamžiku. GA se obvykle skládá z těchto fází: • • • inicializace vyhodnocení cyklus <zastavovací podmínka>: ■ selekce ■ změna ■ vyhodnocení Fáze inicializace vytvoří počáteční generaci. V našem případě jsme zvolili náhodné jedince (proměnné jsou nastavené na 0 nebo 1 s 50% pravděpodobností). Fáze vyhodnocení vypočítá zdatnost všech jedinců v generaci a vyhodnotí pár statistických údajů. Implementovaný jednoduchý GA je postupného typu (steady state evolution). To znamená, že selekční fáze nenahrazuje celé generace novými, ale změnami prochází jen část populace a rodiče „žijí dál“ mezi svými potomky. Generace je zde sestupně seřazena podle zdatnosti a z množiny prvních jedinců (o velikosti best_area_fraction, třetí řídicí parametr) je vybrán určitý počet jedinců (o velikosti selection_ratio, druhý ř. p.). Každý z těchto jedinců je naklonován na místo, které obsazoval některý z jedinců z dolní poloviny populace. Fáze změna poskytuje možnost vývoje populace, všichni jedinci bez rozdílu zdatnosti zde mají šanci projít nahodilou mutací ve své výbavě, což je může posunout v žebříčku výš nebo níž. Používáme pouze jeden z mnoha možných rekombinačních operátorů, mutaci bitu. Míra mutace (mutation_rate, čtvrtý řídicí parametr) určuje, kolika náhodným jedincům bude náhodně vybrán bit k invertování. Druhý, třetí a čtvrtý parametr se zadávají v procentech v poměru k velikosti populace. Při řešení pomocí GA dochází často ke stagnacím vývoje populací, kdy mutace už není schopna dodat zlepšení. Na implementovaném algoritmu je to znát, nicméně je dokáže nalézt řešení a vylepšovat váhu proměnných. Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 14/20 V.3 Vstupní data Jako generátor vstupních dat byl použit program G2.c ze stránky autorů M. Motoki a O. Watanabe. http://www.is.titech.ac.jp/~watanabe/gensat/a1. Bylo třeba jej upravit, aby poskytoval náhodné váhy proměnných a výstup byl vhodnější pro načítání. Nový program byl nazván G2w.c. Příklad (N = 5, M = 5, K = 3), vysvětlí i formát: # instance by # 1 line # N lines # M lines # 5 5 3 1 1 0 10 0 7 0 7 0 2 -5 2 -1 -3 -1 4 1 2 3 5 -2 4 -4 5 -1 G2w, format: <N=#-of-vars> <M=#-of-clauses> <K=vars-per-clause> <nth variable solution> <nth variable weight (random 1-10)> <clause defined by K-tuple (minus means variable negation)> V generujícím programu se na rozdíl od programu s GA pro počet proměnných V používá označení N a pro počet klauzulí C označení M. V.4 Příklad výsledku (V=C=1000, K=3) $ bin/gasat < instance-1000-1000.3sat Loading instance from stdin: Vars 1000, clauses 1000, max weight 5491 Evaluating given solution: Match 1000 of 1000, weight 2788 Self test PASSED. Population size: 100 Selecting 2 (2.0%) individuals from best area of 10 (10.0%) Mutating with rate 10.00% Commencing the eVoLUtioN (Ctrl+C to break) ---------------------------------------------------------Time Gener Best Fitness Satisfied Forms Left ---------------------------------------------------------0h00m08 1000 975.2809 0x 25 0h00m16 2000 999.3230 0x 1 0h00m25 3000 1000.3653 100x 0 0h00m33 4000 1000.4047 209x 0 0h00m41 5000 1000.4306 283x 0 0h00m50 6000 1000.4498 353x 0 0h00m59 7000 1000.4617 403x 0 0h01m08 8000 1000.4699 431x 0 0h01m16 9000 1000.4742 449x 0 0h01m24 10000 1000.4776 462x 0 0h01m31 11000 1000.4795 471x 0 0h01m39 12000 1000.4798 472x 0 0h01m47 13000 1000.4800 473x 0 0h01m56 14000 1000.4801 474x 0 0h02m04 15000 1000.4801 474x 0 0h02m12 16000 1000.4802 475x 0 0h02m20 17000 1000.4802 475x 0 0h02m27 18000 1000.4802 475x 0 ^C - User break (SIGINT) The heaviest solution so far (value 4717) comes from generation 15957: 1111'1111'1111'1101'1011'1111'1111'1111'0011'1111'1111'1111'1011'0101'1111'1011'1101'1 111'1111'1111'1111'1111'1111'0111'1011'1101'1101'1001'1111'1101'1011'1111'1101'1101'1111'1111' 1111'1111'1011'1111'1111'1011'1111'1111'1111'1110'1011'1011'1011'1111'1110'1011'1111'1111'1111 '1110'1111'1111'1111'1111'1011'1010'1111'1111'0111'0111'1111'0110'1111'1011'10... (zkráceno) Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 15/20 Vysvětlivky k výstupu Po načtení vstupní instance jsou vypsány zjištěné údaje a je proveden test za účelem zjištění, zda bude dodané řešení CNF, které je součástí vstupu, vyhodnoceno kladně. V negativním případe je program ukončen a chybu je třeba hledat buď v programu s GA nebo ve vstupních datech. Pak jsou vypsány řídicí údaje evolučního algoritmu, načež začíná výpočet, o jehož průběhu je uživatel informován po každé tisící generaci. Tabulka obsahuje: • Čas od začátku výpočtu. • Číslo generace. • Zdatnost. Tento údaj kombinuje dvě čísla takto: před tečkou (statisíce) je počet splněných disjunkcí zadané formy, za tečkou je váha řešení. GA tedy optimalizuje obě hodnoty společně, přičemž větší hodnotu má míra splněnosti CNF. • Počet nalezených ohodnocení proměnných. Jedná se pouze o informativní údaj, protože jsou zde zahrnuta i ohodnocení, která již mohla být nalezena dříve. • Počet zbývajících disjunkcí. Poskytuje rychlou kontrolu, kolik ještě algoritmu zbývá k nalezení alespoň nějakého splňujícího řešení bez ohledu na váhu. Je to však pouze rozdíl čísla K a vyšší poloviny zdatnosti. Zobrazující se pouze nejlepší dosažené výsledky. Bylo by možné program rozšířit o náhled aktuálního stavu pro ilustraci stagnace. Jsou dvě možná ukončení programu: • Nalezení optimálního řešení. Program zná nejvyšší hodnotu zdatnosti, při jejím dosažení není potřeba pokračovat dále. • Přerušení uživatelem. Program nemá detekci stagnace ani časové omezení na svůj běh. Pak je vypsán nejzdatnější jedinec, jeho váha a generace, ze které pochází. Na příkladu je vidět, že algoritmus už asi 2000 generací neposunul řešení. V.5 Implementace Za účelem časově a paměťově efektivní implementace byl vybrán čistý jazyk C s četným použitím nejprimitivnějších datových typů, dynamického přidělování paměti a jejího opětovného využívání bez realokace a aritmetiky ukazatelů. K dispozici jsou dvě řešení pole popisujícího jedince: Obyčejné pole typů int (kde se pro každý bit plýtvá 31 jinými, ale vhodné pro ladění) a skutečné bitové pole zcela využívající hodnoty typu int. Kromě evolučního postupu je k dispozici ještě pokusná funkce brute_cycle hledající nejlepší řešení hrubou silou. Zdrojový kód Na následujících stránkách. V.6 Závěr Vytvořený genetický algoritmus sice poskytuje zajímavé výsledky, nicméně nabízí prostor pro vylepšení v oblasti softwarového generátoru pseudonáhodných čísel, potlačování stagnace, interakce s uživatelem, komplexnosti selekce mutačních operátorů, … Nikde se žádným způsobem nekontrolují duplicity. To má pozitivní dopad na rychlost, ale zřejmě negativní na přesnost. Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 16/20 / * Solving S A T evolutionarily. (c) 2006-06-23 Vla da Macek <[email protected]> * / # #include #include #include #include #include #include #include #include # <stdio.h> <stdlib.h> <assert.h> <time.h> <sys/time.h> <limits.h> <string.h> <signal.h> # #define BIT_ARRAY_IMPLEMENTATION 0 / * array i mple mentation switch * / #define BITS_PER_OCTET 8 / * we start with tough ones :-) * / #define BITS_PER_INT (BITS_PER_OCTET*sizeof(int)) #define COMBINING_MULTIPLIER 100000 / * puts two n u mbers into one * / / * DA TA LOAD ED FRO M I N S T A NC E FILE. * / / * n u m ber of variables, n u mber of clauses, vars per clause * / int V = -1, C = -1, K = -1; int *solution_ba = NULL; / * bit array denoting given solution, freed early * / int *weight = NULL; / * array of weights for each variable * / int *given_clauses = NULL;/ * given problem * / / * ------ end of loaded data * / int *winner = NULL; / * the fittiest in divid ual fou n d so far * / int winning_generation = 0; / * FIX M E: So mewhere in source R A ND_MA X m ay not be enough, check! * / #define RANDOM rand / * fu nction giving so me ran do m ness in 0..R A ND_MA X * / time_t program_start_time; int sum_weight = 0; / * su m m ary weight of loaded insta nce * / void *getmem(size_t size) { void *ptr = calloc(1, size); if (!ptr) { fprintf(stderr, "\nMEMORY ALLOCATION FAILURE! Terminating.\n"); exit(10); } return ptr; } / * BI T A R R A Y (ba) functions, ba is (int *), bi is n u meric bit in dex * / #if BIT_ARRAY_IMPLEMENTATION == 1 # define ba_size(totalbits) (((totalbits)/BITS_PER_OCTET) + 1) # define ba_sizeint(totalbits) (((totalbits)/BITS_PER_INT) + 1) # define ba_get(ba, bi) \ ((*((ba) + (bi)/BITS_PER_INT)) & (1 << ((bi) % BITS_PER_INT))) Problémy a algoritmy – semestrální práce - Vladimír Macek define ba_set1(ba, bi) \ (*((ba) + (bi)/BITS_PER_INT) |= (1 << ((bi) % BITS_PER_INT))) define ba_set0(ba, bi) \ (*((ba) + (bi)/BITS_PER_INT) &= ~(1 << ((bi) % BITS_PER_INT))) define ba_setvalue(ba, bi, value) \ ((value) ? ba_set1((ba), (bi)) : ba_set0((ba), (bi))) void ba_invert(int *ba, int { int val, sel = (1 << (bi % ba += bi/BITS_PER_INT; val *ba = (val & sel) ? (val & } bi) / * inverts the given bit in array * / BITS_PER_INT)); = *ba; ~sel) : (val | sel); #else / * E L S E: I m plementation with array of integers (for debugging). * / # define ba_size(totalbits) ((totalbits) * sizeof(int)) # define ba_sizeint(totalbits) (totalbits) # define ba_get(ba, bi) (*((ba) + (bi))) # define ba_set1(ba, bi) (*((ba) + (bi)) = 1) # define ba_set0(ba, bi) (*((ba) + (bi)) = 0) # define ba_setvalue(ba, bi, value) \ ((value) ? ba_set1((ba), (bi)) : ba_set0((ba), (bi))) # define ba_invert(ba, bi) (*((ba) + (bi)) = !*((ba) + (bi))) #endif void init_time_seed() { int t = time(NULL); struct timeval tv; program_start_time = t; gettimeofday(&tv, NULL); t = tv.tv_usec % t; srand((int)t); } void load_instance() { char *p, buf[2048]; int vari = -1, ci = -1, *cp = NULL; printf("Loading instance from stdin: "); buf[sizeof(buf)-1] = '\0'; while (fgets(buf, sizeof(buf)-1, stdin)) { p = strchr(buf, '\n'); assert(p); *p = '\0'; / * required \ n ter mination * / while (*(p = buf + strlen(buf)-1) == ' ') *p = '\0'; / * right tri m * / if (*buf == '#') continue; / * com ments out * / p = buf; Stránka 17/20 if (V == -1) { V = atoi(strsep(&p, " ")); C = atoi(strsep(&p, " ")); K = atoi(strsep(&p, " ")); assert(!p); / * syntax check: required out of inp ut now * / assert(C>0); / * assert(K == V A R S_PE R_CLA U S E); * / continue; } return sumw; } if (!solution_ba) { solution_ba = getmem(ba_size(V)); weight = getmem(V*sizeof(int)); vari = 0; } if ((vari >= 0) && (vari < V)) { ba_setvalue(solution_ba, vari, atoi(strsep(&p, " "))); weight[vari] = atoi(strsep(&p, " ")); sum_weight += weight[vari]; assert(!p); / * syntax check: required out of inp ut now * / vari++; continue; } void print_individual(int *indi) { / * d u m ps the bits of the individual * / int v; for (v=0; v<V; v++) { fputc(ba_get(indi, v) ? '1' : '0', stdout ); if (!((v+1)%4) && (v<V-1)) fputc('\'', stdout); } } if (!given_clauses) { given_clauses = getmem(K*C*sizeof(int)); ci = 0; cp = given_clauses; } if ((ci >= 0) && (ci < C)) { int i; for (i=0; i<K; i++, cp++) { *cp = atoi(strsep(&p, " ")); assert(*cp != 0); assert(abs(*cp) <= V); } assert(!p); / * syntax check required out of input now * / ci++; continue; } break; } assert(vari == V); assert(ci == C); assert(weight); assert(sum_weight < COMBINING_MULTIPLIER); printf("Vars %d, clauses %d, max weight %d\n", V, C, sum_weight); } / * load_instance * / int weight_of_individual(int *indi) { / * su m s the weight of variables that are ones * / int v, sumw=0; for (v=0; v<V; v++) if (ba_get(indi, v)) sumw += weight[v]; Problémy a algoritmy – semestrální práce - Vladimír Macek void random_individual(int *indi) { int v; / * 0 / 1 with 50% probability: * / for (v=0; v<V; v++) ba_setvalue(indi, v, (RANDOM() % 10) > 4); } void print_process_time() { / * stopwatch of the process activity * / int ts = time(NULL)-program_start_time; printf("%2dh%02dm%02d", ts/3600, (ts/60)%60, ts%60); } int fitness_of_individual(int *indi); void brute_cycle() { int m, f, best_f = 0, best_m = 0, cnt = 0; int *indi = getmem(ba_size(V)); printf("Brute cycle advancing.\n"); while (1) { random_individual(indi); f = fitness_of_individual(indi); m = f/COMBINING_MULTIPLIER; cnt++; if (m > best_m) best_m = m; if (f > best_f) { best_f = f; printf("Best fitness %4d, best match %d of %d, tested %d individuals.\n", \ best_f, best_m, C, cnt);/ / , weight_of_individual(indi)); fflush(stdout); } } free(indi); printf("\n"); } Stránka 18/20 void user_break(int blind) { printf("\n^C - User break (SIGINT)\n\n"); if (winning_generation) { printf("The heaviest solution so far (value %d) comes from generation " "%d:\n\n\t", weight_of_individual(winner), winning_generation); print_individual(winner+1); printf("\n\n"); exit(0); } } int matchnum_of_individual(int *indi) { / * first part of the fitness counting method: n u m ber of positive clauses * / int c, k, a, *atom = given_clauses; int tuples_ok = 0, tuple_ok; for (c=0; c<C; c++) { tuple_ok = 0; for (k=0; k<K; k++) { / * this is a signed variable reference fro m the clause array, * positive - a-th variable is not inverted in the for m * negative - (minus-a)-th variable is inverted in the form * index starts from one */ a = *atom++; / * m a kes tuple_ok nonzero in case the variable h as good (positive * or negative, given by clause) state in the in divid ual * / tuple_ok |= (a < 0) ? !ba_get(indi, -a-1) : ba_get(indi, a-1); } if (tuple_ok) tuples_ok++; } return tuples_ok; } int fitness_of_individual(int *indi) { int m = matchnum_of_individual(indi), w = weight_of_individual(indi); return m * COMBINING_MULTIPLIER + (w-1); } int compare_firstint(const void *i1, const void *i2) { / * descending order * / return *(int *)i2 - *(int *)i1; } void start_the_evolution(int pop_size, float selection_ratio, float best_area_fraction, float mutation_rate) { int p, *pp, generation=0, max_fitness, matches=0, unmatched=C, Problémy a algoritmy – semestrální práce - Vladimír Macek fitness_limit = COMBINING_MULTIPLIER*C + sum_weight -1, indi_numint = 1 + ba_sizeint(V), / * step between indivs in an array * / *pop = getmem(pop_size*indi_numint*sizeof(int)), / * place for indivs * / selection_num, best_area, mutation_num; / * I NI TIA LI ZA TIO N * / for (p=0, pp=pop; p<pop_size; p++, pp += indi_numint) { random_individual(pp+1); *pp = fitness_of_individual(pp+1); } winner = getmem(indi_numint*sizeof(int)); / * n u m ber of individ uals with best fitness to select for cloning * / selection_num = (int)((float)pop_size * selection_ratio/100); if (selection_num < 1) selection_num = 1; / * n u m ber of individ uals from the top are peloton from w here to clone * / best_area = (int)((float)pop_size * best_area_fraction/100); if (best_area < 1) best_area = 1; / * how m a ny individ uals to rando mly choose to toggle one variable (m utation) * / mutation_num = (int)((float)pop_size * mutation_rate/100); if (mutation_num < 1) mutation_num = 0; printf("Population size: %d\nSelecting %d (%.1f%%) individuals from best area " "of %d (%.1f%%)\nMutating with rate %.2f%%\n\n" "Commencing the eVoLUtioN (Ctrl+C to break)\n" "----------------------------------------------------------\n" " Time Gener Best Fitness Satisfied Forms Left\n" "----------------------------------------------------------\n", pop_size, selection_num, selection_ratio, best_area, best_area_fraction, mutation_rate); signal(SIGINT, user_break); max_fitness = 0; while (1) { generation++; / * S E L EC TIO N (CLO NI NG) * / if (generation > 1) { qsort(pop, pop_size, indi_numint * sizeof(int), compare_firstint); for (p=0; p<selection_num; p++) { int src = RANDOM() % best_area, / * p ut the clone somewhere in the tail half * / dest = (RANDOM() % (pop_size/2)) + pop_size/2; memcpy(pop + indi_numint*dest, pop + indi_numint*src, indi_numint*sizeof(int)); } } Stránka 19/20 / * M U T A TIO N * / for (p=0; p<mutation_num; p++) { int indi_index = RANDOM() % pop_size, var_index = RANDOM() % V; ba_invert(pop + 1 + indi_numint*indi_index, var_index); } / * E V A L U A TIO N * / for (p=0, pp=pop; p<pop_size; p++, pp += indi_numint) { int f = fitness_of_individual(pp+1), m = f/COMBINING_MULTIPLIER; *pp = f; if (f > max_fitness) { max_fitness = f; unmatched = C-m; if (m == C) matches++; int main() { init_time_seed(); load_instance(); self_test(); start_the_evolution(100, 2, 10, 10); return 0; / * u nreachable currently * / / * brute_cycle(); return 0; * / } memcpy(winner, pp, indi_numint*sizeof(int)); winning_generation = generation; if (f >= fitness_limit) { free(pop); printf("\nSuccessfully found assignment with the maximum weight.\n" "Individual %d of generation %d:\n\n\t", p+1, generation); print_individual(pp); printf("\n\n"); return; } } } if (!(generation%1000)) { / * don't print each generation * / print_process_time(); printf("%7d%8d.%-5d%12dx%9d\n", generation, max_fitness/COMBINING_MULTIPLIER, \ max_fitness%COMBINING_MULTIPLIER, matches, unmatched); fflush(stdout); } } free(pop); / * but u nreachable currently * / } void self_test() { int m; printf("Evaluating given solution: "); m = matchnum_of_individual(solution_ba); printf("Match %d of %d, weight %d\n", m, C, weight_of_individual(solution_ba)); if (m != C) { printf("FAILED self-test based on input data! Terminating.\n"); exit(9); } else printf("Self test PASSED.\n\n"); free(solution_ba); solution_ba = NULL; / * we don't need it a ny more * / } Problémy a algoritmy – semestrální práce - Vladimír Macek Stránka 20/20
Podobné dokumenty
Rozhovor - Antecom
Evropa jako obchodní partner na neurčitou dobu odpadá a zemím APEC nezbývá nic jiného než se pokusit vyrovnat tento fakt rozšířením vzájemného
obchodu. I když se jim to zčásti může
– protože mus...
Sborník příspěvků - Občanská futurologická společnost
závisí řešení na včasném budování nových institucí a zavádění nové informační praxe i do
praxe politické.
Změnila se také struktura zájmů o představy o budoucí skutečnosti u uživatelů.
Původně domi...
Sbornik_2011 - Institut pro dopravní ekonomii, geografii a politiku
Možnosti soutěžení jsou v oblasti veřejných služeb v principu dvě, třetí a čtvrtá možnost
jsou určitou modifikací prvních dvou variant:
Předmětem soutěže jsou náklady, uzavřena je tzv. brutto smlou...
Objektové programování 1 - Starší publikace a seriály
a žádný z dostupných jazyků mu nepřipadal dostatečně mocný, tvárný, výkonný a přitom přenositelný. Rozhodl se, že si potřebný nástroj vytvoří „objektovým zorientováním“ jazyka C. V první etapě (198...
spojky KAMLOK
látek v průmyslovém odvětví všeobecně, zejména pak v zemědělství, hutním
a strojírenském průmyslu.
Vysoce kvalitní a robustní spojky KAMLOK jsou zaměnitelné se světově
rozšířeným páčkovým systémem....