19 KiB
19 KiB
Řazení
- Velmi častá operace v mnoha různorodých algoritmech
- Slouží především pro usnadnění následného vyhledávání
- Používá se nejen v programování, ale i v běžném životě (často se setkáváme s abecedním pořadím, např. slova v rejstříku knihy, které usnadní nalezení požadovaného výrazu)
Základní pojmy
- Pro řazení se v angličtině používá slovo „sort“
- Toto slovo má však dva odlišné významy
- Řazení – činnost, kdy přeskupíme prvky posloupnosti (nejčastěji uložené v poli) tak, aby mezi sousedními prvky platily vztahy „předchůdce-následník“ či „menší-větší“
- Např. seřadíme pole čísel podle velikosti od nejmenšího čísla k největšímu nebo seřadíme studenty podle příjmení od A do Z
- V předmětu KIV/PPA1 a navazujících předmětech budeme důsledně používat slovo „řazení“
- Třídění – činnost, kdy prvky z určité skupiny (množiny) rozdělujeme do podskupin podle nějaké společné vlastnosti
- Např. roztřídíme celá čísla na sudá a lichá V českém překladu se občas slovo „třídění“ používá ve významu řazení => NEPOUŽÍVAT!
- Řazení – činnost, kdy přeskupíme prvky posloupnosti (nejčastěji uložené v poli) tak, aby mezi sousedními prvky platily vztahy „předchůdce-následník“ či „menší-větší“
- Toto slovo má však dva odlišné významy
- Klíč
- Řadit je možné prvky primitivních datových typů nebo instance
- Pokud řadíme instance, které mají obecně více atributů, pak typicky řadíme podle jednoho nebo několika z nich
- Atribut, podle kterého řadíme, se nazývá klíč
- Protože se řazení používá velmi často, existuje množství rozmanitých algoritmů, které se navzájem liší některými svými vlastnostmi, zejména svou rychlostí a složitostí implementace
Vlastnosti algoritmů řazení
- Směr řazení
- Řazení vzestupně
- Předchozí prvek je menší nebo roven následujícímu prvku
- Např. 1, 2, 3, 3, 3, 4, 6, 9, 9
- Častější směr řazení
- Předchozí prvek je menší nebo roven následujícímu prvku
- Řazení sestupně
- Předchozí prvek je větší nebo roven následujícímu prvku
- Např. 9, 9, 6, 4, 3, 3, 3, 2, 1
- Předchozí prvek je větší nebo roven následujícímu prvku
- Pokud nebude explicitně uvedeno jinak, budeme ve zbylém textu uvažovat řazení vzestupně
- Řazení vzestupně
- Umístění řazených prvků
- vnitřní řazení
- Všechny prvky, které chceme seřadit, jsou uloženy v jeden okamžik v operační paměti
- Ve zbylém textu budeme uvažovat pouze vnitřní řazení
- Paměti je „dost“ – lze bez problémů řadit desítky milionů prvků
- Vnější řazení
- Řazených prvků je extrémní množství a všechny najednou se do operační paměti nevejdou
- Prvky jsou uloženy v jednom či více souborech nebo v databázi na vnější paměti (např. na pevném disku)
- V jeden okamžik je v operační paměti pouze část prvků
- vnitřní řazení
- Stabilita
- Stabilní řazení
- Řazení je stabilní, pokud relativní pořadí prvků se stejnou hodnotou klíče zůstává v seřazeném poli (posloupnosti) stejné jako v původním poli (posloupnosti)
- Např. pokud budu řadit pole osob s atributy jméno a příjmení podle příjmení, pořadí osob se stejným příjmením zůstane zachované
- Neseřazené osoby: Jana Volná, Tomáš Marný, Petr Dobrý, Martin Marný, Lenka Malá, Jitka Volná
- Seřazené osoby: Petr Dobrý, Lenka Malá, Tomáš Marný, Martin Marný, Jana Volná, Jitka Volná
- Např. pokud budu řadit pole osob s atributy jméno a příjmení podle příjmení, pořadí osob se stejným příjmením zůstane zachované
- Řazení je stabilní, pokud relativní pořadí prvků se stejnou hodnotou klíče zůstává v seřazeném poli (posloupnosti) stejné jako v původním poli (posloupnosti)
- Nestabilní řazení
- Řazení je nestabilní, pokud se relativní pořadí prvků se stejnou hodnotou klíče může po seřazení pole změnit
- Např. pokud budu řadit pole osob s atributy jméno a příjmení podle příjmení, pořadí osob se stejným příjmením nemusí zůstat zachované
- Neseřazené osoby: Jana Volná, Tomáš Marný, Petr Dobrý, Martin Marný, Lenka Malá, Jitka Volná
- Seřazené osoby: Petr Dobrý, Lenka Malá, Tomáš Marný, Martin Marný, Jitka Volná, Jana Volná
- Např. pokud budu řadit pole osob s atributy jméno a příjmení podle příjmení, pořadí osob se stejným příjmením nemusí zůstat zachované
- Má význam pouze u řazení objektů
- Pokud řadíme pole základních datových typů, vzájemná poloha dvou stejných prvků není důležitá
- Protože prvky stejné hodnoty stejně nemůžeme rozlišit
- „Je jedno, která trojka bude první a která druhá“
- Objekty se mohou shodovat v klíči, podle kterého jsme řadili, ale můžou se lišit (a často liší) v hodnotách dalších atributů
- Jejich pořadí může mít význam
- Pokud řadíme pole základních datových typů, vzájemná poloha dvou stejných prvků není důležitá
- Řazení je nestabilní, pokud se relativní pořadí prvků se stejnou hodnotou klíče může po seřazení pole změnit
- Stabilní řazení
- Složitost
- Vlastnost každého algoritmu (nejen algoritmu řazení)
- Dá se použít pro zhodnocení „jak rychlý“ daný algoritmus je nebo „kolik paměti“ daný algoritmus potřebuje
- Udává, jak roste časová (nebo paměťová) náročnost algoritmu v závislosti na velikosti vstupu
- V případě řadících algoritmů je velikost vstupu počet řazených prvků
- Podrobně viz předmět KIV/PPA2
Základní algoritmy řazení
- U všech příkladů budeme předpokládat, že posloupnost prvků, kterou chceme seřadit, je uložená v poli
- Všechny zmíněné algoritmy procházejí pole, přičemž alespoň část pole se prochází opakovaně
- Pro průchod polem jsou využity dva vnořené cykly – typicky cykly for, nebo kombinace cyklů for a while
- Základní operace, které se provádějí v běžných algoritmech řazení, je porovnání prvků a prohození prvků
- Postupným prohazováním dvojic prvků na základě porovnání jejich hodnoty se z neseřazené posloupnosti uložené v poli stane seřazená posloupnost
- Základní algoritmy řazení
- Jsou jednoduché na implementaci (a tedy i na pochopení jejich principu)
- Všechny mají časovou složitost Ο(n²)
- Udává, jak roste čas potřebný pro seřazení posloupnosti prvků v závislosti na počtu prvků n (v nejhorším případě)
- Pokud bude algoritmus potřebovat čas t pro seřazení n prvků, bude pro seřazení 2n prvků potřebovat čas 4t
- Čas roste kvadraticky v závislosti na počtu prvků n
- Pokročilé algoritmy řazení
- Jsou složitější na implementaci a pochopení
- Mají nižší časovou složitost (typicky Ο(nlog2n))
- Podrobně viz předmět KIV/PPA2
Řazení výběrem (selection sort)
- Též řazení výběrem mezního prvku, řazení s přímým výběrem
- Princip řešení
- V celém poli nalezneme index prvku s nejnižší hodnotou
- Vyměníme nalezený prvek s prvním prvkem pole (na indexu 0)
- V poli vyjma prvního prvku nalezneme index prvku s nejnižší hodnotou
- Vyměníme nalezený prvek s druhým prvkem pole (na indexu 1)
- Pokračujeme stejným způsobem pro další prvky, dokud nedosáhneme konce pole
- Vlastnosti řazení výběrem
- Řazení je nestabilní
- Složitost Ο(n²)
Řazení vkládáním (insertion sort)
- Princip řešení
- Pole máme rozdělené na seřazenou (na začátku jeden prvek) a neseřazenou část (na začátku všechny prvky kromě prvního (na indexu 0))
- Vezme se první prvek z neseřazené části a vloží se na správné místo do seřazené části
- Prvky v seřazené části větší než zařazovaný prvek se posunou o jedno místo doprava => seřazená část se zvětší o jeden prvek
- Opakuji, dokud není pole celé seřazené
- Vlastnosti řazení vkládáním
- Řazení je stabilní
- Složitost Ο(n²)
Řazení záměnou (bubble sort)
- Též bublinkové řazení
- Princip řešení
- Porovnáváme vždy dva sousední prvky, začínáme odzadu
- Pokud jsou prvky v nesprávném pořadí (prvek s nižším indexem je větší), prohodíme je
- Porovnávání a prohazování opakujeme, dokud nedojdeme na začátek pole
- Tím se nejmenší prvek dostane na začátek pole („vybublá nahoru“)
- V průběhu vybublání se i ostatní prvky částečně řadí
- Celý postup opakujeme znovu, ale pouze do druhého prvku pole
- Tím vybublá nahoru druhý nejmenší prvek
- Opakujeme pro třetí nejmenší prvek, atd.
- Předčasné ukončení
- Pokud nedojde při běhu vnitřního cyklu k žádné výměně, je pole seřazené a mohu skončit
- Vlastnosti řazení záměnou
- Řazení je stabilní
- Složitost Ο(n²)
Porovnání základních algoritmů řazení
- Všechny tři zmíněné algoritmy mají stejnou složitost Ο(n²)
- To neznamená, že jsou všechny stejně efektivní, pouze, že doba řazení roste stejným tempem (s druhou mocninou počtu řazených prvků)
- Všechny algoritmy řazení provádějí převážně dvě základní operace
- Porovnání – typicky porovnání dvou prvků
- Přiřazení – typicky při prohození dvou prvků (celkem tři přiřazení na jedno prohození)
- Různé řadící algoritmy provedou různý počet porovnání a přiřazení pro stejnou neseřazenou posloupnost
- Podle počtu provedených porovnání a přiřazení lze algoritmy porovnat
- Do algoritmů se přidají dva čítače
- Jeden pro počet porovnání a druhý pro počet přiřazení
- Inkrementují se „ručně“ (tj. je nutné inkrementaci ručně přidat do kódu) při každém provedeném porovnání/přiřazení
- Do algoritmů se přidají dva čítače
- Základní algoritmy řazení jsou efektivní pro malé posloupnosti (cca několik desítek prvků)
- Pro větší množství prvků se používají algoritmy se složitostí Ο(nlog²n), které jsou typicky obsažené v knihovních metodách většiny běžných programovacích jazyků
Řazení pole řetězců (lexikografické řazení)
- Řazení řetězců funguje stejně jako řazení základních datových typů s výjimkou porovnání
- Pro porovnání dvou prvků nelze použít operátory „
<
“ a „>
“ - Je potřeba použít metodu instance třídy
String compareTo()
, která vrací 0, kladnou či zápornou hodnotu podle pořadí porovnávaných řetězců v abecedě- 0 – řetězce jsou stejné (včetně velikosti písmen)
- Záporná hodnota – řetězec, nad kterým je metoda volaná, je „menší“ než řetězec v parametru metody
- Kladná hodnota – řetězec, nad kterým je metoda volaná, je „větší“ než řetězec v parametru metody
- POZOR! – Kladné a záporné hodnoty jsou obecně různá kladná a záporná čísla v závislosti na obsahu řetězců – není to jen -1 a 1
- Pro porovnání dvou prvků nelze použít operátory „
Problémy s lexikografickým řazením
- Uspořádání podle abecedy však může mít neočekávané důsledky, typicky při řazení názvů souborů (což je nejčastěji podle názvu)
- Pokud seřadíme čísla
1, 40, 10, 3, 17, 4
, očekáváme výsledek1, 3, 4, 10, 17, 40
- Pokud ale budeme řadit řetězce (názvy souborů)
"1.txt", "40.txt", "10.txt", "3.txt", "17.txt", "4.txt"
, dostaneme výsledek"1.txt", "10.txt", "17.txt", "3.txt", "4.txt", "40.txt"
- Toto seřazení je důsledkem porovnání řetězců
- Běžné porovnávání řetězců implementované i v metodě
compareTo()
probíhá postupně podle jednotlivých znaků od začátku řetězce- Pokud se řetězce shodují v prvním znaku, porovnají se podle druhého znaku atd.
- Jednotlivé znaky se porovnávají podle jejich hodnot
- Jakmile se narazí na znak, který není shodný, hodnota tohoto znaku určí výsledek porovnání řetězců
- Např. při porovnání řetězců
"Jana"
a"Janicka"
rozhodnou až znaky'a'
a'i'
na indexu 3 protože'a'
má nižší hodnotu než'i'
, „menší“ (tj. blíže začátku abecedy) je řetězec"Jana"
- Např. při porovnání řetězců
- Pokud je jeden řetězec předponou druhého, je „menší“ ten kratší řetězec (např.
"Jan"
je „menší“ než"Jana"
) - Pokud se porovná řetězec
"10.txt"
a"3.txt"
, tak hned první znaky (na indexu 0) jsou rozdílné –'1'
a'3'
- Protože
'1'
je menší než'3'
, je řetězec"10.txt"
„menší“ než řetězec"3.txt"
, i když číslo 3 je menší než 10
- Protože
- Řešení – doplnit nevýznamové nuly, které jsou však z hlediska lexikografického řazení významové => místo
"3.txt"
použít"03.txt"
- Pokud se řetězce shodují v prvním znaku, porovnají se podle druhého znaku atd.
- Pokud seřadíme čísla
Řazení pole objektů
- Pole objektů lze řadit stejnými algoritmy jako pole základních datových typů
- Je ale nutné určit, podle čeho se mají jednotlivé instance porovnávat (tj. určit, co bude klíčem)
- Podle jednoho atributu
- Podle více atributů (víceúrovňové řazení)
Řazení podle jednoho atributu
- Jediný rozdíl oproti řazení pole základních datových typů je, že porovnáváme hodnoty atributů, nikoliv samotné instance
- Pokud je atribut základního datového typu, použijeme přímo operátory porovnání „
<
“ a „>
“ - Pokud je atribut referenční proměnná na instanci jiné třídy, musíme použít porovnání definované pro danou třídu
- Např. pro třídu
String
je to metodacompareTo()
- Např. pro třídu
- Pokud je atribut základního datového typu, použijeme přímo operátory porovnání „
Víceúrovňové řazení provedené najednou (běžný postup)
- Poměrně často se může stát, že chceme řadit pole objektů podle více atributů
- Např. osoby podle příjmení a následně podle jména
- Osoby se tedy seřadí podle příjmení, a pokud mají stejné příjmení, seřadí se ještě podle jména
- Je možné udělat v řazení komplexnější podmínku pro prohození prvků obsahující všechny atributy, podle kterých chceme řadit
- Tento postup podstatně znepřehledňuje samotné řazení, zvláště pokud je atributů, podle kterých řadíme, větší množství
- Je vhodnější udělat metodu pro porovnání dvou instancí stejné třídy, která bude vracet hodnoty indikující, která instance je „větší“
- Tato metoda může obsahovat i poměrně složitou podmínku skládající se z porovnání několika atributů
- Díky umístění v metodě instance nebude znepřehledňovat kód řazení
- Metodu můžeme po vzoru třídy
String
nazvatcompareTo()
a může vracet hodnoty0
,1
a-1
- Metoda
compareTo()
se využívá i při řazení objektů knihovní metodou řazení - Metoda
compareTo()
se může využít i při řazení podle jediného atributu
- Metoda
- Např. osoby podle příjmení a následně podle jména
Víceúrovňové řazení provedené postupně (nepoužívat)
- Další možnost seřazení pole objektů podle více atributů je seřadit ho nejprve podle jednoho atributu a následně podle dalšího
- Je potřeba použít stabilní algoritmus řazení
- Nejprve se pole seřadí podle nejméně důležitého atributu (u osoby podle jména)
- Následně se pole seřadí podle více důležitého atributu (u osoby podle příjmení)
- Protože pořadí prvků se stejným klíčem zůstává při použití stabilního řazení nezměněno, seřazení podle méně důležitého atributu (jména) zůstane zachováno (pro osoby se stejným příjmením)
- Tento postup vyžaduje zopakovat řazení dvakrát a je proto rozumné ho nevyužívat, pokud není nezbytně nutno
- Pokud však není možné řadit podle více atributů najednou (např. v GUI MS Access), tento postup se může hodit
Metody řazení v Java Core API
- V naprosté většině případů není důvod si programovat algoritmus řazení – využijí se knihovní metody pro řazení z třídy
Arrays
(neboCollections
) z balíkujava.util
- Pro pole základních datových typů i pole objektů se používají metody
Arrays.sort(pole)
aArrays.sort(pole, indexOd, indexDo)
- Metody jsou překryté pro pole všech základních datových typů a pole objektů
- Metoda s indexy řadí pouze část pole od indexu
indexOd
(včetně) do indexuindexDo
(vyjma)
Řazení polí základních datových typů metodou z Java Core API
- Řazení pole základních datových typů nevyžaduje žádnou speciální úpravu, pouze se zavolá metoda řazení
- Metody
sort()
pro pole základních datových typů obsahují algoritmus quick sort- Je velmi rychlý
- Má složitost Ο(nlog²n) (v průměrném případě)
- Není stabilní – u základních datových typů nepodstatné
- Viz předmět KIV/PPA2
Řazení polí objektů metodou z Java Core API
- Aby bylo možné řadit pole objektů knihovními metodami řazení, je potřeba, aby třída implementovala rozhraní
Comparable<Třída>
- Mnoho knihovních tříd to dělá, a dají se tak řadit knihovními metodami – např.
String
- Podrobnosti viz předměty KIV/PPA2 a KIV/OOP
- Existuje i alternativní možnost, umožňující řadit podle různých kritérií podle toho, které řazení aktuálně potřebujeme
- Je potřeba, aby třída měla rozšířenou hlavičku
public class Třída implements Comparable<Třída>
- Je potřeba, aby třída obsahovala metodu pro porovnání dvou instancí třídy
public int compareTo(Třída třída)
- Tato metoda musí vracet zápornou hodnotu, pokud je instance, nad kterou se metoda volá, „menší“ než instance v parametru metody, kladnou hodnotu, pokud je „větší“, a nula, pokud jsou obě instance stejné
- Podle čeho se instance porovnávají, záleží na těle metody
compareTo()
, může to být podle jednoho či více atributů
- Mnoho knihovních tříd to dělá, a dají se tak řadit knihovními metodami – např.
Kopie pole
- Protože při knihovním řazení metodami
Arrays.sort()
dojde přímo k seřazení zadaného pole, může se hodit pole před řazením zkopírovat, abychom měli uloženou seřazenou i neseřazenou posloupnost- Pole lze překopírovat ručně
- Vytvořit nové pole stejného typu o stejné velikosti jako původní pole
- V cyklu
for
projít původní pole a do prvků nového pole přiřadit odpovídající prvky původního pole
- Pole lze překopírovat využitím knihovní metody
Arrays.copyOf(pole, nováDélkaPole)
- Metoda je rychlejší než ruční kopírování
- Metoda umožňuje pole také zkrátit nebo prodloužit (při zadání jiné délky než původní délky pole)
- Při zadání menší délky se pole ořízne
- Při zadání větší délky se prvky navíc nastaví na hodnotu
0, 0.0, false
nebonull
podle typu pole
- Pole lze překopírovat ručně
Kopie polí základních datových typů
- Při kopírování polí základních datových typů, kdy jednotlivé hodnoty jsou přímo prvky pole, dostaneme „plnohodnotnou“ kopii pole
- Pokud provedeme změnu prvku v kopii pole, změna se nijak neprojeví v původním poli
Kopie polí objektů
- Při kopírování polí objektů jsou v prvcích pole uloženy pouze reference na jednotlivé instance
- Zkopírováním pole se zkopírují pouze reference, které ale stále ukazují na původní instance
- Pokud provedu změnu atributu prvku v kopii pole, projeví se tato změna i v odpovídajícím prvku původního pole (protože se stále jedná o stejnou instanci)
- Jedná o tzv. mělkou kopii pole