sorted().
O(n²), O(n log n), sorted, list.sort, key=, reverse=Navazujeme plynule: minule děti řešily cestu bludištěm, dnes řešíme jiný typ úlohy — jak rychle seřadit věci.
Dobrá analogie: karty v ruce.
Když je řadíš po jedné výměně vedle sebe, je to pomalé (bubble).
Když je nejdřív rozdělíš na menší hromádky a ty pak chytře spojíš, je to rychlejší (merge/quick).
Co chceme, aby si odnesly:
sorted() nebo .sort().key= je extrémně užitečné pro herní data (inventář, žebříčky).list.sort() se zapomíná, že vrací None (řadí na místě). sorted() vrací nový seznam.Než se vrhneme na celé algoritmy, ukaž si tu úplně nejmenší stavební cihličku, ze které jsou postavené úplně všechny dnešní řadicí algoritmy.
Tohle je základ všeho: podívej se na dvě sousední hodnoty, a pokud jsou v obráceném pořadí, prohoď je. Řadicí algoritmus nedělá nic jiného — jen tohle porovnání a prohození opakuje znovu a znovu, na různých místech pole.
Pro tři čísla to jde napsat ručně — tři porovnání a hotovo. Zkus si představit, kolik porovnání bys musel napsat ručně pro 100 čísel. Přesně proto existují algoritmy: umí tohle opakování udělat za tebe, ať je čísel 3, nebo 3 miliony.
Minule jsme hledali nejkratší cestu bludištěm, dnes budeme „hledat správné pořadí“.
Každé číslo je výška sloupce. Cíl: sloupce od nejnižšího po nejvyšší.
Bubble sort porovnává sousedy. Když jsou obráceně, prohodí je. A pořád dokola.
Představ si řadu čísel jako frontu dětí. Vždycky se podíváš na dvě děti vedle sebe: když vyšší stojí vlevo a menší vpravo, prohodí si místa. Takto projdeš celou řadu zleva doprava a pak to uděláš znovu, znovu a znovu, dokud už není co prohazovat. Je to jako když bublinky postupně "vyplavou" na správné místo. Je to ale pomalé, protože u velké hromady čísel děláš skoro tolik průchodů, kolik je čísel, a v každém průchodu kontroluješ skoro všechno znovu.
Merge sort pole rozdělí, seřadí malé části a pak je postupně sloučí.
Tady použijeme trik "rozděl a panuj". Velkou hromadu nejdřív rozpůlíš, potom každou půlku zase rozpůlíš, až zbudou hromádky po jednom čísle. Hromádka s jedním číslem je vlastně už seřazená. Pak začneš hromádky spojovat zpátky: vždy vezmeš menší číslo zepředu levé nebo pravé hromádky. Je to podobné jako když spojuješ dva už seřazené balíčky karet do jednoho seřazeného balíčku.
Quick sort si vybere pivot a rozdělí pole na menší/větší část.
Quick sort si vždy vybere jedno číslo jako "pivot" (takové dočasné pravítko). Pak projde ostatní čísla a menší dá nalevo od pivotu, větší napravo. Tím vzniknou dvě menší skupiny, které se dají řešit úplně stejným trikem znovu. Znovu vybereš pivot, rozdělíš, a pokračuješ, dokud nejsou skupiny hotové. Nakonec z toho složíš celé seřazené pole.
Reální programátoři většinou nepíšou vlastní řazení od nuly. Použijí sorted() nebo .sort().
V praxi skoro nikdy nepíšeš vlastní bubble, merge ani quick sort od nuly. Python už má vestavěné řazení, které je rychlé, odladěné a používá se v reálných projektech každý den. Nejčastěji sáhneš po sorted() (vrátí nový seřazený seznam) nebo po .sort() (seřadí původní seznam). Parametr key= říká Pythonu, podle čeho se má řadit, třeba podle hodnoty předmětu místo podle jeho názvu. Díky tomu umíš snadno řadit i složitější data, nejen samotná čísla.
Zadání:
- Napiš funkci
bubble_kroky(data), která vrátí seřazené kroky bubble sortu💡 Poradit
Nejdřív si připrav funkci, která dostane seznam a vrátí výsledky. Uvnitř pracuj s kopií, ať původní data zůstanou nedotčená.
Bubble sort je jen opakované porovnání dvou sousedů a jejich případné prohození.def bubble_kroky(data):
arr = data.copy()
for i in range(len(arr)):
for j in range(len(arr) - 1 - i):
... # porovnej arr[j] a arr[j + 1]- Ukládej snímky po každém porovnání a zobraz je přes slider +
st.bar_chart💡 Poradit
Po každém porovnání si ulož „fotku“ pole — tedyarr.copy(), ne samotnéarr(jinak by se ti všechny snímky měnily naráz).
Slider ti pak vybere jeden snímek ze seznamu a ten vykreslíš grafem.kroky.append({"pole": arr.copy(), "popis": popis})
i = st.slider("Krok", 0, len(kroky) - 1, 0)
st.bar_chart(kroky[i]["pole"])- Přidej počitadla
porovnaniaprohozeni💡 Poradit
Založ si dvě proměnné na nulu ještě před smyčkami.porovnanizvyšuj pokaždé, když dva prvky porovnáš — tedy v každém průchodu vnitřní smyčky.prohozenizvyšuj jen tehdy, když opravdu došlo k výměně.
Na konci obě čísla vrať spolu s kroky.- (Rozšíření) Přidej text, co se v aktuálním kroku stalo
💡 Poradit
Text si vytvoř jako obyčejný řetězec a ulož ho do snímku vedle pole (klíčpopis). U slideru pak popis jen vypíšeš.popis = f"Porovnání {porovnani}: {arr[j]} a {arr[j + 1]} — prohozeno"- (Výzva) Přidej tlačítko na nové náhodné pole
💡 Poradit
Bonus: tlačítkost.button("Nové pole")vracíTrue, když se na něj klikne. V tu chvíli si vygeneruj nový seznam přesrandom.sample(range(1, 50), 10)a ulož ho dost.session_state, aby nezmizel při dalším překreslení.
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–3 |
| Rozšíření | Bod 4 |
| Výzva | Bod 5 + čas měření v ms |
Očekávaný výsledek: bubble sort, který jde krokovat sliderem a ukáže počty porovnání/prohození.
Začni úplně jednoduše: bubble sort opakuje pořád stejný krok — porovnat dva sousedy a případně je prohodit. Když si tohle představíš jako jeden "mikrokrok", celé cvičení je jen ukládání těchto mikrokroků za sebou.
Budeš potřebovat dvě vnořené smyčky for (vnější = průchody, vnitřní = sousedi). Po každém porovnání přidej do seznamu kroků slovník s kopií pole, například kroky.append({"pole": arr.copy(), ...}).
arr = data.copy().kroky se startovním stavem.porovnani; při výměně zvýš prohozeni.kroky.Zadání:
- Napiš
merge_sort_kroky(data)(s rekurzí) a vrať seznam kroků💡 Poradit
Merge sort nejdřív rozdělí rozsah na dvě poloviny a na každou zavolá sám sebe. Uvnitř hlavní funkce si udělej pomocnou funkci, která pracuje s indexylar.
Zastavení rekurze je, když už je část jednoprvková.def merge_sort(l, r):
if l >= r:
return
m = (l + r) // 2- Ulož snímek po každém zápisu do původního pole
💡 Poradit
Při slučování zapisuješ hodnoty zpátky doarrna pozicik. Právě po každém takovém zápisu ulož snímekarr.copy()do seznamu kroků — pak uvidíš, jak se pole skládá.arr[k] = leva[i]
kroky.append({"pole": arr.copy(), "popis": "Zápis"})- Zobraz krokování přes slider +
st.bar_chart💡 Poradit
Slider ti vrátí číslo kroku a ty z něj vezmeš uložený snímek. Graf pak kreslíš vždy jen z jednoho snímku.krok = st.slider("Krok", 0, len(kroky) - 1, 0)
st.bar_chart(kroky[krok]["pole"])- (Rozšíření) Přidej počitadlo porovnání
💡 Poradit
Rozšíření:porovnanizvyš vždy, když ve slučování porovnáváš prvek z levé a pravé části. Protože počítáš uvnitř vnořené funkce, přidej na její začáteknonlocal porovnani.- (Výzva) Přidej text, jaké části se právě slučují
💡 Poradit
Výzva: při každém slučování už znáš indexyl,mar— z nich si poskládej popisný text a ulož ho do snímku.popis = f"Slučuji {l}–{m} a {m + 1}–{r}"
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–3 |
| Rozšíření | Bod 4 |
| Výzva | Bod 5 + tlačítko „nové pole“ |
Očekávaný výsledek: merge sort, který se dá projet krok po kroku.
Merge sort funguje tak, že velký problém rozdělí na malé. Nejprve rozdělíš pole na menší části, pak je zase skládáš zpět ve správném pořadí.
Budeš potřebovat vnořenou rekurzivní funkci třeba merge_sort(l, r) a zastavení if l >= r: return. Při slučování pracuj se dvěma dočasnými seznamy (leva, prava) a indexem k pro zápis do původního pole.
arr a založ kroky.arr[k] ulož jako snímek.kroky a případně porovnani.Zadání:
- Napiš
quick_sort_kroky(data)(pivot = poslední prvek)💡 Poradit
Udělej si uvnitř pomocnou funkciquick(lo, hi), která si vezme poslední prvek části jako pivot. Menší nebo rovné prvky posouvej doleva přes indexi, na konci pivot prohoď na pozicii.pivot = arr[hi]
i = lo- Ukládej snímky při důležitých krocích (porovnání + uložení pivotu)
💡 Poradit
Snímek ukládej na dvou místech: po každém porovnání prvku s pivotem a hlavně po tom, co pivot přijde na svou finální pozici. Vždy ukládej kopii pole.kroky.append({"pole": arr.copy(), "popis": f"Pivot {pivot} je na místě"})- Zobraz krokování přes slider +
st.bar_chart💡 Poradit
Zobrazení je stejné jako u ostatních cvičení: slider vybere index kroku, graf vykreslí ten jeden snímek.krok = st.slider("Krok", 0, len(kroky) - 1, 0)
st.bar_chart(kroky[krok]["pole"])- (Rozšíření) Přidej celkový počet porovnání
💡 Poradit
Rozšíření:porovnaninavyš pokaždé, když porovnáš aktuální prvek s pivotem. Ve vnořené funkci nezapomeň nanonlocal porovnania číslo pak zobraz třeba přesst.metric.- (Výzva) Přidej popis „vlevo od pivotu / vpravo od pivotu“
💡 Poradit
Výzva: po porovnání víš, jestli je prvek menší než pivot (patří vlevo), nebo větší (zůstává vpravo). Podle toho si sestav text dopopis.strana = "vlevo od pivotu" if arr[j] <= pivot else "vpravo od pivotu"
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–3 |
| Rozšíření | Bod 4 |
| Výzva | Bod 5 + zvýrazni popisem hodnotu pivotu |
Očekávaný výsledek: quick sort krokovaný sliderem.
Quick sort si vždy zvolí jedno číslo jako pivot a podle něj rozdělí zbytek. Jakmile umíš udělat jedno takové rozdělení, zbytek je jen opakování stejného postupu pro menší části.
Použij rekurzivní funkci quick(lo, hi) a pivot třeba arr[hi]. Proměnná i ukazuje místo, kam patří další prvek menší nebo rovný pivotu.
arr a založ kroky.i).Zadání:
- Připrav jedno náhodné pole (aspoň 150 čísel)
💡 Poradit
Vygeneruj jeden seznam a ulož si ho do proměnné — oba způsoby řazení pak musí dostat přesně stejná data, jinak by porovnání nebylo férové.data = [random.randint(1, 999) for _ in range(150)]- Seřaď ho bubble sortem a změř čas
💡 Poradit
Zavolej svůj bubble sort na kopii dat a čas si změř přestime.perf_counter()před a po. Rozdíl obou hodnot je doba běhu v sekundách.t0 = time.perf_counter()
bubble_vysledek = bubble_sort(data.copy())
cas_bubble = time.perf_counter() - t0- Seřaď stejné pole přes
sorted()a změř čas💡 Poradit
Stejným způsobem změř isorted(data). Pak ověř, že oba výsledky vyšly stejně — porovnáš je obyčejným==.t0 = time.perf_counter()
python_vysledek = sorted(data)
cas_sorted = time.perf_counter() - t0- (Rozšíření) Zobraz časy vedle sebe přes
st.metric💡 Poradit
Rozšíření: vyrob si dva sloupce přesst.columns(2)a do každého dej jednu metriku. Čas převeď na milisekundy, ať jsou čísla čitelná.c1, c2 = st.columns(2)
c1.metric("Bubble", f"{cas_bubble * 1000:.1f} ms")- (Výzva) Ukaž i řazení seznamu slovníků pomocí
key=(+ klidněreverse=True)💡 Poradit
Výzva: u seznamu slovníků musíšsorted()říct, podle čeho řadit — od toho je parametrkey=.serazeno = sorted(hrdinove, key=lambda h: h["level"], reverse=True)
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–3 |
| Rozšíření | Bod 4 |
| Výzva | Bod 5 + reverse=True |
Očekávaný výsledek: jasné porovnání, proč je vestavěné řazení praktické.
Nejdůležitější je férové porovnání: oba způsoby řazení musí dostat úplně stejná vstupní data. Jinak by čas nešel porovnat.
Na měření použij time.perf_counter(). Vezmi čas před řazením a po řazení, rozdíl je doba běhu.
data.bubble_sort(data) a změř čas.sorted(data) a změř čas.Zadání:
- Použij
st.session_state.hrdinase strukturou{"jmeno", "zivoty", "zlato", "inventar"}💡 Poradit
Hrdinu vytvoř jen jednou — hlídej si, jestli už vsession_stateje. Jinak by se ti při každém kliknutí resetoval.if "hrdina" not in st.session_state:
st.session_state.hrdina = {"jmeno": "Aria", "zivoty": 100, "zlato": 50, "inventar": []}- Inventář udělej jako seznam slovníků (
nazev,hodnota,rarita)💡 Poradit
Každý předmět je jeden slovník se stejnými klíči. Celý inventář je pak obyčejný seznam těchto slovníků.inventar = [
{"nazev": "Meč", "hodnota": 120, "rarita": "vzácný"},
]- Přidej volbu, podle čeho řadit (
nazev/hodnota/rarita) a zobraz seřazený inventář💡 Poradit
Volbu nabídni přesst.selectbox— vrátí ti text, který je zároveň klíčem ve slovníku předmětu. Ten pak použij vkey=.podle = st.selectbox("Řadit podle", ["nazev", "hodnota", "rarita"])
serazeno = sorted(h["inventar"], key=lambda x: x[podle])- (Rozšíření) Přidej přepínač vzestupně/sestupně (
reverse)💡 Poradit
Rozšíření:st.togglevracíTrue/Falsea přesně to potřebuje parametrreverse.sestupne = st.toggle("Sestupně", value=False)
serazeno = sorted(h["inventar"], key=lambda x: x[podle], reverse=sestupne)- (Výzva) Přidej sloupcový graf hodnot + tlačítko „náhodná kořist“
💡 Poradit
Výzva: pro graf si vytáhni jen hodnoty předmětů do jednoho seznamu. Tlačítko „náhodná kořist“ pak přidá nový slovník doh["inventar"].st.bar_chart([p["hodnota"] for p in serazeno])
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–3 |
| Rozšíření | Bod 4 |
| Výzva | Bod 5 + tlačítko „náhodná kořist“ |
Očekávaný výsledek: hrdina hned vidí, co je nejcennější kus kořisti.
Nejdřív si pohlídej data: když máš dobře připraveného hrdina a jeho inventar, řazení už je pak jen jeden řádek se sorted(...).
Pro řazení slovníků použij sorted(h["inventar"], key=lambda x: x[podle], reverse=sestupne). Hodnotu podle můžeš vzít ze st.selectbox.
session_state není hrdina, vytvoř ho s výchozím inventářem.podle (nazev/hodnota/rarita).sorted(..., key=...).Postav miniaplikaci „Aréna řazení“: vygeneruj jedno pole čísel a porovnej na něm bubble sort, merge sort, quick sort a
sorted().
Zobraz časy a počty porovnání. U jednoho vybraného algoritmu přidej i krokování přes slider a sloupcový graf.
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.