← Přehled všech hodin

Hledání nejkratší cesty v bludišti

Cíl hodiny: Naučíš se hledat cestu v mapě. Uvidíš na vlastní oči, proč hrubá síla rychle dojde dech, a napíšeš dva algoritmy, které to zvládnou chytře a spolehlivě.
Jak stránku používat: vpravo nahoře je tlačítko Prezentace — schová všechno kromě jednoho bloku a přepíná se šipkami. U ukázek a cvičení je kód vlevo editovatelný: uprav ho a klikni na Spustit (nebo Ctrl+Enter). Program se přeloží a spustí přímo v prohlížeči a vpravo se objeví to, co vypsal do konzole. Nic se neinstaluje.
Kam to dnes směřuje: Tohle je hodina, kdy se z programování stane algoritmus: stejný výsledek, ale tisíckrát rychleji.

1. Vysvětlení a souvislosti

Poznámky pro učiteleJak celou hodinu vést1. Vysvětlení a souvislosti

Tenhle blok je jen pro vás — dětem ho nepromítejte.

Fáze Co se děje Čas
Rozjezd Otázka „zkoušel bys cestou do školy úplně všechny ulice?" 5 min
Mapa a sousedé Bloky 2–4, děti kreslí mřížku do konzole 20 min
Hrubá síla Ukázka 3, společně sledujeme počitadlo volání 15 min
BFS Blok 5 + ukázka 4, vysvětlení fronty 20 min
Dijkstra Blok 6 + ukázka 5, bažina jako cena 15 min
Cvičení Děti pracují samy, vy obcházíte 55 min
Závěr Porovnání počtu navštívených políček 10 min
Hlavní pedagogický cíl

Dnešek není o zapamatování definic. Je o jediném zážitku: stejnou úlohu lze řešit chytře nebo hloupě a je v tom obrovský rozdíl. Když dítě uvidí, že hrubá síla udělá 20 000 volání a BFS 36 kroků, pochopí, proč algoritmy existují.

Klíčové rozlišení, které si mají odnést:

  • BFS — všechny kroky stojí stejně, hledá se nejmenší počet kroků.
  • Dijkstra — každé políčko stojí jinak, hledá se nejnižší celková cena.
Tři věci, které dnes NEVYSVĚTLUJTE
  1. Složitost O(n). Stačí „roste to strašně rychle". Písmeno O nechte na střední.
  2. Prioritní frontu. Dijkstru děláme přes obyčejný List a hledání minima cyklem. Je to pomalejší, ale vidět je do toho.
  3. A*, heuristiky, grafové teorie. Kdo se zeptá, řekněte, že navigace v mobilu používá vylepšenou Dijkstru — a jděte dál.

Jak pracovat se smíšenou úrovní. Cvičení 1 a 3 zvládne každý, kdo umí cykly. Cvičení 2 (rekurze) je nejtěžší — slabším klidně nechte jen dokreslit výpis a počitadlo. Rychlíky posílejte na cvičení 4 a 5, tam je prostor na vlastní mapy.

Blok 1Mapa je jen tabulka písmenek1. Vysvětlení a souvislosti

Bludiště není obrázek, je to tabulka. Nejjednodušší způsob, jak ji v C# napsat, je pole textů: každý text je jeden řádek mapy.

  • MAPA[1] je celý druhý řádek, tedy text "##.#.#".
  • MAPA[1][0] je jeden znak z toho řádku — první políčko.
  • Tečka . znamená volno, mřížka # znamená zeď.

Pořadí indexů je vždycky [řádek][sloupec], tedy nejdřív shora dolů, pak zleva doprava. Zvykni si na to hned teď, ušetříš si dnes hodinu bolesti.

Velká písmena v názvu MAPA znamenají „tohle se během programu nemění". Je to jen zvyk, ale hodně pomáhá v orientaci.

Klikni vlevo na Spustit a objeví se tu výstup programu.
Proč znak a ne číslo

Mapu bys mohl uložit i jako čísla (0 = volno, 1 = zeď). Znaky mají ale obrovskou výhodu: mapa je čitelná už ve zdrojovém kódu. Vidíš rovnou, kudy vede cesta, a snadno ji přepíšeš. Až přidáme bažinu, bude to znak ~.

Pozor na jeden detail: znak se píše do jednoduchých uvozovek ('#'), text do dvojitých ("#"). Porovnání MAPA[1][0] == "#" se nepřeloží.

Blok 2Pozice je dvojice čísel1. Vysvětlení a souvislosti

Pozici v mapě popisují dvě čísla naráz. Dalo by se to řešit dvěma proměnnými r a s, ale pak bys je musel všude tahat v páru a pořád je plést.

C# na to má n-tici (anglicky tuple): dvě hodnoty spojené do jedné.

  • Zápis (int radek, int sloupec) je typ — „dvojice celých čísel, první se jmenuje radek, druhé sloupec".
  • Hodnota se vyrobí prostě jako (2, 3).
  • Na části se dostaneš tečkou: pozice.radek.

Nejlepší vlastnost n-tic: dají se porovnávat rovnítkem. pozice == cil je true, jen když se shodují obě čísla. Díky tomu se dají n-tice použít i jako klíč do Dictionary nebo prvek HashSet — a toho dnes hodně využijeme.

