List<T>, Dictionary<K,V>, cykly a metody z minulých hodin — nebo jen prohlížeč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 |
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:
List a hledání minima
cyklem. Je to pomalejší, ale vidět je do toho.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.
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.. 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.
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ží.
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é.
(int radek, int sloupec) je typ — „dvojice celých čísel, první se jmenuje
radek, druhé sloupec".(2, 3).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.
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) |
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.
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:
r >= 0 && r < mapa.Length — nevypadl z mapy nahoře ani dole,s >= 0 && s < mapa[0].Length — nevypadl vlevo ani vpravo,mapa[r][s] != '#' — není to zeď.Když první dvě kontroly vynecháš, program spadne na
IndexOutOfRangeException hned na kraji mapy.
[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:
navstivene.Hrubá síla není hloupá proto, že by nefungovala. Je hloupá proto, že dělá obrovské množství zbytečné práce.
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čí.
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.
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:
cena hotového + cena vstupu na souseda.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.
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> má 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.
# a spusť znovu.(2, 0).⬛ za 🧱 nebo 🌲.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í.
POZICE na (0, 0). Kolik sousedů má roh mapy?(3, 2) — políčko uprostřed zdi. Co vrátí metoda?r < mapa.Length a zkus pozici (5, 5).posunR/posunS i šikmé směry (−1,−1). Kolik sousedů bude teď?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.
LIMIT na 100. Co program vypíše u otevřené jeskyně?#. Kolikrát klesne počet volání?var hodiny = System.Diagnostics.Stopwatch.StartNew();.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.
[2,3] (dej tam #). Najde BFS jinou cestu?"######". Co program vypíše?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.
vzdalenost — uvidíš cenu ke každému políčku.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.
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() |
Zadání:
Bez mapy nemá smysl nic hledat. Nejdřív ji tedy nakresli a zjisti, kam se z políčka dá jít.
- 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";- 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.- Dokonči metodu
Sousedia 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 };aint[] posunS = { 0, 0, -1, 1 };
V cyklu spočítejint r = pozice.radek + posunR[i];aint 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 |
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:
else if není jen ozdoba.mapa[r][s] == "#" se nepřeloží (CS0019). Znak patří
do jednoduchých uvozovek.vystup += "\n"; na konci řádku se celá mapa
slije do jediného dlouhého řádku. Vypadá to jako chyba emoji, ale není.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.
Zadání:
Napiš hledání, které nemá žádný plán — prostě zkusí všechno. A změř, kolik práce to dá.
- 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.- 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í.- 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-1znamená „zatím nic nenalezeno".- Přidej limit volání a pusť hledání i na otevřené jeskyni
💡 Poradit
Hned zapocetVolani++;přidej pojistku:if (pocetVolani > LIMIT) { zastaveno = true; return; }
Pak zavolejProzkoumej(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 |
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:
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íš".return; v cíli. Program pokračuje z cíle dál a počítá nesmysly.navstivene, ten dostane StackOverflowException
a kartu prohlížeče na chvíli zatuhlou. Proto je v zadání limit.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ší.
Zadání:
Teď to samé chytře. Místo zkoušení všeho se mapou šíří vlna.
- Doplň frontu a množinu navštívených políček
💡 Poradit
Do fronty dej start a donavstiveneho zapiš taky:var fronta = new Queue<(int radek, int sloupec)>();fronta.Enqueue(START);
Ve smyčcewhile (fronta.Count > 0)ber vždyvar aktualni = fronta.Dequeue();a nové sousedy dávej na konec fronty.- U každého objeveného políčka si zapiš předchůdce
💡 Poradit
Slovníkpredchudcesi pamatuje, odkud ses kam dostal:predchudce[dalsi] = aktualni;
Zapisuj ho jen u políček, která ještě nejsou vnavstivene— jinak si přepíšeš kratší cestu tou delší.- 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ů jecesta.Count - 1, protože start se jako krok nepočítá.- 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 |
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:
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)).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.
Zadání:
Mapa dostane bažinu. Přes ni se dá jít, ale stojí čtyřikrát víc energie.
- 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čítejint nova = vzdalenost[aktualni] + Cena(TEREN[dalsi.radek][dalsi.sloupec]);- 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ď znehotovea přidej dohotove, ať ho nezpracuješ dvakrát.- 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 donehotove.- 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ší" |
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:
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.
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.
- 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á".- 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" };- 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íč");- 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 |
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).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.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.
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.csnebo snímek obrazovky s výstupem.
Ú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.
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.
| 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 |
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í.
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.
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.