Heapify All The Things With Heap Sort
Egyszer valaki azt mondta nekem, hogy az informatikában minden fontos dolog a fákra fut ki. Szó szerint csak fákra. Ezek segítségével tudunk dolgokat építeni, elemezni és értelmezni (igen, lehet, hogy itt némi előjelzés történik, ne aggódj, ha még nem érted, mert hamarosan érthető lesz!) És még arra is használhatjuk őket, hogy – kitaláltad! – rendezni a dolgokat.
Ah, rendezés. Annyi mindent csináltunk belőle az elmúlt hetekben, de most már a rendezési kalandozásunk végéhez közeledünk. Azonban lehetetlen és igazságtalan úgy beszélni a rendezésről, hogy ne beszélnénk a rendezés egy speciális fajtájáról, amely a legújabb adatszerkezetet használja az adatszerkezeti eszköztárunkban.
Nemrég tanultuk meg szeretni a heapeket, a bináris fák egy speciális fajtáját, amely szigorú szabályokat követ, és olyan dolgok megvalósítására használják, mint a prioritási sorok és a háttérmunkák. De nem ezek az egyetlen dolgok, amikre a heapek jók. Kiderült, hogy a bináris halmokat gyakran nem másra használják, mint hatékony rendezésre. Sok program támaszkodik a halmazrendezésre, mivel történetesen ez az egyik leghatékonyabb módja egy tömb rendezésének. És most, hogy már tudjuk, mi az a halom, megpróbálhatjuk megérteni, miért működik olyan jól, amikor a rendezés problémájáról van szó!
Mielőtt belevetnénk magunkat a halomrendezésbe, győződjünk meg róla, hogy a halmok rendben vannak a fejünkben. Emlékezhetünk arra, hogy a halom valójában nem más, mint egy bináris fa néhány további szabállyal, amelyet követnie kell: először is, mindig halomszerkezetűnek kell lennie, ahol a bináris fa minden szintje balról jobbra haladva van feltöltve, másodszor pedig vagy max halomként, vagy min halomként kell rendezni. A halomrendezés szempontjából kizárólag max halmokkal fogunk foglalkozni, ahol minden szülő csomópont (beleértve a gyökeret is) nagyobb vagy egyenlő, mint a gyermek csomópontjainak értéke.
Oké, térjünk rá az óra kérdésére: hogyan rendezünk halmok segítségével? Nos, ahhoz, hogy megválaszolhassuk ezt a kérdést, először is meg kell értenünk, mi is az a halmazrendező algoritmus!
Egy rendezetlen tömbtel kezdünk, amelynek öt eleme szuper rendezetlen: .
Memlékezzünk arra, hogy mivel halomrendezéssel dolgozunk, először is ezt a tömböt halommá kell alakítanunk.
Az itt látható ábrán látható, hogy a tömböt fává alakítottuk – ez még nem halom, mert még nincs semmilyen maximális vagy minimális halomsorrendben! Láthatjuk, hogy ez azért van így, mert 3
nem a legnagyobb vagy a legkisebb elem, mégis, jelenleg ez a gyökércsomópont. Ez csak egy fa, amelynek elemei a tömbből közvetlenül bináris fa formátumra vannak lefordítva.
De mivel nekünk egy max halommal kell foglalkoznunk, a struktúránkat bináris fából max halommá kell alakítanunk. Figyeljük meg, hogy a max heapben a szülő csomópontok mind nagyobbak, mint a gyermekeik. Múlt héten megtanultuk azokat az algoritmusokat, amelyek lehetővé teszik, hogy egy tömb indexéből meghatározzuk a gyermek csomópontokat; ezen a héten megnézzük őket a gyakorlatban. Ezeket az algoritmusokat használjuk arra, hogy ezt a tömböt fává, majd halommá alakítsuk.
Oké, most már van egy tényleges max halom. Nagyszerű! Most jön a tényleges munka, a rendezés.
Mivel tudjuk, hogy a legnagyobb elem a gyökércsomópontban van, tudjuk, hogy a tömb legvégére kell tennünk, az utolsó elérhető indexhelyre. Tehát felcseréljük a gyökércsomópontot az utolsó csomóponttal. Miután ezt a cserét elvégeztük, az utolsó csomópontunkban lesz a legnagyobb, maximális értékű elem.
Klassz! Most láthatjuk, hogy 19
, a legnagyobb elem, amely korábban a gyökércsomópont volt, most a tömb utolsó pozíciójában van. És mivel gyakorlatilag a többi elemhez képest “rendezett”, teljesen eltávolíthatjuk a halomból.
Most a jó hír az, hogy eggyel kevesebb csomópontunk van a halomban, amit rendeznünk kell! A rossz hír? A halomunk valójában már nem is halom többé: teljesen megszegi a halom rendezési szabályát, mivel ez nem egy maximális halom. Vegyük észre, hogy 1
a gyökércsomópont, de biztosan nem nagyobb, mint két gyermekcsomópontja, 14
és 7
. Tehát lefelé kell mozgatnunk a fában a megfelelő helyre.
Tegyük halmozottá ezt a fát, és tegyük újra max halommá!
Awesome! A fenti ábrán láthatjuk, hogy először a 1
és a 14
, majd a 1
és a 8
értékeket cseréltük fel. Most már újra egy rendes max halomban vagyunk. Megismételhetjük ugyanazokat a lépéseket, mint a 19
elem rendezésekor:
→ Először az első és az utolsó csomópontot cseréljük fel.
→ Ezután addig halmozzuk a fát, amíg az ismét egy megfelelő max heap lesz.
Megcsináljuk ezt az új gyökércsomópontunkkal, a 14
elemmel. A következő két lépésünk így nézne ki:
Rad! Felcseréltük az első és az utolsó csomópontot, majd eltávolítottuk az utolsó csomópontot, 14
, mivel az a rendezett pozíciójában volt. Ezután már csak annyit kellett tennünk, hogy a gyökércsomópontot áthelyezzük a megfelelő helyre, és addig halmozzuk az 3
elemet, amíg vissza nem kerülünk a maximális halmazállapotba.
Ezt még háromszor folytatnánk. Végül csak 1
maradna, az utolsó csomópont a halomban. Ekkor a halomrendezési algoritmus befejeződne, és tudnánk, hogy 1
lenne az első elem a tömbben, és tudnánk, hogy a tömb végre rendezett.
Itt egy remek vizualizáció az egész folyamatról, amin épp most mentünk keresztül. Figyeljük meg, hogy minden egyes iteratív rendezéssel a legnagyobb rendezetlen elem a megfelelő helyre kerül a halomban, majd a tömbben.
Heap sort: mire jó?
Amikor először olvastam a heap sortról, valami furcsán ismerős volt számomra az algoritmusban. Csak a heap sort illusztrálása után jöttem rá, honnan ered a déjà vu érzésem: a heap sort szinte pontosan olyan, mint a selection sort! Talán emlékszik a sorozat korábbi részeiből, hogy a selection sort egy olyan rendezési algoritmus, amely úgy rendezi a rendezetlen elemek listáját, hogy végigmegy az elemek listáján, megkeresi a legkisebbet, és azt félreteszi egy rendezett listába. A rendezést úgy folytatja, hogy megkeresi a legkisebb rendezetlen elemet, és hozzáadja a rendezett listához.
Nem úgy hangzik, mint a halomrendezés, csak éppen fordítva?
Kiderül, hogy a heap sort logikájában nagyon hasonlít a selection sortra: mindkét algoritmus megtalálja vagy a legkisebb vagy a legnagyobb elemet, “kiválogatja” azt, és az elemet a megfelelő helyre teszi a rendezett listában.
Amilyen hasonlóak is, a heap sort azonban egy hatalmas dologban sokkal jobb a selection sortnál: a teljesítményében! A halomrendezés alapvetően a kiválasztási rendezés szuperjavított változata. Igen, megtalálja a legnagyobb elemet egy rendezetlen gyűjteményben, és a lista végére rendezi – azonban mindezt a munkát sokkal gyorsabban végzi, mint a selection sort tenné!
Oké, de mennyivel gyorsabb a heap sort? És miért gyorsabb?
Nézzük meg a kódot. A heap sortnak különböző implementációi léteznek, az alábbi kódot a Rosetta Code heap sort JavaScript implementációjából adaptáltuk. Emlékezhetsz, hogy a heap sortnak két fontos része van: buildMaxHeap
és heapify
. Ezeket láthatjuk működés közben az alábbi heapSort
változatban.
A buildMaxHeap
függvény végzi a tényleges munkát a max heap létrehozásával. Figyeljük meg, hogy még ez a függvény is a heapify
függvényt hívja, amely azt a munkát végzi, hogy egy-egy elemet lefelé mozgat a megfelelő helyre a halomban.
A heapify
függvény elég fontos, ezért nézzük meg. Vegyük észre, hogy a csomópont bal és jobb gyermekének meghatározására szolgáló algoritmusokra támaszkodik, amelyeket a múlt héten tárgyaltunk, amikor először tanultunk a halmazokról.
És végül, de nem utolsósorban a swap
függvény, amelyet már láttunk más rendezési algoritmusokban, de érdemes gyorsan megnézni, hogy emlékeztessük magunkat, mit csinál:
Oké, most, hogy már van némi kontextusunk arról, hogyan hatnak egymásra és hívják meg egymást ezek a függvények, térjünk vissza eredeti kérdésünkhöz, hogy hogyan és miért sokkal hatékonyabb a heap sort, mint a selection sort! Ha mélyen belenézünk a kódba, két dolgot fogunk észrevenni: először is, egyszer kell felépítenünk a maximális halmazt, átadva neki a tömb összes elemét; másodszor, a halomban lévő összes elemet újra és újra halmozni kell, kivéve az első gyökércsomópont elemét.
Ez a két megfigyelés tulajdonképpen a kulcs ahhoz a kérdéshez, hogy hogyan és miért olyan gyors a heap sort, amilyen gyors. A buildMaxHeap
meghívása O(n) időt vesz igénybe, mivel minden egyes elemet hozzá kell adni a halomhoz, és a nagyobb elemszám nagyobb halmot jelent. Ne feledjük azonban, hogy bináris fával van dolgunk, és a bináris fák logaritmikus természetűek. Tehát annak ellenére, hogy újra és újra meg kell hívnunk a heapify
-t, a függvény meghívása valójában meglehetősen gyors, mivel logaritmikus időben, vagyis O(log n) alatt fut le.
A két időbonyolultság kombinációját már láttuk korábban is! A halomrendezés linearitmikus időben fut, vagy Big O jelöléssel O(n log n). Tehát, annak ellenére, hogy a heap sortolás annyira hasonlít a selection sortolásra, sokkal gyorsabb! A selection sort kvadratikus időben fut, vagyis O(n²), ami sokkal kevésbé hatékony, mint a linearitmikus idő.
Nézzük meg gyorsan, hogy a heap sort milyen más módon hasonlít más rendezési algoritmusokhoz.
A heap sort a rendezés során átalakítja a neki átadott tömböt; néhány rendezési algoritmustól eltérően nem hoz létre egy teljesen külön példányt a bemeneti adatokból. Ez teszi helyben történő rendezési algoritmussá. A Heap sortnak szintén nincs szüksége külső memóriára, és belső rendezési algoritmus. Iteratívan fut (tehát nem rekurzív), és egyszerre két elemet hasonlít össze, amikor cserél és meghívja a heapify függvényt, így összehasonlító rendezési algoritmus.
A halmazok és a heapify függvény természete miatt azonban, ha vannak duplikált elemek, nem bízhatunk abban, hogy az elemek megtartják a sorrendjüket! A halomrendezés tehát instabil; ez az egyik fő különbség az egyesített rendezés és a halomrendezés között, amelyek mindegyike a fa struktúrákra támaszkodik, hogy ilyen hatékonyan működjön. A stabilitás csatájában azonban a merge sort nyer, míg a heap sort ebben a kategóriában elbukik.
A különbségek ellenére a merge sort és a heap sort egy dologban egyetérthet: bináris fák nélkül mindketten elveszettek lennének!
Források
A heap sortolásról van néhány igazán fantasztikus tanfolyami jegyzet és előadás, valamint néhány jó videós bemutató. Én azért gugliztam egy kicsit, hogy neked ne kelljen! Itt van néhány remek hely, ahol elkezdheted, ha többet szeretnél megtudni a halmazrendezésről.
- Introduction to Algorithms: Heap Sort, MIT
- Algorithms: Heap Sort, Professor Ching-Chi Lin
- Heap sort, Growing with the Web
- Heap sort in 4 minutes, Michael Sambol
- Heap sort: Max heap, strohtennis
Leave a Reply