Klikni vlevo na Spustit a objeví se tu výstup programu.
Blok 3Vykreslení mapy z emoji1. Vysvětlení a souvislosti

V konzoli nemáme grafiku — a nepotřebujeme ji. Emoji jsou čtvereček jako každý jiný znak, takže z nich jde poskládat obrázek úplně stejně jako z rovnítek v první hodině.

Metoda Vykresli dělá jedinou věc: projde mapu řádek po řádku, sloupec po sloupci, pro každé políčko vybere jeden emoji a slepí z nich text.

Všimni si pořadí podmínek — start a cíl se testují jako první. Kdyby byla podmínka na zeď výš, přebila by je a start by nebyl vidět.

Metoda nic nevypisuje, jenom vrací hotový text. Vypisuje se až venku. Díky tomu ji můžeš použít i tam, kde si text chceš ještě upravit.

Emoji Význam
🟩 start
🟥 cíl
zeď
volné políčko
🟦 algoritmus se sem podíval
🟨 výsledná trasa
🟫 bažina (přijde u Dijkstry)
Klikni vlevo na Spustit a objeví se tu výstup programu.
Když se ti mapa rozjede

Některé terminály kreslí emoji trochu širší než mezeru, takže mřížka nemusí být přesně čtvercová. Nevadí to. Kdyby to vadilo, vyměň emoji za obyčejné znaky: # pro zeď, . pro volno, * pro trasu.

Blok 4Soused: kam se dá udělat krok1. Vysvětlení a souvislosti

Tohle je nejdůležitější metoda celé hodiny. Všechny tři dnešní algoritmy se pořád dokola ptají na jedinou věc: kam odsud můžu udělat krok?

Čtyři směry = čtyři posuny:

Směr řádek sloupec
nahoru −1 0
dolů +1 0
doleva 0 −1
doprava 0 +1

Soused se do seznamu dostane jen tehdy, když projde třemi kontrolami:

  1. r >= 0 && r < mapa.Length — nevypadl z mapy nahoře ani dole,
  2. s >= 0 && s < mapa[0].Length — nevypadl vlevo ani vpravo,
  3. mapa[r][s] != '#' — není to zeď.

Když první dvě kontroly vynecháš, program spadne na IndexOutOfRangeException hned na kraji mapy.

Klikni vlevo na Spustit a objeví se tu výstup programu.
Blok 5Hrubá síla: zkus úplně všechno1. Vysvětlení a souvislosti
              [0,0]
             /     \
         [0,1]     [1,0]
        /    \         \
   [0,2]    [1,1]      ...
     |        |
    ...      ...   (a tak pořád dál)

Hrubá síla (brute force) je algoritmus bez plánu. Z každého políčka zkusí všechny sousedy, z nich zase všechny sousedy, a tak pořád dokola, dokud nenarazí na cíl. Když narazí do slepé uličky, vrátí se o krok zpátky a zkusí jinou odbočku.

Tomu „zavolej sám sebe o kousek dál" se říká rekurze. Je to mocný nástroj, ale má dvě pasti:

  • Zacyklení. Bez poznámky „tady už jsem byl" by chodil pořád dokola mezi dvěma políčky. Proto si vedeme navstivene.
  • Exploze možností. Počet cest roste tak rychle, že u mapy 10×10 se nedopočítáš. Proto si přidáme limit volání jako pojistku.

Hrubá síla není hloupá proto, že by nefungovala. Je hloupá proto, že dělá obrovské množství zbytečné práce.

Blok 6BFS: prohledávání po vlnách1. Vysvětlení a souvislosti
krok 0        krok 1        krok 2        krok 3
🟩⬜⬜        🟩🟦⬜        🟩🟦🟦        🟩🟦🟦
⬜⬜⬜        🟦⬜⬜        🟦🟦⬜        🟦🟦🟦
⬜⬜🟥        ⬜⬜🟥        🟦⬜🟥        🟦🟦🟥

BFS = Breadth-First Search, česky prohledávání do šířky. Představ si, že do startu hodíš kámen a po mapě se šíří vlna. Nejdřív obejde všechna políčka vzdálená jeden krok, pak dva kroky, pak tři…

Protože se šíří po vlnách, první nalezení cíle je automaticky to nejkratší. Nemusíš porovnávat žádné cesty — až se vlna dotkne cíle, jsi hotov.

Celé to drží pohromadě jediná věc: fronta (Queue). Fronta funguje jako fronta na svačinu — kdo přijde první, jde první ven:

  • fronta.Enqueue(pozice) — postav políčko na konec fronty,
  • fronta.Dequeue() — vezmi políčko ze začátku,
  • fronta.Count — kolik jich čeká.

Kdybys místo fronty použil zásobník (poslední dovnitř, první ven), dostaneš prohledávání do hloubky — a to už nejkratší cestu nezaručí.

Jak si zapamatovat cestu

BFS ti sám o sobě řekne jen „cíl je dosažitelný". Aby šlo vypsat i trasu, potřebuješ druhou poznámku: u každého nově objeveného políčka si zapiš, odkud ses tam dostal:

predchudce[[2,3]] = [2,2]
predchudce[[2,2]] = [1,2]

