<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xml" href="/cjs/screen.xsl" media="screen"?>
<lecture>

<meta>
  <maintitle>Karel</maintitle>
  <author>Jiří Znamenáček</author>
  <title>Hanojské věže</title>
  <date>2026-09-10</date>
  <link><!--a href="http://vyuka.ookami.cz" rel="external">http://vyuka.ookami.cz</a--></link>
</meta>
<!--
  „“–
  ↵ aneb &#x21B5; aneb \r aneb CR aneb CarriageReturn
-->


<slide title="Úvod">

    <p>
        Před dávnými a dávnými věky v jednom těžko přístupném tibetském klášteře se mniši zabývali podivnou úlohou – přendavali zlaté disky (s dírou uprostřed), které byly původně vyskládané na prvním trnu do tvaru kužele (tj. největší úplně vespod a nejmenší nahoře) z jednoho trnu na druhý za pomoci trnu třetího, přičemž dodržovali následující dvě pravidla:
    </p>
    <ol>
        <li>
            je možno položit menší disk na větší, ale ne opačně;
        </li>
        <li>
            na prázdný trn je možno navléct libovolný disk.
        </li>
    </ol>
    <p>
        Když se jich cestovatel ptal, proč něco takového dělají, odpověděli, že až se jim podaří přeskládat všech 64 disků na druhý trn do stejného tvaru, nastane konec vesmíru.
    </p>
    <note>
        Až si ukážeme, jak se tato úloha řeší, dokážete si sami spočítat, zda se máme bát či nikoli ^_~ Ale popravdě – ty disky byly ze zlata, takže už jim je dávno někdo ukradl…
    </note>

</slide>
<slide title="Princip řešení">

    <p>
        Abyste mohli přesunout všechny disky z prvního na druhý trn při dodržení zadaných pravidel, musí během řešení nutně nastat následující situace: <strong>Na prvním trnu zbývá pouze největší disk, druhý trn je prázdný a všechny ostatní (menší) disky jsou vyskládané (do tvaru kužele) na trnu třetím.</strong>
    </p>
    <p>
        V tuto chvíli můžete přesunout největší disk z prvního trnu na druhý, čímž se dostanete do situace, která je vlastně úplně stejná jako ta počáteční, pouze o jeden disk „jednodušší“ – potřebujete přesunout disky z třetího trnu na druhý, přičemž jako pomocný nyní slouží trn první.
    </p>
    <p>
        Je zřejmé, že opakováním tohoto postupu se dříve nebo později (spíše později ;-) dostanete do situace, kdy na druhém trnu budou všechny disky kromě nejmenšího, který bude připraven k finálnímu přesunu na jednom z krajních trnů. Čímž řešení úlohy (a existence vesmíru :-) skončí.
    </p>
    <note>
        Rozbor ůlohy a vlastní algoritmus připraven podle: <cite>Pavel Töpfer: „Algoritmy a programovací techniky“. Prometheus, Praha, 2010.</cite>
    </note>

</slide>
<slide title="Algoritmus">

    <p>
        Algoritmicky se tak možná trochu překvapivě jedná o poměrně primitivní rekurzi, kdy jediným úkolem řešicí funkce/procedury je přenosit N disků z trnu A na trn B za pomoci odkládacího trnu C. Což daná funkce/procedura splní dvěma rekurzivními voláními, jak je naznačeno v následujícím pseudokódu:
    </p>
    <example lang="text">
        přenos(N, A, B, C)
            přenos(N-1, A, C, B)
            přenes(1, A, B, -)
            přenos(N-1, C, B, A)
    </example>
    <p>
        Prostřední přenesení jednoho zbývajícího disku na cílový trn proběhne také ještě rekurzivním voláním, ale třetí trn k tomu již není potřeba a po vykonání této operace se zjevně začne rekurze vracet po příslušném stromu volání nazpátek. Zbývající dvě větve rekurze se vždy volají pro menší (o jeden) počet disků, takže <strong>algoritmus je konečný</strong>.
    </p>

