====== Hanojské věže ====== http://cs.wikipedia.org/wiki/Hanojsk%C3%A9_v%C4%9B%C5%BEe * Máme N disků na první věži * V jednotlivých krocích přenášíme jeden disk z vrcholu některé věže na vrchol jiné věže * Nesmíme položit větší disk na menší * Cílem je přemístit všechny disky na třetí věž * Velikost disků: 1, 2, ... N * Levou věž označíme **A**, prostřední **B**, pravou **C** ===== Datové struktury ==== Datová struktura **Vez** obsahuje * výšku věže **v** = počet disků, v = 0 ... prázdná věž, v = N ... zcela zaplněná * pole **p** obsahující velikosti jednotlivých disků * **p [0]** je dole * **p [v-1]** je vrchol věže (pokud **v** není nulové) Globální proměnné **A**, **B**, **C** typu **Vez**. const int N = 5; // pocet disku struct Vez { int v; // vyska veze int p [N]; // velikosti disku, p[0] je dole, p[v-1] je vrchol }; Vez A, B, C; ===== Prázdné věže ===== Volání funkce **nuluj (B)** a **nuluj (C)** nastaví nulovou výšku věží. \\ Pro přehlednější ladění vynulujeme i celé pole **p**, ale pro běh programu to není nezbytné. Parametr typu **X** typu **Vez** předáváme odkazem (**&**), neboť chceme změnit původní proměnné **B** a **C**. void nuluj (Vez & X) { X.v = 0; // nulova vyska for (int i = 0; i ===== Naplnění levé věže ===== Funkce **init (A)** naplní věž N disky. Nastavíme výšku **X.v** na hodnotu *N*. \\ Pole **X.p** naplníme hodnotami N, N-1, ..., 2, 1. \\ Spodní disk velikosti N uložíme do **X.p[0]**. \\ Na vrchol dáme disk velikosti 1, tj. nastavíme **X.p[N-1]** na jedničku. void init (Vez & X) { X.v = N; // N disku for (int i = 0; i ===== Zobrazení ===== Funkce **zobraz** vytiskne jednu věž. \\ //Parametr zde nemusí být předáván odkazem (**&**), ale je zbytečné vytvářet kopii celé datové struktury.// void zobraz (Vez & X) { for (int i = 0; i < X.v; i++) cout << X.p[i]; for (int i = X.v; i <= N; i++) cout << ' '; // dopln mezerami } Funkce **tiskni** zobrazí tři věže oddělené dvojtečkami. void tiskni (Vez & X, Vez & Y, Vez & Z) { zobraz (X); cout << " : "; zobraz (Y); cout << " : "; zobraz (Z); cout << endl; } Takto by měl vypadat výstup celého programu. * Disk 1 dáme na pravou věž * Disk 2 na prostřední věž * Disk 1 z pravé věže na prosřední 54321 : : 5432 : : 1 543 : 2 : 1 543 : 21 : 54 : 21 : 3 541 : 2 : 3 541 : : 32 54 : : 321 5 : 4 : 321 ... 3 : 2 : 541 3 : 21 : 54 : 21 : 543 1 : 2 : 543 1 : : 5432 : : 54321 ===== Přenos jednoho disku ===== Funkce **krok** přenese jeden disk z vrcholu věže **X** na vrchol věže **Y**. * Do proměnné **h** uložíme disk z vrcholu věže **X** * Snížíme věž **X** * Zvýšíme věž **Y** * Na vrchol **Y** přidáme disk **h** void krok (Vez & X, Vez & Y) // presun disku z veze X na nez Y { int h; h = X.p [X.v-1]; // cislo disku z vrcholu veze X X.p [X.v-1] = 0; // pro ladeni - vynulujeme X.v = X.v - 1; // zmensime vysku veze X Y.v = Y.v + 1; // zvysime vez Y Y.p [Y.v-1] = h; // na vrchol pridame disk h } Nebo totéž s kontrolami zda **X** není prázdná, \\ **Y** není přeplněná, \\ a zda neklademe větší disk na menší. void krok (Vez & X, Vez & Y) // presun disku z veze X na nez Y { int h; assert (X.v > 0); // zkontroluj zda je vez neprazdna h = X.p [X.v-1]; // cislo disku z vrcholu veze X X.p [X.v-1] = 0; // pro ladeni - vynulujeme X.v = X.v - 1; // zmensime vysku veze X assert (Y.v < N); // zkontroluj zda vez neni prilis vysoka if (Y.v > 0) { assert (h < Y.p [Y.v-1]); // zda h je mensi nez soucasny vrchol } Y.v = Y.v + 1; // zvysime vez Y Y.p [Y.v-1] = h; // na vrchol pridame disk h } ===== Přenos několika disků ===== Funkce **hraj** přenese **k** disků. \\ Věže předáme odkazem jako parametry **X**, **Y**, a **Z**. \\ Přesouvat budeme z věže **X**, **Y** bude pomocná věž na odkládání disků, **Z** je cílová věž. Pokud **k** je větší než 1, rekurzivně zavoláme stejnou funkci **hraj** \\ pro přenos **k** disků z věže **X** na původně pomocnou věž **Y**, \\ pro tento přenos bude jako pomocná věž použita původně cílová věž **Z**. void hraj (Vez & X, Vez & Y, Vez & Z, int k) if (k > 1) { hraj (X, Z, Y, k-1); } Např. pro N=10, pokud X byla v počátečním stavu, Y a Z prázdné. {{zalg:veze-start.png?640}} Pro k=6, přeneseme 5 disků z X do Y. {{zalg:veze-zaciname.png?640}} Potom funkcí **krok** přeneseme jeden disk z **X** na cílovou věž **Z** krok (X, Z); // 1 disk, X --> Z {{zalg:veze-rozehrano.png?640}} Nakonec přeneseme **k-1** disků z **Y** na **Z** if (k > 1) hraj (Y, X, Z, k-1); {{zalg:veze-preneseno.png?640}} void hraj (Vez & X, Vez & Y, Vez & Z, int k) { if (k > 1) hraj (X, Z, Y, k-1); // k-1 disku, X --> Y, Z je pomocna vez krok (X, Z); // 1 disk, X --> Z tiskni (A, B, C); // vytiskneme jeden krok if (k > 1) hraj (Y, X, Z, k-1); // k-1 disku, Y --> Z, X je pomocna vez } //X,Y,Z bude vždy nějakou permutací skutečných věží A,B,C// \\ //Pro tisk použijeme globální proměnné A,B,C aby věže byly zobrazeny ve svém opravdovém pořadí// //Pod horními **k** prvky mohou být ve všech věžích "na dně" další větší disky.// ===== Funkce main ===== Vytiskneme počáteční stav. \\ Zavoláme funkci **hraj** s původními věžemi **A**, **B**, **C**. \\ Přeneseme **N** disků z **A** na **C**. int main (int argc, char * argv []) { init (A); // vez A - N disku nuluj (B); // veze B a C jsou prazdne nuluj (C); tiskni (A, B, C); // pocatecni stav hraj (A, B, C, N); // A --> C, B je pomocna vez, preneseme N disku } ===== Složitost ===== * funkce **krok** ... **O(1)** * funkce **hraj (k)** ... **(2^k)-1** volání funkce **krok** Označme h(k) počet volání funkce **krok** v průběhu funce **hraj (k)**. \\ h(1) = 1 \\ h(k) = h(k-1) + 1 + h(k-1) \\ Indukcí dokážeme h(k) = (2^k) - 1 Přenos **N** disků ... **O(2^N)** ===== Celý program ===== http://gitlab.fjfi.cvut.cz/culikzde/zalg/-/blob/master/veze/veze.cpp #include #include using namespace std; const int N = 5; // pocet disku struct Vez { int v; // vyska veze int p [N]; // velikosti disku, p[0] je dole, p[v-1] je vrchol }; Vez A, B, C; void nuluj (Vez & X) { X.v = 0; // nulova vyska for (int i = 0; i 0); // zkontroluj zda je vez neprazdna h = X.p [X.v-1]; // cislo disku z vrcholu veze X X.p [X.v-1] = 0; // pro ladeni - vynulujeme X.v = X.v - 1; // zmensime vysku veze X assert (Y.v < N); // zkontroluj zda vez neni prilis vysoka if (Y.v > 0) { assert (h < Y.p [Y.v-1]); // zda h je mensi nez soucasny vrchol } Y.v = Y.v + 1; // zvysime vez Y Y.p [Y.v-1] = h; // na vrchol pridame disk h } void hraj (Vez & X, Vez & Y, Vez & Z, int k) // presun z veze X na vez Z, vez Y bude pomocna vez // preneseme k disku z vrcholu veze X { if (k > 1) hraj (X, Z, Y, k-1); // k-1 disku, X --> Y, Z je pomocna vez krok (X, Z); // 1 disk, X --> Z tiskni (A, B, C); // vytiskneme jeden krok if (k > 1) hraj (Y, X, Z, k-1); // k-1 disku, Y --> Z, X je pomocna vez } int main (int argc, char * argv []) { init (A); // vez A - N disku nuluj (B); // veze B a C jsou prazdne nuluj (C); tiskni (A, B, C); // pocatecni stav hraj (A, B, C, N); // A --> C, B je pomocna vez, preneseme N disku cout << "O.K." << endl; return 0; } ===== Zobrazení pomocí Qt ===== Pro ilustraci http://gitlab.fjfi.cvut.cz/culikzde/zalg/-/tree/master/veze-qt {{zalg:veze.png?800}} Vlevo dole nastavíme počet věží, stiskeme tlačítko "Run". \\ Posuvníkem (scroll barem) si můžeme prohlížet jednotlivé kroky. \\ V pravém dolním rohu se zobrazuje počet kroků (2^N)-1 zvětšený o jeden výchozí stav (2^N).