# Ř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**! - 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í - Ř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 - Pokud nebude explicitně uvedeno jinak, budeme ve zbylém textu uvažovat ř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ů** - 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á - 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á - 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 - 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í - **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 #### 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ýsledek ```1, 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"``` - 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 - Ř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"``` ### Ř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 metoda ```compareTo()``` #### 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``` nazvat ```compareTo()``` a může vracet hodnoty ```0```, ```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 #### 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``` (nebo ```Collections```) z balíku ```java.util``` - Pro pole základních datových typů i pole objektů se používají metody ```Arrays.sort(pole)``` a ```Arrays.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 indexu ```indexDo``` (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``` - 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``` - 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ů ### 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``` nebo ```null``` **podle typu pole** #### 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**