</slide>
<slide title="Řešení pro Karla I">

    <p>
        V Karlovi to tedy nepůjde úplně tak jednoduše, protože musíme připsat grafickou reprezentaci disků a jejich přenášení. Ale pokud disky nahradíme značkami (větší disk = více značek) a omezíme se v našem případě pouze na osm disků (ostatně jinak bychom se výsledku stěží dočkali), nějak to zařídíme. Na přenášení „disků“ vyhradíme tři sloupečky vedle sebe, první zaplníme v osmi řádcích správným počtem značek a třetí oddělíme od zbytku města neprostupnou zdí:
    </p>
    <img src="_files/hanoi/mesto_1.jpg" width="481" height="568" alt="Hanojské věže – počáteční stav" />
    <note>
        Zdrojový kód města: <a href="_files/hanoi/hanoj_mesto.k">hanoj_mesto.k</a>
    </note>

</slide>
<slide title="Řešení pro Karla II">

    <p>
        Jelikož v Karlovi neumíme říct, co znamená první, druhý nebo třetí trn, musíme to zařídit jinak. Konkrétně tak, že „trny“ (tedy sloupečky) budou uspořádané do kruhu, tj. že Karel v každém sloupečku bude moci jít jak doleva, tak doprava. Což znamená, že u krajních musí umět v případě potřeby přejít na protější stranu bez nabourání do zdi. O to se stará příkaz <code>PŘESUN</code>:
    </p>
    <example lang="karel">
        PŘESUN
          KDYŽ JE ZEĎ
            ČELEM VZAD
            KE ZDI
            ČELEM VZAD
          KONEC, JINAK
            KROK
          KONEC
        KONEC
    </example>
    <p>
        Vidíme, že pokud Karel např. potřebuje jít z prvního sloupečku doleva, kde by normálně naboural do zdi, dojde na sloupeček pravý, ale přitom se otočí na západ, tedy jako kdyby „prošel zdí“. (A to samé i z pravého sloupečku na levý, jen pak samozřejmě kouká na východ.) Pokud před Karlem zeď není, prostě udělá obyčejný krok.
    </p>
    <note>
        Řešení úlohy upraveno podle (elektronické) publikace: <cite>Jiří Osoba: „PC-Karel“, 2001.</cite>
    </note>

</slide>
<slide title="Řešení pro Karla III">

    <p>
        Přenesení disku z aktuálního místa (tedy všech značek v dané řádce aktuálního sloupečku) na sloupeček, na který se Karel dívá, pak provede ve spolupráci s předchozím příkazem <code>PŘESUN</code> následující příkaz <code>PŘENES DISK</code>:
    </p>
    <example lang="karel">
        PŘENES DISK
          KDYŽ JE ZNAČKA
            ZVEDNI
            PŘENES DISK
            POLOŽ
          KONEC, JINAK
            PŘESUN
          KONEC
        KONEC
    </example>
    <note>
        Přenesené „disky“ nám sice zůstavají viset ve vzduchu na stejné řádce, na které byly původně, ale důležité je, že na sebe se – díky posloupnosti (rekurzivních) volání – skládají ve správném pořadí (tj. podle „velikosti“).
    </note>

</slide>
<slide title="Řešení pro Karla IV">

    <p>
        Ještě potřebujeme dvě pomocné procedury pro přesun Karla na vyšší/nižší patro pyramidy, přičemž se Karel otočí <strong>do opačného směru</strong>..
    </p>
    <example lang="karel">
        ÚKROK NAHORU
          RYCHLE
            KDYŽ JE SEVER
              KROK
            KONEC, JINAK
              VLEVO VBOK
              ÚKROK NAHORU
              VLEVO VBOK
            KONEC
          POMALU
        KONEC

        ÚKROK DOLŮ
          RYCHLE
            KDYŽ JE JIH
              KROK
            KONEC, JINAK
              VLEVO VBOK
              ÚKROK DOLŮ
              VLEVO VBOK
            KONEC
          POMALU
        KONEC
    </example>
    <note>
        Proč ten opačný směr? Pokud Karel právě přenesl disk z aktuálního místa, další disk – pokud tam ještě nějaký je – bude nutně větší, takže ho nemůže přenést na stejné místo jako předchozí. Kde <a href="?slajd=3">původní algoritmus</a> při rekurzivním volání příslušně prohodil pořadí trnů pro přesun disků, my musíme v grafickém prostředí Karlova města zajistit totéž kapku komplikovanějším způsobem.
    </note>
    <p>
        ..a dvě standardní usnadňující pohyb Karla:
    </p>
    <example lang="karel">
        ČELEM VZAD
          RYCHLE
            VLEVO VBOK
            VLEVO VBOK
          POMALU
        KONEC

        KE ZDI
          DOKUD NENÍ ZEĎ
            KROK
          KONEC
        KONEC
    </example>
    <note>
        Pro zpřehlednění výsledku práce jsem tři z procedur vybavil rychlým provedením, protože dělají vcelku jednoduchou (téměř atomickou) činnost.
    </note>