Na konci jdeš od cíle po předchůdcích zpátky ke startu a seznam otočíš. Tomuhle triku se říká rekonstrukce cesty a používá ho i Dijkstra.

Blok 7Dijkstra: když každé políčko stojí jinak1. Vysvětlení a souvislosti
mapa cen              nejkratší (BFS)     nejlevnější (Dijkstra)
. . ~ . .             🟩🟨🟨⬜⬜           🟩⬜⬜⬜⬜
. # ~ # .             🟨⬛🟨⬛⬜           🟨⬛🟫⬛⬜
. . ~ . .             ⬜⬜🟨🟨🟥           🟨🟨🟨🟨🟥

. = 1 krok            7 kroků             9 kroků
~ = bažina (4)        cena 13             cena 9

Někdy není nejkratší cesta ta nejlepší. Bažina se dá přejít, ale stojí čtyřnásobek energie. Přes suchou zem to je delší, ale levnější.

BFS tohle neumí — pro něj má každý krok cenu 1. Dijkstrův algoritmus je jeho chytřejší bratr: ke každému políčku si píše dosud nejlevnější známou cenu a vždycky pokračuje z toho nejlevnějšího nehotového políčka.

Postup dokola:

  1. Z nehotových vyber políčko s nejnižší zapsanou cenou.
  2. Označ ho jako hotové (už se nebude měnit).
  3. Pro každého souseda spočítej cena hotového + cena vstupu na souseda.
  4. Když je to méně než dosud zapsaná cena souseda, přepiš ji a poznamenej si předchůdce.

Až vytáhneš z nehotových samotný cíl, jeho zapsaná cena je ta nejlevnější možná.

Přesně tohle dělá navigace v autě, když ti nabídne „delší, ale rychlejší trasu". Kilometry jsou kroky, minuty jsou cena.

Poznámky pro učiteleTechnické pozadí — jen pro vás1. Vysvětlení a souvislosti

Proč n-tice a ne dvě proměnné. (int radek, int sloupec) je ValueTuple, struktura s hodnotovou rovností — proto funguje == i použití v HashSet a Dictionary bez psaní GetHashCode. Kdybychom měli třídy (16. hodina), museli bychom rovnost dopisovat ručně nebo použít record.

Fronta v BFS. Queue<T>Enqueue/Dequeue v konstantním čase. Python v paralelním kurzu používá list.pop(0), což je ve skutečnosti pomalé — tady máme správný nástroj rovnou.

Dijkstra bez prioritní fronty. Vybíráme minimum cyklem přes List, tedy O(n) na každý krok. Pro mapu 6×6 je to naprosto jedno a algoritmus je díky tomu čitelný. Správné řešení je PriorityQueue<TElement, TPriority> (existuje od .NET 6) — zmiňte ji jen rychlíkům.

Proč hrubá síla exploduje. Hledáme všechny jednoduché cesty, kterých je v mřížce exponenciálně mnoho. Na mapě 6×6 to ještě doběhne, na 8×8 už můžete čekat minuty. Proto je ve cvičení limit volání — bez něj by dětem zamrzla záložka prohlížeče a obvinily by z toho stránku, ne algoritmus.

Rekurze a zásobník. Hloubka rekurze je omezená velikostí zásobníku (StackOverflowException). U 36 políček to nehrozí, ale kdyby někdo zkusil mapu 50×50 bez navstivene, spadne to tvrdě a nejde to odchytit try/catch. Proto je poznámka „už navštívené neprocházej" dnes povinná, ne kosmetická.

Co běží v prohlížeči. Tlačítko Spustit překládá kód Roslynem uvnitř prohlížeče. Kolekce, n-tice i rekurze fungují normálně; jediné, co si pohlídejte, je čas běhu — hrubá síla s vysokým limitem umí stránku na pár sekund zaseknout.

2. Ukázky pro projekci

Blok 8Ukázka 1 — Mapa dungeonu v konzoli2. Ukázky pro projekci
Studenti zkuste změnit
  • Přidej do mapy další zeď # a spusť znovu.
  • Posuň cíl na jiné políčko — třeba (2, 0).
  • Vyměň emoji zdi za 🧱 nebo 🌲.
  • Přidej sedmý řádek mapy. Co se stane, když bude kratší než ostatní?
demo/demo1_mapa.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.

Co si z toho odnést: mapa je pole textů, vykreslení je dvojitý cyklus a jedna podmínka na políčko. Nic víc v „grafice" není.

Blok 9Ukázka 2 — Sousedé a první krok2. Ukázky pro projekci
Studenti zkuste změnit
  • Změň POZICE na (0, 0). Kolik sousedů má roh mapy?
  • Změň ji na (3, 2) — políčko uprostřed zdi. Co vrátí metoda?
  • Smaž z podmínky kontrolu r < mapa.Length a zkus pozici (5, 5).
  • Přidej do posunR/posunS i šikmé směry (−1,−1). Kolik sousedů bude teď?
demo/demo2_sousedi.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.

Co si z toho odnést: {nazvy[i],-8} v interpolaci zarovná text na osm znaků doleva. Je to zkratka za PadRight(8) a v tabulkách se hodí pořád.

