====== Osm dam ===== Metoda hledání s návratem, problém osmi dam (skripta 3.4) Zadání úlohy: http://cs.wikipedia.org/wiki/Probl%C3%A9m_osmi_dam [[dama_src|Zdrojový text]] Pocet sloupcu const int N = 8; Jednorozmerne pole pro evidenci dam int p [N]; Prvek pole **p [i]** obsahuje informaci o dame v i-tem slopci: obsahuje cislo radky, kde se nachazi dama. \\ Radky a sloupce cisujeme od 0 (stejne jako indexy poli v jazyce C). Napr. ** p[0] == 0 ** ... dama a8 ... dama v levem hornim rohu (sloupec s indexem 0, radka s indexem 0) \\ ** p[1] == 2 ** ... dama b6 ... dama ve sloupci s indexem 1 (druhy sloupec) je na radku s indexem 2 ( treti radka od shora ) \\ Promenna pocitajici reseni ulohy int cnt = 0; Pro poradek vyplnime pole **p** nedovolenymi hodnotami. \\ A potom zpustime fukci ** place (0) **, ktera ma umistit damu na sloupec 0 ( sloupec **a** na obrazku Wikipedie ) int main() { for (int i = 0; i < N; i++) p[i] = -1; place(0); std::cout << "O.K." << endl; } ===== Rekurzivní funkce umísťující dámy na jednotlivé sloupce ====== Funkce **place** ma jako parametr cislo sloupce **s**, na ktery budeme damu umistovat. \\ V cyklu pro jednotlive radky **r**, zkusime umistit damu. \\ void place (int s) { for (int r = 0; r < N; r++) { /* ... */ } } ===== Testujeme konflikt ===== Promenna **ok** bude oznacovat, zda damu mohu umistit na sloupec **s** a radku **r**, aniz by doslo ke kolizi s damami ve sloupcich vlevo od sloupce **s**. \\ Nejprve **ok** nastavim na ** true**, tj. zatim neni zadny konflikt. Pokud bych misto dam pouzival **veže**, staci zkontrolovat sloupce 0, 1, ... s-1, zda neobsahuji cislo **r**. V pripade dam pouziji promennou **v**, ktera bude predstavovat horizontalni vzdalenost zkoumaneho sloupce od osloupce cislo **s**. Pujdu s **v** od **1** do **s**. \\ Pro **v == 1 ** zkoumam predchazejici sloupecc (cislo s-1). \\ Pro **v == s ** zkoumam prvni sloupecc (cislo 0). \\ Do promenne **d** ulozim cislo radky, kde se nachazi dama ve sloupci vzdalenem o **v** od naseho sloupce. int d = p [s - v]; Pokud **d == r**, dama ve sloupci **s-v** je na stejnem radku, jako dama ve sloupci **s**. \\ Pokud **d == r-v**, dama ve sloupci **s-v** je na stejne (klesajici) diagonale (vlevo nad nasi damou) , jako dama ve sloupci **s**. \\ Pokud **d == r+v**, dama ve sloupci **s-v** je na stejne diagonale (vlevo pod nasi damou), jako dama ve sloupci **s**. \\ if (d == r || d == r-v || d == r+v) ok = false; Cely test: bool ok = true; for (int v = 1; v <= s && ok; v++) { int d = p[s - v]; if (d == r || d == r-v ||d == r+v) ok = false; } Pozn.: **&& ok** zastavi cyklus, pokud se objevi konflikt ... neni nezbytne ===== Pokud konflikt nenastane ===== V promenne **ok** mame vysledek testu, zda lze umistit damu ve sloupci **s** na radek **r**. \\ ( Tento test muze skoncit kladne i pro nekolik hodnot **r**. ) Pokud **ok == true**, ulozime pozici damy do pole: ** p[s] = r; **. Pokud navic jsme na **poslednim sloupci**, mame **jedno reseni cele ulohy** \\ a vytiskneme cisla radek na kterych se nachazeji damy, \\ pridame za dvojtecku cislo reseni a ukoncime tiskovou radku. cnt++; for (int i = 0; i < N; i++) cout << p[i] << " "; cout << " : " << cnt << endl; Pokud **nejsme na poslednim sloupci**, rekurzive zavolame dalsi exemplar funkde **place** tentokrat pro sloupec **s+1**. \\ Nove zavolana funkce bude opet zkouset radky 0, 1, ... N-1. \\ A pokud nebude na poslednim sloupci, zavola dalsi exemplar funkce **place**. if (s < N - 1) place (s + 1); Tedy pokud se nam podari umistit damu ve sloupci **s** na radek **r**, \\ zkousime umistit damy v nasledujicich sloupcich nebo mame jedno reseni. V kazdem pripade pak pokracuje cyklus pro radky **r**, ktery zkousi umistit damu na dalsi radek. ===== Celá funkce place ====== void place (int s) { for (int r = 0; r < N; r++) { bool ok = true; for (int v = 1; v <= s && ok; v++) { int d = p [s - v]; if (d == r || d == r-v || d == r+v) ok = false; } if (ok) { p[s] = r; if (s < N - 1) place(s + 1); else { cnt++; for (int i = 0; i < N; i++) cout << p[i] << " "; cout << " : " << cnt << endl; } } } } ===== Celý program ====== #include using namespace std; const int N = 8; int p [N]; int cnt = 0; void place (int s) { /* ... */ } int main () { for (int i = 0; i < N; i++) p[i] = -1; place (0); cout << "O.K." << endl; } ===== Úkoly ===== a) Upravte program, aby řešil úlohu pro osm věží \\ ( Stačí napsat jednu řádku. ) b) Kolik v bude v případě veží řešení ? \\ ( Odpověď je na pár znaků klávesnice. Nemusíte program spouštět. ) c) Proč nebude podobná úprava fungovat pro střelce ? d) Použijme náš algoritmus, ale předpokládejme že se dámy neohrožují. \\ ( V každém sloupci smí být jedna dáma, ale ve vedlejším sloupci může být i na stejném řádku další. ) \\ Tedy náš algoritmus, ale **ok** bude stále **true**. \\ Kolik bude variant ? \\ ( Otázka odpovídá 9-patrovému stromu. Z kažého vrcholu, který není koncovým listem, vede osm větví. ) e) Uvažujme jiný postup: na každém ze 64 políček šachovnice může být umístěna figurka. \\ Figurky jsou jednoho druhu. \\ Máme k dispozici 64 figurek. \\ (Šachovnice může být například zcela prázdná nebo také zcela zaplněná.) \\ Kolik variant přichází v úvahu ? \\ Pokud na počítači stihneme prozkoumat 4 miliardy variant za sekundu ( nebo 2^32 variant za sekundu ), jak dlouho by výpočet trval ? ===== Zobrazení pomocí Qt ====== Jen pro ilustraci, náš algoritmus můžeme použít i v programu s grafickým okénkem. http://kmlinux.fjfi.cvut.cz/~culikzde/zalg/2020/dama-qt.zip {{dama:creator-configure.png}} Metoda **umisti** je obdobou nasi funkce **place**. Strisknete zeleny trojuhelnik. {{dama:creator-run.png}} Stisknete tlacitko "Run" a potom pomoci "scrollbaru" nad sachovnici muzete zobrazovat jednotliva reseni. {{dama:dama.png}}