</slide>
<slide title="Řešení pro Karla V">

    <p>
        A nyní již samotné řešení problému <em>Hanojských věží</em>:
    </p>
    <example lang="karel">
        HANOJ
          DOKUD JE ZNAČKA
            ÚKROK NAHORU
            HANOJ
            ÚKROK DOLŮ
            PŘENES DISK
            PŘESUN
            ÚKROK NAHORU
            HANOJ
            ÚKROK DOLŮ
            PŘESUN
          KONEC
        KONEC
    </example>
    <p>
        Nemusíte mu věřit, ale zkuste si ho pustit :-)
    </p>
    <note>
        Program začíná okrajovou podmínkou – pokud není značka (tedy nějaký disk), tak se nemusí nic přenášet. Pokud značka (tedy disk) je, tak přeneseme subvěž (věž o nejspodnější disk menší) na druhou stranu (ÚKROK NAHORU + HANOJ). Jelikož Karel po provedení této akce skončí tam, kde začal, musíme s ním zase slézt dolů (ÚKROK DOLŮ) a přenést ten poslední disk (PŘENES DISK). Pak se můžeme přesunout na právě přenošenou subvěž (PŘESUN + ÚKROK NAHORU) a přenést ji na čerstvě přesunutý základní disk (HANOJ). Následuje návrat na dolní úroveň (ÚKROK DOLŮ) a přesun na výchozí místo (PŘESUN), kde jsme začali.
    </note>

</slide>
<slide title="Komentáře">

    <p>
        Postup přesunu jednotlivých částí pyramidy se dá z průběhu algoritmu ve městě vysledovat, nicméně to, že Karel z levého sloupečku doleva „skrz stěnu“ musí z implementačních důvodů vlastně doprava – a na druhé straně naopak – dost ruší, takže sledovat jeho pohyby vlevo-vpravo je velmi náročné.
    </p>
    <note>
        Karel při hledání vrcholku pyramidy v aktuálním sloupečku potřebuje vyjít o jednu řadu výše, ale to je pro osm značek a rozměry města 10×10 splněno.
    </note>
    <p>
        „Věž“ samozřejmě nemusí na začátku stát v levém sloupečku – protože jsme zařídili, že věže jsou vlastně poskládané za sebou dokola, může být kdekoliv. Pouze Karel musí stát na jejím spodku (ale jeho směr také není důležitý).
    </p>
    <p>
        Pro pochopení, jak celý program funguje, je asi nejlepší vrátit se k dřevním dobám programování a zkusit si problém prokrokovat tužkou na papíře, nejdříve pro jednopatrovou a dvoupatrou věž, případně pak možná i pro třípatrovou.
    </p>
    <notes>
        <note>
            Ostatně takový (vystřihovací) papírový Karel byl své doby i součástí knihy <cite>Markéta Synovcová: „Martina si hraje s počítačem“. Albatros, 1989.</cite>
        </note>
        <note>
            PS: Když jsem v roce 1989 nastoupil na gymnázium, programování jsme sice měli už od prváku, ale (školní) počítač jsme poprvé viděli až ve druháku. Celý první rok jsme probírali pouze teorii a programovali jsme na papír. A to ještě ve vývojových diagramech. V ročníku druhém nás pak čekal počítač IQ151 a na něm víte kdo? No přece Karel ^_~
        </note>
    </notes>

</slide>


</lecture>