Blok 10Ukázka 3 — Hrubá síla: zkus všechny cesty2. Ukázky pro projekci
Studenti zkuste změnit
  • Sniž LIMIT na 100. Co program vypíše u otevřené jeskyně?
  • Přidej do otevřené jeskyně pár zdí #. Kolikrát klesne počet volání?
  • Zvyš otevřenou jeskyni na 6×6 (šest řádků po šesti tečkách). Doběhne to?
  • Vypiš i čas běhu pomocí var hodiny = System.Diagnostics.Stopwatch.StartNew();.
demo/demo3_hruba_sila.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.

Co si z toho odnést: v úzkém dungeonu je zdí tolik, že cest skoro není — hrubá síla se zdá být v pohodě. Jakmile zdi zmizí, počet cest exploduje: v otevřené jeskyni 5×5 je jich 8 512 a rekurze se zavolá přes 90 000krát. Kdyby byla jeskyně 6×6, je to přes 18 milionů volání. A přitom pořád mluvíme o mapě velké jako šachovnice pro trpaslíky.

Blok 11Ukázka 4 — BFS: nejkratší cesta v krocích2. Ukázky pro projekci
Studenti zkuste změnit
  • Vypiš i počet navštívených políček a porovnej ho s počtem volání u hrubé síly.
  • Zazdi políčko [2,3] (dej tam #). Najde BFS jinou cestu?
  • Zazdi celý řádek "######". Co program vypíše?
  • Přidej výpis pořadí, v jakém BFS políčka vytahuje z fronty.
demo/demo4_bfs.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.

Co si z toho odnést: fronta, množina navštívených a slovník předchůdců. Tyhle tři věci stačí na nejkratší cestu v jakémkoli bludišti.

Blok 12Ukázka 5 — Dijkstra: nejlevnější cesta přes bažinu2. Ukázky pro projekci
Studenti zkuste změnit
  • Změň cenu bažiny ze 4 na 1. Kudy povede trasa teď?
  • Změň cenu bažiny na 20. Změní se něco?
  • Uber jeden sloupec bažiny (dej tam tečku). Při jaké šířce se vyplatí bažinu přejít?
  • Vypiš i obsah slovníku vzdalenost — uvidíš cenu ke každému políčku.
demo/demo5_dijkstra.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.

Co si z toho odnést: BFS by na téhle mapě proletělo bažinou rovnou doprava — 5 kroků, ale cena 14. Dijkstra jde oklikou po suchu: 11 kroků a cena 11. Delší trasa může být levnější a přesně o tom je dnešní druhá polovina hodiny.

Blok 13Když se něco pokazí2. Ukázky pro projekci
Unhandled exception. System.IndexOutOfRangeException:
  Index was outside the bounds of the array.

Nejčastější chyba dneška. Sáhl jsi na políčko, které v mapě není — většinou proto, že v Sousedi chybí kontrola okraje.

Pamatuj na pořadí v podmínce: nejdřív se ptej, jestli je index uvnitř mapy, a až potom na obsah políčka. C# vyhodnocuje && zleva doprava a při prvním false se zastaví — takže r < mapa.Length && mapa[r][s] != '#' je bezpečné, opačné pořadí spadne.

Druhá varianta téhle chyby: mapa má řádky různě dlouhé. Zkontroluj, že všechny texty v poli mají stejný počet znaků.

Hláška / projev Co to znamená Jak to opravit
IndexOutOfRangeException sáhnutí mimo mapu doplň kontroly r >= 0 && r < mapa.Length
KeyNotFoundException čteš predchudce[uzel], který tam není cesta neexistuje — nejdřív ověř navstivene.Contains(CIL)
program běží donekonečna chybí navstivene už navštívené políčko nikdy neprocházej znovu
StackOverflowException rekurze bez zastavení přidej podmínku na cíl a poznámku o navštívených
CS0019: Operator '==' cannot be applied porovnáváš znak s textem mapa[r][s] == '#', ne == "#"
BFS najde delší cestu políčko se přidá do fronty vícekrát do navstivene ho zapiš hned při vložení do fronty
Dijkstra vrací stejnou cestu jako BFS všechna políčka mají cenu 1 zkontroluj, že bažina ~ opravdu vrací 4
cesta je obrácená rekonstrukce jde od cíle na konci zavolej cesta.Reverse()

3. Cvičení

Jak cvičení používat: zadání promítněte. Části Pro učitele a Řešení jsou schované — rozbalí se až kliknutím, takže je můžete mít otevřené a děti je neuvidí, dokud nechcete. Kód ve cvičení i v řešení jde spustit a upravovat přímo tady v prohlížeči.
Blok 14Cvičení 1 — Nakresli mapu a najdi sousedy (15 min)3. Cvičení

Zadání:

Bez mapy nemá smysl nic hledat. Nejdřív ji tedy nakresli a zjisti, kam se z políčka dá jít.

  1. Dokonči metodu Vykresli, ať poskládá mapu z emoji ⬛ a ⬜
    💡 Poradit
    Projdi mapu dvěma cykly: vnější přes řádky, vnitřní přes sloupce.
    Pro každé políčko přidej do textu jeden emoji: if (mapa[r][s] == '#') vystup += "⬛"; else vystup += "⬜";
    Na konci každého řádku přidej odřádkování: vystup += "\n";
  2. Označ start 🟩 a cíl 🟥 — pozor na pořadí podmínek
    💡 Poradit
    Start a cíl musí být otestované dřív než zeď, jinak je zeď přebije.
    if (r == start.radek && s == start.sloupec) vystup += "🟩";
    else if (r == cil.radek && s == cil.sloupec) vystup += "🟥";
    Teprve pak přijde podmínka na zeď a nakonec volné políčko.
  3. Dokonči metodu Sousedi a vypiš, kam se dá z políčka [2,2] jít
    💡 Poradit
    Čtyři směry si připrav jako dvě pole posunů: int[] posunR = { -1, 1, 0, 0 }; a int[] posunS = { 0, 0, -1, 1 };
    V cyklu spočítej int r = pozice.radek + posunR[i]; a int s = pozice.sloupec + posunS[i];
    Soused se počítá jen tehdy, když je uvnitř mapy a není to zeď:
    if (r >= 0 && r < mapa.Length && s >= 0 && s < mapa[0].Length && mapa[r][s] != '#')
Úroveň Co má zvládnout
Minimum Body 1–2, mapa se vykreslí i se startem a cílem
Rozšíření Bod 3, výpis sousedů políčka [2,2]
Výzva Vypsat počet sousedů pro každé volné políčko mapy
cviceni/cviceni1_mapa.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Pro učitele — na co si dát pozor

Očekávaný výsledek: vykreslená mřížka 6×6 se zeleným startem, červeným cílem a tři vypsaní sousedé políčka [2,2].

Kde se zaseknou:

  • Pořadí podmínek. Když je test na zeď první, start i cíl zmizí. Nechte je na to přijít samy — je to nejlepší ukázka toho, že else if není jen ozdoba.
  • Znak vs. text. mapa[r][s] == "#" se nepřeloží (CS0019). Znak patří do jednoduchých uvozovek.
  • Zapomenuté odřádkování. Bez vystup += "\n"; na konci řádku se celá mapa slije do jediného dlouhého řádku. Vypadá to jako chyba emoji, ale není.
  • Prohozené indexy. mapa[s][r] na čtvercové mapě nespadne, jen mapu překlopí. Poznají to podle toho, že zdi jsou „naležato".

Kdo je hotový dřív: ať vypíše počet sousedů pro každé volné políčko a najde políčko s nejvíc sousedy (křižovatku).

Otázka na závěr cvičení: „Proč testujeme okraj mapy dřív než obsah políčka?" Odpověď: protože jinak sáhneme mimo pole a program spadne.

Řešení 1 — jedna z možných variant
reseni/reseni1_mapa.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Blok 15Cvičení 2 — Hrubá síla: zkus všechny cesty (20 min)3. Cvičení

Zadání:

Napiš hledání, které nemá žádný plán — prostě zkusí všechno. A změř, kolik práce to dá.

  1. Doplň tělo rekurze Zkus: projdi sousedy, kteří ještě nejsou navštívení
    💡 Poradit
    V cyklu přes sousedy přeskoč ty, kde už jsi byl: if (navstivene.Contains(dalsi)) continue;
    Pak políčko označ, zavolej rekurzi a po návratu značku zase ukliď:
    navstivene.Add(dalsi);
    Zkus(dalsi, kroku + 1, navstivene);
    navstivene.Remove(dalsi);
    Bez toho úklidu bys prohledal jen jednu jedinou cestu.
  2. Když jsi v cíli, započítej nalezenou cestu a vrať se
    💡 Poradit
    Úplně na začátku metody se zeptej, jestli už nejsi v cíli:
    if (pozice == cil) { pocetCest++; return; }
    return; je důležité — z cíle se nikam dál nechodí.
  3. Přidej počitadlo volání a výpis nejkratší nalezené cesty
    💡 Poradit
    Počitadlo zvyš hned na prvním řádku metody: pocetVolani++;
    Délku si hlídej v cíli: if (nejkratsi == -1 || kroku < nejkratsi) nejkratsi = kroku;
    Hodnota -1 znamená „zatím nic nenalezeno".
  4. Přidej limit volání a pusť hledání i na otevřené jeskyni
    💡 Poradit
    Hned za pocetVolani++; přidej pojistku:
    if (pocetVolani > LIMIT) { zastaveno = true; return; }
    Pak zavolej Prozkoumej(JESKYNE, (0, 0), (4, 4), "otevřená jeskyně 5x5"); a porovnej obě čísla.
Úroveň Co má zvládnout
Minimum Body 1–2, program najde cesty do cíle
Rozšíření Bod 3 s počtem volání a délkou nejkratší cesty
Výzva Bod 4 a odpověď na otázku, kolikrát víc práce dá otevřená mapa
cviceni/cviceni2_hruba_sila.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Pro učitele — na co si dát pozor

Očekávaný výsledek: dungeon 24 volání a 2 cesty, otevřená jeskyně přes 90 000 volání a 8 512 cest. Ten rozdíl je celé poselství hodiny.

Kde se zaseknou:

  • Zapomenutý úklid navstivene.Remove(dalsi);. Program doběhne, ale najde jen jednu cestu. Je to nejtěžší moment cvičení — vysvětlete to jako „drobečky si po sobě musíš posbírat, jinak už se tou cestou nikdy nevrátíš".
  • Chybějící return; v cíli. Program pokračuje z cíle dál a počítá nesmysly.
  • Zacyklení. Kdo vynechá navstivene, ten dostane StackOverflowException a kartu prohlížeče na chvíli zatuhlou. Proto je v zadání limit.
  • Počitadlo uvnitř rekurze. V C# je pocetVolani normální proměnná z okolní metody a rekurze do ní zapisuje přímo. (V Pythonu by na to bylo potřeba nonlocal — kdo dělá oba kurzy, tohle ocení.)

Kdo je hotový dřív: ať zkusí jeskyni 6×6 (šest řádků po šesti tečkách). S limitem 200 000 uvidí hlášku o zastavení — ve skutečnosti je tam přes 18 milionů volání.

Otázka na závěr cvičení: „Kolikrát víc práce dá mapa bez zdí?" Odpověď: skoro čtyřtisíckrát. A přitom je o jedno políčko menší.

Řešení 2 — jedna z možných variant
reseni/reseni2_hruba_sila.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Blok 16Cvičení 3 — BFS: nejkratší cesta v krocích (20 min)3. Cvičení

Zadání:

Teď to samé chytře. Místo zkoušení všeho se mapou šíří vlna.

  1. Doplň frontu a množinu navštívených políček
    💡 Poradit
    Do fronty dej start a do navstivene ho zapiš taky:
    var fronta = new Queue<(int radek, int sloupec)>();
    fronta.Enqueue(START);
    Ve smyčce while (fronta.Count > 0) ber vždy var aktualni = fronta.Dequeue(); a nové sousedy dávej na konec fronty.
  2. U každého objeveného políčka si zapiš předchůdce
    💡 Poradit
    Slovník predchudce si pamatuje, odkud ses kam dostal:
    predchudce[dalsi] = aktualni;
    Zapisuj ho jen u políček, která ještě nejsou v navstivene — jinak si přepíšeš kratší cestu tou delší.
  3. Poskládej cestu od cíle ke startu a vypiš počet kroků
    💡 Poradit
    Jdi od cíle po předchůdcích, dokud nedojdeš na start, a nakonec seznam otoč:
    var uzel = CIL;
    while (uzel != START) { uzel = predchudce[uzel]; cesta.Add(uzel); }
    cesta.Reverse();
    Počet kroků je cesta.Count - 1, protože start se jako krok nepočítá.
  4. Vykresli navštívená políčka 🟦 i výslednou trasu 🟨
    💡 Poradit
    Cestu si převeď na množinu, ať se v ní rychle hledá: var trasa = new HashSet<(int radek, int sloupec)>(cesta);
    Ve vykreslování pak přidej dvě nové větve před volné políčko:
    else if (trasa.Contains(p)) vystup += "🟨";
    else if (navstivene.Contains(p)) vystup += "🟦";
Úroveň Co má zvládnout
Minimum Body 1–2, BFS projde mapu a ví, že cíl našel
Rozšíření Bod 3 s vypsanou délkou cesty
Výzva Bod 4 a porovnání počtu navštívených políček s hrubou silou
cviceni/cviceni3_bfs.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Pro učitele — na co si dát pozor

Očekávaný výsledek: 21 navštívených políček, cesta dlouhá 10 kroků a vykreslená mapa, kde je žlutá trasa vidět uprostřed modrých „ohledaných" políček.

Kde se zaseknou:

  • Zápis do navstivene až při vytažení z fronty. Políčko se pak do fronty dostane několikrát a cesta vyjde delší. Správně: zapiš hned při vložení.
  • KeyNotFoundException u predchudce[CIL]. Znamená, že cíl není dosažitelný. Proto je před skládáním cesty podmínka if (navstivene.Contains(CIL)).
  • Zapomenuté cesta.Reverse();. Trasa se vypíše od cíle ke startu. Není to chyba programu, jen obrácený směr — a je to dobrá otázka k zamyšlení.
  • Queue vs List. Kdo použije List s RemoveAt(0), dostane stejný výsledek. Pochvalte ho, ale ukažte, že Queue přesně tohle umí pojmenovaně.

Kdo je hotový dřív: ať pustí BFS i na otevřené jeskyni 5×5 z minulého cvičení a porovná: hrubá síla 90 111 volání, BFS 25 políček.

Otázka na závěr cvičení: „Proč je první nalezení cíle automaticky nejkratší?" Odpověď: protože fronta zpracuje všechna políčka vzdálená 1 krok dřív než ta na 2 kroky.

Řešení 3 — jedna z možných variant
reseni/reseni3_bfs.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Blok 17Cvičení 4 — Dijkstra s bažinou (20 min)3. Cvičení

Zadání:

Mapa dostane bažinu. Přes ni se dá jít, ale stojí čtyřikrát víc energie.

  1. Doplň cenu políčka a tabulku nejlevnějších vzdáleností
    💡 Poradit
    Cena se neplatí za krok, ale za políčko, na které vstoupíš:
    int Cena(char znak) => znak == '~' ? 4 : 1;
    Do tabulky si zapiš start s nulou: var vzdalenost = new Dictionary<(int radek, int sloupec), int> { [START] = 0 };
    Pro souseda pak spočítej int nova = vzdalenost[aktualni] + Cena(TEREN[dalsi.radek][dalsi.sloupec]);
  2. Vždycky vyber nehotové políčko s nejnižší zapsanou cenou
    💡 Poradit
    Minimum najdeš obyčejným cyklem:
    var aktualni = nehotove[0];
    foreach (var uzel in nehotove) if (vzdalenost[uzel] < vzdalenost[aktualni]) aktualni = uzel;
    Vybrané políčko pak vyhoď z nehotove a přidej do hotove, ať ho nezpracuješ dvakrát.
  3. Novou cenu zapiš jen tehdy, když je nižší než ta dosavadní
    💡 Poradit
    Tohle je jádro celé Dijkstry:
    if (!vzdalenost.ContainsKey(dalsi) || nova < vzdalenost[dalsi])
    Uvnitř zapiš novou cenu, poznamenej předchůdce a pokud políčko ještě nikde není, přidej ho do nehotove.
  4. Vypiš cenu i počet kroků a porovnej s BFS na stejné mapě
    💡 Poradit
    Celková cena už v tabulce leží: vzdalenost[CIL].
    BFS máš připravené v metodě BfsCesta() — zavolej ho a jeho cenu si dopočítej součtem cen políček na trase:
    foreach (var p in bfsCesta.Skip(1)) bfsCena += Cena(TEREN[p.radek][p.sloupec]);
Úroveň Co má zvládnout
Minimum Body 1–2, algoritmus projde mapu
Rozšíření Bod 3 a správná cena do cíle
Výzva Bod 4 s porovnáním „nejkratší vs. nejlevnější"
cviceni/cviceni4_dijkstra.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Pro učitele — na co si dát pozor

Očekávaný výsledek: BFS 5 kroků za cenu 14, Dijkstra 11 kroků za cenu 11. Delší trasa je levnější — přesně o tom je celé cvičení.

Kde se zaseknou:

  • Cena za krok místo za políčko. Když přičtou 1 za každý krok a bažinu neřeší, vyjde jim totéž co BFS. Zdůrazněte: platí se za vstup na políčko.
  • Zapomenutá podmínka „jen když je nová cena nižší". Bez ní se tabulka přepisuje horšími hodnotami a výsledek je náhodný.
  • Políčko dvakrát v nehotove. Proto ta dvojitá kontrola !hotove.Contains(dalsi) && !nehotove.Contains(dalsi).
  • vzdalenost[CIL] u nedosažitelného cíle. Spadne na KeyNotFoundException. Kdo si hraje se zazděním mapy, ať to ošetří ContainsKey.

Kdo je hotový dřív: ať zkusí, při jaké ceně bažiny se přestane vyplácet obcházet (odpověď: kolem 2 — tehdy se obě trasy potkají).

Otázka na závěr cvičení: „Kdy je Dijkstra zbytečná?" Odpověď: když mají všechna políčka stejnou cenu. Pak dělá totéž co BFS, jen pomaleji.

Řešení 4 — jedna z možných variant
reseni/reseni4_dijkstra.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Blok 18Cvičení 5 — CsQuest: naplánuj výpravu dungeonem (25 min)3. Cvičení

Zadání:

Hrdina stojí u vchodu do dungeonu, v jeho útrobách je truhla. Naplánuj mu cestu a spočítej, co ho to bude stát.

  1. Najdi hrdinovi trasu k truhle pomocí hotové metody NajdiCestu
    💡 Poradit
    Metoda už je napsaná — stačí ji zavolat a výsledek si uložit:
    var cesta = NajdiCestu();
    Vždycky ošetři případ, kdy cesta neexistuje: if (cesta.Count == 0) { ... }. Prázdný seznam znamená „truhla je zazděná".
  2. Vytvoř hrdinu: jméno, životy, zlato a inventář
    💡 Poradit
    Stačí obyčejné proměnné a jeden seznam:
    string jmeno = "Arkon";
    int zivoty = 100;
    int zlato = 40;
    var inventar = new List<string> { "Meč", "Lektvar" };
  3. Každý krok ubere 2 životy, za výpravu přidej zlato a kořist
    💡 Poradit
    Počet kroků máš z délky trasy: int kroky = cesta.Count - 1;
    zivoty = Math.Max(0, zivoty - kroky * 2);
    zlato += 30 - kroky;
    Kořist přidej do inventáře: inventar.Add("Zlatý klíč");
  4. Vypiš deník výpravy a vykresli trasu na mapě
    💡 Poradit
    Deník je obyčejný seznam vět, do kterého během výpravy přidáváš:
    var denik = new List<string>();
    denik.Add($"Vyrazil jsem z [{START.radek},{START.sloupec}].");
    Na konci ho vypiš cyklem a pod něj přidej vykreslenou mapu s trasou 🟨.
Úroveň Co má zvládnout
Minimum Body 1–2, program najde trasu a vypíše hrdinu
Rozšíření Bod 3, hrdina po výpravě opravdu ztratí životy a získá zlato
Výzva Bod 4 s deníkem, mapou a hláškou, když hrdina výpravu nepřežije
cviceni/cviceni5_vyprava.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Pro učitele — na co si dát pozor

Očekávaný výsledek: deník o čtyřech větách, hrdina po výpravě s 80 životy a 60 zlaťáky a mapa se žlutou trasou od 🟩 k 🟥.

Kde se zaseknou:

  • cesta.Count - 1 u prázdné cesty. Vyjde -1 a čísla jsou nesmyslná. Proto se nejdřív ptáme if (cesta.Count == 0).
  • Zapomenutý Math.Max(0, ...). Hrdina může mít záporné životy. Je to dobrý moment připomenout, že hra musí hlídat i nesmyslné stavy.
  • return; uprostřed programu. V top-level kódu funguje a ukončí program — někoho to překvapí. Stojí za to to říct nahlas.
  • Deník se vypisuje průběžně. Ať si zvyknou nejdřív posbírat data a teprve na konci je vypsat. Hodí se to, až budeme hru ukládat do souboru.

Kdo je hotový dřív: ať do dungeonu přidá bažinu ~ a použije Dijkstru ze cvičení 4 — cesta pak bude „nejméně vyčerpávající", ne „nejkratší".

Otázka na závěr hodiny: „Která ze tří dnešních metod by se hodila do navigace v mobilu?" Odpověď: Dijkstra, protože silnice mají různou rychlost — a hrubá síla by ti našla cestu do školy tak za tři roky.

Řešení 5 — jedna z možných variant
reseni/reseni5_vyprava.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.

4. Domácí úkol

Blok 19Domácí úkol na příští hodinu4. Domácí úkol

Vytvoř program „Navigátor dungeonu", který na jedné mapě porovná oba chytré algoritmy:

  • mapa má zdi #, suchou zem . a bažinu ~ a vykreslí se emoji,
  • BFS najde trasu s nejmenším počtem kroků,
  • Dijkstra najde trasu s nejnižší cenou,
  • program vypíše u obou počet kroků, cenu a počet navštívených políček,
  • a nakonec napíše větu, která trasa se hrdinovi víc vyplatí a proč.

Přines soubor Program.cs nebo snímek obrazovky s výstupem.

Pro učitele

Úkol schválně nemá jedinou správnou mapu. Nejcennější část je poslední bod — věta, ve které dítě vysvětlí rozdíl vlastními slovy. Tam se pozná, jestli hodina dosedla.

Na začátku příští hodiny promítněte dvě tři mapy od dětí a nechte třídu hádat, který algoritmus vyhraje. Zabere to pět minut a je to nejlepší opakování.

Kdo nemá doma .NET, může úkol udělat rovnou tady v prohlížeči a poslat zkopírovaný výstup.

Zobrazit ukázkové řešení domácího úkolu

Toto je jedna z možných variant, jak mohl úkol dopadnout. Tvoje řešení se může lišit a to je v pořádku.

domaci/domaci_ukol12.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Blok 20Oficiální dokumentace4. Domácí úkol
Co jsme použili Oficiální dokumentace Co tam najdeš
Queue<T> Queue<T> fronta, Enqueue, Dequeue, Count
HashSet<T> HashSet<T> množina bez duplicit a rychlé Contains
Dictionary<K,V> Dictionary<TKey,TValue> klíč a hodnota, ContainsKey
List<T> List<T> seznam, Add, Remove, Reverse
n-tice (int, int) N-tice v C# pojmenované části, porovnávání, rozbalení
lokální metody Lokální funkce metoda uvnitř metody a přístup k okolním proměnným
string.Join String.Join spojení seznamu do jednoho textu
Math.Max Math.Max větší ze dvou čísel (hlídání záporných životů)
PriorityQueue PriorityQueue<TElement,TPriority> rychlá varianta Dijkstry pro rychlíky
Jak s dokumentací pracovat na hodině

Nechte je najít Queue<T> a ukažte, že Enqueue a Dequeue mají v dokumentaci přesně tu jednu větu, kterou dnes potřebovali. Je to dobrá lekce v tom, že dokumentace není kniha ke čtení, ale slovník k nahlédnutí.

5. Umíme informatiku

Blok 21Navazující cvičení na Umíme informatiku5. Umíme informatiku

Pro rychlejší studenty, kteří dokončí cvičení dřív, nebo na posledních 10–15 minut.

Cvičení Odkaz Proč se hodí
Robot na mřížce umimeinformatiku.cz Pohyb po políčkách a plánování trasy bez psaní kódu
Bludiště umimeinformatiku.cz Hledání cesty jako hlavolam — přesně dnešní téma
Grafy a sítě umimeinformatiku.cz Políčka jako uzly a kroky jako hrany

Robot na mřížce je nejvhodnější, protože děti řeší přesně tu úlohu, kterou dnes programovaly — jen rukou. Kdo si to zkusí na papíře, lépe pochopí, proč BFS objevuje mapu po vrstvách.

Jak to zařadit

Nedávejte to celé třídě. Použijte to jako záložní aktivitu pro tři až pět nejrychlejších dětí.

Dobrá alternativa pro rychlíky: ať si nakreslí vlastní mapu 8×8 na papír, napíšou ji do programu a zkusí uhodnout, kolik políček BFS navštíví, než ho spustí. Většina lidí se plete o polovinu.