FAV-ZCU/KIV PPA1/13. Řazení.md
2022-12-19 19:30:45 +01:00

258 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Ř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ý**
-**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ů
### 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**