Top Banner
116

Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Sep 24, 2019

Download

Documents

dariahiddleston
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Algoritmusok és adatszerkezetek I.

el®adásjegyzet

Ásványi Tibor [email protected]

2019. szeptember 5.

Page 2: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Tartalomjegyzék

1. Bevezetés 5

2. Tematika 7

3. Néhány alapvet® jelölés és elméleti háttere 83.1. Tömbök . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93.2. Szögletes zárójelek közé írt utasítások . . . . . . . . . . . . . . 103.3. A struktogramok paraméterlistái,

érték szerinti és cím szerinti paraméterátadás . . . . . . . . . . 103.4. Tömb típusú paraméterek a struktogramokban . . . . . . . . . 113.5. Eljárások, függvények, ciklusok, rekurzió . . . . . . . . . . . . 123.6. Programok, alprogramok és hatékonyságuk . . . . . . . . . . . 14

4. Az algoritmusok témakör bevezetésea beszúró rendezésen keresztül 174.1. Vektor monoton növekv® rendezése . . . . . . . . . . . . . . . 17

4.1.1. Beszúró rendezés (Insertion sort) . . . . . . . . . . . . 184.1.2. Programok hatékonysága és a beszúró rendezés . . . 21

4.2. A futási id®kre vonatkozó becslések magyarázata* . . . . . . . 244.3. Rendezések stabilitása . . . . . . . . . . . . . . . . . . . . . . 254.4. Kiválasztó rendezések (selection sorts) . . . . . . . . . . . . . 26

5. Az oszd meg és uralkodj elven alapuló gyors rendezések 285.1. Összefésül® rendezés (merge sort) . . . . . . . . . . . . . . . . 28

5.1.1. A merge eljárás m¶veletigénye . . . . . . . . . . . . . . 315.1.2. A merge sort m¶veletigénye: szemléletes megközelítés . 31

5.2. Gyorsrendezés (Quicksort) . . . . . . . . . . . . . . . . . . . . 325.2.1. A gyorsrendezés (quicksort) m¶veletigénye . . . . . . . 365.2.2. Vegyes gyorsrendezés . . . . . . . . . . . . . . . . . . . 365.2.3. A gyorsrendezés végrekurzió-optimalizált változata* . . 37

6. Elemi adatszerkezetek és adattípusok 386.1. Vermek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386.2. Sorok . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

7. Láncolt listák (Linked Lists) 427.1. Egyirányú listák (one-way or singly linked lists) . . . . . . . . 43

7.1.1. Egyszer¶ egyirányú listák (S1L) . . . . . . . . . . . . . 437.1.2. Fejelemes listák (H1L) . . . . . . . . . . . . . . . . . . 457.1.3. Egyirányú listák kezelése . . . . . . . . . . . . . . . . . 46

2

Page 3: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

7.1.4. Dinamikus memóriagazdálkodás . . . . . . . . . . . . . 497.1.5. Beszúró rendezés H1L-ekre . . . . . . . . . . . . . . . . 507.1.6. Az összefésül® rendezés S1L-ekre . . . . . . . . . . . . 517.1.7. Ciklikus egyirányú listák . . . . . . . . . . . . . . . . . 52

7.2. Kétirányú listák (two-way or doubly linked lists) . . . . . . . . 537.2.1. Egyszer¶ kétirányú listák (S2L) . . . . . . . . . . . . . 537.2.2. Ciklikus kétirányú listák (C2L) . . . . . . . . . . . . . 547.2.3. Példaprogramok fejelemes, kétirányú ciklikus listákra

(C2L) . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

8. Függvények aszimptotikus viselkedése(a Θ, O,Ω,≺,, o, ω matematikája) 598.1. N× N értelmezési tartományú függvények . . . . . . . . . . . 65

9. Fák, bináris fák 669.1. Listává torzult, szigorúan bináris,

teljes és majdnem teljes bináris fák . . . . . . . . . . . . . . . 689.2. Bináris fák mérete és magassága . . . . . . . . . . . . . . . . . 699.3. (Bináris) fák bejárásai . . . . . . . . . . . . . . . . . . . . . . 69

9.3.1. Fabejárások alkalmazása: bináris fa magassága . . . . . 729.4. Bináris fák reprezentációi . . . . . . . . . . . . . . . . . . . . . 72

9.4.1. Bináris fák láncolt ábrázolásai . . . . . . . . . . . . . . 729.4.2. Bináris fák zárójelezett, szöveges formája . . . . . . . . 749.4.3. Bináris fák aritmetikai ábrázolása . . . . . . . . . . . . 74

9.5. Bináris keres®fák . . . . . . . . . . . . . . . . . . . . . . . . . 749.6. Bináris keres®fák: keresés, beszúrás, törlés . . . . . . . . . . . 769.7. Szintfolytonos bináris fák, kupacok . . . . . . . . . . . . . . . 799.8. Szintfolytonos bináris fák aritmetikai ábrázolása . . . . . . . . 809.9. Kupacok és els®bbségi (prioritásos) sorok . . . . . . . . . . . . 81

9.9.1. Rendezés els®bbségi sorral . . . . . . . . . . . . . . . . 839.10. Kupacrendezés (heap sort) . . . . . . . . . . . . . . . . . . . . 84

9.10.1. A kupacrendezés m¶veletigénye . . . . . . . . . . . . . 869.10.2. A kupaccá alakítás m¶veletigénye lineáris . . . . . . . . 87

9.11. A merge sort m¶veletigényének kiszámítása . . . . . . . . . . . 89

10.Az összehasonlító rendezésekalsókorlát-elemzése 9210.1. Összehasonlító rendezések és a döntési fa modell

(Comparison sorts and the decision tree model) . . . . . . . . 9210.2. Alsó korlát a legrosszabb esetre

(A lower bound for the worst case) . . . . . . . . . . . . . . . 93

3

Page 4: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

11.Rendezés lineáris id®ben 9511.1. Radix rendezés (listákra) . . . . . . . . . . . . . . . . . . . . . 9511.2. Leszámláló rendezés (counting sort) . . . . . . . . . . . . . . . 10011.3. Radix rendezés (Radix-Sort) tömbökre ([4] 8.3) . . . . . . . . 10311.4. Egyszer¶ edényrendezés (bucket sort) . . . . . . . . . . . . . . 104

12.Hasító táblák (hash tables) 10612.1. Direkt címzés (direct-address tables) . . . . . . . . . . . . . . 10612.2. Hasító táblák (hash tables) . . . . . . . . . . . . . . . . . . . . 10712.3. Kulcsütközések feloldása láncolással

(collision resolution by chaining) . . . . . . . . . . . . . . . . . 10712.4. Jó hasító függvények (good hash functions) . . . . . . . . . . . 10912.5. Nyílt címzés (open addressing) . . . . . . . . . . . . . . . . . . 110

12.5.1. Nyílt címzés: beszúrás és keresés, ha nincs törlés . . . . 11012.5.2. Nyílt címzés¶ hasítótábla m¶veletei, ha van törlés is . . 11212.5.3. Lineáris próba . . . . . . . . . . . . . . . . . . . . . . . 11212.5.4. Négyzetes próba . . . . . . . . . . . . . . . . . . . . . . 11312.5.5. Kett®s hasítás . . . . . . . . . . . . . . . . . . . . . . . 114

4

Page 5: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

1. Bevezetés

Az itt következ® el®adásjegyzetekben bizonyos fejezetek még nem teljesek. Azel®adásokon tárgyalt programok struktogramjait igyekeztem minden esetbenmegadni, a másolási hibák kiküszöbölése érdekében. E tananyagon kívül amegértést segít® ábrák találhatóak b®ségesen az ajánlott segédanyagokban.

Ezúton szeretnék köszönetet mondani Umann Kristófnak az ebben a jegy-zetben található szép, színvonalas szemléltet® ábrák elkészítéséért, az ezekreszánt id®ért és szellemi ráfordításért!

A vizsgára való készülésben els®sorban az el®adásokon és a gyakorlatokonkészített jegyzeteikre támaszkodhatnak. További ajánlott források:

Hivatkozások

[1] Ásványi Tibor, Algoritmusok és adatszerkezetek I. el®adásjegyzet(2018)http://aszt.inf.elte.hu/∼asvanyi/ad/ad1jegyzet.pdf

[2] Fekete István, Algoritmusok jegyzethttp://ifekete.web.elte.hu/

[3] Rónyai Lajos Ivanyos Gábor Szabó Réka, Algoritmusok,TypoTEX Kiadó, 1999. ISBN 963 9132 16 0

[4] Cormen, T.H., Leiserson, C.E., Rivest, R.L., Stein, C.,magyarul: Új Algoritmusok, Scolar Kiadó, Budapest, 2003.ISBN: 963 9193 90 9angolul: Introduction to Algorithms (Third Edititon),The MIT Press, 2009.

[5] Wirth, N., Algorithms and Data Structures,Prentice-Hall Inc., 1976, 1985, 2004.magyarul: Algoritmusok + Adatstruktúrák = Programok, M¶szakiKönyvkiadó, Budapest, 1982. ISBN 963 10 3858 0

[6] Weiss, Mark Allen, Data Structures and Algorithm Analysis,Addison-Wesley, 1995, 1997, 2007, 2012, 2013.

5

Page 6: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Saját jegyzeteiken kívül els®sorban az ebben a jegyzetben [1], illetve az itthivatkozott helyeken [2, 4, 5] leírtakra támaszkodhatnak. A CLRS könyv [4],valamint Rónyai [3], Wirth [5] és Weiss [6] klasszikus munkáinak megfelel®fejezetei értékes segítséget nyújthatnak a mélyebb megértéshez. Ennek ajegyzetnek a *-gal jelölt alfejezetei szintén a mélyebb megértést szolgálják,azaz nem részei a vizsga anyagának.

Az angol nyelv¶ szakirodalom jelent®s része letölthet® pl. az alábbi hon-lapról.http://gen.lib.rus.ec/

A vizsgákon az elméleti kérdések egy-egy tétel bizonyos részleteire vonat-koznak. Lesznek még megoldandó feladatok, amelyekhez hasonlók az ebbena jegyzetben találhatókhoz.

6

Page 7: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

2. Tematika

Minden tételhez: Egy algoritmus, program, m¶velet bemutatásának min-dig része a m¶veletigény elemzése. Hivatkozások: például a [4] 2, 7 jelen-tése: a [4] sorszámú szakirodalom adott fejezetei.

1. Az algoritmus fogalma, programok hatékonysága: Intuitív bevezetés. Pél-da: beszúró rendezés (insertion sort) ([1]; [2]; [4] 1-3.)

2. Az oszd meg és uralkodj elv, összefésül® (összefuttató) rendezés (mergesort), gyorsrendezés (quicksort) ([1]; [4] 2, 7; [2]).

3. Az adatszerkezet és az adattípus fogalma. Elemi adattárolók: vermek(gyakorlat), sorok ([1]; [2]; [4] 10.1), megvalósításuk tömbös és láncolt rep-rezentációk esetén (láncolt listás megvalósítás a gyakorlatokon). Vermek fel-használása.

4. Elemi, lineáris adatszerkezetek: tömbök, láncolt listák, láncolt listáktípusai, listakezelés. ([1]; [2]; [4] 10; [5] 4.1 - 4.3)

5. Függvények aszimptotikus viselkedése (O, o,Ω, ω,Θ,≺,) . Programokm¶veletigénye (futási id® nagyságrendje: T (n),mT (n), AT (n),MT (n)) ([1];[2]; [4] 1-3.)

6. Fák, bináris fák, bejárások, láncolt reprezentáció, példák ([1]; [2]; [4] 10.4,12.1).

7. Bináris keres®fák és m¶veleteik, bináris rendez®fák ([1]; [2]; [4] 12; [5] 4.4).

8. Majdnem teljes bináris fák, aritmetikai ábrázolás, prioritásos sorok, ku-pac, kupac m¶veletei, kupacrendezés (heap sort) ([1]; [2]; [4] 6).

9. A beszúró, összefésül®, kupac és gyors rendezés összehasonlítása. Azösszehasonlító rendezések alsókorlát-elemzése ([1]; [4] 8.1; [2]).

10. Rendezés lineáris id®ben [1], [4] 8.2. A stabil rendezés fogalma. Leszám-láló rendezés (Counting-Sort). Radix rendezés (Radix-Sort) tömbökre ([4]8.3) és láncolt listákra ([1, 2]). Edényrendezés (bucket sort [1], [4] 8.4, [2]).

11. Hasító táblák [1], [4] 11. Direkt címzés (direct-address tables). Hasítótáblák (hash tables). A hasító függvény fogalma (hash functions). Kulcsüt-közések (collisions).

7

Page 8: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Kulcsütközések feloldása láncolással (collision resolution by chaining); ke-resés, beszúrás, törlés (search and update operations); kitöltöttségi arány(load factor); egyszer¶ egyenletes hasítás (simple uniform hashing), m¶velet-igények.

Jó hash függvények (good hash functions), egy egyszer¶ hash függvény(kulcsok a [0; 1) intervallumon), az osztó módszer (the division method), aszorzó módszer (the multiplication method).

Nyílt címzés (open addressing); próba sorozat (probe sequence); kere-sés, beszúrás, törlés (search and update operations); üres és törölt rések(empty and deleted slots); a lineáris próba, els®dleges csomósodás (linearprobing, primary clustering); négyzetes próba, másodlagos csomósodás (qu-adratic probing, secondary clustering); kett®s hash-elés (double hashing); azegyenletes hasítás (uniform hashing) fogalma; a keresés és a beszúrás próbasorozata várható hosszának fels® becslései egyenletes hasítást feltételezve.

3. Néhány alapvet® jelölés és elméleti háttere

B = false; true a logikai (boolean) értékek halmaza.N = 0; 1; 2; 3; . . . a természetes számok halmaza.Z = . . .− 3;−2,−1; 0; 1; 2; 3; . . . az egész számok halmaza.R a valós számok halmaza.P a pozitív valós számok halmaza.P0 a nemnegatív valós számok halmaza.

lg n =

log2 n ha n > 00 ha n = 0

fele(n) =⌊n2

⌋, ahol n ∈ N

Fele(n) =⌈n2

⌉, ahol n ∈ N

A képletekben és a struktogramokban alapértelmezésben (tehát ha a környe-zetb®l nem következik más) az i, j, k, l,m, n, I, J,K,M,N bet¶k egész számo-kat (illetve ilyen típusú változókat), míg a p, q, r, s, t, F, L bet¶k pointereket(azaz mutatókat, memóriacímeket, illetve ilyen típusú változókat) jelölnek.

A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett)típust jelöl, amelyen értékadás (pl. x := y) és általában teljes rendezés (az=, 6=, <,>,≤,≥ összehasonlításokkal) van értelmezve.

A változók láthatósága és hatásköre is az ®ket tartalmazó struktogram,élettartamuk pedig az els®, ®ket tartalmazó utasítás végrehajtásától a struk-togram befejezéséig tart. Kivételt képeznek a globális változók, amelyek aprogram egész végrehajtása alatt élnek és láthatók is. A változók lokálisannem deniálhatók felül.

8

Page 9: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

3.1. Tömbök

A tömböket pl. így deklarálhatjuk: A,Z : T[n]. Ekkor A és Z is n elem¶, Telemtípusú vektorok. A tömbök ismerik a méretüket, az A vektor méretepl. A.M , ami nem változtatható meg. (Itt tehát A.M = Z.M = n.)

A tömböket általában 1-t®l indexeljük, de azokat, amelyek neve z vagyZ bet¶vel végz®dik, zérustól.

A fenti A változó tehát egy n elem¶ vektor-objektumra hivatkozik, aminekelemeit az A[1], . . . , A[n] kifejezésekkel azonosíthatjuk, a Z pedig egy másikn elem¶ vektor-objektumra hivatkozik, de ennek elemeit a Z[0], . . . , Z[n− 1]kifejezésekkel azonosíthatjuk. A és Z valójában pointerek, amik a megfelel®vektor-objektum memóriacímét tartalmazzák.

Ha csak a T elemtípusú tömbökre hivatkozó P pointert akarjuk deklarálni,ezt a P : T[] deklarációs utasítással tehetjük meg.

Ezután a P pointer inicializálható pl. a P := Z értékadó utasítással,miután P is a Z által hivatkozott tömb-objektumra mutat, és így P.M =Z.M , valamint Z[0]-nak P [1], . . ., Z[n− 1]-nek P [n] felel meg.

Új tömb-objektumot dinamikusan pl. a new T[n] kifejezéssel hozhatunklétre, ami egy n elem¶, T elemtípusú vektort-objektumot hoz létre, és acímét visszaadja. A P := new T[n] utasítás hatására pl. a P pointer azúj vektor-objektumra fog hivatkozni, amelynek elemei sorban P [1], . . . P [n];mérete pedig P.M = n.

A Qz := new T[n] utasítás hatására a Qz pointer új, n elem¶ vektor-objektumra fog hivatkozni, amelynek elemei sorban Qz[0], . . . Qz[n− 1]. Ez-után a Zr := Qz értékadás hatására a Zr pointer is a Qz által hivatkozotttömb-objektumra hivatkozik, amelynek elemei a Zr pointeren keresztül sor-ban a Zr[1], . . . Zr[n] kifejezésekkel érhet®k el. Pl. a Zr[1] := 3 értékadásután Qz[0] = 3 is igaz lesz, hiszen Zr[1] és Qz[0] ugyanazt a memóriarekesztazonosítják. Hasonlóan, a Qz[1] := 5 értékadás után Zr[2] = 5 is igaz leszstb.

A mi (C/C++-hoz hasonló) modellünkben, a memóriaszivárgás elkerüléseérdekében, a dinamikusan (new utasítással) létrehozott, és már feleslegessévált objektumokat expliciten törölni kell. Ezzel ugyanis az objektum által le-foglalt memóriaterület újra felhasználhatóvá, míg ellentétes esetben az alkal-mazás számára elérhetetlenné válik. Az ilyen memóriadarabok felhalmozódá-sa jelent®sen csökkentheti a programunk által használható memóriát, abbanszemetet képez. (A JAVA és más hasonló környezetek a memória-szemétkezelésére automatikus eszközöket biztosítanak, de ennek a hatékonyság ol-daláról nézve súlyos ára van. Mivel a mi algoritmusaink egyik legfontosabbakalmazási területe a rendszerprogramozás, mi ilyen automatizmusokat nemfeltételezünk.)

9

Page 10: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

A new utasítással létrehozott objektumok törlésére a delete utasításszolgál, pl.

delete P ; delete Qz;amik törlik a P és a Qz pointerek által hivatkozott tömb-objektumokat,

de nem törlik a pointereket magukat. A fenti törlések hatására a P és a Qzpointerek nem deniált memóriacímeket tartalmaznak, és kés®bb újra érteketkaphatnak.

3.2. Szögletes zárójelek közé írt utasítások

A struktogramokban néha szerepelnek szögletes zárójelek közé írt utasítá-sok. Ez azt jelenti, hogy a bezárójelezett utasítás bizonyos programozásikörnyezetekben szükséges lehet. Ha tehát a program megbízhatósága és mó-dosíthatósága a legfontosabb szempont, ezek az utasítások is szükségesek. Haa végletekig kívánunk optimalizálni, akkor bizonyos esetekben elhagyhatók.

3.3. A struktogramok paraméterlistái,érték szerinti és cím szerinti paraméterátadás

A továbbiakban az eljárásokat, függvényeket és az osztályok metódusaitegyütt alprogramoknak nevezzük.

A struktogramokhoz mindig tartozik egy alprogram név és általában egyparaméterlista is (ami esetleg üres, de a () zárójelpárt ott is, és a megfelel®alprogram hívásban is kiírjuk). Ha egy struktogramhoz csak név tartozik,akkor az úgy értend®, mintha a benne található kód a hívás helyén lenne. Aparaméterek típusát és függvények esetén a visszatérési érték típusát azUML dobozokban szokásos módon jelöljük.

Ha egy paraméterlistával ellátott alprogram struktogramjában olyan vál-tozónév szerepel, ami a paraméterlistán nem szerepel, és nem is az alprogramküls® (azaz globális) változója, akkor ez a struktogrammal leírt alprogramlokális változója.

A skalár típusú1 paramétereket alapértelmezésben érték szerint vesszük át.2

A skalár típusú paramétereket cím szerint is átvehetjük, de akkor ezt aformális paraméter listán a paraméter neve el®tt egy & jellel jelölni kell. Pl.az alábbi eljárást a swap(a, b) utasítással meghíva, a és b értéke felcserél®dik.

1A skalár típusok az egyszer¶ típusok: a szám, a pointer és a felsorolás (pl. a logikai ésa karakter) típusok.

2Az érték szerinti paraméterátadás esetén, az eljáráshíváskor az aktuális paraméterértékül adódik a formális paraméternek, ami a továbbiakban úgy viselkedik, mint egylokális változó, és ha értéket kap, ennek nincs hatása az aktuális paraméterre.

10

Page 11: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

swap(&x,&y : T)

z := x

x := y

y := z

A cím szerinti paraméterátadás esetén ugyanis, az alprogram híváskor az ak-tuális paraméter összekapcsolódik a megfelel® formális paraméterrel, egészena hívott eljárás futásának végéig, ami azt jelenti, hogy bármelyik megválto-zik, vele összhangban változik a másik is. Amikor tehát a formális paraméterértéket kap, az aktuális paraméter is ennek megfelel®en változik. Ha az eljá-rásfej swap(x,&y) lenne, az eljáráshívás hatása b := a lenne, ha pedig azeljárásfej swap(x, y) lenne, az eljáráshívás logikailag ekvivalens lenne a SKIPutasítással.

A formális paraméter listán a felesleges &-prexek hibának tekintend®k,mert a cím szerint átadott skalár paraméterek kezelése az eljárás futása sorána legtöbb implementációban lassúbb, mint az érték szerint átadott paramé-tereké.

Az aktuális paraméter listán nem jelöljük külön a cím szerinti paramé-terátadást, mert a formális paraméter listáról kiderül, hogy egy tetsz®legesparamétert érték vagy cím szerint kell-e átadni. (Összhangban a C++ jelö-lésekkel.) Pl. a swap eljárás egy lehetséges meghívása: swap(A[i], A[j]).

Ha az alprogramokra szövegben hivatkozunk, a paraméterátadás módját néhány kivételes esett®l eltekintve szintén nem jelöljük. (Pl.: A swap(x, y)eljárás megcseréli az x és az y paraméterek értékét.)

A nem-skalár3 típusú paraméterek csak cím szerint adhatók át. (Pl. a töm-böket, rekordokat nem szeretnénk a paraméterátvételkor lemásolni.) A nem-skalár típusok esetén ezért egyáltalán nem jelöljük a paraméterátadás módját,hiszen az egyértelm¶.

3.4. Tömb típusú paraméterek a struktogramokban

A tömböket a formális paraméter listákon tömbre hivatkozó pointerként je-lölhetjük, pl. a

linearSearch(A : T[] ; x : T) : Nfüggvényfej olyan függvényre utalhat, ami az A vektorban megkeresi az

3A nem-skalár típusok az összetett típusok. Pl. a tömb, sztring, rekord, fájl, halmaz,zsák, sorozat, fa és gráf típusok, valamint a tipikusan struct, illetve class kulcsszavakkaldeniált osztályok.

11

Page 12: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

x els® el®fordulását, és visszaadja annak indexét; vagy nullát, ha x 6∈A[1], . . . , A[A.M ]. Alprogram híváskor a tömb paramétereknél az aktuálisparaméterben lev® memóriacím amely a megfelel® tömb-objektum címe a formális paraméterbe másolódik, így az is ugyanarra a tömb-objektumrafog hivatkozni. Ezért, ha a hívott alprogram futása során, a formális pa-raméter által hivatkozott tömböt megváltoztatjuk, ez az aktuális paraméteráltal hivatkozott tömbbel is azonnal megtörténik. Így alprogram híváskoraz aktuális paraméter tömb címét ugyan érték szerint adjuk át, a cím (azazpointer) által hivatkozott tömb-objektum mégis cím szerint adódik át.

3.5. Eljárások, függvények, ciklusok, rekurzió

El®ször egy egyszer¶ eljárást nézünk meg két változatban, ami egy tetsz®legesegy dimenziós tömb elemeit ugyanazzal az értékkel inicializálja. Mindkétesetben a Pascal programozási nyelvb®l esetleg már ismer®s léptet® ciklustalkalmazunk. Annyi a különbség, hogy az els® esetben sorban haladunk azelemeken, míg a másodikban sorban visszafelé, és az A vektor 1-t®l, míg a Zzérustól indexel®dik (mivel a neve Z-re végz®dik, ld. (3.1)).

Vegyük észre, hogy az init eljárás két változata a tömbök és a paraméter-átvétel tulajdonságai miatt ekvivalens. init(A : T[] ; x : T)

i := 1 to A.M

A[i] := x

init(Z : T[] ; x : T)

i := Z.M − 1 downto 0

Z[i] := x

Ebben a jegyzetben megkülönböztetjük az eljárás és a függvény fogalmát. Azeljárások a környezetükkel csak a paramétereiken (és esetleg küls® változó-kon) keresztül kommunikálnak, míg a függvényeknek visszatérési értékük isvan, amit a szokásos módon használhatunk fel. (A mi eljárás fogalmunknaka C programozási nyelvben és leszármazottaiban a void function felel meg.)Alább láthatunk példákat függvényekre. A linearSearch(A, x) függvényhí-vás az A vektorban megkeresi az x els® el®fordulását, és visszaadja annakindexét; vagy nullát, ha x 6∈ A[1], . . . , A[A.M ].

12

Page 13: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

linearSearch(A : T[] ; x : T) : N

i := 1

i ≤ A.M ∧ A[i] 6= x

i+ +

AAi > A.M

i := 0 SKIP

return i

A binarySearch(A, x) függvényhívás az A monoton növekv®en rendezett vek-torban megkeresi az x egyik el®fordulását, és visszaadja annak indexét; vagynullát, ha x 6∈ A[1], . . . , A[A.M ].

binarySearch(A : T[] ; x : T) : N

return binSearch(A, 1, A.M, x)

A fenti binarySearch(A, x) függvény, megfelel®en paraméterezve meghívja azalábbi binSearch(A, u, v, x) függvényt, ami az A[u..v] résztömbön keresi x-et(Ezt az állítást hamarosan igazoljuk.) A fenti paraméterezéssel tehát az egésztömbön keresi az x értéket. Így az alábbi függvény a fenti általánosítása.

binSearch(A : T[] ; u, v : N ; x : T) : N

AAu > v

return0

m :=⌊u+v2

⌋AA

A[m] > x

returnbinSearch(A, u,m− 1, x)

AAA[m] < x

returnbinSearch(A,m+ 1, v, x)

AAA[m] = x

returnm

Formailag a binSearch(A, u, v, x) rekurzív függvény, mivel van olyan prog-ramága, ahol önmagát hívja (amit rekurzív hívásnak nevezünk). A számító-gép minden alprogram hívásra ugyanúgy, az alprogram hívások lokális adataita call stack-ben tárolja, tehát a rekurzív hívásokat is ugyanúgy kezeli, mint anemrekurzívakat: tetsz®leges alprogram lokális adatainak akár több példánya

13

Page 14: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

is lehet a call stack-ben. Ez tehát önmagában nem okoz technikai nehézséget.A rekurzív alprogramoknál azonban gondoskodnunk kell a rekurzió megfelel®leállításáról, hiszen az alprogram elvileg a végtelenségig hívogathatja önma-gát. Ezt szolgálják a rekurzív alprogramokban az alább ismertetetend® ún.leálló ágak, amiket a bemen® adatok közül az ún. alapesetek aktiválnak.

Most igazoljuk, hogy a binSearch(A, u, v, x) függvény visszad egy k ∈u..v indexet, amelyre A[k] = x; vagy nullát, ha ilyen k index nem létezik.M¶ködését tekintve, el®ször megnézi, hogy az u..v intervallum nem üres-e. Ha üres, akkor az A[u..v] résztömb is az, tehát x-et nem tartalmazza,azaz nullát kell visszaadni. Ha az u..v intervallum nemüres, m lesz az A[u..v]résztömb középs® elemének indexe. Ha A[m] > x, akkor az A vektor monotonnövekv® rendezettsége miatt x csak az A[u..(m − 1)] résztömbben lehet, hapedig A[m] < x, akkor x csak az A[(m + 1)..v] résztömbben lehet. Mindkétesetben egy lépésben feleztük a résztömb méretét, amin keresni kell, és atovábbiakban, rekurzívan, ugyanez történik. (Ha szerencsénk van, és A[m] =x, akkor persze azonnal leállhatunk.) Így, könnyen belátható, hogy n =A.M jelöléssel, legfeljebb dlg ne + 1 lépésben elfogy az u..v intervallum, ésmegáll az algoritmus, hacsak nem áll meg hamarabb az A[m] = x feltétel¶programágon.

A binarySearch(A, x) függvény binSearch(A, u, v, x) rekurzív függvényszámára interfészt biztosít. A programozási tapasztalatok szerint a rekur-zív alprogramokhoz az esetek túlnyomó többségében szükség van egy ilyeninterfész alprogramra. A rekurzív alprogram ugyanis az esetek többségében,mint a fenti példában is, az eredeti feladat egy általánosítását számítja ki, ésgyakran több paramétere is van, mint az eredeti alprogramnak.

Figyeljük meg azt is, hogy a binSearch(A, u, v, x) függvénynek van kétrekurzív és két nemrekurzív programága. A nemrekurzív ágakat leálló ágak-nak nevezzük. Tetsz®leges rekurzív alprogramban kell lennie ilyenleálló ágnak, hiszen ez szükséges (bár önmagában még nem elégséges) arekurzió helyes megállásához. A leálló ágakon kezelt esetekeket alapesetek-nek nevezzük. (Ebben a függvényben tehát két alapeset van. Az egyik azüres intervallum esete, amikor nincs megoldás. A másik az A[m] = x eset,amikor megtaláltunk egy megoldást.) Ha egy rekurzív alprogramnak nincsleálló ága, akkor tuhatjuk, hogy vagy végtelen rekurzióba fog esni, vagy hibásm¶ködéssel fog megállni.

3.6. Programok, alprogramok és hatékonyságuk

Emlékeztetünk rá, hogy az eljárásokat, függvényeket és az osztályok metódu-sait együtt alprogramoknak nevezzük, így az általunk vizsgált szekvenciálisprogramok futása lényegében véve az alprogram hívások végrehajtásából áll.

14

Page 15: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

A programok hatékonyságát általában a ciklusiterációk és az alprogramhívások számának összegével mérjük, ésm¶veletigénynek nevezzük. A tapasz-talatok, és bizonyos elméleti megfontolások alapján is, a program valóságosfutási ideje a m¶veletigényével nagyjából egyenesen arányos. Mivel ennek azarányosságnak a szorzója els®sorban a számítógépes környezet sebességét®lfügg, így a m¶veletigény a programok hatékonyságáról jó, a programozá-si környezett®l alapvet®en független nagyságrendi információval szolgál. Alegtöbb program esetében a nemrekurzív alprogram hívások számlálása am¶veletigény nagyságrendje szempontjából elhanyagolható.

Most sorban megvizsgáljuk az el®z® alfejezetb®l ismer®s alprogramok m¶-veletigényeit, és ezzel kapcsolatban szemléletesen bevezetünk néhány m¶ve-letigény osztályt is. Az egyszer¶ség kedvéért a formális paraméterként adottvektor méretét mindegyik esetben n-nel jelöljük, és a m¶veletigényeket nfüggvényében adjuk meg. Általában is szokás a m¶veletigényt a bemenetméretének függvényében megadni.

Az init(A : T[] ; x : T) eljárás pontosan n iterációt végez, ahol n = A.M .A m¶veletigényt T (n)-nel jelölve tehát azt mondhatjuk, hogy T (n) = n+ 1.4

Ha (mint most is) T (n) az n pozitív együtthatós lineáris függvénye5, aztszokás mondani, hogy T (n) ∈ Θ(n), ahol Θ(n) az el®bbinél kicsit pontosab-ban azokat a függvényeket jelenti, amelyek aluról és felülr®l is az n pozitívegyütthatós lineáris függvényeivel becsülhet®k.(A T (n) = n+1 függvény alsóés fels® becslése is lehet önmaga.)

Ezt általánosítva azt mondhatjuk, hogy limn→∞ g(n) =∞ esetén Θ(g(n))az a függvényosztály, aminek elemei alulról és felülr®l is a g(n) pozitív együtt-hatós lineáris függvényeivel becsülhet®k. (A Θ(g(n)) füügvényosztály szoká-sos deníciója a 8. fejezetben olvasható. Könnyen látható, hogy ez az el®bbimeghatározással ekvivalens.)

A linearSearch(A : T[] ; x : T) : N függvény esetében nem tudunk ilyenáltalános, minden lehetséges inputra érvényes T (n) m¶veletigényt megadni,hiszen el®fordulhat, hogy azonnal megtaláljuk a keresett elemet, de az is,hogy végignézzük az egész vektort, de így sem találjuk. Ezért itt megkü-lönböztetünk minimális m¶veletigényt [legjobb eset: mT (n)] és maximálism¶veletigényt [legrosszabb eset: MT (n)].

Világos, hogy most mT (n) = 1. Ez akkor áll el®, amikor x a vektor els®eleme, és így egyet sem iterál a keres® ciklus. Ilyenkor, nagyságrendileg aztmondhatjuk, hogy mT (n) ∈ Θ(1), ahol Θ(1) azokat az f(n) függvényeket

4Nyilván ugyanez érvényes az init eljárás másik válozatára is.5Itt ez a pozitív együttható az egy.

15

Page 16: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

jelenti, amelyek két (n-t®l független) pozitív konstans közé szoríthatók, leg-alábbis nagy n értékekre. (Most minden n értékre 1 ≤ mT (n) ≤ 1, azaz azel®bbi követelmény teljesül.)

Továbbá MT (n) = n + 1. Ez az eset akkor áll el®, amikor a vektornem tartalmazza x-et. Az init eljárásnál mondottak alapján tehát mostMT (n) ∈ Θ(n).

A binarySearch(A : T[] ; x : T) : N függvény esetében nyilvánmT (n) = 2,ahonnét mT (n) ∈ Θ(1). (Ez az eset akkor realizálódik, amikor x a tömb⌊n+12

⌋sorszámú eleme.)

Azt mondhatjuk, hogy a bináris keresés m¶veletigénye a legrosszabb eset-ben körülbelül lg n-nel arányos, hiszen az aktuális résztömb minden rekurzívhívásnál felez®dik. (A legrosszabb eset akkor realizálódik, amikor x nemeleme a tömbnek.) Ebb®l arra következtethetünk, hogy MT (n) ∈ Θ(lg n).

A lineáris és a bináris keresést összehasonlítva, a legjobb eset m¶veletigényelényegében véve ugyanaz (bár a két keresés legjobb esete különbözik egymás-tól). A legrosszabb esetben a lineáris keresés Θ(n), míg a bináris keresésΘ(lg n) m¶veletigény¶, viszont a bináris keresés rendezett input vektort igé-nyel. Ha tehát az input rendezett, és elég sok elemet tartalmaz, a maximálism¶veletigényt tekintve a bináris keresés lényegesen gyorsabb, és az el®nyecsak tovább n®, amikor még nagyobb vektorokra hívjuk meg, hiszen

limn→∞

lg n

n= 0

Ha például n ≈ 1000 akkor lg n ≈ 10, ha n ≈ 106 akkor lg n ≈ 20, és ha n ≈109 akkor lg n ≈ 30, ami azt mutatja, hogy a bináris keresés m¶veletigényenagyon lassan n®; gyakorlati méret¶ rendezett vektorokra, kevesebb, mint40 rekurzív hívás b®ven elegend®, míg a lineáris keresés akár sok milliárdciklusiterációt is igényelhet.

Szokás még az algoritmusok AT (n) átlagos m¶veletigényér®l is beszélni, aholn az input mérete. Ezt általában a m¶veletigény várható értékeként határoz-zák meg, úgy hogy felteszik, minden lehetséges bemenetnek ugyanakkora avalószín¶sége (ami nem mindig tükrözi a valóságot). Mindenféleképppen igazkell legyen, hogy mT (n) ≤ AT (n) ≤ MT (n). Részletes kiszámítását me-felel® matematikai felkészültség híján néhány kivételt®l eltekintve mell®znifogjuk.

16

Page 17: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

4. Az algoritmusok témakör bevezetése

a beszúró rendezésen keresztül

Az algoritmus egy jól deniált kiszámítási eljárás, amely valamely adatok (be-menet vagy input) felhasználásával újabbakat (kimenet, eredmény vagy out-put) állít el® [4]. (Gondoljunk pl. két egész szám legnagyobb közös osztójára[lnko(x, y : Z) : Z], a lineáris keresésre a maximum keresésre, az összegzésrestb.) Az algoritmus bemenete adott el®feltételnek kell eleget tegyen. (Azlnko(x, y) függvény esetén pl. x és y egész számok, és nem mindkett® nulla.)Ha az el®feltétel teljesül, a kimenet adott utófeltételnek kell eleget tegyen. Azutófeltétel az algoritmus bemenete és a kimenete közt elvárt kapcsolatot írjale. Maga az algoritmus számítási lépésekb®l áll, amiket általában szekven-ciák, elágazások, ciklusok, eljárás- és függvényhívások segítségével, valamelypszeudo-kódot (pl. struktogramokat) felhasználva formálunk algoritmussá.

Szinte minden komolyabb számítógépes alkalmazásban szükséges, els®-sorban a tárolt adatok hatékony visszakeresése céljából, azok rendezése. Ígytémánk egyik klasszikusa a rendezési feladat. Most megadjuk, a rendez® algo-ritmusok bemenetét és kimenetét milyen el®- és utófeltétel páros, ún. feladatspecikáció írja le. Ehhez el®ször megemlítjük, hogy kulcs alatt olyan adatotértünk, aminek típusán teljes rendezés deniált. (Kulcs lehet pl. egy számvagy egy sztring.)

Bemenet: n darab kulcs 〈a1, a2, . . . , an〉 sorozata.

Kimenet: A bemenet egy olyan 〈ap1 , ap2 , . . . , apn〉 permutációja, amelyreap1 ≤ ap2 ≤ . . . ≤ apn .

A fenti feladat nagyon egyszer¶, ti. könnyen érthet®, hatékony megoldásáraviszont kinomult algoritmusokat (és hozzájuk kapcsolódó adatszerkezeteket)dolgoztak ki, így az algoritmusok témakörnek a szakirodalomban jól beváltbevezetése lett.

4.1. Vektor monoton növekv® rendezése

Rendezés alatt a továbbiakban, alapértelmezésben mindig monoton növekv®,pontosabban nem-csökken® rendezést fogunk érteni, úgy, hogy a rendezésmegfeleljen a fenti specikációnak.

Egy sorozatot legegyszer¶bben egy vektorban tárolhatunk, amit az egyesrendez® eljárások paraméterlistáján általában A : T[]-vel fogunk jelöl-ni. Emlékeztetünk, hogy az A zikailag egy pointer, amely az úgynevezettvektor-objektum memóriacímét tartalmazza, és így alkalmas a vektor azono-sítására. A vektor-objektum a vektor elemeinek számát (A.M), és a vektor

17

Page 18: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

elemeit (A[1], . . . , A[A.M ]) tartalmazza. Ha A.M = 0, akkor a vektornaknincs eleme. A[k..u] az a részvektor, amelyben k az els® elem indexe, u pedigaz utolsó elem indexe. Ha k > u, akkor a részvektor üres. Az A[1..A.M ]részvektor az A vektor minden elemét tartalmazza. A vektor rendezések-nél feltesszük, hogy a vektor T elemtípusára teljes rendezés deniált, és azértékadó utasítás is értelmezve van.

4.1.1. Beszúró rendezés (Insertion sort)

Ha valaki semmit sem tud a rendezésekr®l, és megkapja azt a feladatot, hogyrakjon 10-30 dolgozatot nevek szerint sorba, jó eséllyel ösztönösen ezt azalgoritmust fogja alkalmazni: Kiválaszt egy dolgozatot, a következ®t ábécérendben elé vagy mögé teszi, a harmadikat e kett® elé, közé, vagy mögé teszi amegfelel® helyre stb. Ha a rendezést egy számsorra kell alkalmaznunk, pl. az〈5, 4, 2, 8, 3〉-ra, el®ször felosztjuk a sorozatot egy rendezett és egy ezt követ®rendezetlen szakaszra, úgy, hogy kezdetben csak az els® szám van a rendezettrészben: 〈5 | 4, 2, 8, 3〉. Ezután beszúrjuk a rendezetlen szakasz els® eleméta rendezett részbe a megfelel® helyre, és ezt ismételgetjük, amíg a sorozatrendezetlen vége el nem fogy:〈5, 4, 2, 8, 3〉 = 〈5 | 4, 2, 8, 3〉 → 〈4, 5 | 2, 8, 3〉 →→ 〈2, 4, 5 | 8, 3〉 → 〈2, 4, 5, 8 | 3〉 → 〈2, 3, 4, 5, 8 | 〉 = 〈2, 3, 4, 5, 8〉.

A rendezett beszúrás technikája attól függ, hogyan tároljuk a sorozatot.Ha egy tömbben, akkor az a legegyszer¶bb megoldás, ha a beszúrandó elemetaddig cserélgetjük a bal szomszédjával, amíg a helyére nem ér. naiveInsertionSort(A : T[])

i := 2 to A.Mj := i

j > 1 ∧ A[j − 1] > A[j]

swap(A[j − 1], A[j])

j := j − 1

A fenti naiv megoldás azonban sok felesleges adatmozgatással jár, hiszen aztaz elemet, amit a helyére szeretnénk vinni, újra és újra kivesszük a vektorból,majd visszatesszük bele. Nyilván hatékonyabb lenne, ha az elején kivennénk,majd amikor már megvan a helye, csak akkor tennénk vissza a tömbbe. (Hapersze már eleve a helyén van, akkor meg se mozdítjuk.)

Az el®bbi megfontolás alapján a beszúró rendezés alapváltozatában a be-szúrást úgy végezzük el, hogy a rendezetlen szakasz els® elemét (legyen x)

18

Page 19: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

összehasonlítjuk a rendezett szakasz utolsó elemével (legyen u). Ha u ≤ x,akkor x a helyén van, csak a rendezett szakasz fels® határát kell eggyel nö-velni. Ha u > x, akkor x-et elmentjük egy temporális változóba, és u-t az xhelyére csúsztatjuk. Úgy képzelhetjük, hogy u régi helyén most egy lyukkeletkezett. Az x a lyukba pontosan akkor illik bele, ha nincs bal szomszédja,vagy ez ≤ x. Addig tehát, amíg a lyuknak van bal szomszéja, és ez nagyobb,mint x, a lyuk bal szomszédját mindig a lyukba tesszük, és így a lyuk balramozog. Ha a lyuk a helyére ér, azaz x beleillik, akkor bele is tesszük. (Ld.az 1. ábrát és az alábbi struktogramot!)

Tekintsük például a 〈2, 4, 5, 8, 3〉 tömböt, ami a 8-ig rendezett, és márcsak a 3-at kell rendezetten beszúrni. El®ször úgy találjuk, hogy 8 > 3, így a3-at kivesszük x-be, majd a lyukat (jelölje _) a helyére mozgatjuk, és végülbeletesszük a 3-at: 〈2, 4, 5, 8, 3〉 → 〈2, 4, 5, 8,_〉, x = 3 → 〈2, 4, 5,_, 8〉, x =3→ 〈2, 4,_, 5, 8〉, x = 3→ 〈2,_, 4, 5, 8〉, x = 3→ 〈2, 3, 4, 5, 8〉. insertionSort(A : T[])

i := 2 to A.M

AAA[i− 1] > A[i]

x := A[i]

A[i] := A[i− 1]

j := i− 2

j > 0 ∧ A[j] > x

A[j + 1] := A[j]

j := j − 1

A[j + 1] := x

SKIP

A fenti eljárás az A vektort rendezi monoton növekv®en az el®bb ismertetettegyszer¶ beszúró rendezéssel. A f® ciklus invariánsa:

2 ≤ i ≤ (A.M + 1) ∧ A[1..A.M ] az input vektor egy permutáltja,ami az (i− 1) -edik eleméig monoton növekv®en rendezett.

Összefoglalva a m¶ködést: Ha A.M < 2, akkor az A vektor üres, vagy egyele-m¶, ezért rendezett, és a program f® ciklusa egyszer sem fut le. Ha A.M ≥ 2,a rendezés meghívásakor csak annyit tudhatunk, hogy A[1..1] rendezett, te-hát i := 2-re fennáll az invariáns. A f® ciklus magja ezután mindig A[i]-tszúrja be a vektor rendezett szakaszába, i-t eggyel növeli és tartja az invari-ánst. Mikor tehát i eléri az A.M +1 értéket, már a teljes A vektor rendezett,és ekkor be is fejez®dik az eljárás.

19

Page 20: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

5 2 7 1 4 6 8 3

2 5 7 1 4 6 8 3

2 5 7 1 4 6 8 3

1 2 5 7 4 6 8 3 (*)

1 2 4 5 7 6 8 3

1 2 4 5 6 7 8 3

1 2 4 5 6 7 8 3

1 2 3 4 5 6 7 8

(*) kifejtése:

1 2 5 7 4 6 8 3

x = 4

1 2 5 7 6 8 3

x = 4

1 2 5 7 6 8 3

x = 4

1. ábra. A beszúró rendezés szemléltetése.

20

Page 21: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

4.1. Feladat. Az 1. ábrának megfelel® módon illusztrálja a beszúró rendezés(insertion sort) m¶ködését az alábbi tömbre! A második 22 beszúrását fejtseis ki! A = 〈31; 41; 59; 22; 58; 7; 22; 91; 41〉.

4.1.2. Programok hatékonysága és a beszúró rendezés

Fontos kérdés, hogy egy S program, például a fenti rendezés mennyire ha-tékony. Hatékonyság alatt az eljárás er®forrás igényét, azaz futási idejét éstárigényét értjük.6 Az algoritmusok er®forrásigényét a bemenet mérete, ren-dez® algoritmusoknál a rendezend® adatok száma (n) függvényében szokásmegadni. (A mi esetünkben tehát n = A.M .) Mivel ez az egyszer¶ ren-dezés a rendezend® vektoron kívül csak néhány segédváltozót igényel, extratárigénye minimális, n-t®l független konstans, azaz MIS(n) ∈ Θ(1) [ahol azM a memóriaigényre utal, az IS pedig a rendezés angol nevének (InsertionSort) a rövidítése]. Így els®sorban a futási idejére lehetünk kíváncsiak. Mintmár említettük (3.6), ezzel kapcsolatos nehézség, hogy nem ismerjük a leen-d® programozási környezetet: sem a programozási nyelvet, amiben kódolnifogják, sem a fordítóprogramot vagy interpretert, sem a leend® futtatási kör-nyezetet, sem a számítógépet, amin futni fog, így nyilván a futási idejét semtudjuk meghatározni.

Meghatározhatjuk, vagy legalább becslés(eke)t adhatunk viszont arra,hogy adott n méret¶ input esetén valamely adott S algoritmus hány eljá-ráshívást hajt végre + hányat iterálnak összesen kódban szerepl® különböz®ciklusok. Emlékeztetünk rá, hogy megkülönböztetjük a legrosszabb vagy ma-ximális MTS(n), a várható vagy átlagos ATS(n) és a legjobb vagy minimálismTS(n) eseteket (3.6). A valódi maximális, átlagos és minimális futási id®káltalában ezekkel arányosak lesznek. Ha MTS(n) = mTS(n), akkor dení-ció szerint TS(n) a minden esetre vonatkozó m¶veletigény (tehát az eljárás-hívások és a ciklusiterációk számának összege), azaz TS(n) = MTS(n) =ATS(n) = mTS(n)

A továbbiakban, a programok futási idejével kapcsolatos számításoknál, am¶veletigény és a futási id®, valamint a költség kifejezéseket szinonímákkéntfogjuk használni, és ezek alatt az eljáráshívások és a ciklusiterációk összegérevonatkozó MTS(n), ATS(n), mTS(n) és ha létezik, TS(n) függvényeketértjük, ahol n az input mérete.

Tekintsük most példaként a fentebb tárgyalt beszúró rendezést (inserti-on sort)!7 A rendezés során egyetlen eljáráshívás hajtódik végre, és ez az

6Nem különböztetjük meg most a különféle hardver komponenseket, hiszen ezeket azalgoritmus szintjén nem is ismerjük.

7A legtöbb program esetében a nemrekurzív alprogram hívások számlálása a m¶ve-letigény nagyságrendje szempontjából elhanyagolható. A gyakorlás kedvéért most mégis

21

Page 22: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

insertionSort(A : T[]) eljárás hívása. Az eljárás f® ciklusa minden esetbenpontosan (n − 1) -szer fut le. (Továbbra is használjuk az n = A.M rövidí-tést.)

El®ször adjunk becslést a beszúró rendezés minimális futási idejére, amitjelöljünk mTIS(n)-nel, ahol n a rendezend® vektor mérete, általában a kér-déses kód által manipulált adatszerkezet mérete.8 Lehet, hogy a bels® ciklusegyet sem iterál, pl. ha a f® ciklus mindig a jobboldali ágon fut le, mertA[1..n] eleve monoton növekv®en rendezett. Ezért

mTIS(n) = 1 + (n− 1) = n

(Egy eljáráshívás + a küls® ciklus (n− 1) iterációja.)Most adjunk becslést (MTIS(n)) a beszúró rendezés maximális futási ide-

jére! Világos, hogy az algoritmus ciklusai akkor iterálnak a legtöbbet, hamindig a küls® ciklus bal ágát hajtja végre, és a bels® ciklus j = 0-ig fut. (Ezakkor áll el®, ha a vektor szigorúan monoton csökken®en rendezett.) Vég-rehajtódik tehát egy eljáráshívás + a küls® ciklus (n− 1) iterációja, amiheza küls® ciklus adott i értékkel való iterációjakor a bels® ciklus maximum(i− 2)-ször iterál. Mivel az i, 2-t®l n-ig fut, a bels® ciklus összesen legfeljebb∑n

i=2 (i− 2) iterációt hajt végre. Innét

MTIS(n) = 1 + (n− 1) +n∑i=2

(i− 2) = n+n−2∑j=0

j = n+(n− 1) ∗ (n− 2)

2

MTIS(n) =1

2n2 − 1

2n+ 1

Látható, hogy a minimális futási id® becslése az A[1..n] input vektor mére-tének lineáris függvénye, míg a maximális, ugyanennek négyzetes függvénye,ahol a polinom f® együtthatója mindkét esetben pozitív. A továbbiakbanezeket a következ®képpen fejezzük ki:

mTIS(n) ∈ Θ(n), MTIS(n) ∈ Θ(n2).

A Θ(n) (Theta(n)) függvényosztály ugyanis tartalmazza az n összes, pozitívegyütthatós lineáris függvényét, Θ(n2) pedig az n összes, pozitív együtthatósmásodfokú függvényét. (Általában egy tetsz®leges g(n), a program haté-konyságának becslésével kapcsolatos függvényre a Θ(g(n)) függvényosztálypontos denícióját a 8. fejezetben fogjuk megadni.)

gyelembe vesszük ®ket.8Az IS a rendezés angol nevének (Insertion Sort) a rövidítése.

22

Page 23: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Mint a maximális futási id®re vonatkozó példából látható, a Θ jelölés sze-repe, hogy elhanyagolja egy polinom jelleg¶ függvényben (1) a kisebb nagy-ságrend¶ tagokat, valamint (2) a f® tag pozitív együtthatóját. Az el®bbi azértjogos, mert a futási id® általában nagyméret¶ inputoknál igazán érdekes, hi-szen tipikusan ilyenkor lassulhat le egy egyébként logikailag helyes program.Elég nagy n-ekre viszont pl. az a ∗ n2 + b ∗ n + c polinomban a ∗ n2 mellettb∗n és c elhanyagolható. A f® tag pozitív együtthatóját pedig egyrészt azérthanyagolhatjuk el, mert ez a programozási környezet, mint például a számí-tógép sebességének ismerete nélkül tulajdonképpen semmitmondó, másrésztpedig azért, mert ha az a ∗ f(n) alakú f® tag értéke n -et végtelenül növelvemaga is a végtelenhez tart (ahogy az lenni szokott), elég nagy n-ekre az akonstans szorzó sokkal kevésbé befolyásolja a függvény értékét, mint az f(n).

Látható, hogy a beszúró rendezés a legjobb esetben nagyon gyorsan ren-dez: Nagyságrendileg a lineáris m¶veletigénynél gyorsabb rendezés elvileg islehetetlen, hiszen ehhez a rendezend® sorozat minden elemét el kell érnünk.A legrosszabb esetben viszont, ahogy n n®, a futási id® négyzetesen növek-szik, ami, ha n milliós vagy még nagyobb nagyságrend¶, már nagyon hosszúfutási id®ket eredményez. Vegyünk példának egy olyan számítógépet, amimásodpercenként 2 ∗ 109 elemi m¶veletet tud elvégezni. Jelölje most mT (n)az algoritmus által elvégzend® elemi m¶veletek minimális, míg MT (n) a ma-ximális számát! Vegyük gyelembe, hogy mTIS(n) = n, ami közelít®leg aküls® ciklus iterációinak száma, és a küls® ciklus minden iterációja legalább8 elemi m¶veletet jelent; továbbá, hogy MTIS(n) ≈ (1/2) ∗ n2, ami közelít®-leg a bels® ciklus iterációinak száma, és itt minden iteráció legalább 12 elemim¶veletet jelent. Innét a mT (n) ≈ 8 ∗ n és a MT (n) ≈ 6 ∗ n2 képletekkelszámolva a következ® táblázathoz jutunk:

n mTIS(n) in secs MTIS(n) in time1000 8000 4 ∗ 10−6 6 ∗ 106 0.003 sec106 8 ∗ 106 0.004 6 ∗ 1012 50 min107 8 ∗ 107 0.04 6 ∗ 1014 ≈ 3.5 days108 8 ∗ 108 0.4 6 ∗ 1016 ≈ 347 days109 8 ∗ 109 4 6 ∗ 1018 ≈ 95 years

Világos, hogy már tízmillió rekord rendezésére is gyakorlatilag használha-tatlan az algoritmusunk. (Az implementációs problémákat most gyelmenkívül hagytuk.) Látjuk azt is, hogy hatalmas a különbség a legjobb és alegrosszabb eset között.

Felmerülhet a kérdés, hogy átlagos esetben mennyire gyors az algoritmus.Itt az a gond, hogy nem ismerjük az input sorozatok eloszlását. Ha példáulaz inputok monoton növekv®en el®rendezettek, ami alatt azt értjük, hogy az

23

Page 24: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

input sorozat elemeinek a rendezés utáni helyükt®l való távolsága általábanegy n -t®l független k konstanssal felülr®l becsülhet®, azok száma pedig,amelyek a végs® pozíciójuktól távolabb vannak, egy szintén n -t®l függetlens konstanssal becsülhet® felülr®l, az algoritmus m¶veletigénye lineáris, azazΘ(n) marad, mivel a bels® ciklus nem többször, mint (k + s) ∗ n -szer futle. Ha viszont a bemenet monoton csökken®en el®rendezett, az algoritmusm¶veletigénye is közel marad a legrosszabb esethez. (Bár ha ezt tudjuk,érdemes a vektort a rendezés el®tt Θ(n) id®ben megfordítani, és így monotonnövekv®en el®rendezett vektort kapunk.)

Véletlenített input sorozat esetén, egy-egy újabb elemnek a sorozat márrendezett kezd® szakaszába való beszúrásakor, átlagosan a rendezett szakasz-ban lév® elemek fele lesz nagyobb a beszúrandó elemnél. A rendezés várhatóm¶veletigénye ilyenkor tehát:

ATIS(n) ≈ 1 + (n− 1) +n∑i=2

(i− 2

2

)= n+

1

2∗n−2∑j=0

j =

= n+1

2∗ (n− 1) ∗ (n− 2)

2=

1

4n2 +

1

4n+

1

2

Nagy n -ekre tehát ATIS(n) ≈ (1/4) ∗ n2. Ez körülbelül a fele a maximálisfutási id®nek, ami a rendezend® adatok milliós nagyságrendje esetén már ígyis nagyon hosszú futási id®ket jelent. Nagyságrenddel jobb m¶veletigényeketkapunk majd a heap sort, valamint az oszd meg és uralkodj elven alapulórendezések (merge sort, quicksort) esetén. Összegezve az eredményeinket:

mTIS(n) ∈ Θ(n)

ATIS(n),MTIS(n) ∈ Θ(n2)

Ehhez hozzátehetjük, hogy el®rendezett inputok esetén (ami a programozásigyakorlatban egyáltalán nem ritka) a beszúró rendezés segítségével lineárisid®ben tudunk rendezni, ami azt jelenti, hogy erre a feladatosztályra nagyság-rendileg, és nem túl nagy k és s konstansok esetén valóságosan is az optimálismegoldás a beszúró rendezés.

4.2. A futási id®kre vonatkozó becslések magyarázata*

Jelölje most az insertionSort(A : T[]) eljárás tényleges maximális és minimálisfutási idejét sorban MT (n) és mT (n), ahol n = A.M !

Világos, hogy a rendezés akkor fut le a leggyorsabban, ha a f® ciklusminden elemet a végs® helyén talál, azaz mindig a jobboldali ágon fut le. (Ez

24

Page 25: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

akkor áll el®, ha a vektor már eleve monoton növekv®en rendezett.) Legyena a f® ciklus jobboldali ága egyszeri végrehajtásának futási ideje, b pedigaz eljárás meghívásával, a f® ciklus el®készítésével és befejezésével, valamintaz eljárásból való visszatéréssel kapcsolatos futási id®k összege! Ekkor a ésb nyilván pozitív konstansok, és mT (n) = a ∗ (n − 1) + b. Legyen mostp = min(a, b) és P = max(a, b); ekkor 0 < p ≤ P , ésp ∗ (n− 1) + p ≤ mT (n) = a ∗ (n− 1) + b ≤ P ∗ (n− 1) + P , ahonnétp ∗ n ≤ mT (n) ≤ P ∗ n, azaz

p ∗mTIS(n) ≤ mT (n) ≤ P ∗mTIS(n)

Most adjunk becslést a beszúró rendezés maximális futási idejére, (MT (n))!Világos, hogy az algoritmus akkor dolgozik a legtöbbet, ha mindig a küls®ciklus bal ágát hajtja végre, és a bels® ciklus j = 0-ig fut. (Ez akkor áll el®,ha az input vektor szigorúan monoton csökken®en rendezett.) Legyen mosta bels® ciklus egy lefutásának a m¶veletigénye d; c pedig a küls® ciklus balága egy lefutásához szükséges id®, eltekintve a bels® ciklus lefutásaitól, dehozzászámolva a bels® ciklusból való kilépés futási idejét (amibe beleértjüka bels® ciklus feltétele utolsó kiértékelését, azaz a j = 0 esetet), ahol c, d > 0állandók. Ezzel a jelöléssel:

MT (n) = b+ c ∗ (n− 1) +n∑i=2

d ∗ (i− 2) = b+ c ∗ (n− 1) + d ∗n−2∑j=0

j =

= b+ c ∗ (n− 1) + d ∗ (n− 1) ∗ (n− 2)

2Legyen most q = min(b, c, d) és Q = max(b, c, d); ekkor 0 < q ≤ Q, ésq + q ∗ (n− 1) + q ∗ (n−1)∗(n−2)

2≤MT (n) ≤ Q+Q ∗ (n− 1) +Q ∗ (n−1)∗(n−2)

2

q ∗ (n+ (n−1)∗(n−2)2

) ≤MT (n) ≤ Q ∗ (n+ (n−1)∗(n−2)2

), azaz

q ∗MTIS(n) ≤ mT (n) ≤ Q ∗MTIS(n)

Mindkét esetben azt kaptuk tehát, hogy a valódi futási id® az eljáráshívásokés a ciklusiterációk számának összegével becsült futási id® megfelel® pozitívkonstansszorosaival alulról és felülr®l becsülhet®, azaz, pozitív konstans szor-zótól eltekintve ugyanolyan nagyságrend¶. Könny¶ meggondolni, hogy ez amegállapítás tetsz®leges program minimális, átlagos és maximális futási ide-jeire is általánosítható. (Ezt azonban már az Olvasóra bízzuk.)

4.3. Rendezések stabilitása

Egy rendezés akkor stabil, ha megtartja az egyenl® kulcsú elemek eredetisorrendjét.

25

Page 26: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

A beszúró rendezés (insertion sort) például úgy, ahogy ebben a jegyzet-ben tárgyaljuk stabil. A fenti vektorrendez® algoritmusnál ez abból látható,hogy a tömb rendezett szakaszába az újabb elemeket jobbról balra szúrjukbe, és a beszúrandóval egyenl® kulcsú elemeket már nem lépjük át.

Hasonlóképpen látni fogjuk, hogy a kés®bb ismertetend® összefésül® ren-dezés (merge sort) is stabil, míg a kupacrendezés (heap sort) és gyorsrendezés(quicksort) nem stabilak.

A stabilitás el®nyös tulajdonság lehet, ha rekordokat rendezünk, és van-nak azonos kulcsú rekordok. Tegyük fel például, hogy a rekordok emberekadatait tartalmazzák, és név szerint vannak rendezve. Ha most ugyanezeketa rekordokat stabil rendezéssel pl. születési év szerint rendezzük, akkor azazonos évben születettek névsorban maradnak.

A stabilitás nélkülözhetetlen tulajdonság lesz majd kés®bb a (lineáris m¶-veletigény¶) radix rendezésnél (ami nem kulcsösszehasonlításokkal dolgozik,eltér®en a beszúró és a fent említett másik három rendezést®l).

4.4. Kiválasztó rendezések (selection sorts)

4.2. Feladat. Tekintsük az A[1..n] tömb rendezését a következ® algoritmus-sal! El®ször megkeressük a tömb minimális elemét, majd megcseréljük A[1]-gyel. Ezután megkeressük a második legkisebb elemét és megcseréljük A[2]-vel.Folytassuk ezen a módon az A[1..n] els® (n − 1) elemére! (Itt tehát a vek-tort egy rendezett és egy rendezetlen szakaszra bontjuk. A rendezett szakasz atömb elején kezdetben üres. A minimumkeresés mindig a rendezetlen részentörténik, és a csere után a rendezett szakasz mindig eggyel hosszabb lesz.)Pl.:

〈3; 9; 7; 1; 6; 2〉 → 〈1 | 9; 7; 3; 6; 2〉→ 〈1; 2 | 7; 3; 6; 9〉 → 〈1; 2; 3 | 7; 6; 9〉→ 〈1; 2; 3; 6 | 7; 9〉 → 〈1; 2; 3; 6; 7 | 9〉→ 〈1; 2; 3; 6; 7; 9〉

Írjunk struktogramot erre a minimumkiválasztásos rendezés néven közis-mert algoritmusra MinKivRend(A : T[]) néven! Mi lesz a f® ciklus invari-ánsa? Miért elég csak az els® (n−1) elemre lefuttatni? (n = A.M jelöléssel.)Adjuk meg az MT (n) és mT (n) függvényeket a minimumkiválasztásos ren-dezésre a szokásos Θ-jelöléssel!

4.3. Feladat. Tekintsük az A[1..n] tömb rendezését a következ® algorit-mussal! El®ször megkeressük a tömb maximális elemét, majd megcseréljükA[n]-nel. Ezután megkeressük a második legnagyobb elemét és megcseréljükA[n − 1]-gyel. Folytassuk ezen a módon az A[1..n] utolsó (n − 1) elemére!Pl.:

26

Page 27: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

〈3; 1; 9; 2; 7; 6〉 → 〈3; 1; 6; 2; 7 | 9〉→ 〈3; 1; 6; 2 | 7; 9〉 → 〈3; 1; 2 | 6; 7; 9〉→ 〈2; 1 | 3; 6; 7; 9〉 → 〈1 | 2; 3; 6; 7; 9〉→ 〈1; 2; 3; 6; 7; 9〉

Írjunk struktogramot erre a maximumkiválasztásos rendezés néven közis-mert algoritmusra MaxKivRend(A : T[]) néven! Mi lesz a f® ciklus in-variánsa? Miért elég csak az utolsó (n − 1) elemre lefuttatni? (n = A.Mjelöléssel.) Adjuk meg az MT (n) és mT (n) függvényeket a maximumkivá-lasztásos rendezésre a szokásos Θ-jelöléssel!

4.4. Feladat. Stabilak-e a fenti kiválasztó rendezések? Miért?

27

Page 28: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

5. Az oszd meg és uralkodj elven alapuló gyors

rendezések

Az oszd meg és uralkodj elv lényege, hogy az eredeti problémát (rekurzí-van) két vagy több részproblémára bontjuk fel, kivéve, ha annyira egyszer¶,hogy direkt módon is könnyedén megoldható. Az részproblémák ugyanolyanjelleg¶ek, mint az eredeti, csak valamilyen értelemben kisebb méret¶ek, ésugyanazzal az algoritmussal oldjuk meg ®ket, mint az eredetit. A részprob-lémák megoldásaiból rakjuk össze az eredeti feladat megoldását.

A fent vázolt oszd meg és uralkodj technika sokféle probléma hatékony,algoritmikus megoldásának alapja. Ebben a fejezetben a gyorsrendezést (qu-icksort) és az összefésül® (összefuttató) rendezést (merge sort) hozzuk példá-nak.

5.1. Összefésül® rendezés (merge sort)

Az oszd meg és uralkodj módszerrrel gyakran adhatunk optimális megol-dást. Az adott problémát két (vagy több) az eredetihez hasonló, de egysze-r¶bb, azaz kisebb részfeladatra bontjuk, majd ezeket megoldva, a részered-ményekb®l összerakjuk az felbontott probléma megoldását. Ha a megoldandó(rész)probléma elég egyszer¶, akkor ezt már közvetlenül oldjuk meg.

Ha például adott egy rendezend® kulcssorozat, az általános elvnek megfele-l®en most is két esetet különböztetünk meg:

Az üres és az egyelem¶ sorozatok eleve rendezettek; a hosszabb sorozato-kat pedig elfelezzük, a két fél-sorozatot ugyanezzel a módszerrel rendezzük,és a rendezett fél-sorozatokat rendezetten összefésüljük.

Ezt az eljárást hívjuk összefésül®, vagy más néven összefuttató rende-zésnek (angolul merge sort, ld. a 2. ábrát).

Az összefésül® rendezés stabil (azaz meg®rzi az egyenl® kulcsú elemekbemeneti sorrendjét). A legrosszabb esetének m¶veletigénye aszimptotikusanoptimális az ún. összehasonító rendezések között (a részletek a 10. fejezetbenolvashatók).

Az összefésül® rendezés (merge sort, rövidítve MS) nagy elemszámú so-rozatokat is viszonylag gyorsan rendez. Ráadásul a legjobb és a legrosszabbeset között nem mutatkozik nagy eltérés. Els® megközelítésben azt mond-hatjuk, hogy a maximális és a minimális futási ideje is n lg n -nel arányos.Ezt a szokásos Θ jelöléssel a következ®képpen fejezzük ki.

MTMS(n),mTMS(n) ∈ Θ(n lg n)

28

Page 29: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

5 3 1 6 8 2 4

5 3 1 6 8 2 4

5 3 1 6 8 2 4

3 1 6 8 2 4

1 3 6 8 2 4

1 3 5 2 4 6 8

1 2 3 4 5 6 8

2. ábra. Az összefésül® rendezés szemléltetése.

mergeSort(A : T[]))

// sort A[1..A.M ]

ms(A, 1, A.M)

ms(A : T[] ; u, v : 1..A.M)

// sort A[u..v]

AAu < v

m :=⌊u+v−1

2

⌋ms(A, u,m)

ms(A,m+ 1, v)

merge(A, u,m, v)

SKIP

A fenti választással A[u..m] és A[(m + 1)..v] egyforma hosszúak lesznek, haA[u..v] páros hosszúságú, és A[u..m] eggyel rövidebb lesz, mint A[(m+1)..v],ha A[u..v] páratlan hosszúságú, ugyanis

hossz(A[u..m]) = m− u+ 1 =

⌊u+ v − 1

2

⌋− u+ 1 =⌊

u+ v − 1

2− u+ 1

⌋=

⌊v − u+ 1

2

⌋=

⌊hossz(A[u..v])

2

29

Page 30: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

merge(A : T[] ; u,m, v : 1..A.M)

// sorted merge of A[u..m] and A[(m+ 1)..v] into A[u..v]

d := m− uZ : T[d+ 1] // copy A[u..m] into Z[0..d]

i := u to m

Z[i− u] := A[i]

// sorted merge of Z[0..d] and A[(m+ 1)..v] into A[u..v]

k := u // copy into A[k]

j := 0 ; i := m+ 1 // from Z[j] or A[i]

i ≤ v ∧ j ≤ d

AAA[i] < Z[j]

A[k] := A[i]

i := i+ 1

A[k] := Z[j]

j := j + 1

k := k + 1

j ≤ d

A[k] := Z[j]

k := k + 1 ; j := j + 1

A tulajdonképpeni összefésülést a merge(A, u,m, v) eljárás második és har-madik ciklusa végzi el. A Z[0..d] segédtömbre azért van szükség, hogy azösszefésülés során az output ne írja felül az inputot. A triviális megoldásmindkét résztömböt átmásolná, de elég a baloldalit, mert az összefésüléssorán végig igaz lesz, hogy k < i. Ugyanis mindkét összefésül® ciklus mag-jában j ≤ d = m − u, továbbá e ciklusok invariáns tulajdonsága, hogy azA[(m+ 1)..v] résztömbb®l eddig i−m− 1 elemet másoltunk A[u..v]-be, míga Z[0..d] segédtömbb®l j elemet, míg az A[u..v] résztömbbe másolt elemekszáma pontosan k− u, ami a másik kett®b®l kimásolt elemek összege. Ezért

k − u = i−m− 1 + j ≤ i−m− 1 +m− u

k − u ≤ i− 1− u

k ≤ i− 1

Most megvizsgáljuk, miért osztottuk szét a tulajdonképpeni összefésülést kétegymás utáni ciklusba. Az összefésül eljárás második ciklusa addig fut, amígZ[0..d]-ben és A[(m+ 1)..v]-ben is van A[u..v]-be átmásolandó elem.

30

Page 31: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Ha el®ször a Z[0..d] tömbnek érünk a végére, akkor j = d+1 = m−u+1,és így Z[0..d]-b®l mind a d + 1 = m − u + 1 elemet átmásoltuk A[u..v]-be,míg az A[(m + 1)..v]-b®l i − m − 1 elemet másoltunk oda. Mivel összesenk − u elemet másoltunk A[u..v]-be, így

k − u = (m− u+ 1) + (i−m− 1)

k − u = i− u

k = i

azaz az A[(m + 1)..v] résztömb hátralev® elemei már a helyükön vannak.Ilyenkor az utolsó ciklus egyszer sem fog lefutni, és erre is van szükség.

Ha viszont a középs® ciklusban el®ször az A[(m+1)..v] résztömbnek érünka végére, akkor az utolsó ciklus a lehet® leghatékonyabban másolja a helyükrea Z[0..d] tömb még hátralév® elemeit.

Az összefésül® rendezés (merge sort) stabilitását az biztosítja, hogy a márrendezett résztömbök (általánosságban sorozatok) összefésülésekor, egyenl®kulcsok esetén a baloldali résztömbb®l (sorozatból) származó kulcsot tesszükel®ször a helyére.

5.1.1. A merge eljárás m¶veletigénye

A merge(A, u,m, v) eljárás m¶veletigényének meghatározásához bevezetjükaz l = v − u + 1 jelölést. A merge eljárást csak akkor hívjuk meg, hau < v, azaz l ≥ 2. mTmerge(l) az eljárás minimális, MTmerge(l) a maximálism¶veletigénye. Most csak 1 eljáráshívás van + az 1. ciklus bl/2c iterációja+ a 2. és 3. ciklus iterációi: a B vektorban lév® bl/2c elemet biztosanvisszamásoljuk az A-ba, ami legalább bl/2c iteráció. Ha viszont a 2. és a 3.ciklus mindegyik elemet átmásolja, az összesen a maximális l iteráció. Innét

mTmerge(l) ≥ 1 +⌊l2

⌋+⌊l2

⌋≥⌈l2

⌉+⌊l2

⌋= l, valamint

MTmerge(l) ≤ 1 +⌊l2

⌋+ l ≤ 2l, ahonnét

l ≤ mTmerge(l) ≤MTmerge(l) ≤ 2l

(Innét mTmerge(l),MTmerge(l) ∈ Θ(l) adódik.)

5.1.2. A merge sort m¶veletigénye: szemléletes megközelítés

Az 5.1. alfejezet elején megemlítettük, hogy az összefésül® rendezés (mergesort, rövidítve MS) nagy elemszámú sorozatokat is viszonylag gyorsan ren-dez. Ráadásul a legjobb és a legrosszabb eset között nem mutatkozik nagy

31

Page 32: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

eltérés. Els® megközelítésben azt mondhatjuk, hogy a maximális és a mini-mális futási ideje is n lg n -nel arányos, ahol n = A.M . Ezt a szokásos Θjelöléssel a következ®képpen fejezzük ki.

MTMS(n),mTMS(n) ∈ Θ(n lg n)

A fenti összefüggést szemléletesen a következ®képpen indokolhatjuk. Látha-tó módon a m¶veletek túlnyomó részét a merge eljárás végzi el. Ezért azez által végzett munkára adunk becslést, hogy az egész rendezés m¶velet-igényének nagyságrendjét is megkapjuk. A merge(A, u,m, v) eljárás mindenegyes meghívásának m¶veletigénye az 5.1.1. alfejezet szerint Θ(l), ahol l azaktuális résztömb hossza (l = v−u+ 1). Mivel a rekurzív ms(A, u, v) ejárás-minden hívásban felezi a rendezend® résztömb hosszát, ezért a rekurziónakkb. lg n + 1 szintje van, és az alsó egy vagy két szint kivételével mindenrekurziós szinten igaz az, hogy a merge hívások résztömbjei együtt lefedikaz egész A tömböt. Így egy tetsz®leges szint összes merge hívásának m¶-veletigényét összeadva Θ(n) nagyságrend¶ m¶veletigény adódik (az alsó kétszintet leszámítva, ahol ez kevesebb is lehet). A szintenkénti m¶veletigényta szintek számával szorozva nagyságrendben Θ(n lg n) m¶veletigény adódik.

A fenti m¶veletigény matematikailag precíz kiszámítását a 9.11. alfeje-zetben fogjuk elvégezni.

5.2. Gyorsrendezés (Quicksort)

A gyorsrendezés (quicksort) az oszd meg és uralkodj elvet képvisel® algo-ritmusok másik klasszikus példája. Tetsz®leges, nagy méret¶ zsákból el®szörkiválasztunk egy tengelyt (pivot), majd a maradékot két kisebb részre bont-juk: az egyik a tengelynél kisebb, a másik a nagyobb elemeket tartalmazza.(A tengellyel egyenl®k bármelyik részbe kerülhetnek.) Ezután a quicksortrekurzívan rendezi a részeket.

Az algoritmus lépései vektorokra, pontokba szedve:

• Válaszd ki a rendezend® (rész)tömb egy tetsz®leges elemét! Ez lesz atengely (angolul pivot).

• Részekre bontás (partitioning): Rendezd át úgy a vektort, hogy min-den, a tengelynél kisebb elem a tengely el®tt, a nagyobbak pedig utánajöjjenek! (A tengellyel egyenl®k bármelyik részbe kerülhetnek.) Ez-zel az ún. particionálással (partition) a tengely már a végleges helyérekerült.

32

Page 33: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

• Alkalmazd rekurzívan a fenti lépéseket, külön a tengelynél kisebb ele-mek résztömbjére, és külön a tengelynél nagyobb elemek résztömbjére!

• Az üres és az egyelem¶ résztömbök a rekurzió alapesetei. Ezek ui. máreleve készen vannak, így nem is kell ®ket rendezni.

A tengely kiválasztása és a részekre bontás lépései sokféleképpen elvégez-het®k. A módszerek konkrét megválasztása er®sen befolyásolja a rendezéshatékonyságát. Alapvet® követelmény, hogy a tengely kiválasztása és a ré-szekre bontás lépései együtt lineáris id®ben befejez®djenek. Quicksort(A : T[])

Quicksort(A, 1, A.M) Quicksort(A : T[] ; p, r : N)

AAp < r

q := partition(A, p, r)

Quicksort(A, p, q − 1)

Quicksort(A, q + 1, r)

SKIP

partition(A : T[] ; p, r : N) : N

i := random(p, r)

x := A[i] ; A[i] := A[r]

i := p

i < r ∧ A[i] ≤ x

i := i+ 1

AAi < r

j := i+ 1

j < r

AAA[j] < x

swap(A[i], A[j])

i := i+ 1SKIP

j := j + 1

A[r] := A[i] ; A[i] := x

A[r] := x

return i

33

Page 34: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

A partition függvény m¶ködésének magyarázata és szemléltetése:A partition fv szemléltetéséhez vezessük be a következ® jelöléseket:

• A[k..m] ≤ x akkor és csak akkor, hatetsz®leges l-re, k ≤ l ≤ m esetén A[l] ≤ x

• A[k..m] ≥ x akkor és csak akkor, hatetsz®leges l-re, k ≤ l ≤ m esetén A[l] ≥ x

Feltesszük, hogy az A[p..r] résztömböt bontjuk részekre, és a tengely a máso-dik 5-ös, azaz a résztömb 4. (a p+3 index¶) elemét választottuk tengelynek.

A bemenet:p r

A : 5 3 8 5 6 4 7 1

Az 1. (a keres®) ciklus el®készítése:p r

A : 5 3 8 1 6 4 7 x = 5

Az 1. ciklus megkeresi az els®, a tengelynél (x) nagyobb elemet, ha van ilyen.i=p i i r

A : 5 3 8 1 6 4 7 x = 5

Találtunk a tengelynél nagyobb elemet. A j változó a következ® elemre áll.Ett®l a pillanattól igaz, hogy p ≤ i < j ≤ r. Felbontottuk az A[p..r] részvek-tort négy szakaszra:Ezek: A[p..(i−1)], A[i..(j−1)], A[j..(r−1)] és A[r].Ezekre a szakaszokra az igaz, hogyA[p..(i−1)] ≤ x ∧ A[i..(j−1)] ≥ x, A[j..(r−1)] ismeretlen és A[r] deniálat-lan (ez a tengely üres helye). Ez a tulajdonság a p ≤ i < j ≤ r állítássalegyütt a 2. ciklus invariáns tulajdonsága. (Vegyük észre, hogy a tengellyelegyenl® elemek az 1. és a 2. szakaszban is lehetnek.)) A ciklus végrehajtásátelkezdve A[j]-r®l kiderül, hogy meg kell cserélni A[i]-vel, hogy csatlakozhas-son az A[p..r] els®, a tengelynél ≤ elemek szakaszához. Mivel az invariánsalapján A[i] ≥ x, neki a 2. szakasz (a tengelynél ≥ elemek szakasza) végénis jó helye lesz. (Megvastagítottuk a megcserélend® elemeket.)

p i j rA : 5 3 8 1 6 4 7 x = 5

Megcseréljük az A[i] és az A[j] elemeket. Így az A[p..r] els® szakasza (atengelynél ≤ elemek szakasza) eggyel hosszabb lett, a második szakasza (a

34

Page 35: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

tengelynél ≥ elemek szakasza) pedig eggyel arrébb ment. Ezért az i és a jváltozókat is eggyel megnöveljük, hogy a ciklusinvariáns igaz maradjon.

Most A[j] = 6 ≥ x = 5, ezért A[j]-t hozzávesszük az A[p..r] 2. (atengelynél ≥ elemek) szakaszához. Ehhez j-t megnöveljük eggyel.

Most viszont már A[j] = 4 < x = 5, ezért A[j]-r®l kiderül, hogy meg kellcserélni A[i]-vel. (Most is megvastagítottuk a megcserélend® elemeket.)

p i j j rA : 5 3 1 8 6 4 7 x = 5

Megcseréljük az A[i] és az A[j] elemeket. Így az A[p..r] els® szakasza (atengelynél ≤ elemek szakasza) eggyel hosszabb lett, a második szakasza (atengelynél ≥ elemek szakasza) pedig eggyel arrébb ment. Ezért az i és a jváltozókat is eggyel megnöveljük, hogy a ciklusinvariáns igaz maradjon.

Most A[j] = 7 ≥ x = 5, ezért A[j]-t hozzávesszük az A[p..r] 2. (atengelynél ≥ elemek) szakaszához. Ehhez j-t megnöveljük eggyel.

Most viszont már j = r, ezért az A[p..r] 3. szakasza elfogyott.p i j j=r

A : 5 3 1 4 6 8 7 x = 5

Most már az els® 2 szakasz lefedi A[p..(r−1)]-et, és a 2. szakasz az i < jinvariáns miatt nemüres. Ezért a tengelyt berakhatjuk a nála ≤ és a nála≥ elemek közé úgy, hogy a 2. szakasz els® elemét az A[p..r] résztömb végéretesszük, és a megürült helyre, A[i]-be bemásoljuk a tengelyt. (A szemléletes-ség kedvéért a tengelyt megvastagítottuk.)

p i j=rA : 5 3 1 4 5 8 7 6

Ezzel az A[p..r] résztömb részekre bontását befejeztük. Most még visszaté-rünk a tengely i indexével, hogy a Quicksort(A, p, r) rekurzív eljárás tudja,az A[p..r] mely résztömbjeire kell meghívnia önmagát.

A partition eljárás másik esete az, amikor a tengely az A[p..r] maximuma.Ez az eset triviális. Meggondolását az Olvasóra bízzuk.

Megjegyzés: Természetesen egyszer¶bb lenne, ha a fenti partition függ-vényben a tengely A[r] lenne. Ekkor azonban el®rendezett inputokra a Qu-icksort lelassulna, mert a partition fv egyenetlenül vágna. Annak érdekében,hogy az ilyen balszerencsés bemenetek valószín¶ségét csökkentsük, és azértis, mert az el®rendezett inputok rendezése a gyakorlatban egy fontos feladat-osztály, érdemes a tengelyt az A[p..r] résztömb egy véletlenszer¶ eleménekválasztani.

35

Page 36: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

A partition fv helyességének ellen®rzéséhez vezessük még be a követ-kez® jelöléseket:

• A0 az A vektor kezdeti állapota, a partition függvény meghívásakor.

• A[u..v] + x tömb objektum az x elemnek az A[u..v] résztömb végéhezkapcsolásával adódik.

A partition fv el®feltétele: 1 ≤ p < r ≤ A.M(A p és r indexhatárok a függvényen belül természetesen konstansok.)

A partition fv második ciklusának invariánsa:A[p..(r−1)] + x egy permutációja az A0[p..r] résztömbnek ∧ p ≤ i < j ≤ r ∧A[p..(i−1)] ≤ x ∧ A[i..(j−1)] ≥ x

A partition fv utófeltétele:A[p..r] az A0[p..r] permutációja ∧ p ≤ i ≤ r ∧A[p..(i−1)] ≤ A[i] ∧ A[(i+1)..r] ≥ A[i], ahol i a visszatérési érték.

5.2.1. A gyorsrendezés (quicksort) m¶veletigénye

A fenti szétvágás (partition) m¶veletigénye nyilván lineáris, hiszen a két cik-lus együtt r − p− 1 vagy r − p iterációt végez.

A quicksort m¶veletigényére ebb®l a következ® adódik. (A részleteket ld.az MSc-n!) A várható vagy átlagos m¶veletigény aszimptotikusan a legjobbesethez esik közel, és a legrosszabb eset valószín¶sége nagyon kicsi.

mT (n), AT (n) ∈ Θ(n lg n)MT (n) ∈ Θ(n2)

5.1. Feladat. Lássuk be, hogy a gyorsrendezésre mT (n) ∈ O(n lg n) ésMT (n) ∈ Ω(n2), ahol n = A.M , és az O(g(n), valamint az Ω(g(n) függ-vényosztályok deníciója a 8. fejezetben található.

5.2.2. Vegyes gyorsrendezés

Ismert, hogy kisméret¶ tömbökre a beszúró rendezés hatékonyabb, minta gyors rendezések (merge sort, heap sort, quicksort). Ezért pl. aQuicksort(A, p, r) eljárás jelent®sen gyorsítható, ha kisméret¶ tömbökre át-térünk beszúró rendezésre. Mivel a szétvágások (partitions) során sok kicsitömb áll el®, így ezzel a program futása sok ponton gyorsítható:

36

Page 37: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Quicksort(A : T[] ; p, r : N)

AAp+ c < r

q := partition(A, p, r)

Quicksort(A, p, q − 1)

Quicksort(A, q + 1, r)

insertion_sort(A, p, r)

Itt c ∈ N konstans. Optimális értéke sok tényez®t®l függ, de általában 20 és40 között mozog.

5.2. Feladat. Hogyan tudnánk az összefésül® rendezést hasonló módon gyor-sítani? (Az így adódó vegyes összefésül® rendezés továbbfejlesztése a Timsort,ami ráadásul még az inputban el®forduló monoton növekv®, illetve csökken®szakaszokat is kihasználja. [Ld. Python, Java stb.])

A Quicksort legrosszabb esetének Θ(n2) m¶veletigénye kiküszöbölhet®, azazbiztosítható az MT (n) ∈ Θ(n lg n) m¶veletigény, ha a vegyes gyorsrendezésrekurzív eljárásában gyeljük a rekurziós mélységet is, és pl. 2 lg n mélységmeghaladása esetén az aktuális résztömbre ha még a beszúró rendezésrenem érdemes átváltani áttérünk valamelyik olyan gyors rendezésre, amitudja garantálni a Θ(n lg n) legrosszabb m¶veletigényt. Alkalmazhatunk ittsegédeljárásként pl. kupacrendezést (ld. 9.10) vagy összefésül® rendezést is.Így társíthatjuk a gyorsrendezés átlagosan legjobb futási idejét valamelyikmásik gyors rendez® algoritmus tökéletes megbízhatóságával.

5.2.3. A gyorsrendezés végrekurzió-optimalizált változata* Quicksort(A : T[])

Quicksort(A, 1, A.M)

Quicksort(A : T[] ; p, r : N)

p+ c < r

q := partition(A, p, r)

Quicksort(A, p, q − 1)

p := q + 1

insertion_sort(A, p, r)

37

Page 38: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

6. Elemi adatszerkezetek és adattípusok

Adatszerkezet alatt adatok tárolásának és elrendezésének egy lehetséges mód-ját értjük, ami lehet®vé teszi a tárolt adatok elérését és módosítását, beleértveújabb adatok eltárolását és tárolt adatok törlését is. [4]

Nincs olyan adatszerkezet, ami univerzális adattároló lenne. A megfelel®adatszerkezetek kiválasztása vagy megalkotása legtöbbször a programozásifeladat megoldásának alapvet® része. A programok hatékonysága nagymér-tékben függ az alkalmazott adatszerkezetekt®l.

Az adattípus a mi értelmezésünkben egy adatszerkezet, a rajta értelmezettm¶veletekkel együtt.

Az absztrakt adattípus (ADT) esetében nem deniáljuk pontosan az adat-szerkezetet, csak informálisan a m¶veleteket. Az ADT megvalósítása kétrészb®l áll: reprezentálása során megadjuk az adatszerkezetet, implementálása során pedig a m¶veletei kódját.

Az adattípusok megvalósítását gyakran UML jelöléssel, osztályok segít-ségével fogjuk leírni. A lehet® legegyszer¶bb nyelvi elemekre szorítkozunk.(Nem alkalmazunk sem örökl®dést, sem template-eket, sem kivételkezelést.)

6.1. Vermek

A verem (stack) adattípus LIFO (Last-In First-Out) adattároló, aminél tehátmindig csak az utoljára benne eltárolt, és még benne lév® adat érhet® el,illetve törölhet®. Tipikus m¶veleteit az alábbi megvalósítás mutatja.

A vermet most dinamikus tömb (A : T[]) segítségével reprezentáljuk, aholA.M a verem maximális mérete, T a verem elemeinek típusa.

Stack- A : T[] // T is some known type ; A.M is the max. size of the stack- n : N // n ∈ 0..A.M is the actual size of the stack+ Stack(m : N) A := new T[m] ; n := 0 // create an empty stack+ ∼ Stack() delete A + push(x : T) // push x onto the top of the stack+ pop() : T // remove and return the top element of the stack+ top() : T // return the top element of the stack+ isFull() : B return n = A.M+ isEmpty() : B return n = 0+ setEmpty() n := 0 // reinitialize the stack

38

Page 39: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Stack::push(x : T)

AAn < A.M

n+ +

A[n] := xfullStackError

Stack::pop():T

AAn > 0

n−−return A[n+ 1]

emptyStackError

Stack::top():T

AAn > 0

return A[n] emptyStackError

Példa a verem egyszer¶ használatára a gyakorlat anyagából: n db input adatkiírása fordított sorrendben. Feltesszük, hogy a read(x) a kurrens inputrólolvassa be x-be a következ® input adatot. A write(x) a kurrens outpura írjax értékét. reverse(n : N)

v : Stack(n)

n > 0

read(x)

v.push(x)

n := n− 1

¬v.isEmpty()

write(v.pop())

A vermek m¶veleteit egyszer¶, rekurziót és ciklust nem tartalmazó metó-dusokkal írtuk le. Ezért mindegyik m¶veletigénye Θ(1), ami legalábbisegyütt az összes elvégzett különféle m¶velet átlagos m¶veletigényét tekintve alapkövetelmény minden verem megvalósítással kapcsolatban.

6.1. Feladat. Írjuk meg a Stack osztályt dinamikusan allokált tömbbel! Haa push m¶velet úgy találja, hogy már tele van a tömb, cserélje le nagyobbra,pontosan kétszer akkorára! Ügyeljünk a nagyságrendileg optimális átlagosfutási id®re! (Ebben az esetben a push m¶veletre mT (n) ∈ Θ(1) és MT (n) ∈Θ(n), de együtt az összes elvégzett különféle m¶velet átlagos m¶veltigényetovábbra is Θ(1) marad.)

39

Page 40: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

6.2. Sorok

A sor (queue) adattípus FIFO (First-In First-Out) adattároló, aminél teháta még benne lév® adatok közül adott pillanatban csak a legrégebben benneeltárolt érhet® el, illetve törölhet®. Tipikus m¶veleteit az alábbi megvalósításmutatja.

A sort nullától indexelt tömb (Z : T[]) segítségével reprezentáljuk, ahol azZ.M a sor maximális mérete, T a sor elemeinek típusa. (A sort természetesenábrázolhatjuk láncolt listák segítségével is (ld. a 7. fejezetet), a lista végéhezközvetlen hozzáférést biztosítva.)

Queue−Z : T[] T is some known type−n : N // n ∈ 0..Z.M is the actual length of the queue−k : N // k ∈ 0..(Z.M − 1) is the starting position of the queue in array Z+ Queue(m : N) Z := new T[m] ; n := 0 ; k := 0 // create an empty queue+ add(x : T) // join x to the end of the queue+ rem() : T // remove and return the rst element of the queue+ rst() : T // return the rst element of the queue+ length() : N return n+ isFull() : B return n = Z.M+ isEmpty() : B return n = 0+ ∼ Queue() delete Z + setEmpty() n := 0 // reinitialize the queue

Queue::add(x : T)

AAn < Z.M

Z[(k + n) mod Z.M ] := x

n+ +fullQueueError

Queue::rem() : T

AAn > 0

n−−i := k

k := (k + 1) mod Z.M

return Z[i]

emptyQueueError

Queue::rst() : T

AAn > 0

return Z[k] emptyQueueError

40

Page 41: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

A vermek és a sorok m¶veleteit egyszer¶, rekurziót és ciklust nem tartalmazómetódusokkal írtuk le. Ezért mindegyik m¶veletigénye Θ(1), ami legalábbisegyütt az összes elvégzett különféle m¶velet átlagos m¶veletigényét tekintve alapkövetelmény minden verem és sor megvalósítással kapcsolatban.

6.2. Feladat. Írjuk meg a Queue osztályt dinamikusan allokált tömbbel! Haaz add m¶velet úgy találja, hogy már tele van a tömb, cserélje le nagyobbra,pontosan kétszer akkorára! Ügyeljünk a nagyságrendileg optimális átlagosfutási id®re! (Ebben az esetben az add m¶veletre mT (n) ∈ Θ(1) és MT (n) ∈Θ(n), de együtt az összes elvégzett különféle m¶velet átlagos m¶veltigényetovábbra is Θ(1) marad.)

6.3. Feladat. Tegyük fel, hogy adott a Stack osztály, ami a Stack(),∼Stack(), push(x:T), pop():T, isEmpty():B m¶veletekkel (elvileg) korlátlanméret¶ vermeket tud létrehozni és kezelni. A destruktor m¶vetigénye Θ(n),a többi m¶veleté Θ(1).

Valósítsuk meg a Queue osztályt két verem (és esetleg néhány egysze-r¶ segédváltozó) segítségével, a következ® m¶veletekkel: Queue(), add(x:T),rem():T, length():N. Miért nincs szükség destruktorra? Mit tudunk mon-dani a m¶veletigényekr®l? Elérhet®-e valamilyen értelemben a Θ(1) átlagosm¶veletigény?

6.4. Feladat. Tegyük fel, hogy adott a Queue osztály, ami a Queue(),∼Queue(), add(x:T), rem():T, length():N m¶veletekkel (elvileg) korlátlan mé-ret¶ sort tud létrehozni és kezelni. A destruktor m¶vetigénye Θ(n), a többim¶veleté Θ(1).

Valósítsuk meg a Stack osztályt egy sor (és esetleg néhány egyszer¶ segéd-változó) segítségével, a következ® m¶veletekkel: Stack(), push(x:T), pop():T,isEmpty():B. Miért nincs szükség destruktorra? Mit tudunk mondani a m¶-veletigényekr®l?

41

Page 42: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

7. Láncolt listák (Linked Lists)

A beszúró rendezésnél, a vermeknél, a soroknál és a rendezett prioritásos so-roknál, egy dimenziós tömbökkel véges sorozatokat reprezentáltunk. Tegyükfel például, hogy adott az A : Z[100] vektor, aminek az A[1..90] résztömb-jében egy 90 elem¶ számsort tárolunk, és az n = 90 változó tartalmazza avektor aktuálisan felhasznált prexének hosszát. Ennek a módszernek az af® el®nye, hogy a sorozat bármely eleme közvetlenül, Θ(1) id®ben elérhet®az A[i] indexeléssel (i ∈ 1..n). Hátránya, hogy ha pl. a 2. pozícióra beszeretnénk szúrni sorrendtartó módon az x számot, akkor az A[2] := x érték-adás el®tt az A[2..90] résztömb minden elemét eggyel jobbra kell csúsztatni.Hasonlóan, ha a tömb els® elemét sorrendtartó módon szeretnénk törölni,akkor ehhez az A[2..90] résztömb minden elemét eggyel balra kell csúsztat-ni. (Mindkét esetben az n értékét is megfelel®en kell módosítani.) Látható,hogy minkét m¶velet költsége lineárisan arányos a tömb aktuális pozíciójátólhátralév® elemek számával. Ha pedig az el®bbi példában többszöri beszúrásután n = 100 lesz, további beszúrás már nem valósítható meg. (Bár ez utóbbiprobléma nem túl hatékonyan megoldható pl. dinamikusan változtathatóméret¶ vektorokkal.)

A láncolt listák a véges sorozatok tárolására egy alternatív megoldást kí-nálnak. El®nyük, hogy a sorrendtartó beszúrás és törlés hatékonyan, Θ(1)id®ben megoldható, ha a m¶velet pozíciója már megfelel®képpen adott. Hát-rányuk, hogy a láncolt lista i. elemét a legrosszabb esetben csak Θ(i) id®alatt érhetjük el.

Mivel a vektorok és a láncolt listák is véges sorozatokat, azaz lineárisstruktúrákat tárolnak9, ezért ezeket lineáris adatszerkezeteknek nevezzük.

A láncolt listák legalapvet®bb típusai az egyirányú és a kétirányú listák.Míg az egyirányú listákon csak a lista elejét®l a vége felé tudunk haladni, akétirányú listákon visszafelé is mozoghatunk, ami néhány feladat megoldásá-nál el®nyös lehet. Ennek az az ára, hogy adott hosszúságú nemüres sorozatotkétirányú listában tárolva több memóriára van szükségünk, mintha ugyan-azt a sorozatot a neki megfelel® egyirányú listában tárolnánk, és a kétirányúlisták esetében az elemi listamódosító m¶veletek (bef¶zés, kif¶zés) is többértékadó utasításból állnak.

9Ezen azt sem változtat, amikor halmazt vagy zsákot (multihalmazt) reprezentálunkvelük, hiszen a reprezentáció akaratunktól függetlenül ekkor is sorrendiséget határozmeg a halmaz vagy zsák elemei között.

42

Page 43: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

7.1. Egyirányú listák (one-way or singly linked lists)

Ebben az alfejezetben egyirányú listák két legfontosabb altípusa, az egyszer¶ egyirányú listák (S1L = Simple 1-way List) és a fejelemes egyirányú listák (H1L = Header node + 1-way List)részletes tárgyalására kerül sor, de bevezetjük még a végelemes és a ciklikusegyirányú listákat is.

L1 ==

L1 9 16 4 1 /

L1 25 9 16 4 1 /

L1 25 9 16 4 /

3. ábra. Az L1 mutató egyszer¶ egyirányú listákat azonosít egy képzeletbeliprogram futásának különböz® szakaszaiban. Az els® sorban a lista, üres listaállapotában látható.

Az egyirányú listák elemeinek osztálya a következ®:

E1+key : T. . . // satellite data may come here+next : E1*+E1() next :=

Itt az E1* olyan mutatót (pointert) jelöl, ami E1 típusú objektum címéttartalmazhatja; vagy az értéke ,10 vagy deniálatlan.

Ha pl. adott a p : E1* pointer, ami egy egy E1 típusú objektumramutat, akkor a mutatott objektumot ∗p jelöli. Ezután a mutatott objektummez®i (adattagjai) (∗p).key és (∗p).next, amiket, a C/C++ nyelveket követveszemléletesebben p → key (a p által mutatott objektum key mez®je) ésp→ next (a p által mutatott objektum next mez®je) alakban szokás írni.

7.1.1. Egyszer¶ egyirányú listák (S1L)

Az L:E1* üres S1L pontosan akkor, ha L = .Az L:E1* nemüres S1L pontosan akkor, ha L 6= , és L → next egy S1L-t

10A szimbólum szokásos olvasatai a null, illetve a nil.

43

Page 44: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

azonosít (ami persze lehet üres, vagy nemüres).

A 3. ábrán az els® négy sorban egyszer¶ egyirányú listákra láthatunk példá-kat. Az els® sorban az L1 = formulával az L1 üres listát írtuk le. A másodiksorban látható egyszer¶ egyirányú lista a 〈9; 16; 4; 1〉 sorozatot reprezentálja.Ennek megfelel®en a lista négy db E1 típusú objektumból, pontosabban lis-taelemb®l áll, amiknek a key mez®i sorban tartalmazzák a 〈9; 16; 4; 1〉 sorozatelemeit. Az L1 pointer az els® objektumra, azaz listaelemre mutat. Ennekmegfelel®en L1 → key = 9, és az L1 → next pointer mutat a lista máso-dik elemére, amib®l L1 → next → key = 16 következik. Ha végrehajtjuk ap := L1 → next→ next értékadást, akkor p a lista harmadik elemére mutat,p→ key = 4, p→ next a negyedik listaelemre mutat, p→ next→ key = 1,p → next → next = , és p-re a p := p → next → next értékadást isvégrehajtva p = lesz.

Ha p = , akkor a ∗p kifejezés hibás, így a p → key és a p → nextkifejezések is azok, kiértékelésük futási hibát eredményez. (C/C++-ban pl.ez a hiba a segmentation violation egyik esete.)11

Ha a p pointernek egyáltalán nem adunk értéket, akkor az értéke ugyan-úgy, mint más típusú változók esetében deniálatlan: Lehet, hogy p = ,de az is lehet, hogy p 6= ; a p tulajdonképpen bármilyen memóriacímettartalmazhat, de az is lehet, hogy nem létez® memóriacímre hivatkozik. Ek-kor a ∗p, a p → key és a p → next kifejezések is deniálatlanok, azaz nemtudhatjuk, hogy mi lesz a kiértékelésük eredménye.

Els® programozási példának megadjuk az S1L_length(L:E1*):N függ-vényt, ami az L S1L, azaz egyszer¶ egyirányú lista hosszát számolja ki. Nefelejtsük el, hogy absztrakt (azaz logikai) szinten L egy lista, míg konkrét(azaz zikai) szinten L csak egy memóriacím, ami a listát azonosítja. Az,hogy az L:E1* pointer absztrakt szinten L S1L, tulajdonképpen a függvényáltal elvégzett számítás helyességének el®feltétele.

11A p = esetet úgy is elképzelhetjük, hogy a p pointer egy úgynevezett seholsincs

objektumra (NO = Nowhere Object) mutat, ami teljesen üres, és így, ha a tartalmáhozakarunk hozzáférni, akkor nem létez® dologra akarunk hivatkozni, ami szükségszer¶enprogramfutási hibához vezet. (Tehát a NO címe , de ezen a címen csak a semmit talál-hatjuk.)

44

Page 45: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

S1L_length(L : E1*) : N

n := 0

p := L

p 6= n := n+ 1

p := p→ next

return n

Ha n jelöli az L lista hosszát,akkor

a ciklus n iterációt végez,tehát

TS1L_length(n) ∈ Θ(n)

7.1.2. Fejelemes listák (H1L)

A fejelemes listák (H1L) szerkezete hasonló az S1L-ekéhez, de a H1L-ek min-dig tartalmaznak egy nulladik, ún. fejelemet. A H1L-t a fejelemére mutatópointer azonosítja. A fejelem key mez®je deniálatlan12, a next pointere pe-dig a H1L-nek megfelel® S1L-t azonosítja. Ebb®l következik, hogy az üresH1L-nek is van fejeleme, aminek a next pointere . Fejelemes listákra a 4.ábrán láthatunk példákat.

L2 /

L2 4 8 2 /

L2 17 4 8 2 /

L2 17 8 2 /

4. ábra. Az L2 fejelemes listákat azonosít egy képzeletbeli program futásánakkülönböz® szakaszaiban. Az els® sorban a lista, üres lista állapotában látható.

A 4. ábra els® sorában L2 üres H1L, amit az mutat, hogy a fejelem nextmez®je , azaz L2 → next = . Ezenkívül L2 → key deniálatlan.13

A 4. ábra második sorában L2 három elem¶ H1L, mert a fejelemet nemszámoljuk bele a fejelemes lista hosszába. L2 → next mutat a H1L els®elemére. Így L2 → next → key = 4, L2 → next → next mutat a listamásodik elemére stb.

12esetleg a lista hosszát, vagy valamely egyéb tulajdonságát tartalmazhatja13Ha a lista hosszát tartalmazná, itt L2 → key = 0 lenne.

45

Page 46: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

A következ® H1L_length(H:E1*):N függvény tetsz®leges H H1L hosszátszámolja ki. Kihasználjuk, hogy H → next a H1L-nek megfelel® S1L. H1L_length(H : E1*) : N

return S1L_length(H → next)TH1L_length(n) ∈ Θ(n)ahol n a H H1L hossza

7.1.3. Egyirányú listák kezelése

A különböz® listam¶veleteknél a listaelemekben lev® kulcsok (és az esetlegesegyéb járulékos adatok) listaelemek közti mozgatását kerüljük.

El®nyben részesítjük a listák megfelel® átláncolását, mivel kerüljük a fe-lesleges adatmozgatást, és nem tudjuk, hogy egy gyakorlati alkalmazásbanmennyi járulékos adatot tartalmaznak az egyes listaelemek. (Lehet hogy alista elemei E1 részosztályaihoz tartoznak.)

A follow(p, q:E1*) eljárás tetsz®leges egyirányú lista ∗p eleme után f¶zia ∗q objektumot. Feltesszük, hogy a follow(p, q) hívás el®tt ∗q éppen nincslistába f¶zve. Tfollow ∈ Θ(1).

Az out_next(p:E1*):E1* függvény kif¶zi tetsz®leges egyirányú lista ∗peleme utáni elemét, és visszaadja a kif¶zött elem címét. Feltesszük, hogy ∗pvalamely egyirányú lista eleme, de nem az utolsó. Tout_next ∈ Θ(1).

follow(p, q : E1*)

q → next := p→ next

p→ next := q

out_next(p : E1*) : E1*

q := p→ next

p→ next := q → next

q → next := return q

A fentiek az egyirányú listák alapm¶veletei. Fejelemes egyirányú listák(H1L) esetén ezek segítségével minden összetett listamódosító m¶velet meg-adható. Egyszer¶ egyirányú listák (S1L) esetén még két további alapm¶veletszükséges, a lista legelejére történ® beszúrásra, illetve az els® elem kif¶zésé-re. (Ezek megírását az Olvasóra bízzuk.) Emiatt a fejelemes listákat kezel®programok kódja gyakran kevesebb esetszétválasztást tartalmaz, mint a ne-kik megfelel®, egyszer¶ listákat kezel® programok, hiszen mindig valami utánkell beszúrni, és mindig valami mögül kell kif¶zni.

Cserébe, minden egyes fejelemes lista eggyel több objektumot tartalmaz,mint a neki megfelel® egyszer¶ lista, ami valamelyest megnöveli a program

46

Page 47: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

tárigényét. Ha egy alkalmazásban sok rövid listánk van, a tárigények kü-lönbsége szignikáns lehet. Ezért pl. hasító táblákban (hash tables) sosemhasználunk fejelemes listákat.

Az is lehet, hogy egy (rész)feladatban a listát mindig csak a legelején,azaz veremszer¶en kell módosítani; vagy olyan a feladat, hogy a lista els®eleme biztosan a helyén marad. Ezekben az esetekben a fejelem csak akadály.Ha éppen van, célszer¶ a feladatot megoldó alprogramot a fejelemet követ®egyszer¶ egyirányú listára (S1L) meghívni.

Akkor sem célszer¶ minden egyes lista elejére fejelemet generálni, ha egyprogram mint pl. a láncolt listákra alkalmazott összefésül® rendezés (mergesort) átmenetileg sok rövid listát hoz létre, hiszen így a fejelemek alloká-lása és deallokálása a futási id®t jelent®sen megnövelheti (hacsak nem élünkvalamilyen ügyes trükkel :).

Olyan eset is lehet, amikor a fejelem helyett ún. végelemet célszer¶ al-kalmazni, azaz a lista végén van egy olyan elem, aminek a kulcsát nem hasz-náljuk, és az üres lista csak egy végelemet tartalmaz. Erre példa a sorokláncolt, optimális megvalósítása: Ilyenkor a listára két küls® pointer mutat,az egyik a lista els®, a másik a végelemére. (A részleteket itt is az Olvasórabízzuk.) A láncolt listákon szerepl® extra elemeket, mint a fejelem vagy avégelem, és más, a lista egy-egy szakaszát határoló listaelemeket összefoglalónéven ®rszem (sentinel) elemeknek hívjuk.

Most még röviden tárgyalunk két alprogramot, amelyek hasznosak lesznek. cut(L : E1* ; n : N) : E1*

p := L

n > 1

n := n− 1

p := p→ next

q := p→ next

p→ next := return q

H1L_read() : E1*

H := v := new E1

read(x)

v := v → next := new E1

v → key := x

// satellite data may be read here

return H

A cut(L:E1*;n:N):E1* függvény kettévágja az L S1L-t. Az els® n eleméthagyja L-ben, és visszaadja a lista levágott maradékát azonosító pointert. Alistaelemek sorrendjét megtartja. Tcut(n) ∈ Θ(n).

A H1L_read():E1* függvény beolvas egy adatsort a kurrens inputról, abemenet sorrendje szerint egy H1L-t épít bel®lük, és visszaadja a fejelemecímét. Feltesszük, hogy a read(&x:T):B függvény képes a következ® adatbeolvasására x-be, és akkor ad vissza igaz értéket, ha a beolvasás el®tt még

47

Page 48: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

volt adat a bemeneten. (Különben x deniálatlan marad, és a függvényhamis értéket ad vissza.) TH1L_read(n) ∈ Θ(n), ahol n a már felépített H1Lhossza.

7.1. Feladat. Próbáljuk megírni az S1L_read():E1* függvényt, amely beol-vas egy adatsort a kurrens inputról, a bemenet sorrendje szerint egy S1L-tépít bel®lük, és visszaadja az els® eleme címét, vagy -t, ha az input üresvolt! Tartsuk meg a Θ(n) m¶veletigényt! Egyszer¶bb vagy bonyolultabb lett akód, mint a H1L_read():E1* függvény esetében? Miért?

7.2. Feladat. Az L pointer egy monoton növekv®en rendezett egyszer¶ lán-colt lista (S1L) els® elemére mutat (L 6= ).

Írjuk meg a duplDel(L,&D:E1*) eljárást, ami a duplikált adatoknak csakaz els® el®fordulását hagyja meg! T (n) ∈ O(n), ahol n az L lista hossza (n aprogram számára nem adott). A lista tehát szigorúan monoton növekv® lesz.A feleslegessé váló elemekb®l hozzuk létre a D pointer¶, monoton csökken®,egyszer¶ láncolt listát!

7.3. Feladat. Adott az A : T[n] tömb.Írjuk meg a listaba(A : T[] ; &H:E1*) eljárást, ami el®állítja az A tömb

által reprezentált absztrakt sorozat láncolt ábrázolását a H H1L-ben. A szük-séges listaelemeket az ismert new E1 m¶velet segítségével nyerjük. Feltesszük,hogy ha nincs elég memória, akkor a new m¶velet null pointert ad vissza. Eb-ben az esetben írjuk ki azt, hogy Memory Overow!, aztán azonnal fejezzükbe az eljárást! Ilyenkor a H listában csak azok az elemek legyenek, ame-lyeket sikeresen generáltunk! Tlistaba(n) ∈ O(n) legyen! (Feltesszük, hogyTnew ∈ Θ(1).)

7.4. Feladat. Adott az A : T[n] tömb.Írjuk meg a listaba(A : T[] ; &L:E1*) eljárást, ami el®állítja az A tömb

által reprezentált absztrakt sorozat láncolt ábrázolását az L S1L-ben. A szük-séges listaelemeket az ismert new E1 m¶velet segítségével nyerjük. Feltesszük,hogy ha nincs elég memória, akkor a new m¶velet null pointert ad vissza. Eb-ben az esetben írjuk ki azt, hogy Memory Overow!, aztán azonnal fejezzükbe az eljárást! Ilyenkor az L listában csak azok az elemek legyenek, ame-lyeket sikeresen generáltunk! Tlistaba(n) ∈ O(n) legyen! (Feltesszük, hogyTnew ∈ Θ(1).)

7.5. Feladat. Tekintsük a H fejpointer¶ H1L rendezését a következ® algo-ritmussal! El®ször megkeressük a lista minimális elemét, majd átf¶zzük afejelem után. Ezután megkeressük a második legkisebb elemét és átf¶zzük azels® minimum után. Folytassuk ezen a módon a lista els® (n − 1) elemére!

48

Page 49: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Pl.:〈3; 9; 7; 1; 6; 2〉 → 〈1 | 9; 7; 3; 6; 2〉→ 〈1; 2 | 7; 3; 6; 9〉 → 〈1; 2; 3 | 7; 6; 9〉→ 〈1; 2; 3; 6 | 7; 9〉 → 〈1; 2; 3; 6; 7 | 9〉→ 〈1; 2; 3; 6; 7; 9〉

Írjunk struktogramot erre a minimumkiválasztásos rendezés néven közis-mert algoritmusra MinSelSort(H:E1*) néven! Mi lesz a f® ciklus invarián-sa? Miért elég csak az els® (n − 1) elemre lefuttatni? (n = a lista hossza.)Adjuk meg az MT (n) és mT (n) függvényeket a minimumkiválasztásos ren-dezésre az ismert Θ-jelöléssel!

7.6. Feladat. Adott a P :E1*[n] pointer tömb, amelynek elemei egyszer¶ lán-colt listákat azonosítanak. (Mindegyik vagy pointer, vagy egy lista els®elemére mutat.)

Írjuk meg az összef¶z(P,L) eljárást, ami a listákat sorban egymás utánf¶zi az L egyszer¶ listába. (A formális paraméterek pontos specikálása afeladat része.) T (m,n) ∈ O(m + n), ahol m az eredeti listák összhossza (ma program számára ismeretlen, n = P.M).

7.7. Feladat. Az L pointer egy rendezetlen, egyszer¶ láncolt lista els® ele-mére mutat. Feltehet®, hogy a lista nem üres.

Írjuk meg a MaxRend(L) eljárást, ami a listát monoton növekv®en, Lhosszától független, konstans mennyiség¶ memóriafelhasználással, maximum-kiválasztással rendezi! (A formális paraméter pontos specikálása a feladatrésze.) MT (n) ∈ O(n2), ahol n az L lista hossza (n a program számára nemadott).

[Felveszünk egy rendezett, kezdetben üres segédlistát. (Így inicializáljuk.)A rendezés menetekb®l áll. Minden menetben kiválasztjuk a rendezetlen listalegnagyobb elemét, és átf¶zzük a rendezett lista elejére.]

7.8. Feladat. Legyenek Hu és Hi szigorúan monoton növekv®en rendezettH1L-ek! Írjuk meg a unionIntersection(Hu, Hi : E1*) eljárást, ami a Hu

listába Hi megfelel® elemeit átf¶zve, a Hu listában az eredeti listák minthalmazok unióját állítja el®, míg a Hi listában a metszetük marad! Neallokáljunk és ne is deallokáljunk listaelemeket, csak az listaelemek átf¶zéséveloldjuk meg a feladatot! MT (nu, ni) ∈ Θ(nu+ni), ahol a Hu H1L hossza nu, aHi H1L hossza pedig ni. Minkét lista maradjon szigorúan monoton növekv®enrendezett H1L!

7.1.4. Dinamikus memóriagazdálkodás

Az objektumok dinamikus létrehozására a new T m¶veletet fogjuk használni,ami egy T típusú objektumot hoz létre, és visszadja a címét. Ezért tudunk

49

Page 50: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

egy vagy több mutatóval hivatkozni a frissen létrehozott objektumra. Az ob-jektumok dinamikus létrehozása egy speciálisan a dinamikus helyfoglalásrafenntartott memóriaszegmens szabad területének rovására történik. Fontosezért, hogy ha már egy objektumot nem használunk, a memória neki meg-felel® darabja visszakerüljön a szabad memóriához. Erre fogjuk használni adelete p utasítást, ami a p mutató által hivatkozott objektumot törli.

Maga a p mutató a p := new T utasítás végrehajtása el®tt is létezik,mert azt a mutató deklarációjának kiértékelése hozza létre.14 Ugyanígy, a pmutató a delete p végrehajtása után is létezik, egészen az ®t (automatikusan)deklaráló eljárás vagy függvény végrehajtásának befejezéséig.

Mi magunk is írhatunk optimalizált, hatékony dinamikus memória hely-foglaló és felszabadító rutinokat; speciális esetekben akár úgy is, hogy a fentim¶veletek konstans id®t igényelnek. (Erre egy példa a gyakorlatokon is el-hangzik.)

Általános esetben azonban a dinamikus helyfoglalásra használt szabadmemória a különböz® típusú és méret¶ objektumokra vonatkozó létrehozá-sok és törlések (itt new és delete utasítások) hatására feldarabolódik, és vi-szonylag bonyolult könyvelést igényel, ha a feldarabolódásnak gátat akarunkvetni. Ezt a problémát a különböz® rendszerek különböz® hatékonysággalkezelik.

Az absztrakt programokban az objektumokat dinamikusan létrehozó(new) és törl® (delete) utasítások m¶veletigényeit konstans értékeknek, az-az Θ(1)-nek vesszük, azzal a megjegyzéssel, hogy valójában nem tudjuk,mennyi. Ezért a lehet® legkevesebbet használjuk a new és a delete utasítá-sokat.

A tömbök dinamikus létrehozásáról és törlésér®l ld. (3.1)!

7.1.5. Beszúró rendezés H1L-ekre

Ismertetjük a fejelemes listákra a beszúró rendezést. Látható, hogy a rende-zett beszúrás m¶velete egyszer¶bb, mintha egyszer¶ egyirányú listára írtukvolna meg, mert a lista elejére való beszúrást nem kell külön kezelni.

14Az absztrakt programokban (struktogram, pszeudokód) automatikus deklarációt fel-tételezünk: az eljárás vagy függvény meghívásakor az összes benne használt lokális skalárváltozó automatikusan deklarálódik (egy változó alapértelmezésben lokális).

50

Page 51: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

H1L_insertionSort(H : E1*)

r := H → next

AAr 6=

s := r → next

s 6=

AAr → key ≤ s→ key

r := s

r → next := s→ next

p := H ; q := H → next

q → key ≤ s→ key

p := q ; q := q → next

s→ next := q ; p→ next := s

s := r → next

SKIP

7.9. Feladat. Lássuk be a fenti algoritmus helyességét, és bizonyítsuk be,hogy a m¶veletigénye, ugyanúgy, mint a tömbös változaté:

mTIS(n) ∈ Θ(n)

ATIS(n),MTIS(n) ∈ Θ(n2)

ahol n a H fejelemes lista hossza. Gondoljuk meg azt is, hogy mi biztosítja arendezés stabilitását!

7.1.6. Az összefésül® rendezés S1L-ekre

A merge sort-ot most egyszer¶ (egyirányú) listákra írjuk meg. Ha fejelemeslistákkal dolgoznánk, itt a listák darabolása során sok fejelemet kellene ke-zelni, ami nem lenne hatékony, már amennyiben a fejelemeket dinamikusankell (pl. new utasítással) létrehozni.

mergeSort(&L : E1*)

// L is an S1L.

n := S1L_length(L)

ms(L, n)

ms(&L : E1* ; n : N)

AAn > 1

n1 := bn2c

L2 := cut(L, n1)

ms(L, n1)

ms(L2, n− n1)

L := merge(L,L2)

SKIP

51

Page 52: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

merge(L1, L2 : E1*) : E1*

AAL1→ key ≤ L2→ key

L := t := L1

L1 := L1→ next

L := t := L2

L2 := L2→ next

L1 6= ∧ L2 6=

AAL1→ key ≤ L2→ key

t := t→ next := L1

L1 := L1→ next

t := t→ next := L2

L2 := L2→ next

AAL1 6=

t→ next := L1 t→ next := L2

return L

7.10. Feladat. Lássuk be a fenti algoritmus helyességét, és indokoljuk, hogya m¶veletigényére, hasonlóan, mint a tömbös változatéra

mTMS(n),MTMS(n) ∈ Θ(n lg n)

teljesül, ahol n az L lista hossza. Gondoljuk meg a stabilitást is!

7.11. Feladat. A fenti merge(L1, L2) függvény szimmetrikus. Próbáljunkaszimmetrikus megoldás adni, ami az L1 lista elemei közé fésüli L2 elemeit!Melyik megoldás adott gyorsabb kódot?

7.1.7. Ciklikus egyirányú listák

Ciklikus esetben az utolsó listaelem next mez®je nem a -t tartalmazza,hanem visszamutat a lista elejére. Ciklikus egyirányú listák segítségével jóllehet pl. körkörös vagy sor jelleg¶ absztrakt struktúrákat reprezentálni.

Fejelem nélküli nemüres lista esetén az utolsó elem next pointere az els®listaelemre mutat, és a listára kívülr®l gyakran az utolsó elemén keresztülérdemes hivatkozni (ha egyáltalán értelmezzük az els® és utolsó elem fogalmátebben az esetben). Üres lista esetén viszont a fejelem nélküli ciklikus listáta reprezentálja.

Fejelemes egyirányú ciklikus lista esetén az utolsó listaelem next pointerea fejelemre mutat, speciálisan üres lista esetén tehát a fejelem next pointerevisszamutat a fejelemre. A ciklikus listák esetében a fejelemes és a végelemeslista ugyanazt jelenti, hiszen ez a két ®rszem ugyanaz. A listát azonosítóküls® pointer az ®rszemre mutat.

52

Page 53: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

7.12. Feladat. Valósítsuk meg a Queue (sor) adattípust (ld. (6.2) alfejezet) egyszer¶ láncolt listával (S1L), úgy, hogy amennyiben a lista nemüres, azels® elemére az L, az utolsó elemére a t pointer mutat, ha pedig a lista üres,akkor L = , t értéke pedig nem deniált, végelemes, egyirányú, nemciklikus listával (47. oldal), ®rszemes egyirányú ciklikus listával, ®rszem nélküli egyirányú ciklikus listával!Tartsuk meg a különböz® megvalósítások mindegyik m¶veletére a Θ(1) m¶ve-letigényt, kivéve a destruktorokat, ahol Θ(n) lesz az új m¶veletigény, n-nela sor hosszát jelölve! Hasonlítsuk össze a négy implementációt egymással,majd a tömbös megvalósítással (6.2)!

7.2. Kétirányú listák (two-way or doubly linked lists)

Ebben az alfejezetben a kétirányú listák két legfontosabb altípusa, az egyszer¶ kétirányú listák (S2L = Simple 2-way List) és a fejelemes ciklikus kétirányú listák (C2L = Cyclic 2-way List with header)tárgyalására kerül sor, ez utóbbira részletesen.

A kétirányú listák elemeiben a next pointer mellett találhatunk egy prevpointert is, ami a lista megel®z® elemére mutat.

7.2.1. Egyszer¶ kétirányú listák (S2L)

L1 =

L1 / 9 16

keyprev next

4 1 /

L1 / 25 9 16 4 1 /

L1 / 25 9 16 1

5. ábra. Az L1 mutató egyszer¶ kétirányú listákat (S2L) azonosít egy kép-zeletbeli program futásának különböz® szakaszaiban. Az els® sorban a listátüresre inicializáló értékadás látható.

Az 5. ábra 1. sorában Az L1 listát üresre inicializátuk, a második sorbana 〈9; 16; 4; 1〉 sorozatot rerezentálja. A harmadik sorban beszúrtuk még a25 kulcsú kétirányú listaelemet a lista elejére. A negyedik sorban töröltük

53

Page 54: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

az utolsó el®tti elemét. Látható, hogy a lista módosításai során különböz®-képpen kell kezelni a lista els®, utolsó és közbüls® elemeit. Mindazonáltala hasító tábláknál ez a listatípus bizonyul majd célszer¶bbnek a fejelemesciklikus kétirányú listákhoz képest, amiket a következ® alfejezetekben tár-gyalunk, és ahol a listamódosító m¶veletek egyszer¶bbek és hatékonyabbak,mint ennél a listatípusnál.

Mivel azonban a listaelemek beszúrása és eltávolítása is másképpen megya listák elején, végén és közbül, ezen kényelmetlenség miatt, és plusz futási id®miatt ezt a listatípust a hasító táblákon kívül viszonylag ritkán használjuk.

7.2.2. Ciklikus kétirányú listák (C2L)

A fejelemes és a fejelem nélküli ciklikus kétirányú listák (C2L) elemei-nek osztálya, és az alapvet® listakezel® m¶veleteik is ugyanazok. Általábanszoktunk használni fejelemet, mert így a listakezelés tovább egyszer¶södik.Nem kell ugyanis külön kezelni az üres listába való beszúrást (hiszen az istartalmaz már egy fejelemet) és az utolsó listaelem törlését sem (ui. a fejelemakkor is a listában marad). Az alábbiakban ezért C2L alatt alapértelmezés-ben fejelemes ciklikus kétirányú listát értünk.15

L2

L2 9 16 4 1

L2 25 9 16 4 1

L2 25 9 16 4

6. ábra. Az L2 mutató fejelemes kétirányú ciklikus listákat (C2L) azonosítegy képzeletbeli program futásának különböz® szakaszaiban. Az els® sorbanaz L2 lista üres állapotában látható.

A ciklikus kétirányú listák (C2L) elemeinek osztálya és a listák alapvet®m¶veletei következ®k. Vegyük észre, hogy mindegyik m¶veletigény Θ(1).

15Ha a programunk sok rövid listát használ, és takarékoskodni szeretnénk a memóriával,ennek ellenére célszer¶bb fejelem nélküli C2L-eket, vagy esetleg S2L-eket használni.

54

Page 55: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

E2+prev, next : E2* // refer to the previous and next neighbour or be this+key : T+ E2() prev := next := this precede(q, r : E2*)

// insert (∗q) before (∗r)p := r → prev

q → prev := p ; q → next := r

p→ next := r → prev := q

follow(p, q : E2*)

// after (∗p) insert (∗q)r := p→ next

q → prev := p ; q → next := r

p→ next := r → prev := q

out(q : E2*)

// remove (∗q)p := q → prev ; r := q → next

p→ next := r ; r → prev := p

q → prev := q → next := this

7.13. Feladat. Deniálja az egyszer¶ kétirányú listák (S2L) elemtípusát, ésa fenti három m¶velet megfelel®it. Milyen plusz paraméterekre lesz szükségaz egyes m¶veleteknél? Tartsa mindháromnál a Θ(1) m¶veletigényt!

7.2.3. Példaprogramok fejelemes, kétirányú ciklikus listákra(C2L)

A C2L listatípust szemlélteti a 6. ábra. C2L_read(&H : E2*)

H := new E2

read(x)

p := new E2

p→ key := x

precede(p,H)

55

Page 56: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

insertionSort(H : E2*)

r := H → next ; s := r → next

s 6= H

AAr → key ≤ s→ key

r := s

out(s)p := r → prev

p 6= H ∧ p→ key > s→ key

p := p→ prev

follow(p, s)

s := r → next

7.14. Feladat. Lássuk be a fenti algoritmusok helyességét, és bizonyítsuk be,hogy a m¶veletigényekre, ugyanúgy, mint a korábbi változatokéra, az alábbiállítások teljesülnek!

TC2L_read(n) ∈ Θ(n)

mTIS(n) ∈ Θ(n)

ATIS(n),MTIS(n) ∈ Θ(n2)

ahol n a H fejelemes lista hossza (a C2L_read(&H : E2*)-nél az eljáráshí-vás után), és IS az insertionSort rövidítése. Gondoljuk meg azt is, hogy mibiztosítja a beszúró rendezés stabilitását!

7.15. Feladat. Adott az A : T[n] tömb.Írjuk meg a listaba(A : T[] ; &H:E2*) eljárást, ami el®állítja az A tömb

által reprezentált absztrakt sorozat láncolt ábrázolását a H C2L-ben. A szük-séges listaelemeket az ismert new E2 m¶velet segítségével nyerjük. Feltesszük,hogy ha nincs elég memória, akkor a new m¶velet null pointert ad vissza. Eb-ben az esetben írjuk ki azt, hogy Memory Overow!, aztán azonnal fejezzükbe az eljárást! Ilyenkor a H listában csak azok az elemek legyenek, ame-lyeket sikeresen generáltunk! Tlistaba(n) ∈ O(n) legyen! (Feltesszük, hogyTnew ∈ Θ(1).)

7.16. Feladat. A H pointer egy monoton növekv®en rendezett nemüres C2Lfejelemére mutat. A D pointer egy üres C2L fejelemére mutat.

Írjuk meg a duplDel(H,D:E2*) eljárást, ami a duplikált adatoknak csakaz els® el®fordulását hagyja meg! T (n) ∈ O(n), ahol n a H lista hossza (n a

56

Page 57: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

program számára nem adott). A lista tehát szigorúan monoton növekv® lesz.A feleslegessé váló elemekb®l hozzuk létre a D fejpointer¶, monoton növekv®C2L-t! (A fejelem már megvan.)

7.17. Feladat. Tekintsük a H fejpointer¶ C2L rendezését a következ® algo-ritmussal! El®ször megkeressük a lista maximális elemét, majd átf¶zzük afejelem elé. Ezután megkeressük a második legnagyobb elemét és átf¶zzük azels® maximum elé. Folytassuk ezen a módon, összesen (n− 1) maximumke-resést végezve! (n = a lista hossza.) Itt tehát a listát egy rendezetlen és egyrendezett szakaszra bontjuk, ahol a rendezett szakasz a lista végén kezdetbenüres. Mindig a rendezetlen szakaszon keresünk maximumot, és a maximáliskulcsú elemet átf¶zzük a rendezett szakasz elejére. Pl.:

〈3; 1; 9; 2; 7; 6〉 → 〈3; 1; 6; 2; 7 | 9〉→ 〈3; 1; 6; 2 | 7; 9〉 → 〈3; 1; 2 | 6; 7; 9〉→ 〈2; 1 | 3; 6; 7; 9〉 → 〈1 | 2; 3; 6; 7; 9〉 → 〈1; 2; 3; 6; 7; 9〉

Írjunk struktogramot erre a maximumkiválasztásos rendezés néven közis-mert algoritmusra MaxSelSort(H:E2*) néven! Mi lesz a f® ciklus inva-riánsa? Miért elég csak az els® (n − 1) elemre lefuttatni? Adjuk meg azMT (n) és mT (n) függvényeket a maximumkiválasztásos rendezésre az ismertΘ-jelöléssel!

7.18. Feladat. Tekintsük az H fejpointer¶, E2 elemtípusú C2L rendezését akövetkez® algoritmussal! El®ször megkeressük a lista minimális elemét, majdátf¶zzük a fejelem után. Ezután megkeressük a második legkisebb elemét ésátf¶zzük az els® minimum után. Folytassuk ezen a módon a lista els® (n− 1)elemére (ahol n jelöli a lista elemeinek számát)! Pl.:

< 3;9;7;1;6;2 > → < 1; 3;9;7;6;2 >

→ < 1;2; 3;9;7;6 > → < 1;2;3; 9;7;6 >

→ < 1;2;3;6; 9;7 > → < 1;2;3;6;7; 9 >

Írjunk struktogramot erre a minimumkiválasztásos rendezés néven közis-mert algoritmusra, minKivRend(H) néven (n a program számára nincsmegadva)! Mi lesz a f® ciklus invariánsa? Miért elég csak az els® (n − 1)elemre lefuttatni? Adjuk meg az MT (n) és mT (n) függvényeket a mini-mumkiválasztásos rendezésre az el®adásról ismert Θ-jelöléssel, ahol n a listahossza!

7.19. Feladat. A H pointer egy C2L fejelemére mutat. P :E2*[k] deniálat-lan pointerek tömbje, m deniálatlan egész szám.

Írjuk meg a növBont(L, P,m) eljárást, ami meghatározza és m-benvisszadja a H-beli, monoton növekv®en rendezett szakaszok számát. Ezenszakaszok els® elemeire az eljárás végrehajtásának eredményeként sorban a P

57

Page 58: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

tömb P [1..m] résztömbjének elemei mutatnak. MT (n) ∈ O(n), ahol n az Llista hossza (a program számára nem adott).

Feltehet®, hogy P -nek elég sok eleme van, azaz az eljárás által kiszámoltm értékre k ≥ m. A szigorúan monoton csökken® részeket egyelem¶ mo-noton növekv® szakaszok sorozatának tekintjük. Pl. 〈5, 6, 4, 2, 1, 3〉 monotonnövekv® szakaszai [〈5, 6〉, 〈4〉, 〈2〉, 〈1, 3〉], ahol m = 4. Az eljárás az L listátnem változtatja meg, csak m-et és P [1..m]-et állítja be.

7.20. Feladat. Legyenek Hu és Hi szigorúan monoton növekv®en rendezettC2L-ek! Írjuk meg a unionIntersection(Hu, Hi : E2*) eljárást, ami a Hu

listába Hi megfelel® elemeit átf¶zve, a Hu listában az eredeti listák minthalmazok unióját állítja el®, míg a Hi listában a metszetük marad! Neallokáljunk és ne is deallokáljunk listaelemeket, csak az listaelemek átf¶zéséveloldjuk meg a feladatot! MT (nu, ni) ∈ Θ(nu+ni), ahol a Hu C2L hossza nu, aHi C2L hossza pedig ni. Minkét lista maradjon szigorúan monoton növekv®enrendezett C2L!

58

Page 59: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

8. Függvények aszimptotikus viselkedése

(a Θ, O,Ω,≺,, o, ω matematikája)

E fejezet célja, hogy tisztázza a programok hatékonyságának nagyságrendje-ivel kapcsolatos alapvet® fogalmakat, és az ezekhez kapcsolódó függvényosz-tályok legfontosabb tulajdonságait.

8.1. Deníció. Valamely P (n) tulajdonság elég nagy n -ekre pontosan akkorteljesül, ha ∃N ∈ N, hogy ∀n ∈ N-re n ≥ N esetén igaz P (n).

8.2. Deníció. Az f AP (aszimptotikusan pozitív) függvény,ha elég nagy n-ekre f(n) > 0.

Egy tetsz®leges helyes program futási ideje és tárigénye is nyilvánvalóan, tet-sz®leges megfelel® mértékbegységben (másodperc, perc, Mbyte stb.) mérvepozitív számérték. Amikor (alsó és/vagy fels®) becsléseket végzünk a futásiid®re vagy a tárigényre, legtöbbször az input adatszerkezetek méretének16

függvényében végezzük a becsléseket. Így a becsléseket leíró függvények ter-mészetesen N → R típusúak. Megkövetelhetnénk, hogy N → P típusúaklegyenek, de annak érdekében, hogy képleteink minél egyszer¶bbek legyenek,általában megelégszünk azzal, hogy a becsléseket leíró függvények aszimpto-tikusan pozitívak (AP) legyenek.

8.3. Jelölések. Az f, g, h (esetleg indexelt) latin bet¶kr®l ebben a fejezetbenfeltesszük, hogy N→ R típusú, aszimptotikusan pozitív függvényeket jelölnek,míg a ϕ, ψ görög bet¶kr®l csak azt tesszük fel, hogy N → R típusú függvé-nyeket jelölnek.

8.4. Deníció. Az O(g) függvényhalmaz olyan f függvényekb®l áll, amiketelég nagy n helyettesítési értékekre, megfelelel® pozitív konstans szorzóval fe-lülr®l becsül a g függvény:

O(g) = f : ∃d ∈ P, hogy elég nagy n -ekre d ∗ g(n) ≥ f(n).f ∈ O(g) esetén azt mondjuk, hogy g aszimptotikus fels® korlátja f -nek.

8.5. Deníció. Az Ω(g) függvényhalmaz olyan f függvényekb®l áll, amiketelég nagy n helyettesítési értékekre, megfelelel® pozitív konstans szorzóvalalulról becsül a g függvény:

Ω(g) = f : ∃c ∈ P, hogy elég nagy n -ekre c ∗ g(n) ≤ f(n).f ∈ Ω(g) esetén azt mondjuk, hogy g aszimptotikus alsó korlátja f -nek.

16vektor mérete, láncolt lista hossza, fa csúcsainak száma stb.

59

Page 60: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

8.6. Deníció. A Θ(g) függvényhalmaz olyan f függvényekb®l áll, amiketelég nagy n helyettesítési értékekre, megfelelel® pozitív konstans szorzókkalalulról és felülr®l is becsül a g függvény:

Θ(g) = f : ∃c, d ∈ P, hogy elég nagy n -ekre. c ∗ g(n) ≤ f(n) ≤ d ∗ g(n).f ∈ Θ(g) esetén tehát azt mondhatjuk, hogy g aszimptotikus alsó és fels®korlátja f -nek. (Ezt a 8.9 tulajdonságnál is láthatjuk.)

Arra, hogy egy függvény egy másikhoz képest nagy n értékekre elhanyagol-ható, bevezetjük az aszimptotikusan kisebb fogalmát.

8.7. Deníció.

f ≺ g ⇐⇒ limn→∞

f(n)

g(n)= 0

Ilyenkor azt mondjuk, hogy f aszimptotikusan kisebb, mint g. Ezt úgy isírjuk, hogy f ∈ o(g), azaz deníció szerint:

o(g) = f : f ≺ g

8.8. Deníció.

f g ⇐⇒ g ≺ f

Ilyenkor azt mondjuk, hogy f aszimptotikusan nagyobb, mint g. Ezt úgy isírjuk, hogy f ∈ ω(g), azaz deníció szerint:

ω(g) = f : f g

8.9. Tulajdonság. (A függvényosztályok kapcsolata)

Θ(g) = O(g) ∩ Ω(g)

o(g) $ O(g) \ Ω(g)

ω(g) $ Ω(g) \O(g)

Példa függvények nagyságrendjére:lg n ≺ n ≺ n lg n ≺ n2 ≺ n2 lg n ≺ n3

60

Page 61: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

8.10. Tulajdonság. (Tranzitivitás)

f ∈ O(g) ∧ g ∈ O(h) =⇒ f ∈ O(h)

f ∈ Ω(g) ∧ g ∈ Ω(h) =⇒ f ∈ Ω(h)

f ∈ Θ(g) ∧ g ∈ Θ(h) =⇒ f ∈ Θ(h)

f ≺ g ∧ g ≺ h =⇒ f ≺ h

f g ∧ g h =⇒ f h

8.11. Tulajdonság. (Szimmetria)

f ∈ Θ(g) ⇐⇒ g ∈ Θ(f)

8.12. Tulajdonság. (Felcserélt szimmetria)

f ∈ O(g) ⇐⇒ g ∈ Ω(f)

f ≺ g ⇐⇒ g f

8.13. Tulajdonság. (Aszimmetria)

f ≺ g =⇒ ¬(g ≺ f)

f g =⇒ ¬(g f)

8.14. Tulajdonság. (Reexivitás)

f ∈ O(f) ∧ f ∈ Ω(f) ∧ f ∈ Θ(f)

8.15. Következmény. (8.11 és 8.14 alapján)

f ∈ Θ(g) ⇐⇒ Θ(f) = Θ(g)

8.16. Tulajdonság. (A ≺ és a relációk irreexívek.)

¬(f ≺ f)

¬(f f)

8.17. Következmény. Mivel az · ∈ Θ(·) bináris reláció reexív, szimmet-rikus és tranzitív, azért az aszimptotikusan pozitív függvények halmazánakegy osztályozását adja, ahol f és g akkor és csak akkor tartozik egy ekviva-lenciaosztályba, ha f ∈ Θ(g). Ilyenkor azt mondhatjuk, hogy az f függvényaszimptotikusan ekvivalens a g függvénnyel.

61

Page 62: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Mint a továbbiakban látni fogjuk, megállapíthatók ilyen ekvivalenciaosztá-lyok, és ezek a programok hatékonyságának mérése szempontjából alapvet®eklesznek. Belátható például, hogy tetsz®leges k-adfokú, pozitív f®együtthatóspolinom aszimptotikusan ekvivalens az nk függvénnyel. Ilyen ekvivalencia-osztályok sorba is állíthatók az alábbi tulajdonság alapján.

8.18. Tulajdonság.

f1, g1 ∈ Θ(h1) ∧ f2, g2 ∈ Θ(h2),∧f1 ≺ f2 =⇒ g1 ≺ g2

A most következ® deníció tehát értelmes az el®bbi tulajdonság miatt.

8.19. Deníció.

Θ(f) ≺ Θ(g) ⇐⇒ f ≺ g

A függvények aszimptotikus viszonyának megállapításához hasznos az alábbitétel.

8.20. Tétel.

limn→∞

f(n)

g(n)= 0 =⇒ f ≺ g

limn→∞

f(n)

g(n)= c ∈ P =⇒ f ∈ Θ(g)

limn→∞

f(n)

g(n)=∞ =⇒ f g

Bizonyítás. Az els® és az utolsó állítás a ≺ és a relációk deníciójábólközvetlenül adódik. A középs®höz vegyük gyelembe, hogy limn→∞

f(n)g(n)

= c,

így elég nagy n értékekre |f(n)g(n)− c| < c

2, azaz

c

2<f(n)

g(n)< 2c

Mivel g AP, elég nagy n-ekre g(n) > 0, ezért átszorozhatunk vele. Innét

c

2∗ g(n) < f(n) < 2c ∗ g(n)

azaz f ∈ Θ(g)

8.21. Következmény.

k ∈ N∧a0, a1, . . . , ak ∈ R∧ak > 0 =⇒ aknk+ak−1n

k−1+. . .+a1n+a0 ∈ Θ(nk)

62

Page 63: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Bizonyítás.

limn→∞

aknk + ak−1n

k−1 + . . .+ a1n+ a0nk

=

limn→∞

(akn

k

nk+ak−1n

k−1

nk+ . . .+

a1n

nk+a0nk

)=

limn→∞

(ak +

ak−1n

+ . . .+a1nk−1

+a0nk

)=

limn→∞

ak + limn→∞

ak−1n

+ . . .+ limn→∞

a1nk−1

+ limn→∞

a0nk

=

ak + 0 + . . .+ 0 + 0 = ak ∈ P =⇒

aknk + ak−1n

k−1 + . . .+ a1n+ a0 ∈ Θ(nk)

8.22. Lemma. Az alábbi, ún. L'Hospital szabályt gyakran alkalmazhat-juk, amikor a 8.20. tétel szerinti limn→∞

f(n)g(n)

határértéket szeretnénk kiszá-mítani.

Ha elég nagy helyettesítési értékekre az f és g függvények valós kiterjesztésedierenciálható, valamint

limn→∞

f(n) =∞∧ limn→∞

g(n) =∞∧ ∃ limn→∞

f ′(n)

g′(n)=⇒

limn→∞

f(n)

g(n)= lim

n→∞

f ′(n)

g′(n)

8.23. Következmény. (8.20. és 8.22. alapján)

c, d ∈ R ∧ c < d =⇒ nc ≺ nd

c, d ∈ P0 ∧ c < d =⇒ cn ≺ dn

c, d ∈ R ∧ d > 1 =⇒ nc ≺ dn

d ∈ P0 =⇒ dn ≺ n! ≺ nn

c, d ∈ P ∧ c, d > 1 =⇒ logc n ∈ Θ(logd n)

ε ∈ P =⇒ lg n ≺ nε

c ∈ R ∧ ε ∈ P =⇒ nc lg n ≺ nc+ε

63

Page 64: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Bizonyítás. Az ε ∈ P =⇒ lg n ≺ nε állítás bizonyításához szükségünk lesza L'Hospital szabályra (8.22. Lemma).

limn→∞

lg n

nε= lg e lim

n→∞

lnn

nε= lg e lim

n→∞

ln′ n

(nε)′=

lg e

εlimn→∞

1n

nε−1=

=lg e

εlimn→∞

1

nε=

lg e

ε0 = 0

8.24. Következmény.

Θ(lg n) ≺ Θ(n) ≺ Θ(n ∗ lg n) ≺ Θ(n2) ≺ Θ(n2 ∗ lg n) ≺ Θ(n3)

8.25. Tulajdonságok. .(A O(·),Ω(·),Θ(·), o(·), ω(·) függvényosztályok zártsági tulajdonságai)

f ∈ O(g) ∧ c ∈ P =⇒ c ∗ f ∈ O(g)

f ∈ O(h1) ∧ g ∈ O(h2) =⇒ f + g ∈ O(h1 + h2)

f ∈ O(h1) ∧ g ∈ O(h2) =⇒ f ∗ g ∈ O(h1 ∗ h2)f ∈ O(g) ∧ |ϕ| ≺ f =⇒ f + ϕ ∈ O(g)

(Hasonlóan az Ω(·),Θ(·), o(·), ω(·) függvényosztályokra.)

Most arra térünk ki, hogy az O(g),Ω(g),Θ(g) függvényosztályok deníciójahogyan viszonyul a Θ(g) függvényosztályról a korábbi fejezetekben kialakí-tott képhez. Kiderül, hogy a korábbi jellemzés pontosan megfelel a fentidenícióknak.

8.26. Tétel. f ∈ O(g) ⇐⇒ ∃d ∈ P és ψ, hogy limn→∞ψ(n)g(n)

= 0, és elégnagy n-ekre

d ∗ g(n) + ψ(n) ≥ f(n)

8.27. Tétel. f ∈ Ω(g) ⇐⇒ ∃c ∈ P és ϕ, hogy limn→∞varphi(n)g(n)

= 0, és elégnagy n-ekre

c ∗ g(n) + ϕ(n) ≤ f(n)

8.28. Tétel. f ∈ Θ(g) ⇐⇒ ∃c, d ∈ P és ϕ, ψ, hogy limn→∞varphi(n)g(n)

= 0,

limn→∞ψ(n)g(n)

= 0, és elég nagy n-ekre

c ∗ g(n) + ϕ(n) ≤ f(n) ≤ d ∗ g(n) + ψ(n)

Ld. még ezzel kapcsolatban az alábbi címen az 1.3. alfejezetet! [2]<http://people.inf.elte.hu/fekete/algoritmusok_jegyzet

. /01_fejezet_Muveletigeny.pdf>

64

Page 65: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

8.1. N× N értelmezési tartományú függvények

Vegyük észre, hogy a fenti függvényosztályokat eddig csak olyan függvényekreértelmeztük, amelyek értelmezési tartománya a természetes számok halmaza.Ha értelmezési tartománynak az N×N-et tekintjük, az alapvet® fogalmak akövetkez®k.

8.29. Deníció. g : N× N→ R függvény AP,ha elég nagy n és elég nagy m értékekre g(n,m) > 0.

8.30. Mj. Az alfejezet hátralev® részében az egyszer¶ség kedvéért feltesszük,hogy f, g, h : N× N→ R AP függvényeket jelölnek.

8.31. Deníció. O(g) = f | ∃d ∈ P, hogy f(n,m) ≤ d∗g(n,m), tetsz®legeselég nagy n és elég nagy m értékekre.

8.32. Deníció. Ω(g) = f | ∃c ∈ P, hogy f(n,m) ≥ c∗g(n,m), tetsz®legeselég nagy n és elég nagy m értékekre.

8.33. Deníció. Θ(g) = f | ∃c, d ∈ P, hogy c ∗ g(n,m) ≤ f(n,m) ≤d ∗ g(n,m), tetsz®leges elég nagy n és elég nagy m értékekre.

8.34. Mj. A korábban a természetes számokon értelmezett függvényekre vo-natkozó tételek az itt tárgyaltakra természetes módon általánosíthatók.

65

Page 66: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

9. Fák, bináris fák

A (bináris) fákat nagy méret¶ adathalmazok és multihalmazok (zsákok) áb-rázolására, de egyéb adatreprezentációs célokra is gyakran használjuk.

Az egydimenziós tömbök és a láncolt listák esetében minden adatelemneklegfeljebb egy rákövetkez®je van, azaz az adatelemek lineárisan kapcsolódnakegymáshoz a következ® séma szerint: O O O O O .

A bináris fák esetében minden adatelemnek vagy szokásos nevén csúcsnak(angolul node) legfeljebb kett® rákövetkez®je van: egy bal (left) és/vagy egyjobb (right) rákövetkez®je. Ezeket a csúcs gyerekeinek (children) nevezzük.A csúcs a gyerekei szül®je (parent), ezek pedig egymás testvérei (siblings).Ha egy csúcsnak nincs gyereke, levélnek (leaf) hívjuk, ha pedig nincs szül®je,gyökér (root) csúcsnak nevezzük. Bels® csúcs (internal node) alatt nem-levélcsúcsot értünk. A fában egy csúcs leszármazottai (descendants) a gyerekei ésa gyerekei leszármazottai. Hasonlóan, egy csúcs ®sei (ancestors) a szül®je ésa szül®je ®sei. A fákat felülr®l lefelé szokás lerajzolni: fent van a gyökér, lenta levelek (ld. 7. ábra).

t1 t2 t3 t4

7. ábra. Egyszer¶ bináris fák. Egy konkrét elemét a fának körrel jelöljük.Amennyiben nem fontos, hogy milyen szerkezete van egy adott részfának,azt háromszöggel jelöljük.

Az üres fának (empty tree) nincs csúcsa.

Egy tetsz®leges nemüres t fát a gyökércsúcsa (∗t) határoz meg, mivel enneka fa többi csúcsa a leszármazottja.

A ∗t bal/jobb gyerekéhez tartozó fát a t bal/jobb részfájának nevezzük.Jelölése t→ left illetve t→ right (szokás a left(t) és right(t) jelölés is). Ha∗t-nek nincs bal/jobb gyereke, akkor t→ left = illetve t→ right = . Haa gyerek létezik, jelölése ∗t→ left illetve ∗t→ right. (Itt a → er®sebbenköt, mint a ∗. Pl. ∗t→ left = ∗(t→ left))

A t bináris fának (t = esetén is) részfája önmaga. Ha t 6= , részfáimég a t→ left és a t→ right részfái is. A t valódi részfája f , ha t részfájaf , és t 6= f 6= .

66

Page 67: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

/

/ /

/ / / /

/

/

/ /

/

/ /

8. ábra. Bináris fa szül® mutatóval.

A ∗t-ben tárolt kulcs jelölése t→ key (illetve key(t)).Ha ∗g egy fa egy csúcsa, akkor a szül®je a ∗g → parent, és a szül®jéhez,

mint gyökércsúcshoz tartozó fa a g → parent (illetve parent(g)). Ha ∗g ateljes fa gyökere, azaz nincs szül®je, akkor g → parent = .

Megjegyezzzük, hogy a gyakorlatban ugyanúgy, mint a tömbök és a lán-colt listák elemeinél a kulcs általában csak a csúcsban tárolt adat egy kisrésze, vagy abból egy függvény segítségével számítható ki. Mi az egysze-r¶ség kedvéért úgy tekintjük, mintha a kulcs az egész adat lenne, mert azadatszerkezetek m¶veleteinek lényegét így is be tudjuk mutatni.

A bináris fa fogalma általánosítható. Ha a fában egy tetsz®leges csúcsnaklegfeljebb r rákövetkez®je van, r-áris fáról beszélünk. Egy csúcs gyerekeit ésa hozzájuk tartozó részfákat ilyenkor a 0..r − 1 szelektorokkal szokás sorszá-mozni. Ha egy csúcsnak nincs i-edik gyereke (i ∈ 0..r − 1), akkor az i-edikrészfa üres.

Így tehát a bináris fa és a 2-áris fa lényegében ugyanazt jelenti, azzal,hogy itt a left ∼ 0 és a right ∼ 1 szelektor-megfeleltetést alkalmazzuk.

Beszélhetünk a fa szintjeir®l (levels). A gyökér van a nulladik szinten. Azi-edik szint¶ csúcsok gyerekeit az (i + 1)-edik szinten találjuk. A fa magas-sága (height) egyenl® a legmélyebben fekv® levelei szintszámával. Az üresfa magassága h() = −1. Néha szoktak a fa mélységér®l is beszélni, amiugyanaz, mint a magassága.

67

Page 68: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Az itt tárgyalt fákat gyökeres fáknak is nevezik, mert tekinthet®k olyan irá-nyított gráfoknak, amiknek az élei a gyökércsúcstól a levelek felé vannakirányítva, a gyökérb®l minden csúcs pontosan egy úton érhet® el, és valamelyr ∈ N-re tetsz®leges csúcs kimen® élei a 0..r − 1 szelektorok egy részhal-maza elemeivel egyértelm¶en17 van címkézve. (Ezzel szemben a szabad fákösszefügg®, körmentes irányítatlan gráfok.)

9.1. Listává torzult, szigorúan bináris,teljes és majdnem teljes bináris fák

Azokat a fákat, amelyekben minden bels® (azaz nem-levél) csúcsnak egy gye-reke van, listává torzult fáknak nevezzük. Például a 8. ábrán látható binárisfa jobboldali részfájának bal részfája listává torzult. A 7. ábrán t2 és t3listává torzult.

Azokat a bináris fákat, amelyekben minden bels® (azaz nem-levél) csúcs-nak két gyereke van, szigorúan bináris fáknak nevezzük. Ha ez utóbbi-aknak minden levele azonos szinten van, teljes bináris fákról beszélünk.(Ilyenkor az összes levél szükségszer¶en a fa legmélyebb szintjén találha-tó, a fels®bb szinteken lev® csúcsok pedig bels® csúcsok, így két-két gyere-kük van.) Tetsz®leges h magasságú teljes bináris fa csúcsainak száma tehát1 + 2 + 4 + ...+ 2h = 2h+1 − 1.

Például a 7. ábrán t3 0 magasságú, t1 1 magasságú teljes bináris fa,a 9. ábrán pedig kett® magasságú, teljes bináris fák láthatók. Megjegyezzük,hogy a fenti deníció szerint az üres fa is teljes.

Ha egy teljes bináris fa levélszintjér®l nulla, egy vagy több levelet elveszünk,de nem az összeset, az eredményt majdnem teljes bináris fának nevezzük.

Az üres fát is majdnem teljesnek tekintjük, így minden teljes bináris faegyben majdnem teljes is (fordítva viszont nem igaz).

Tetsz®leges h mélység¶, nemüres, majdnem teljes bináris fa csúcsainakszáma a fenti deníció szerint n ∈ 2h..2h+1 − 1, és így h = blg nc. Az alsószinten lev® leveleket elvéve pedig egy h−1 mélység¶ teljes bináris fát kapunk.

Például a 7. ábrán t3 0 magasságú, t2 és t1 1 magasságú, majdnem teljesbináris fák; a 8. ábrán látható bináris fát pedig 3 magasságú, majdnem teljesbináris fává alakíthatnánk, ha a legalsó (4.) szintjén lév® csúcsát törölnénk.

17Az egyértelm¶ség itt azt jelenti, hogy egyetlen csúcsnak sincs két azonos címkéj¶kimen® éle.

68

Page 69: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

9.2. Bináris fák mérete és magassága

Bináris fa mérete alatt a csúcsainak számát értjük. A t bináris fa méretét |t|,vagy n(t), vagy ha a szövegösszefüggésb®l egyértelm¶, melyik fáról van szó,egyszer¶en csak n jelöli.

Emlékeztetünk, hogy tetsz®leges bináris fában a gyökér van a nulladik szinten.Az i-edik szint¶ csúcsok gyerekeit az (i + 1)-edik szinten találjuk. A famagassága (height) egyenl® a legmélyebben fekv® levelei szintszámával.

A t bináris fa magasságát h(t), vagy ha a szövegösszefüggésb®l egyértelm¶,melyik fáról van szó, egyszer¶en csak h jelöli.

Az üres fa magassága h() = −1. Így tetsz®leges nemüres t bináris fára:

h(t) = 1 +max(h(t→ left), h(t→ right))

9.1. Tétel. Tetsz®leges n > 0 méret¶ és h ≥ 0 magasságú (azaz nemüres)bináris fára

blg nc ≤ h ≤ n− 1

Bizonyítás. El®ször a blg nc ≤ h egyenl®tlenséget bizonyítjuk be. A hmélység¶ bináris fák között nyilván a teljes bináris fának van a legtöbb csúcsa.Mivel erre n = 2h+1−1 (ld. 9.1.), tetsz®leges bináris fára n < 2h+1. Innét n >0 miatt lg n < lg 2h+1 = h + 1, amib®l blg nc ≤ h közvetlenül adódik. Mint9.1-ben láttuk, tetsz®leges majdnem teljes bináris fára teljesül az egyenl®ség.

A h ≤ n− 1 egyenl®tlenséghez gondoljuk meg, hogy tetsz®leges h magas-ságú fa szintjeit 0-tól h-ig sorszámoztuk, ami összesen h + 1 szintet jelent.Mivel a fa minden szintjén van legalább egy csúcs, ezért tetsz®leges fáran ≥ h + 1, azaz h ≤ n − 1, ahol h = n − 1 pontosan akkor teljesül, ha a falistává torzult.

9.2. Feladat. Mutassunk példát olyan t bináris fára, amire blg nc = h, bárt-re a majdnem teljesség kritériuma nem teljesül. Melyik az a legkisebb hmagasság, amire adható ilyen bináris fa?

9.3. (Bináris) fák bejárásai

A (bináris) fákkal dolgozó programok gyakran kapcsolódnak a négy klasszikusbejárás némelyikéhez, amelyek adott sorrend szerint bejárják a fa csúcsait,és minden csúcsra ugyanazt a m¶veletet hívják meg, amivel kapcsolatbanmegköveteljük, hogy futási ideje Θ(1) legyen (ami ett®l még persze összetettm¶velet is lehet). A ∗f csúcs feldolgozása lehet például f → key kiíratása.

Üres fára mindegyik bejárás az üres program. Nemüres r-áris fákra- a preorder bejárás el®ször a fa gyökerét dolgozza fel, majd sorban bejárja a

69

Page 70: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

0..r − 1-edik részfákat;- a postorder bejárás el®bb sorban bejárja a 0..r − 1-edik részfákat, és a fagyökerét csak a részfák után dolgozza fel;- az inorder bejárás el®ször bejárja a nulladik részfát, ezután a fa gyökerétdolgozza fel, majd sorban bejárja az 1..r − 1-edik részfákat;- a szintenkénti bejárás (Breadth First or Level Order traversal) a csúcsokat agyökért®l kezdve szintenként, minden szintet balról jobbra bejárva dolgozzafel.

Az els® három bejárás tehát nagyon hasonlít egymásra. Nevük megsúgja,hogy a gyökércsúcsot a részfákhoz képest mikor dolgozzák fel. Bináris fákra18

az absztrakt programok a következ®k. (A BinTree egyenl®re a bináris fákabsztrakt típusát jelöli.) preorder(t : BinTree)

AAt 6=

process(t)

preorder(t→ left)

preorder(t→ right)

SKIP

inorder(t : BinTree)

AAt 6=

inorder(t→ left)

process(t)

inorder(t→ right)

SKIP

postorder(t : BinTree)

AAt 6=

postorder(t→ left)

postorder(t→ right)

process(t)

SKIP

levelOrder(t : BinTree)

AAt 6=

Q : Queue ; Q.add(t)

¬Q.isEmpty()

s := Q.rem()

process(s)

AAs→ left 6=

Q.add(s→ left) SKIP

AAs→ right 6=

Q.add(s→ right) SKIP

SKIP

Állítás: Tpreorder(n), Tinorder(n), Tpostorder(n), TlevelOrder(n) ∈ Θ(n), ahol n a famérete, azaz csúcsainak száma.

Igazolás: Az els® három bejárás pontosan annyiszor hívódik meg,amennyi részfája van az eredeti bináris fának (az üres részfákat is beleszá-molva), és egy-egy hívás végrehajtása Θ(1) futási id®t igényel. Másrészt n

18A struktogramokban a ∗t csúcs feldolgozását process(t) jelöli.

70

Page 71: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

t

1

2

3 4

5

6 7

t

4

2

1 3

6

5 7

t

7

3

1 2

6

4 5

t

1

2

4 5

3

6 7

9. ábra. Bal fels® sarokban preorder, jobb fels®ben inorder, bal alsóban pos-torder, jobb alsóban szintenkénti bejárása látható a t bináris fának.

szerinti teljes indukcióval könyen belátható, hogy tetsz®leges n csúcsú binárisfának 2n+ 1 részfája van. A szintenkénti bejárás pedig mindegyik csúcsot aciklus egy-egy végrehajtásával dolgozza fel.

A szintenkénti bejárás helyessége azon alapszik, hogy egy fa csúcsainakszintenkénti felsorolásában a korábbi csúcs gyerekei megel®zik a kés®bbi csúcsgyerekeit. Ez az állítás nyilvánvaló, akár egy szinten, akár különböz® szintenvan a két csúcs a fában.

A preorder és az inorder bejárások hatékonységa konstans szorzóval javítha-tó, ha a végrekurziókat ciklussá alakítjuk. (Mivel a t paramétert érték szerintadjuk át, az aktuális paraméter nem változik meg. [Általában nem célszer¶cím szerint átvett formális paramétereket ciklusváltozóként vagy segédválto-zóként használni.]) preorder(t : BinTree)

t 6= process(t)

preorder(t→ left)

t := t→ right

inorder(t : BinTree)

t 6= inorder(t→ left)

process(t)

t := t→ right

71

Page 72: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

9.3.1. Fabejárások alkalmazása: bináris fa magassága

Természetesen az a legegyszer¶bb, ha a deníciót kódoljuk le: h(t : BinTree) : Z

AAt 6=

return 1+max(h(t→ left),h(t→ right)) return −1

Mint látható, ez lényegében véve egy függvényként kódolt postorder bejá-rás.19 Talán els®re meglep®, de ezt a feladatot preorder bejárással is könnyenmegoldhatjuk. Mint a legtöbb rekurzív programnál, itt is lesz egy nemrekur-zív keret, ami el®készíti a legküls® rekurzív hívást, s a végén is biztosítja amegfelel® interfészt. Lesz két extra paraméterünk. Az egyik (level) azt tá-rolja, milyen mélyen (azaz hányadik szinten) járunk a fában. A másik (max)pedig azt, hogy mi a legmélyebb szint, ahol eddig jártunk. Ezután már csaka bejárás és a maximumkeresés összefésülésére van szükség.20

h(t : BinTree) : Z

max := level := −1

preorder_h(t, level,max)

return max

preorder_h(t : BinTree ; level,&max : Z)

t 6= level := level + 1

AA level > max

max := level SKIP

preorder_h(t→ left, level,max)

t := t→ right

9.4. Bináris fák reprezentációi

9.4.1. Bináris fák láncolt ábrázolásai

A legtermészetesebb és az egyik leggyakrabban használt a bináris fák láncoltábrázolása. Az üres fa reprezentációja a pointer, jelölése tehát nem válto-

19Azért csak lényegében véve, mert a részfák bejárásának sorrendje a max() függvényparamétereinek kiértékelési sorrendjét®l függ, és az eljárások, függvények aktuális paramé-tereinek kiértékelési sorrendjét általában nem ismerjük.

20Így a végrehajtáshoz csak egy függvényhívásra, n + 1 eljáráshívásra és n ciklus-iterációra lesz szükség, míg az el®bbi esetben 2n+1 függvényhívásra, ha a max() függvényhívásait nem számítjuk (ami könnyen kibontható egy szekvencia + elágazássá). Mivel egyciklus-iteráció a gyakorlatban gyorsabb, mint egy rekurzív hívás, a preoder magasságszámítás a gyorsabb.

72

Page 73: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

zik. A bináris fa csúcsait pl. az alábbi osztály objektumaiként ábrázolhatjuk,ahol a BinTree absztrakt típus reprezentációja egyszer¶en a Node*.

Node+ key : T // T valamilyen ismert típus+ left, right : Node*+ Node() left := right := // egycsúcsú fát képez bel®le+ Node(x : T) left := right := ; key := x

Néha hasznos, ha a csúcsokban van egy parent szül® pointer is, mert a fábanígy felfelé is tudunk haladni. A 8. ábrán meggyelhetünk egy szül® mutatóvalrendelkez® bináris fát.

Node3+ key : T // T valamilyen ismert típus+ left, right, parent : Node3*+ Node3(p:Node3*) left := right := ; parent := p + Node3(x : T, p:Node3*) left := right := ; parent := p ; key := x

El®fordul például olyan alkalmazás, ahol szükségünk van a bináris fában egyp : Node3* pointer által mutatott csúcs (p 6= ) inorder bejárás szerintirákövetkez®jének címére. Ha nincs ilyen rákövetkez®, a függvény -t advissza. Nyilván MT (h(t)) ∈ O(h(t)), ahol t a ∗p csúcsot tartalmazó binárisfa. inorder_next(p : Node3*) : Node3*

q := p→ right

AAq 6=

q → left 6=

q := q → left

q := p→ parent

q 6= ∧ q → left 6= p

p := q ; q := q → parent

return q

9.3. Feladat. Írjuk meg az inorder_megel(p) függvényt, ami a ∗p csúcsinorder bejárás szerinti megel®z®jét adja vissza; ha nincs ilyen, -t! Tartsukmeg az O(h(t)) maximális m¶veletigényt!

73

Page 74: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

9.4.2. Bináris fák zárójelezett, szöveges formája

Tetsz®leges nemüres bináris fa egyszer¶ zárójeles alakja:(balRészFa Gyökér jobbRészFa)Az üres fát az üres string reprezentálja.A zárójeles ábrázolás lexikai elemei:- nyitó zárójel,- csukó zárójel- és a csúcsok címkéi.Például, az

3

/ \

2 6

/ / \

1 4 7

\

5

bináris fa egyszer¶, zárójeles alakja: ( ( (1) 2) 3 ( (4 (5) ) 6 (7) ) )Az elegáns zárójeles alak hasonló, de többféle zárójelet használunk. Pl. haegy részfa gyökércsúcsa a nulladik szinten van, akkor a hozzá tartozó részfát , ha az els®n, akkor [ ], ha a másodikon, akkor ( ), ha a harmadikon,akkor újra zárójelek közé tesszük és így tovább; ha az aktuális l szintrel mod 3 = 0, akkor , ha l mod 3 = 1, akkor [ ], ha l mod 3 = 2, akkorakkor ( ) zárójeleket használunk. Például a fenti bináris fa elegáns zárójelesalakja: [ (1) 2 ] 3 [ ( 4 5 ) 6 (7) ] .

9.4.3. Bináris fák aritmetikai ábrázolása

A részleteket ld.: 9.7, 9.8.

9.5. Bináris keres®fák

Egy bináris fát keres®fának nevezünk, ha minden nemüres r részfájára ésannak a gyökerében lév® y kulcsra igazak az alábbi követelmények:- Ha x egy tetsz®leges csúcs kulcsa az r bal részfájából, akkor x < y.- Ha z egy tetsz®leges csúcs kulcsa az r jobb részfájából, akkor z > y.

Egy bináris fát rendez®fának nevezünk, ha minden nemüres r részfájára ésannak a gyökerében lév® y kulcsra igazak az alábbi követelmények:

74

Page 75: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

- Ha x egy tetsz®leges csúcs kulcsa az r bal részfájából, akkor x ≤ y.- Ha z egy tetsz®leges csúcs kulcsa az r jobb részfájából, akkor z ≥ y.

A keres®fában tehát minden kulcs egyedi, míg a rendez®fában lehetnek dup-likált és többszörös kulcsok is. A továbbiakban a m¶veleteket bináris kere-s®fákra írjuk meg, de ezek könnyen átírhatók a bináris rendez®fák esetére.

A bináris keres®fák tekinthet®k véges halmazok, vagy szigorúan monotonnövekv® sorozatok reprezentációinak.

JelöljeH(t) a t bináris keres®fa által reprezentált halmazt! Ekkor deníciószerint H() = , illetve H(t) = H(t→ left)∪ t→ key ∪H(t→ right),amennyiben t 6= .

A t bináris keres®fa által reprezentált sorozatot megkaphatjuk, ha Inorderbejárással kiíratjuk a fa csúcsainak tartalmát:

inorderPrint(t : Node*)

AAt 6=

inorderPrint(t→ left)

write(t→ key)

inorderPrint(t→ right)

SKIP

9.4. Feladat. Bizonyítsuk be, hogy a fenti program a t bináris keres®fa kul-csait szigorúan monoton növekv® sorrendben írja ki! (Ötlet: teljes indukciót mérete vagy magassága szerint.)

Bizonyítsuk be azt is, hogy ha a fenti program a t bináris fa kulcsait szi-gorúan monoton növekv® sorrendben írja ki, akkor az keres®fa!

A fenti feladat állításának alapvet® következménye, hogy amennyiben egybináris fa transzformáció a fa inorder bejárását nem változtatja meg, és adottegy bináris keres®fa, akkor a fa a transzformáció végrehajtása után is bináriskeres®fa marad (mivel a kiindulási fa inorder bejárása szigorúan monotonnövekv® kulcssorozatot ad, és ez a transzformáció után is így marad).

Ezt a tulajdonságot a bináris keres®fák kiegyensúlyozásánál fogjuk ki-használni, az AVL fákról szóló fejezetben.

A továbbiakban feltesszük, hogy a bináris keres®fákat láncoltan ábrázoljuk,szül® pointerek nélkül, azaz a fák csúcsai Node típusúak, és az üres fáta pointer reprezentálja. (Ld. a Bináris fák reprezentációi: láncoltábrázolás fejezetet!)

75

Page 76: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

9.6. Bináris keres®fák: keresés, beszúrás, törlés

A gyakorlati programokban egy adathalmaz tipikus m¶veletei a következ®k:adott kulcsú rekordját keressük, a rekordot a kulcsa szerint beszúrjuk, illetveadott kulcsú rekordot törlünk. Ha a keresés a megtalált rekord címét adjavissza, így az adatmez®k frissítése is megoldható, és ha nincs a keresett kulcsúrekord, azt egy pointer visszadásával jelezhetjük.

Az alábbi programokban az egyszer¶ség kedvéért az adat és a kulcs ugyan-az, mert a bináris fák kezelésének jellegzetességeit így is be tudjuk mutatni.Ugyanezen okból a m¶veleteket egy halmaz és egy kulcs közötti halmazm¶-veletek megvalósításának tekintjük.

A bináris keres®fák m¶veletei:- search(t, k) függvény : ha k ∈ H(t), akkor a k kulcsú csúcsra mutató poin-terrel tér vissza, különben a hivatkozással,- insert(t, k) : a H(t) := H(t) ∪ k absztrakt m¶velet megvalósítása,- min(t) függvény : a t 6= fában a minimális kulcsú csúcs címét adja vissza,pontosabban, az inorder bejárás szerinti els® csúcsét,- remMin(t,minp) : a t 6= fából kivesszük a min(t) függvény által megha-tározott csúcsot, és a címét a minp pointerben adjuk vissza.- del(t, k) : a H(t) := H(t) \ k absztrakt m¶velet megvalósítása,Mindegyik m¶veletre MT (h) ∈ Θ(h) (ahol h = h(t)).(Ld. az alábbi struktogramokat!)

A search(t, k) függvény a t fában, a bináris keres®fa deníciója alapján meg-keresi a k kulcs helyét. A kulcsot akkor és csak akkor találja meg, ha ottnemüres részfa van.

A insert(t, k) eljárás is megkeresi a t fában a k kulcs helyét. Ha ott együres részfát talál, akkor az üres részfa helyére tesz egy új levélcsúcsot, kkulccsal.

A min(t) függvény a t nemüres fa "bal alsó" csúcsára hivatkozó pointerreltér vissza.

A remMin(t,minp) eljárásminp-ben a t nemüres fa "bal alsó" (a legkisebbkulcsot tartalmazó) csúcsára mutató pointerrel tér vissza, de még el®tte acsúcsot kif¶zi a fából, azaz a csúcshoz tartozó részfa helyére teszi a csúcsjobboldali részfáját.

A del(t, k) eljárás szintén megkeresi a t fában a k kulcs helyét. Ha meg-találta a k kulcsot tartalmazó csúcsot, még két eset lehet. (1) Ha a csúcsegyik részfája üres, akkor a csúcshoz tartozó részfa helyére teszi a csúcs másikrészfáját. (2) Ha a csúcsnak két gyereke van, akkor a minKivesz eljárás segít-ségével kiveszi a jobboldali részfából a minimális kulcsú csúcsot, és a k kulcsúcsúcs helyére teszi, hiszen ez a kulcs a baloldali részfa kulcsainál nagyobb, a

76

Page 77: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

jobboldali részfa maradék kulcsainál pedig kisebb. (Vegyük észre, hogy a (2)esetben a baloldali részfából a maximális kulcsú csúcsot is kivehetnénk!)

9.5. Feladat. Írjuk meg a t 6= bináris keres®fából a maximális kulcsúcsúcs kiolvasása / kivétele m¶veleteket. Mekkora lesz a futási id®? Miért?Írjuk át a fenti m¶veleteket szül® pointeres csúcsok esetére! Próbáljuk mega nemrekurzív programokat rekurzívvá, a rekurzívakat nemrekurzívvá átírni,megtartva a futási id®k nagyságrendjét! search(t : Node* ; k : T) : Node*

t 6= ∧ t→ key 6= k

AAk < t→ key

t := t→ left t := t→ right

return t

insert(&t : Node* ; k : T)

AAt =

t :=new Node(k)

AAk < t→ key

insert(t→ left, k)AA

k > t→ key

insert(t→ right, k)AAk = t→ key

SKIP

min(t : Node*) : Node*

t→ left 6= t := t→ left

return t

remMin(&t,&minp : Node*)

AAt→ left =

minp := t

t := minp→ right

minp→ right := remMin(t→ left,minp)

del(&t : Node* ; k : T)

AAt 6=

AAk < t→ key

del(t→ left, k)AAk > t→ key

del(t→ right, k)AA

k = t→ key

delRoot(t)SKIP

77

Page 78: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

delRoot(&t : Node*)

AAt→ left =

p := t

t := p→ right

delete p

AAt→ right =

p := t

t := p→ left

delete p

AAt→ left 6= ∧ t→ right 6=

remMin(t→ right, p)

p→ left := t→ left ; p→ right := t→ right

delete t ; t := p

Mivel mindegyik m¶veletre MT (h) ∈ Θ(h) (ahol h = h(t)), a m¶veletekhatékonysága alapvet®en a bináris keres®fa magasságától függ. Ha a fánakn csúcsa van, a magassága blg nc (majdnem teljes fa esete) és (n − 1) (lis-tává torzult fa esete) között változik. Ez teljesen attól függ, hogy milyenm¶veleteket, milyen sorrendben és milyen kulcsokkal hajtunk végre.

Szerencsére az a tapasztalat, hogy ha a kulcsok sorrendje, amikkel a be-szúrásokat és törléseket végezzük, véletlenszer¶, akkor a fa magassága ál-talában O(lg n) (bizonyítható, hogy nagy n-ekre átlagosan kb. 1, 4 lg n), ésígy a beszúrás és a törlés sokkal hatékonyabb, mintha az adathalmaz táro-lására tömböket vagy láncolt listákat használnánk, ahol csak O(n) átlagosm¶veletigényt tudnánk garantálni.

9.6. Feladat. Igaz-e, hogy a bináris keres®fák fenti m¶veleteinek bármelyi-kére mT (n) ∈ Θ(1)?

Sok alkalmazás esetén azonban nem megengedhet® az a kockázat, hogyha a keres®fa (majdnem) listává torzul, akkor a m¶veletek hatékonysága ishasonló lesz, mint a láncolt listák esetén. Az ideális megoldás az lenne, hatudnánk garantálni, hogy a fa majdnem teljes legyen. Nem ismerünk azonbanolyan O(lg n) futási idej¶ algoritmusokat, amelyek pl. a beszúrási és törlésim¶veletek során ezt biztosítani tudnák.

A vázolt probléma megoldására sokféle kiegyensúlyozott keres®fa fogalmatvezettek be. Ezek közös tulajdonsága, hogy a fa magassága O(lg n), és a ke-resés, beszúrás, törlés m¶veleteinek futási ideje a fa magasságával arányos,azaz szintén O(lg n). A kiegyensúlyozott keres®fák közül a legismertebbek azAVL fák, a piros-fekete fák (ezek idáig bináris keres®fák), a B fák és a B+ fák(ezek viszont már nem bináris fák). A legrégebbi, talán a legegyszer¶bb, ésa központi tárban kezelt adathalmazok ábrázolására mindmáig széles körbenhasznált konstrukció az AVL fa. A modern adatbáziskezel® programok, fájl-rendszerek stb., tehát azok az alapszoftverek és alkalmazások, amik háttér-táron kezelnek keres®fákat, leginkább a B+ fákat részesítik el®nyben. Ezérta következ® félévben ez utóbbi két keres®fa típus tárgyalásával folytatjuk.

78

Page 79: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

9.7. Szintfolytonos bináris fák, kupacok

A feladatok el®tt felidézzük a teljes és a majdnem teljes fákkal kapcsolatostudnivalókat.

Azokat a bináris fákat, amelyekben minden bels® (azaz nem-levél) csúcsnakkét gyereke van, szigorúan bináris fáknak nevezzük. Ha ez utóbbiaknak min-den levele azonos szinten van, teljes bináris fákról beszélünk. (Ilyenkor azösszes levél szükségszer¶en a fa legmélyebb szintjén található, a fels®bb szin-teken lev® csúcsok pedig bels® csúcsok, így két-két gyerekük van.) Tetsz®legeshmélység¶ teljes bináris fa csúcsainak száma tehát 1+2+4+...+2h = 2h+1−1.

Ha egy teljes bináris fa levélszintjér®l nulla, egy vagy több levelet elve-szünk, de nem az összeset, az eredményt majdnem teljes bináris fának ne-vezzük. Tetsz®leges h mélység¶, majdnem teljes bináris fa csúcsainak számaezért n ∈ 2h..2h+1− 1, és így h = blg nc. Az alsó szinten lev® leveleket elvévepedig egy h− 1 mélység¶ teljes bináris fát kapunk.

9.7. Feladat. Bizonyítsuk be, hogy tetsz®leges nemüres, szigorúan binárisfának pontosan eggyel több levélcsúcsa van, mint bels® csúcsa.

9.8. Feladat. Egy bináris fa méret szerint kiegyensúlyozott, ha tetsz®legesnemüres részfája bal és jobb részfájának mérete legfeljebb eggyel térhet el.Bizonyítsuk be, hogy a méret szerint kiegyensúlyozott bináris fák halmaza amajdnem teljes bináris fák halmazának valódi részhalmaza!

9.9. Feladat. Írjunk olyan eljárást, ami egy szigorúan monoton növekv®vektorból méret szerint kiegyensúlyozott bináris keres®fa másolatot készít,O(n) futási id®vel!

Egy majdnem teljes bináris fa balra tömörített, ha az alsó szintjén egyetlenlevélt®l balra sem lehet új levelet beszúrni. Ez azt jelenti, hogy egy veleazonos mélység¶ teljes bináris fával összehasonlítva csak az alsó szint jobbszélér®l hiányozhatnak csúcsok (de a bal széls® csúcs kivételével akár az összestöbbi csúcs is hiányozhat). Eszerint bármely balra tömörített, majdnemteljes bináris fa alsó szintje fölötti szintjének bal szélét®l egy vagy több bels®csúcsot találunk, amelyeknek az utolsó kivételével biztosan két-két gyerekevan. Ha az utolsónak csak egy gyereke van, akkor ez bal gyerek. A szint jobbszélén lehetnek levelek. A magasabb szinteken minden csúcsnak két gyerekevan.

A balra tömörített, majnem teljes bináris fákat más néven szintfolytonosbináris fáknak is nevezzük (hiszen, tetsz®leges ilyen fát szintenként balról

79

Page 80: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

jobbra bejárva, egészen a legutolsó csúcsáig egyetlen csúcs sem hiányzik, avele azonos magasságú teljes bináris fához viszonyítva).

Egy szintfolytonos bináris fát maximum-kupacnak (heap) nevezünk, ha min-den bels® csúcs kulcsa nagyobb-egyenl®, mint a gyerekeié. Ha minden bels®csúcs kulcsa kisebb-egyenl®, mint a gyerekeié, minimum-kupacról beszélünk.Ebben a félévben kupac alatt maximum-kupacot értünk.

Vegyük észre, hogy bármely nemüres kupac maximuma a gyökércsúcsá-ban mindig megtalálható, minimuma ugyanígy a levelei között, továbbá akupac részfái is mindig kupacok. Egy kupac bal- és jobboldali részfájábanlev® kulcsok között viszont nincs semmi nagyságrendi kapcsolat. Az els®bb-ségi (prioritásos) sorokat általában kupacok segítségével ábrázoljuk.

Egy szintfolytonos bináris fát csonka kupacnak nevezünk, ha minden szül®-gyerek párosban a szül® kulcsa nagyobb-egyenl®, mint a gyereke kulcsa, ki-véve, ha a szül® a gyökércsúcs. A gyökércsúcs kulcsa is deniált, de lehet,hogy kisebb, mint a gyereke kulcsa.

A csonka kupacok egy tömb kupaccá alakítása során jönnek majd létre,mint átmeneti adatszerkezetek. (Ld. a "Kupacrendezés" fejezetet!)

9.8. Szintfolytonos bináris fák aritmetikai ábrázolása

A szintfolytonos bináris fákat, speciálisan a kupacokat, szokás szintfolyto-nosan, egy vektorban ábrázolni. Ha pl. egy A : T[m] tömb els® n eleméthasználjuk, akkor az i index¶ csúcs gyerekeinek indexei 2i, illetve 2i + 1,feltéve, hogy a gyerekek léteznek, azaz 2i ≤ n, illetve 2i+ 1 ≤ n. A baloldaligyerek indexe azért 2i, mert az i-edik csúcsot a szintfolytonos ábrázolásban(i− 1) csúcs el®zi meg. Az i-edik csúcs baloldali gyerekét tehát megel®zi en-nek az (i− 1) csúcsnak a (2i− 2) gyereke, plusz a gyökércsúcs, aminek nincsszül®je. Ez összesen (2i−1) csúcs, és így az i-edik csúcs baloldali gyerekénekindexe 2i, a jobboldalié pedig 2i+ 1.

Más részr®l, ha egy csúcs indexe j > 1, akkor a szül®jének indexe b j2c.

Ez abból következik, hogy akár j = 2i, akár j = 2i+ 1 esetén b j2c = i.

9.10. Feladat. Bizonyítsuk be, hogy ha egy n elem¶, szintfolytonos binárisfát a nullától indexelt Z : T[m] tömbben szintfolytonosan tárolunk, akkor aZ[i] csúcs gyerekei Z[2i+1] illetve Z[2i+2], a 2i+1 < n, illetve a 2i+2 < nfeltétellel, a Z[j] csúcs szül®je pedig j > 0 esetén Z[b j−1

2c].

80

Page 81: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

9.9. Kupacok és els®bbségi (prioritásos) sorok

Az els®bbségi sor egy zsák (multihalmaz), amelybe be tudunk tenni újabbelemeket, és ki tudjuk választani, illetve kivenni az egyik maximális elemét.(Min prioritásos sor esetén az egyik minimális elemét tudjuk kiválasztani,illetve kivenni.)

Az alábbiakban a prioritásos sor típust a PrQueue osztály segítségévelírjuk le. Az els®bbségi sor aktuális elemeit az A[1..n] résztömb tartalmazza,ami egy kupac.

PrQueue- A : T[] // T is some known type- n : N // n ∈ 0..A.M is the actual length of the priority queue+ PrQueue(m : N) A := new T[m]; n := 0 // create an empty priority queue+ add(x : T) // insert x into the priority queue+ remMax():T // remove and return the maximal element of the priority queue+ max():T // return the maximal element of the priority queue+ isFull() : B return n = A.M+ isEmpty() : B return n = 0+ ∼ PrQueue() delete A + setEmpty() n := 0 // reinitialize the priority queue

Ha az A[1..n] résztömb egy kupac aritmetikai ábrázolása,MTadd(n) ∈ Θ(lg n), mTadd(n) ∈ Θ(1), MTremMax(n) ∈ Θ(lg n),mTremMax(n) ∈ Θ(1), Tmax(n) ∈ Θ(1), mivel az alábbi add" és sink" el-járások f® ciklusa maximum annyiszor fut le, amennyi a fa magassága.

Az add(x) esetében a szintfolytonosan els® üres helyen x-et hozzákapcsoljuk akupachoz (A[j]). Ezzel elronthatjuk a kupacot, ezért x-et addig cserélgetjük mindig az aktuális szül®jével (A[i]) amíg van szül®je, és x > mint a szül®.Így a lyuk felfelé mozog a kupacban, és x > mint a leszármazottai. Ha feléra gyökérbe, vagy x már ≤ mint a szül®je, akkor már helyreállt a kupac. PrQueue::add(x : T)

AAn < A.M

j := n := n+ 1

A[j] := x ; i := b j2c

i > 0 ∧ A[i] < A[j]

swap(A[i], A[j])

j := i ; i := b i2c

fullPrQueueError

81

Page 82: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

MTadd(n) ∈ Θ(lg n); hiszen a ciklus legfeljebb annyiszor fut le, amennyi afa magassága, azaz (n input értékéhez viszonyítva) blg(n + 1)c-szer, amib®llg n ≤MTadd(n) = blg(n+ 1)c+ 1 ≤ lg n+ 2.

mTadd(n) ∈ Θ(1), ha ugyanis x elég kicsi, akkor a ciklus egyszer sem futle, és innét mTadd(n) = 1.

Tmax(n) ∈ Θ(1), mivel Tmax(n) = 1.

PrQueue::max() : T

AAn > 0

return A[1] emptyPrQueueError

PrQueue::remMax() : T

AAn > 0

max := A[1] ; A[1] := A[n]

n := n− 1

sink(A, 1, n)

return max

emptyPrQueueError

A remMax() metódus a maximum elmentése után a kupac gyökerébe, A[1]-beteszi át a szintfolytonosan utolsó elemet. Ezzel a kupac mérete eggyel csö-ken, és a gyökerénél valószín¶leg el is romlik (ún. csonka kupac lesz). Ezérta "sink" eljárás segítségével (amit mindig k = 1-gyel hív meg) a gyökérbeátrakott elemet (A[i]) addig süllyeszti lefelé, amíg a helyére nem kerül. En-nek során a lesüllyesztend® elemet mindig az aktuálisan nagyobb gyerekévelcseréli meg, amíg még a levélszint fölött van, és a nagyobbik gyereke nagyobbnála. Ilyen módon a ciklus során a lesüllyed® elem mindig kisebb, mint az®sei. Amikor a ciklus megáll, akkor vagy leért a levélszintre, vagy ≥ mint agyerekei, és kupac helyreáll.

Vegyük észre, hogy a sink eljárást az itt szükségesnél kicsit általánosabbanírtuk meg! Akkor is m¶ködik, ha a lesüllyesztend® elem eredetileg tetsz®legesrészfa gyökerében van. Ilyenkor az adott részfa mint a gyökerénél szabály-talan kupac kupaccá alakítására fogjuk használni. (Ld. a "Kupacrendezés"fejezetet!)

82

Page 83: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

sink(A : T[] ; k, n : N)

i := k ; j := 2k ; b := true

j ≤ n ∧ b// A[j] is the left child

AAj < n ∧ A[j + 1] > A[j]

j := j + 1 SKIP

// A[j] is the greater child

AAA[i] < A[j]

swap(A[i], A[j])

i := j ; j := 2jb := false

A remMax() metódus m¶veletigényének meghatározásához el®ször megvizs-gáljuk a sink() eljárást. Ha a k gyöker¶ lyukas kupac magassága h, ak-kor MTsink(h) = h + 1, ugyanis a ciklus legfeljebb h iterációt végez, és1 ≤ mTsink(h) ≤ 2, mert lehet, hogy a ciklus legfeljebb egyet iterál. Spe-ciálisan k = 1 esetére: lg n ≤MTsink1(n) = h+ 1 = blg nc+ 1 ≤ lg n+ 1.

Innét n input értékére lg n ≤ lg(n− 1) + 1 ≤MTremMax(n) ≤ lg(n− 1) + 2 ≤lg n+ 2.Ebb®l MTremMax(n) ∈ Θ(lg n).

mTremMax(n) ∈ Θ(1), hiszen2 = 1 + 1 ≤ mTremMax(n) = 1 +mTsink1(n− 1) ≤ 1 + 2 = 3.

9.9.1. Rendezés els®bbségi sorral

A fenti els®bbségi sor segítségével könnyen és hatékonyan rendezhetünk töm-böket:

sortWithPrQueue(A : T[])

Q : PrQueue(A.M)

i := 1 to A.M

Q.add(A[i])

i := A.M downto 1

A[i] := Q.remMax()

83

Page 84: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

A fenti rendezésben a szokásos n = A.M jelöléssel a Q.add(A[i]) és azA[i] := Q.remMax() utasítások O(lg n) hatékonyságúak, hiszen az els®bbségisort reprezentáló kupac magassága ≤ lg n. Ezért a rendezés m¶veletigényeO(n lg n).

Maximális m¶veletigénye Θ(n lg n). Ha ugyanis az input vektor szigorúanmonoton növekv®, akkor az els® ciklus által megvalósított kupacépítés min-den új csúcsot a fa gyökeréig mozgat fel. Amikor pedig a végs® kupac leend®leveleit szúrjuk be, már legalább blg nc− 1 mélység¶ a fa, és a leend® levelekszáma dn

2e. Ekkor tehát csak a levelek beszúrásának futási ideje Ω(n lg n),

így a teljes futási id® is. Az el®bbi O(n lg n) korláttal együtt adódik az állítás.A fenti rendezés maximális m¶veletigénye tehát aszimptotikusan kisebb,

mint a beszúró rendezésé, ami Θ(n2). Ráadásul az n lgnn2 hányados már

n = 1000 esetén is csak ≈0.01, n = 106 esetén pedig ≈0.00003, tehát gyorsantart a nullához. A sortWithPrQueue hátránya, hogy a rendezend® vektorralazonos méret¶ M(n) ∈ Θ(n) munkamemóriát igényel, a prioritásos sorbantárolt kupac számára, míg a beszúró rendezésre M(n) ∈ Θ(1), hiszen csaknéhány egyszer¶ segédváltozóra van szükségünk. Ezt a problémát kupacren-dezéssel oldhatjuk meg.

9.10. Kupacrendezés (heap sort)

A kupacrendezés a fenti sortWithPrQueue(A : T[]) optimalizálása.Egyrészt az algoritmust helyben rendez®vé alakítjuk: az A : T[n] tömbön

kívül csak Θ(1) memóriát használunk, míg a fenti eljárásnak szüksége vanegy Θ(n) méret¶ segédtömbre, amiben a prioritásos sort ábrázoló kupacottárolja.

Másrészt a kupac felépítését optimalizáljuk, ami a fenti esetben magábanvéve is O(n lg n) m¶veletigény¶, maximális futási ideje pedig Θ(n lg n).

El®ször tehát kupaccá alakítjuk a tömböt, most lineáris m¶veletigénnyel:

buildMaxHeap(A : T[])

k := bA.M2c downto 1

sink(A, k,A.M)

A fenti eljárás magyarázatához: Eredetileg az A : T[n] tömb, mint balratömörített, majdnem teljes bináris fa magassága h = blg nc. Levelei önma-gukban egyelem¶ kupacok. A (h−1)-edik szinten lev® bels® csúcsokhoz, mintgyökerekhez tartozó bináris fák tehát (egy magasságú) úgynevezett csonka

84

Page 85: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

t

18

23

42

53 60

14

19

51

35 97

t

18

23

42

53 60

19

14

51

35 97

t

18

23

60

53 42

19

14

51

35 97

t

18

23

60

53 42

19

14

97

35 51

t

18

60

23

53 42

19

14

97

35 51

t

18

60

53

23 42

19

14

97

35 51

t

97

60

53

23 42

19

14

18

35 51

t

97

60

53

23 42

19

14

51

35 18

10. ábra. A 〈18, 23, 51, 42, 14, 35, 97, 53, 61, 19〉 tömb kupaccá alakítása. Je-gyezzük meg, hogy végig tömbbel dolgozunk, a bináris fák csak a szemlélte-téshez használatosak. Végeredményben a 〈97, 60, 51, 53, 19, 35, 18, 23, 42, 14〉tömböt kapjuk.

Page 86: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

kupacok, amikben egyedül a gyökércsúcs tartalma lehet "rossz helyen". Eze-ket tehát helyreállíthatjuk a gyökér lesüllyesztésével az alatta lev® csonka ku-pacba. Ezután már a (h− 2)-edik szinten lev® csúcsokhoz, mint gyökerekheztartozó bináris fák lesznek (kett® magasságú) csonka kupacok, amiket hason-lóan állíthatunk helyre, és így tovább, szintenként visszafelé. Utolsóként azA[1]-et süllyesztjük le az alatta lév® csonka kupacba, és ezzel az A : T[n]tömb kupaccá alakítása befejez®dött. Annak érdekében, hogy a szintekkelne kelljen külön foglalkozni, a fenti eljárásban a lesüllyesztéseket a szintfoly-tonosan utolsó bels® csúccsal kezdtük, és innét haladtunk szintfolytonosanvisszafelé.

Ezután a teljes rendezés: heapSort(A : T[])

buildMaxHeap(A)

m := A.M

m > 1

swap(A[1], A[m]) ; m := m− 1

sink(A, 1,m)

A kupaccá alakítás után tehát ezt ismételjük: Megcseréljük a kupac maxi-mumát, azaz gyökerében lév® elemet (A[1]) a kupac szintfolytonosan utolsóelemével, levágjuk a maximumot, majd lesüllyesztjük a helyére tett elemet.Így az el®bbinél eggyel kisebb méret¶ kupacot kapunk. Ezt a ciklusmagotaddig ismételjük, amíg a kupac mérete nagyobb, mint egy. Így az A tömb-ben visszafelé megkapjuk az els®, második stb. maximumokat, és végül azA[1]-ben a minimumot, azaz a tömb rendezve lesz.

9.10.1. A kupacrendezés m¶veletigénye

A kupaccá alakítás utáni rész futási idejét a sink(A, 1,m) hívások határozzákmeg, amiknek m¶veletigénye O(lgm) (m ∈ n− 1, n− 2, . . . , 1), durva fels®becsléssel O(lg n), ahol n = A.M . Az (n− 1) hívás tehát összesen O(n lg n)futási idej¶, és ezt nagyságrendileg a ciklus (n−1) iterációja nem befolyásolja,mert ez csak O(n) m¶veletigényt ad hozzá, ami aszimptotikusan kisebb, mintO(n lg n).

A kupaccá alakításról hasonlóan látható, hogy maga is O(n lg n), de en-nél nomabb becsléssel alább belátjuk, hogy Θ(n) m¶veletigény¶. Ett®l ateljes futási id® továbbra is O(n lg n), hiszen a szekvenciában ebb®l a szem-pontból az aszimptotikusan nagyobb m¶veletigény¶ programrész dominál,

86

Page 87: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

de a sortWithPrQueue(A) eljárás kupacépít® ciklusához képest hatékonyabbkupaccá alakítással a gyakorlatban így is jelent®s futási id®t takaríthatunkmeg.

buildMaxHeap(A : T[])

k := bA.M2c downto 1

sink(A, k,A.M)

9.11. Feladat. Lássuk be, hogy a kupacrendezés maximális m¶veletigényeΘ(n lg n), ahol n = A.M , és ez például akkor áll el®, amikor az input tömbszigorúan monoton csökken®!

9.12. Feladat. Lássuk be, hogy a kupacrendezés minimális m¶veletigényeΘ(n), ahol n = A.M , és ez például akkor áll el®, amikor az input tömbminden eleme egyenl®!

9.10.2. A kupaccá alakítás m¶veletigénye lineáris

Annak belátásához, hogy a buildMaxHeap(A : T[]) m¶veletigénye Θ(n), aholn = A.M , szükségünk lesz a balra tömörített, majdnem teljes bináris fáknéhány tulajdonságára.

Emlékeztetünk arra, hogy ha egy ilyen, n csúcsú fát szintfolytonosan azA : T[n] tömbben tárolunk, akkor az i index¶ csúcs gyerekeinek indexei 2i,illetve 2i+1, feltéve, hogy a gyerekek léteznek, azaz 2i ≤ n, illetve 2i+1 ≤ n.Más részr®l, ha egy csúcs indexe j > 1, akkor a szül®jének indexe b j

2c.

A fentiekb®l adódik, hogy az 1..k sorszámú csúcsok szülei az 1..bk2c sor-

számú csúcsok, mert- egyrészt, ha egy csúcs j indexére 1 ≤ j ≤ k, és van szül®je, azaz j ≥ 2,akkor a szül®je indexére 1 ≤ b j

2c ≤ bk

2c;

- másrészt, ha az i index¶ csúcsra 1 ≤ i ≤ bk2c, akkor ennek bal gyerekére

2 ≤ 2i ≤ 2bk2c ≤ k, azaz van gyereke az 1..k sorszámú csúcsok között.

Eszerint egy n csúcsú, balra tömörített, majdnem teljes bináris fánakfele(n) = bn

2c bels® csúcsa van. Ha ugyanis a csúcsokat sorfolytonosan az

1..n sorszámokkal indexeljük, akkor az el®bbi állítás szerint a szüleik sorszá-mai 1..bn

2c.

Az el®bb belátott állítás szerint egy n csúcsú, balra tömörített, majdnemteljes bináris fának dn

2e levele van.

87

Page 88: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Jelölje a továbbiakban nd tetsz®leges n méret¶, balra tömörített, majdnemteljes bináris fa d magasságú részfáinak számát, n≥d pedig a fa legalább d ma-gasságú részfáinak számát, ahol 1 ≤ d ≤ h = blg nc, azaz h az eredeti fa ma-gassága! Ekkor n≥1 = fele(n), hiszen a legalább egy magasságú részfák gyö-kércsúcsai éppen a bels® csúcsok. Továbbá n≥2 = fele(fele(n)) = fele2(n),hiszen a legalább kett® magasságú részfák gyökércsúcsai éppen a legalábbegy magasságú részfák gyökércsúcsainak szülei, és mivel az utóbbiak szin-folytonosan 1..fele(n) sorszámúak, azért az el®bbiek 1..fele2(n) sorszámúak.Teljes indukcióval adódik

h∑m=d

nm = n≥d = feled(n) ≤ n

2d

Most már áttérhetünk a kupaccá alakítás lineáris m¶veletigényének bizo-nyítására.

Szemléletesen fogalmazva, a sortWithPrQueue(A) eljárás kupacépít® cik-lusához képest abból adódik a hatékonyság növekedése, hogy ott az elemektúlnyomó részét már a végs®höz közeli magasságú kupacba szúrjuk be, mígitt a fa leveleit, az elemek felét egyáltalán nem kell lesüllyeszteni, ezek szüle-it, az elemek negyedét egy magasságú fában süllyesztjük le, nagyszüleit, azelemek nyolcadát kett® magasságú fában stb.

Ahhoz, hogy a buildMaxHeap(A) m¶veletigénye Θ(n), ahol n = A.M ,el®ször belátjuk, hogy maximális m¶veletigényére MTb(n) ≤ 2n+ 1 teljesül.

A kupaccá alakítás, a buildMaxHeap(A) MTb(n) maximális m¶veletigé-nyét az eljárás meghívása, a ciklus bn

2c iterációja, a lesüllyeszt® eljárás bn

2c-

szeri meghívása és benne a lesüllyeszt® ciklus végrehajtásai adják. Tetsz®le-ges d magasságú részfára a lesüllyeszt® ciklus m¶veletigénye legfeljebb d. Azösszes, nd darab d magasságú részfákra tehát a lesüllyeszt® ciklusok m¶ve-letigénye összesen legfeljebb d ∗ nd. Mivel a lesüllyeszt® eljárás hívásai soránd ∈ 1..h, a kupaccá alakítás alatt a lesüllyeszt® ciklusok m¶veletigényénekösszege legfeljebb

∑hd=1 d ∗ nd. Innét

MTb(n) ≤ 1 + 2⌊n

2

⌋+

h∑d=1

d ∗ nd

88

Page 89: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Vegyük most gyelembe, hogy∑h

d=1 d ∗ nd az alábbi alakba írható:n1+n2 + n2+n3 + n3 + n3+. . .nh + nh + nh + . . .+ nh (h tagú összeg)Ezt oszloponként összeadva azt kapjuk, hogy

h∑d=1

d ∗ nd =h∑d=1

h∑m=d

nm =h∑d=1

n≥d =h∑d=1

feled(n) ≤h∑d=1

n

2d≤ n

∞∑d=1

1

2d= n

Eredményeinket behelyettesítve kapjuk, hogy

MTb(n) ≤ 1 +(⌊n

2

⌋+⌊n

2

⌋)+

h∑d=1

d ∗ nd ≤ 1 + n+ n = 2n+ 1

Most belátjuk még, hogy mTb(n) ≥ n, amihez elég meggondolni, hogy a ku-paccá alakításból a lesüllyesztések bels® m¶veletigényét elhanyagolva, tehátcsak a küls® eljáráshívást, a ciklusiterációkat és a lesüllyeszt-hívásokat szá-molva mTb(n) ≥ 1 +

⌊n2

⌋+⌊n2

⌋≥ n. Innét n ≤ mTb(n) ≤MTb(n) ≤ 2n+ 1,

amib®lMTb(n),mTb(n) ∈ Θ(n)

adódik.

9.11. A merge sort m¶veletigényének kiszámítása

Most, hogy már otthonosan mozgunk a bináris fák világában, újra megpró-bálkozhatunk a címben jelzett feladat kivitelezésével. Az 5.1. alfejezetben aztállítottuk, hogy az összefésül® rendezés (merge sort, rövidítve MS) tömbösváltozatának m¶veletigényére n = a rendezend® vektor mérete jelöléssel azalábbi összefüggés igaz.

MTMS(n),mTMS(n) ∈ Θ(n lg n)

Tekintsük ehhez az összefésül® rendezés tömbös változatának fels® szint-jét!

89

Page 90: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

mergeSort(A : T[]))

// sort A[1..A.M ]

ms(A, 1, A.M)

ms(A : T[] ; u, v : 1..A.M)

// sort A[u..v]

AAu < v

m :=⌊u+v−1

2

⌋ms(A, u,m)

ms(A,m+ 1, v)

merge(A, u,m, v)

SKIP

9.13. Feladat. Vegyük észre, hogy a fenti rekurzív program ms(. . .) eljárás-hívásai szigorúan bináris fát alkotnak, és bizonyítsuk be, hogy ebb®l követ-kez®en a fenti merge sort algoritmus tetsz®leges n > 0 méret¶ input vektoresetén pontosan 2n − 1 -szer hívja meg az ms(. . .) rekurzív eljárást, ahol azms(. . .) hívások fájának n− 1 bels® csúcsa és n levele van.

9.14. Feladat. Egy bináris fa a levelek száma szerint kiegyensúlyozott, hatetsz®leges nemüres részfája bal és jobb részfája leveleinek száma legfeljebbeggyel térhet el.

Bizonyítsuk be, hogy a levelek száma szerint kiegyensúlyozott szigorúanbináris fák halmaza a majdnem teljes bináris fák halmazának valódi részhal-maza! (Alkalmazhatunk például a fa magassága szerinti teljes indukciót!

Vegyük észre, hogy a fenti rekurzív program ms(. . .) eljáráshívásai a leve-lek száma szerint kiegyensúlyozott szigorúan bináris fát alkotnak, és ebb®lkövetkez®en az ms(. . .) hívások fája majdnem teljes, és a 9.13. feladat sze-rint n > 0 méret¶ input vektor esetén pontosan 2n − 1 csúcsa van, így amagasságárablg nc ≤ h = blg(2n− 1)c ≤ blg(2n)c = blg(n) + 1c = blg nc+ 1,tehát blg nc ≤ h ≤ blg nc+ 1.

A 9.13. feladat szerint tehát n > 0 méret¶ input vektor esetén, az ms(. . .)hívások fájának 2n − 1 csúcsa van, így a teljes merge sort m¶veletigénye amerge(. . .) eljárás végrehajtásai nélkül 2n.

Az 5.1.1. alfejezetben beláttuk, hogy l = v−u+1 jelöléssel az összefésül®rendezésben a merge(A, u,m, v) eljárás végrehajtásának m¶veletigényére

l ≤ mTmerge(l) ≤MTmerge(l) ≤ 2l

Most a merge(A, u,m, v) eljárás összes végrehajtásának teljes m¶veletigé-nyére adunk becsléseket. Ehhez vegyük észre, hogy az ms(A, u, v) rekurzív

90

Page 91: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

eljárás hívásai majdnem teljes fájának leveleiben u = v, míg bels® csúcsa-iban u < v, tehát ez utóbbiakban fog meghívódni a merge(A, u,m, v) el-járás. Az ms(. . .) hívások fájának alsó szintjén tehát nem hívódik meg amerge(A, u,m, v) eljárás. Egy szinttel feljebb valahányszor meghívódik, míga magasabb szinteken mindig meghívódik. A legalsó szintt®l eltekintve bár-melyik szintre igaz az, hogy az adott szint ms(. . .) hívásai együtt lefedik ateljes A vektort, azaz az egyes hívások által lefedett részvektorok összhosszapontosan n.

Az alsó két szintt®l eltekintve minden szinten, az ms(A, u, v) rekurzíveljárás minden hívásában meghívódik a merge(A, u,m, v) eljárás, így az adottszinten, ez utóbbi hívások is lefedik az A vektort. Az l ≤ mTmerge(l) ≤MTmerge(l) ≤ 2l becslés, valamint a szorzás és összeadás diszributivitásánakszabályai miatt tehát a rekurzió alsó két szintjét®l eltekintve minden szinten,a merge(. . .) hívások összes m¶veletigénye ≥ n. A rekurzió legalsó szintjét®leltekintve pedig minden szinten, a merge(. . .) hívások összes m¶veletigénye≤ 2n. (A legalsó szinten ez nulla.)

Mivel a rekurzió h mélységére a 9.14. feladat szerint h ≥ blg nc (ahola rekurziós fa gyökere 0 mélségben van), a merge(A, u,m, v) eljárás összesvégrehajtására együtt

mT (n) ≥ n(blg nc − 1) ≥ n(lg n− 2)

Mivel a rekurzió h mélységére a 9.14. feladat szerint h ≤ blg nc + 1, amerge(A, u,m, v) eljárás összes végrehajtására együtt

MT (n) ≤ 2n(blg nc+ 1) ≤ 2n(lg n+ 1)

Összegezve, a merge(A, u,m, v) eljárás összes végrehajtására együtt

n lg n− 2n ≤ mT (n) ≤MT (n) ≤ 2n(lg n) + 2n

Hozzáadva ehhez a merge(. . .) eljárás végrehajtásai nélküli 2n m¶veletigényt,a teljes mergeSort(A) eljárásra

n lg n ≤ mTMS(n) ≤MTMS(n) ≤ 2n(lg n) + 4n

adódik. Ebb®l a 8.28. tétellel kapjuk a bizonyítandó összefüggést.

mTMS(n),MTMS(n) ∈ Θ(n lg n)

9.15. Feladat. A fentiekhez hasonlóan lássuk be, hogy a 7.1.6. alfejezetbenismertetet, egyszer¶ láncolt listákra (S1L) vonatkozó mergeSort(L) eljárásrais teljesül a fenti m¶veletigény, ahol n a rendezend® L lista hossza.

91

Page 92: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

10. Az összehasonlító rendezések

alsókorlát-elemzése

10.1. Tétel. Tetsz®leges rendez® algoritmusra mT (n) ∈ Ω(n).

Proof. Clearly we have to check all the n items and only a limited numberof items is checked without starting a new subprogram call or loop iteration.Let this limit be k. Then mT (n) ≥ 1

kn =⇒ mT (n) ∈ Ω(n).

10.1. Összehasonlító rendezések és a döntési fa modell(Comparison sorts and the decision tree model)

10.2. Deníció. Tetsz®leges rendez® algoritmus akkor összehasonlító ren-dezés (comparison sort), ha az input elemeinek rendezéséhez csak az elemekkulcsainak összehasonlításából nyer információt. Ez azt jelenti, hogy ha adottaz 〈a1, a2, . . . an〉 input kulcssorozat, és ennek két kulcsát akarjuk összehason-lítani, akkor az ai < aj, ai ≤ aj, ai = aj, ai 6= aj, ai ≥ aj, vagy ai > ajkulcs-összehasonlítások valamelyikét végezzük el. Nem vizsgáljuk meg a kul-csok bels® szerkezetét, és más egyéb módon sem szerzünk róluk információt.

Az eddig tárgyalt rendezési algoritmusok, tehát az insertion sort, heap sort,merge sort és a quicksort is összehasonlító rendezések.

In this section, we assume without loss of generality that all the inputelements are distinct21. Given this assumption, comparisons of the formai = aj and ai 6= aj are useless, so we can assume that no comparisons of thisform are made22. We also note that the comparisons ai < aj, ai ≤ aj, ai ≥ aj,and ai > aj are all equivalent in that they yield identical information aboutthe relative order of ai and aj. We therefore assume that all comparisonshave the form ai ≤ aj. [4]

We can view comparison sorts abstractly in terms of decision trees. A decisiontree is a strictly binary tree that represents the comparisons between elementsthat are performed by a particular sorting algorithm operating on an input ofa given size. Control, data movement, and all other aspects of the algorithmare ignored. [4]

21In this way we restrict the set of possible inputs and we are going to give a lower boundfor the worst case of comparison sorts. Thus, if we give a lower bound for the maximumnumber of key comparisons (MC(n)) and for maximum time complexity (MT (n)) on thisrestricted set of input sequences, it is also a lower bound for them on the whole set of inputsequences, because MC(n) and MT (n) are surely ≥ on a larger set than on a smaller set.

22Anyway, if such comparisons are made, neither MC(n) nor MT (n) are decreased.

92

Page 93: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Let us suppose that 〈a1, a2, . . . an〉 is the input sequence to be sorted. Ina decision tree, each internal node is labeled by ai ≤ aj for some elementsof the input. We also annotate each leaf by a permutation of 〈a1, a2, . . . an〉.The execution of the sorting algorithm corresponds to tracing a simple pathfrom the root of the decision tree down to a leaf. Each internal node indicatesa comparison ai ≤ aj. The left subtree then dictates subsequent compari-sons once we know that ai ≤ aj, and the right subtree dictates subsequentcomparisons knowing that ai > aj. When we come to a leaf, the sorting al-gorithm has established the appropriate ordering of 〈a1, a2, . . . an〉. Becauseany correct sorting algorithm must be able to produce each permutation ofits input, each of the n! permutations on n elements must appear as one ofthe leaves of the decision tree for a comparison sort to be correct. Thus, weshall consider only decision trees in which each permutation appears as a leafof the tree. [4]

10.2. Alsó korlát a legrosszabb esetre(A lower bound for the worst case)

10.3. Tétel. Bármely összehasonító rendezés végrehejásához a legrosszabbesetben MC(n) ∈ Ω(n lg n) kulcsösszehasonlítás szükséges.

Proof. From the preceding discussion, it suces to determine the height h =MC(n) of a decision tree in which each permutation appears as a reachableleaf. Consider a decision tree of height h with l leaves corresponding to acomparison sort on n elements. Because each of the n! permutations of theinput appears as some leaf, we have n! ≤ l. Since a binary tree of height hhas no more than 2h leaves, we have n! ≤ l ≤ 2h. [4]

Consequently

MC(n) = h ≥ lg n! =n∑i=1

lg i ≥n∑

i=dn2 elg i ≥

n∑i=dn2 e

lg⌈n

2

⌉≥⌈n

2

⌉∗ lg

⌈n2

⌉≥

≥ n

2∗ lg

n

2=n

2∗ (lg n− lg 2) =

n

2∗ (lg n− 1) =

n

2lg n− n

2∈ Ω(n lg n)

10.4. Tétel. Tetsz®leges összehasonlító rendezésre MT (n) ∈ Ω(n lg n).

Proof. Only a limited number of key comparisons are performed withoutstarting a new subprogram call or loop iteration. Let this limit be k. Then

93

Page 94: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

MT (n) ≥ 1kMC(n) =⇒ MT (n) ∈ Ω(MC(n)). Together with theorem 10.3

and transitivity we receive this theorem.

Vegyük észre, hogy a heap sort és a merge sort, aszimptotikusan optimá-lisak abban az értelemben, hogy a m¶veletigényük O(n lg n) fels® korlátjaösszeillik a (legrosszabb esetre vonatkozó) MT (n) ∈ Ω(n lg n) alsó korláttal.(Ld. a 10.4. tételt!) Az el®bbi tulajdonságokból azonnal közetkezik, hogymindkett®re MT (n) ∈ Θ(n lg n).

Ld. még:http://people.inf.elte.hu/fekete/algoritmusok_jegyzet/. 19_fejezet_Rendezesek_alsokorlat_elemzese.pdf

94

Page 95: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

11. Rendezés lineáris id®ben

Alapvet®en nem kulcsösszehasonlítással rendezünk ([4] 8.2), így ezekre azalgoritmusokra nem vonatkozik az öszehasonlító rendezések alaptétele ([4]8.1 tétel).

Beláttuk, hogy tetsz®leges összehasonlító rendezésre (comparison sort al-gorithm) MT (n) ∈ Ω(n lg n). Ha tehát aszimptotikusan jobb rendezéseketkeresünk, olyan algoritmusokra van szükségünk, amelyek a kulcsok összeha-sonlítása helyett (vagy mellett) másképpen (is) nyernek információt a kulcsoknagyság szerinti sorrendjére vonatkozóan.

Az alábbiakban ismertetend® radix sort (számjegypozíciós rendezés) éscounting sort (leszámláló rendezés) a kulcsok összehasonlítása helyett osztá-lyozzák a kulcsokat. A bucket sort (egyszer¶ edényrendezés) emellett mégkulcsösszehasonlító segédrendezést is használ.

Mivel a 10.1. tétel szerint tetsz®leges rendez® algoritmusra mT (n) ∈Ω(n), egy S rendezés aszimptotikusan optimális, ha MTS(n) ∈ O(n). (Eb-ben az esetben tehát MTS(n),mTS(n) ∈ Θ(n).) Az ebben a fejezetben tár-gyalt radix sort és counting sort aszimptotikusan optimálisak. A bucket sortesetében viszont csak a várható (azaz átlagos) és a minimális m¶veletigénylineáris.

11.1. Radix rendezés (listákra)

Meglep® helyen találhatunk megoldást a lineáris id®ben való rendezés problé-májára. Számítógép múzeumokban még láthatók lyukkártya rendez® gépek.Minden kártyának 80 oszlopa és 12 sora van. Egy gép mindegyik oszlopbaezen 12 hely bármelyikére lyukat tudott ütni. A lyukkártya rendez® gépetmechanikusan programozták, hogy adott i (i ∈ 1..80) érték szerint, sorbanmindegyik lyukkátyát az i-edik oszlopában található lyuk helye alapján 12sor-szer¶en m¶köd® kártyatároló valamelyikébe helyezze el.23 Ezután egyoperátor összegy¶jtötte a kártyákat, úgy, hogy az i-edik oszlopukban az el-s® helyen kilyukasztottak kerültek felülre, a második helyen kilyukasztottakalájuk, és így tovább, a 12. helyen kilyukasztottak legalulra. Ha a kártyákradecimális számokat írunk, mindegyik oszlopban csak 10 helyet használunk.Egy d-jegy¶ szám d oszlopot igényel.

A fenti példától elvonatkoztatva, a Radix rendezés általában felteszi, hogya kulcsok r alapú számrendszerben felírt, d számjegy¶, el®jel nélküli egészszámok. (Szükség esetén vezet® nullákat írunk a számok elé, hogy pontosan d

23Hasonló alapelven m¶köd® card sorting machine-ek ma is forgalomban vannak, perszea modern gépek már nem lyukkártyákat válogatnak szét.

95

Page 96: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

számjegyük legyen.) A számok számjegyeit jobbról balra sorszámozzuk. Azels® számjegy a legkevésbé szignikáns, a jobb széls® számjegy, és így tovább,a d-edik a legszignikánsabb, a bal széls® számjegy. A rendezésnek d menetevan. Az els® menetben az els® (a jobb széls®, a legkevésbé szignikáns)számjegy szerint rendezünk stabil rendezéssel, az i-edik menetben a jobbróli-edik számjegy szerint, és az utolsó menetben a jobbról d-edik (a bal széls®,a legszignikánsabb) számjegy szerint, mindig stabil rendezéssel, az alábbiséma szerint (ahol L absztrakt lista, vagy más néven véges sorozat). radix_sort( L : list ; d : N )

i := 1 to d

use a stable sort to sort list L on digit i

Az els® menet után tehát számok a legkevésbé szignikáns (jobbról els®,azaz jobbszéls®) számjegyük szerint rendezettek. Mikor (a következ®, máso-dik menetben) a jobbról második számjegyük szerint rendezünk, a rendezésstabilitása miatt az azonos (jobbról) második számjegy¶ek a (jobbról) els®számjegyük szerint rendezve maradnak, hiszen a stabil rendezések az azonoskulcsú elemeket meghagyják a bemenet (azaz az input) sorrendjében. Így a2. menet után a számok már a két jobbszéls® számjegyük szerint lesznek ren-dezve. Mikor a 3. menetben jobbról a 3. számjegy szerint rendezünk, akkoraz azonos 3. számjegy¶ek már a jobbról els® két számjegyük szerint marad-nak rendezve. Így a 3. menet után a számok már a 3 jobbszéls® számjegyükszerint lesznek rendezve. Így tovább, amikor a d-edik menetben jobbról a d-edik számjegy szerint rendezünk, akkor az azonos d-edik számjegy¶ek már ajobbról els® d−1 számjegyük szerint maradnak rendezve. Így a d-edik menetután a számok már a d jobbszéls® számjegyük szerint lesznek rendezve. Mivelösszesen d számjegyük van, a számok ekkor már teljesen rendezve lesznek.

Annak érdekében, hogy a Radix rendezés helyesen m¶ködjön, szükséges, hogya számjegyek szerinti rendezések stabilak legyenek. A lyukkártya rendez® gépáltal végrehajtott szétválogatás stabil, de az operátornak is oda kell gyel-ni, hogy ne keverje össze a kártyákat, ahogy összegy¶jti ®ket a sor-szer¶enm¶köd® tárolókból, bár ugyanabban a tárolóban minden kártyának a szortí-rozást vezérl® oszlopában ugyanaz a számjegy található. (Ezután ezeket asor-szer¶en m¶köd® tárolókat egyszer¶en csak polcoknak fogjuk nevezni.)

A lyukkártyáktól ismert séma szerint, a radix rendezés által felhasznált stabilrendezés, r alapú számrendszer esetén, a számokat az i-edik számjegyükértéke szerint sorban szétválogatja a Z0, Z1, Z2, . . . Zr−1 kezdetben üres

96

Page 97: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

polcokra (bins), ügyelve arra, hogy az egyes polcokon megmaradjon a szá-moknak a szétválogatás el®tti sorrendje. Ezután a polcok tartalmát a pol-cok egymás közötti és a számoknak a polcokon belüli sorrendjét is megtartva összef¶zi L-be.24 Mivel r darab polcot kell üresre inicializálni, n elemetszétválogatni, majd az r db polcot, valójában listát összef¶zni, ez megoldha-tó Θ(n + r) m¶veletigénnyel. Mivel a radix rendezés ezt a nyilvánvalóanstabil rendezést d-szer hívja meg, a teljes m¶veletigénye Θ(d ∗ (n+ r)). Had konstans, és r ∈ O(n), akkor ebb®l

Tradix_sort(n) ∈ Θ(n).

A 11. ábrán látható példában r = 4 és d = 3, azaz a kulcsok 4-es számrend-szerbeli, 3 jegy¶ számok, és a Z0, Z1, Z2, Z3 polcokat használjuk.

Természetes módon adódik, hogy a gyakorlatban a fenti absztrakt listák (másnéven véges sorozatok), azaz a bemenet, a polcok és az algoritmus kimene-te is láncolt listák legyenek, mivel nem tudhatjuk, hogy az egyes polcokon0..n között (ahol n az input mérete) hány tételt akarunk elhelyezni, r darabn méret¶ segédtömb pedig a gyakorlatban túl sok munkamemóriát jelentene.Ha viszont a polcokat láncolt listák reprezentálják, akkor a memória allo-kálások (pl. new utasítás) és deallokálások (pl. delete utasítás) elkerülésevégett célszer¶, ha a bemenet és a kimenet is a polcokkal azonos típusú láncolt lista. (Ha az interfész egy vektor, de a polcok láncolt listák, összesenn ∗ d memória allokálás, és ugyanennyi deallokálás szükséges, ami várható-lag annyira megnöveli a Θ(n) m¶veletigényben rejtett konstanst, hogy arendezés gyakorlatilag használhatatlanná válik.)

Ha a polcok láncolt listák, akkor ezen listák végéhez is direkt hozzáférésszükséges, hogy az egyes menetek bemeneti listája elemeinek szétpakolásahatékony legyen, azaz minden elemre Θ(1) id® alatt megtörténjen. Ez meg-valósítható pl. S1L-ekkel és a nemüres polcok végére mutató pointerekkel,illetve (ha lusták vagyunk, némi konstans szorzó árán) C2L-ek alkalmazásá-val.

Néha használjuk a radix rendezést összetett kulcsú rekordok rendezésére.Ilyen összetett kulcs például a dátum, ami három komponenst (év, hó, nap)

24Sok ember számára az lenne természetes, hogy a számokat el®ször a balszéls® és utol-jára a jobbszéls® számjegyük szerint rendezze. A radix rendezésben azonban a kés®bbimenetek fontosabbak az eredmény szempontjából, mint a korábbiak: Az i-edik menetbenaz els® i−1 menet eredménye alárendel®dik az i-edik menetnek. Így a végén a számok el-s®sorban a jobbszéls® számjegyük szerint lennének rendezve, és általában nem ezt akarjuk.Egy másik megközelítés szerint szintén a balszéls® számjeggyel kezdhetnénk, de utána,

még az összef¶zés el®tt, rekurzívan rendezhetnénk a polcokat a többi számjegy szerint,minden rekurziós szinten újra és újra, újabb és újabb segédpolcokat létrehozva, majd tö-rölve. Így viszont a polcokkal kapcsolatos rengeteg adminisztráció lelassítaná a programot.

97

Page 98: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Az input (azaz bemeneti) lista, szimbolikus jelöléssel (r = 4; d = 3):L = 〈103, 232, 111, 013, 211, 002, 012〉

1. menet (a számok jobbról 1., azaz jobbszéls® számjegyei szerint):Z0 = 〈〉Z1 = 〈111, 211〉Z2 = 〈232, 002, 012〉Z3 = 〈103, 013〉L = 〈111, 211, 232, 002, 012, 103, 013〉

2. menet (a számok jobbról 2., azaz középs® számjegyei szerint):Z0 = 〈002, 103〉Z1 = 〈111, 211, 012, 013〉Z2 = 〈〉Z3 = 〈232〉L = 〈002, 103, 111, 211, 012, 013, 232〉

3. menet (a számok jobbról 3., azaz balszéls® számjegyei szerint):Z0 = 〈002, 012, 013〉Z1 = 〈103, 111〉Z2 = 〈211, 232〉Z3 = 〈〉L = 〈002, 012, 013, 103, 111, 211, 232〉

11. ábra. A szétválogató-összef¶z® Radix rendezés bemutatása

98

Page 99: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

tartalmaz. El®ször tehát a legkevésbé szignikáns nap, majd a hó, végüla legszignikánsabb év mez® szerint rendezünk, stabil rendezéssel. (Termé-szetesen használhatnánk összehasonlító rendezést is, amikor két kulcs [azazdátum] összehasonlításánál el®ször az év mez®ket vennénk gyelembe, haezek egyenl®k, a hó mez®ket, és ha ezek is egyenl®k, a nap mez®ket. Tekin-tetbe véve azonban, hogy a radix rendezéshez itt elég 3 menet, és feltételezve,hogy nincs túl sok lehetséges évszám, a fenti szétválogató-összef¶z® stratégi-ával valószín¶leg már néhány ezer tétel esetén is gyorsabb rendezést kapunk,mint bármelyik összehasonlító rendezéssel.)

11.1. Feladat. Tegyük fel, hogy adott az L fejelemes C2L, a listaelemekkulcsai r alapú számrendszerben felírt, d számjegy¶ számok, és adott adigit(i, r, x) függvény, ami Θ(1) m¶veletigénnyel ki tudja nyerni az x kulcsjobbról i-edik számjegyét, ahol a számjegyeket 1-t®l d-ig sorszámozzuk.

Adja meg a radix rendezés struktogramját a fenti feltételekkel, Theta(n)m¶veletigénnyel, ahol n a lista hossza, r ∈ O(n), d pedig pozitív egész kons-tans.

Megoldás: radix_sort( L : E2* ; d, r : N )

BinHeadZ : E2[r] // the headers of the lists representing the bins

Bz : E2*[r] // pointers to the headers

i := 0 to r − 1

Bz[i] := &BinHeadZ[i] // Initialize the ith pointer.

i := 1 to d

distribute(L, i, Bz) // Distribute L on the ith digits of keys.

gather(Bz, L) // Gather form the bins back into L distribute( L : E2* ; i : N ; Bz : E2*[] )

L→ next 6= L

p := L→ next ; out(p)

precede( p,Bz[digit(i, Bz.M, p→ key)] gather( Bz : E2*[] ; L : E2* )

i := 0 to Bz.M − 1

append(L,Bz[i]) // add to the end of L the elements form Bz[i]

99

Page 100: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

append( L,B : E2* )

AAB → next 6= B

p := L→ prev ; q := B → next ; r := B → prev

p→ next := q ; q → prev := p

r → next := L ; L→ prev := r

B → next := B → prev := B

SKIP

Clearly, Tappend ∈ Θ(1), so Tgather ∈ Θ(r) where r = Bz.M .And Tdistribute(n) ∈ Θ(n) where n = |L|.Thus Tradix_sort(n, d, r) ∈ Θ(r + d(n+ r)).Consequently, if d is constant and r ∈ O(n), then Tradix_sort(n) ∈ Θ(n).

11.2. Feladat. Oldjuk meg a 11.1. feladatot azzal a változtatással, hogy LS1L!

11.3. Feladat. Oldjuk meg a 11.1. és a 11.2. feladatokat azzal a változta-tással, hogy az L kulcsai 4 bájtos, el®jel nélküli egész számok, r = 256 ésnem áll rendelkezésünkre a számjegyeket kinyer® függvény, a C-ben szokásosaritmetikai léptetések és a bitenkénti & m¶velet viszont adott.

11.2. Leszámláló rendezés (counting sort)

Míg a radix sort fentebb ismertetett verziója (a számjegyek szerinti szétvá-logatásokkal és az összef¶zésekkel) láncolt listák rendezésére alkalmazhatóhatékonyan, a leszámláló rendezés a radix sort ideális segédrendezése, ha arendezend® adatokat egy vektor tartalmazza.

Emlékeztetünk arra, hogy egy rendezés akkor stabil, ha megtartja azegyenl® kulcsú elemek eredeti sorrendjét. A leszámláló rendezés stabil, ésm¶veletigénye miatt is megfelel®, mint a radix sort segédrendezése.

Az alábbiakban a leszámláló rendezést egy kicsit általánosabban tárgyal-juk, mint ahogy azt a számjegypozíciós rendezéshez szükséges. Ha a radixsort-hoz használjuk, feltehet®, hogy a counting sort ϕ függvénye a megfelel®számjegyet választja ki.

A rendezési feladat: Adott az A:T[n] tömb, r ∈ O(n) pozitív egész,ϕ : T → 0..(r−1) kulcsfüggvény. Rendezzük az A tömböt lineáris id®benstabil rendezéssel úgy, hogy az eredmény a B:T[n] tömbben keletkezzék!

100

Page 101: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

counting_sort(A,B : T[] ; r : N ; ϕ : T → 0..(r−1))

Z : N[r] // counter array

k := 0 to r−1

Z[k] := 0 // init the counter array

i := 1 to A.M

Z[ϕ(A[i])]++ // count the items with the given key

k := 1 to r−1

Z[k] += Z[k − 1] // Z[k] = the number of items with key ≤ k

i := A.M downto 1

k := ϕ(A[i]) // k := the key of A[i]

B[Z[k]] := A[i] //Let A[i] be the last of the unprocessed items with key k

Z[k]−− // The next one with key k must be put before A[i]

A fenti struktogram els® ciklusában kinullázzuk a Z számláló tömböt.A második ciklusban minden lehetséges k kulcsra Z[k]-ban megszámoljuk,

hogy hány db k kulcsú elem van.A harmadik ciklusban minden lehetséges k kulcsra összeadjuk Z[k]-ban,

hogy hány ≤ k kulcsú elem van. Mivel ≤ 0 kulcsú elem ugyanannyi van,mint 0 kulcsú, Z[0] értéke nem változik. Nagyobb k kulcsokra viszont annyi≤ k kulcsú elem van, amennyi pontosan k kulcsú + k-nál kisebb kulcsú(vagyis ≤ k−1 kulcsú). Z[k] új értékét tehát úgy kaphatjuk meg, ha Z[k]régi értékéhez hozzáadjuk Z[k−1] új értékét.

A negyedik ciklusban a bemeneti tömbön visszafelé haladva minden ele-met az eredmény vektorba a helyére teszünk, vagyis tetsz®leges k kulcsrael®ször az utolsó k kulcsú elemet dolgozzuk fel, és betesszük az utolsó helyre,ahova k kulcsú elem kerülhet, pontosan az eredmény tömb Z[k]-adik elemé-be. Z[k] mutatja meg ugyanis, hogy hány darab legfeljebb k kulcsú elem vana bemeneten. Ezután a Z[k]-t eggyel csökkentjük, és így a fordított bejárásszerint következ® k kulcsú elem közvetlenül a mostani elé fog kerülni stb.Emiatt tehát tetsz®leges k kulcsra a k kulcsú elemek az eredmény tömbbenis az eredeti sorrendjükben maradnak, és a kisebb kulcsú elemek meg is el®zika nagyobb kulcsúakat, azaz stabil rendezést kapunk.

A m¶veletigény nyilván Θ(n + r). Feltételezve, hogy r ∈ O(n), Θ(n + r) =Θ(n), azaz T (n) ∈ Θ(n).

101

Page 102: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

A leszámláló rendezés szemléltetése: Feltesszük, hogy kétjegy¶, négyesszámrendszerbeli számokat kell rendezni a jobboldali számjegyük, mint kulcsszerint, azaz a ϕ kulcsfüggvény a jobboldali számjegyet választja ki.

A bemenet:1 2 3 4 5 6

A : 02 32 30 13 10 12

A Z:N[4] számláló tömb alakulása [ahol az els® oszlopban a struktogramels® ciklusának megfelel®en kinullázzuk a Z számláló tömböt; a következ®hat oszlopban a struktogram második ciklusának megfelel®en minden le-hetséges k kulcsra (itt számjegyre) megszámoljuk, hogy hány k kulcsú elemvan; a

∑jelzés¶ oszlopban a struktogram harmadik ciklusának megfele-

l®en minden lehetséges k kulcsra (itt számjegyre) összeadjuk, hogy hány≤ k kulcsú elem van; az utolsó hat oszlopban pedig a struktogram negye-dik ciklusának megfelel®en a bemeneti tömbön visszafelé haladva mindenelemet a helyére teszünk, ahogy azt fentebb részleteztük]:

Z 02 32 30 13 10 12∑

12 10 13 30 32 020 0 1 2 2 1 01 0 22 0 1 2 3 5 4 3 23 0 1 6 5

A kimenet :1 2 3 4 5 6

B : 30 10 02 32 12 13

Most pedig feltesszük, hogy az el®z® leszámláló rendezés eredményeként adó-dott kétjegy¶, négyes számrendszerbeli számok sorozatát kell rendezni a bal-oldali számjegyük, mint kulcs szerint, azaz a ϕ kulcsfüggvény a baloldaliszámjegyet választja ki.

A bemenet:1 2 3 4 5 6

B : 30 10 02 32 12 13

A Z:N[4] számláló tömb alakulása:Z 30 10 02 32 12 13

∑13 12 32 02 10 30

0 0 1 1 01 0 1 2 3 4 3 2 12 0 43 0 1 2 6 5 4

A kimenet :1 2 3 4 5 6

A : 02 10 12 13 30 32

102

Page 103: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Mivel az els® leszámláló rendezés a bemenetet a jobboldali számjegyek sze-rint rendezte, és a második leszámláló rendezés az els® eredményét rendeztetovább a baloldali számjegyek szerint stabil rendezéssel, a végeredménybenaz azonos bal-számjegy¶ számok a jobb-számjegyük szerint rendezve marad-tak, és így a végeredményben a számok már mindkét számjegyük szerintrendezettek.

Ezért tehát a fent szemléltetett két counting sort egy radix rendezés 1. és2. menetét adja, és mivel a számainknak most csak két számjegyük van, egyteljes radix rendezést hajtottunk végre.

11.3. Radix rendezés (Radix-Sort) tömbökre ([4] 8.3)

A rendezend® A tömb kulcsai r alapú számrendszerben felírt, d-jegy¶ nem-negatív egész számok. A jobbról els® számjegy helyiértéke a legkisebb, míga d-ediké a legmagasabb.25 radix_sort( A : T[] ; d : N )

i := 1 to d

Rendezzük az A tömböt az elemek kulcsainakjobbról i-edik számjegye szerint stabil rendezéssel

Ha a stabil rendezés a leszámláló rendezés, akkor a m¶veletigény Θ(d(n +r))(n = A.M) , mivel a teljes rendezés d leszámláló rendezésb®l áll. Feltéte-lezve, hogy d konstans és r ∈ O(n), Θ(d(n+ r)) = Θ(n), azaz T (n) ∈ Θ(n).

Ha pl. a kulcsok négy bájtos nemnegatív egészek, választhatjuk számje-gyeknek a számok bájtjait, így d = 4 és r = 256, mindkett® n-t®l függetlenkonstans, tehát a feltételek teljesülnek, és a rendezés lineáris id®ben lefut. Azi-edik számjegy, azaz bájt kinyerése egyszer¶ és hatékony. Ha key a kulcs,akkor pl. C++-ban

(key >> (8 ∗ (i− 1)))&255

az i-edik számjegye26. Ld. még [4] 8.3-ban, hogy n rendezend® adat, b bitestermészetes szám kulcsok és m bites számjegyek (r = 2m) esetén, a radixrendezésben, b és n függvényében, hogyan érdemes m-et megválasztani!

25Általánosítva, a kulcs felbontható d kulcs direkt szozatára, ahol a jobbról els® legle-vésbé szignikáns, míg a d-edik a leglényegesebb. Pl. ha a kulcs dátum, akkor el®ször anapok, majd a hónapok és végül az évek szerint alkalmazunk stabil rendezést. Ez azértjó, mert a stabilitás miatt amikor a hónapok szerint rendezünk, az azonos hónapbaes® elemek a napok szerint rendezettek maradnak, és amikor az évek szerint rendezünk,az azonos évbe es® elemek hónapok és ezen belül napok szerint szintén sorban maradnak.

26A fenti C++-os képlet tovább egyszer¶södik, ha a programban i helyett az eltolásmértékét tartjuk nyilván (ez persze egyenl® 8 ∗ (i− 1)-gyel).

103

Page 104: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

11.4. Feladat. Részletezzük a radix-sort fenti, absztrakt programját úgy,hogy a stabil rendezéshez leszámláló rendezést használunk, és a számjegyeka teljes b bites kulcs m bites szakaszai! Vegyük gyelembe, hogy ett®l a count-ing sort paraméterezése is változik, és hogy érdemes felváltva hol az eredetiA[1..n] tömbb®l a B[1..n] segédtömbbe, hol a B[1..n]-b®l az A[1..n]-be végeznia leszámláló rendezést!

11.4. Egyszer¶ edényrendezés (bucket sort)

Feltesszük, hogy a rendezend® elemek kulcsai a [0; 1) valós intervallum elemei.(Ha a kulcs egész vagy valós szám típusú, vagy azzá konvertálható, továbbátudunk a kulcsok számértékeire alsó és fels® határt mondani, akkor a kulcsokszámértékei nyilván normálhatók a [0; 1) valós intervallumra. Ha ugyanisa < b valós számok, és a k kulcsra k ∈ [a; b), akkor k−a

b−a ∈ [0; 1).)Az alábbi algoritmus akkor lesz hatékony, ha az input kulcsai a [0; 1)

valós intervallumon egyenletesen oszlanak el. (Az L [absztrakt] listát (másnéven véges sorozatot) mint mindig most is monoton növekv®en rendez-zük. Az edények [buckets] rendezésére valamilyen korábbról ismert rendezésthasználunk.) bucket_sort( L : list )

n := the length of L

Z : list[n] // Create the buckets Z[0..(n−1)]

j := 0 to (n−1)

Let Z[j] be empty list

L 6=

Remove the rst element of list L

Insert this element according to its key k into list Z[bn ∗ kc]j := 0 to (n−1)

Sort list Z[j] nondecreasingly

Append lists Z[0], Z[1], . . . , Z[n− 1] in order into list L

Nyilván mT (n) ∈ Θ(n), és a fenti egyenletes eloszlást feltételezve AT (n) ∈Θ(n) is teljesül. MT (n) pedig attól függ, hogy a Z[j] listákat milyen mód-szerrel rendezzük. Pl. egyszer¶ beszúró rendezést használvaMT (n) ∈ Θ(n2),összefésül® rendezéssel viszont MT (n) ∈ Θ(n lg n).

104

Page 105: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

11.5. Feladat. Részletezze az elemi utasítások szintjéig a fenti kódot, felté-ve, hogy a listák egyszer¶ láncolt listák (S1L)! Vegyük észre, hogy az edénybe(bucket) való beszúrásnál ha nem törekszünk a rendezés stabilitására azedényt reprezentáló S1L elejére érdemes a listaelemet beszúrni. (Az edényekrendezését nem kell részletezni.)

11.6. Feladat. Tegye a 11.5. feladatot megoldó rendezést stabillá,MT (n) ∈ Θ(n lg n); mT (n), AT (n) ∈ Θ(n) m¶veletigénnyel!

105

Page 106: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

12. Hasító táblák (hash tables)

A mindennapi programozási gyakorlatban sokszor van szükségünk ún. szótá-rakra, amelyek m¶veletei: (1) adat beszúrása a szótárba, (2) kulcs alapjána szótárban a hozzá tartozó adat megkeresése, (3) a szótárból adott kulcsú,vagy egy korábbi keresés által lokalizált adat eltávolítása.

Az AVL fák, B+ fák (ld. a következ® félévben) és egyéb kiegyensúlyozottkeres®fák mellett a szótárakat gyakran hasító táblákkal valósítják meg, fel-téve, hogy a m¶veleteknek nem a maximális, hanem az átlagos futási idejétszeretnék minimalizálni. (A kiegyensúlyozott keres®fák esetében ti. els®sor-ban a maximális m¶veletigényt optimalizáljuk: beszúrás, keresés és törlésesetén is elvárt az O(lg n) maximális m¶veletigény.) Hasító táblát használvaugyanis a fenti m¶veletekre elérhet® az ideális, Θ(1) átlagos futási id® azonaz áron, hogy a maximális m¶veletigény általában Θ(n).

Jelölések:m : a hasító tábla méreteZ[0..(m− 1)] : a hasító táblaZ[0], Z[1], . . . , Z[m− 1] : a hasító tábla rései (slot-jai) : üres rés a hasító táblában (direkt címzésnél és a kulcsütközések lánco-lással való feloldása esetén)E : üres rés kulcsa a hasító táblában (nyílt címzésnél)D : törölt rés kulcsa a hasító táblában (nyílt címzésnél)n : a hasító táblában tárolt adatok számaα = n/m : a hasító tábla kitöltöttségi aránya (load factor)U : a kulcsok univerzuma; k, k′, ki ∈ Uh : U → 0..(m− 1) : hasító függvény

Feltesszük, hogy a hasító tábla nem tartalmazhat két vagy több azonos kulcsúelemet, és hogy h(k), Θ(1) id®ben számolható.

12.1. Direkt címzés (direct-address tables)

Feltesszük, hogy U = 0..(m− 1), ahol m ≥ n, de m nem túl nagy.A Z : D ∗ [m] hasító tábla rései pointerek, amik D típusú adatrekordokramutatnak. A rekordoknak van egy k : U kulcsmez®jük és járulékos mez®ik.A hasító táblát pointerekkel inicializáljuk.

D+ k : U // k is the key+ . . . // satellite data

106

Page 107: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

init( Z:D*[] )

i := 0 to Z.M−1

Z[i] :=

nd( Z:D*[] ; k:U ):D*

return Z[k] insert( Z:D*[] ; p:D* ):B

AAZ[p→ k] =

Z[p→ k] := p

return truereturn false

remove( Z:D*[] ; k:U ):D*

p := Z[k]

Z[k] := return p

Nyilván Tinit(m) ∈ Θ(m), ahol m = Z.M . A másik három m¶veletre pedigT ∈ Θ(1).

12.2. Hasító táblák (hash tables)

Hasító függvény (hash function): Ha |U | >> n, a direkt címzés nem alkal-mazható, vagy nem gazdaságos, ezért h : U → 0..(m−1) hasító függvénytalkalmazunk, ahol tipikusan |U | >> m (a kulcsok U univerzumának elem-száma sokkal nagyobb, mint a hasító tábla m mérete). A k kulcsú adatot aZ[0..(m−1)] hasító tábla Z[h(k)] résében tároljuk (próbáljuk tárolni).

Feltesszük, hogy a hasító táblában minden rekord kulcsa egyedi, azaz kéttetsz®leges rekord kulcsa különböz®.

A h : U → 0..(m−1) függvény egyszer¶ egyenletes hasítás, ha a kulcsokata rések között egyenletesen szórja szét, azaz hozzávet®leg ugyanannyi kulcsotképez le az m rés mindegyikére. Tetsz®leges hasító függvénnyel kapcsolatoselvárás, hogy egyszer¶ egyenletes hasítás legyen.

Kulcsütközések (collisions): Ha két adat k1, k2 kulcsára h(k1) = h(k2), kulcs-ütközésr®l beszélünk. Mivel |U | >> m, kulcsütközés szinte biztosan el®for-dul, ezért kezelni kell.

Ha például a kulcsok egész számok, és h(k) = k mod m, akkor pontosanazok a kulcsok képez®dnek le az s-edik résre, amelyekre r = k mod m.

12.3. Kulcsütközések feloldása láncolással(collision resolution by chaining)

Feltesszük, hogy a hasító tábla rései egyszer¶ láncolt listákat azonosítanak,azaz Z:E1*[m], ahol a listaelemekben a szokásos key és a next mez®kön kívüláltalában járulékos mez®k (satellite data) is vannak. Ha a hasító függvény

107

Page 108: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

két vagy több elem kulcsait a hasító táblának ugyanarra a résére képzi le,akkor ezeket az elemeket az ehhez a réshez tartozó listában tároljuk.

E1+key : U. . . // satellite data may come here+next : E1*+E1() next := init( Z:E1*[] )

i := 0 to Z.M−1

Z[i] :=

search( Z:E1*[] ; k:U ):E1*

return searchS1L(Z[h(k)],k) insert( Z:E1*[] ; p:E1* ):B

k := p→ key ; s := h(k)

AAsearchS1L(Z[s], k) =

p→ next := Z[s]

Z[s] := p

return true

return false

searchS1L( q:E1* ; k:U ):E1*

q 6= ∧ q → key 6= k

q := q → next

return q

remove( Z:E1*[] ; k:U ):E1*

s := h(k) ; p := ; q := Z[s]

q 6= ∧ q → key 6= k

p := q ; q := q → next

AAq 6=

AAp =

Z[s] := q → next p→ next := q → next

q → next := SKIP

return q

Nyilván Tinit(m) ∈ Θ(m), ahol m = Z.M . A másik három m¶veletre pedigmT ∈ Θ(1), MT (n) ∈ Θ(n), AT (n,m) ∈ Θ(1 + n

m).

AT (n,m) ∈ Θ(1 + nm

) feltétele, hogy a h : U → 0..(m−1) függvény egy-szer¶ egyenletes hasítás legyen, és még az indokolja, hogy a résekhez tartozólisták átlagos hossza = n

m= α.

108

Page 109: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Általában feltesszük még, hogy nm∈ O(1). Ebben az esetben nyilván

AT (n,m) ∈ Θ(1) is teljesül.

12.4. Jó hasító függvények (good hash functions)

Osztó módszer (division method): Ha a kulcsok egész számok, gyakranválasztják a

h(k) = k mod m

hasító függvényt, ami gyorsan és egyszer¶en számolható, és ha m olyan prím,amely nincs közel a kett® hatványokhoz, általában egyenletesen szórja szét akulcsokat a 0..(m−1) intervallumon.

Ha pl. a kulcsütközést láncolással szeretnénk feloldani, és kb. 2000 rekor-dot szeretnénk tárolni α ≈ 3 kitöltöttségi aránnyal, akkor a 701 jó választás:A 701 ui. olyan prímszm, ami közel esik a 2000/3-hoz, de a szomszádos kett®hatványoktól, az 512-t®l és az 1024-t®l is elég távol van.

Kulcsok a [ 0 ; 1) intervallumon: Ha egyenletesen oszlanak el, a

h(k) = bk ∗mc

függvény is kielégíti az egyszer¶, egyenletes hasítás feltételét.

Szorzó módszer (multiplication method): Ha a kulcsok valós számok,tetsz®leges 0 < A < 1 konstanssal alkalmazható a

h(k) = bk ∗ A ∗mc

hasító függvény. (x az x törtrésze.) Nem minden lehetséges konstanssalszór egyformán jól. Knuth az A =

√5−12≈ 0, 618 választást javasolja, mint

ami a kulcsokat valószín¶leg szépen egyenletesen fogja elosztani a rések kö-zött. Az osztó módszerrel szemben el®nye, hogy nem érzékeny a hasító táblaméretére.

El®jel nélküli egész kulcsok esetén, ha a táblaméretet kett® hatványnakválasztjuk, kikerülhet® a viszonylag lassú, valós aritmetika. Tegyük fel, hogya kulcsok w biten ábrázolt természetes számok. Ekkor 0 ≤ k ≤ 2w − 1.Ábrázoljuk szintén w biten az s = bA ∗ 2wc konstanst. Tegyük fel, hogy atáblaméret m = 2p. Legyen q = 2w − 1 és r = w − p. Ekkor a fenti szorzómódszert jól közelíthetjük az alábbi hasító függvény denícióval, feltéve, hogydupla szavas, el®jel nélküli egész aritmetikát használunk. (& a bitenkénti ésm¶velet, és >> jelöli a bitléptetést jobbra.)

h(k) = ((s ∗ k)&q) >> r

109

Page 110: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

Mindhárom módszer feltételezi, hogy a kulcsok számok. Ha pl. sztringek,a karaktereket tekinthetjük megfelel® számrendszerbeli el®jel nélküli egészszámok számjegyeinek, és így a sztringeket könnyen értelmezhetjük úgy, mintnagy, természetes számokat.

12.5. Nyílt címzés (open addressing)

Feltesszük, hogy az adatrekordok közvetlenül a résekben vannak; a Z : R[m]hasító tábla R típusú rekordjainak van egy k : U ∪ E,D kulcsmez®jük ésjárulékos mez®ik, ahol E és D extremális konstansok (E,D /∈ U), sorban azüres (Empty) és a törölt (Deleted) rések jelölésére.

R+ k : U ∪ E,D // k is a key or it is Empty or Deleted+ . . . // satellite data

init( Z:R[] )

i := 0 to Z.M−1

Z[i].k := E

Jelölések a nyílt címzéshez:h : U × 0..(m− 1)→ 0..(m− 1) : hasító próba〈h(k, 0), h(k, 1), . . . , h(k,m− 1)〉 : potenciális próbasorozat

Feltesszük, hogy a hasító táblában nincsenek duplikált kulcsok27.

Az üres és a törölt réseket együtt szabad réseknek nevezzük. (A többi résfoglalt.) Egyetlen hasító függvény helyett most m darab hasító függvényünkvan:

h(·, i) : U → 0..(m− 1) (i ∈ 0..(m− 1))

12.5.1. Nyílt címzés: beszúrás és keresés, ha nincs törlés

Ha a hasító táblából nem akarunk törölni (ahogy ez sok alkalmazásban ígyis van), a beszúrás is egyszer¶bb.

A k kulcsú r adat beszúrásánál például el®ször a h(k, 0) réssel próbálko-zunk. Ha ez foglalt és a kulcsa nem k, folytatjuk a h(k, 1)-gyel stb., mígnem

27Tetsz®leges adattárolóban egy kulcs duplikált, ha az adattárolóban (itt a hasító táb-lában) legalább kétszer fordul el®.

110

Page 111: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

üres rést találunk, vagy k kulcsú foglalt rést találunk, vagy kimerítjük azösszes lehetséges próbát, azaz a 〈h(k, 0), h(k, 1), . . . , h(k,m − 1)〉 potenciá-lis próbasorozatot. Ha üres rést találunk, ebbe tesszük az adatot, különbensikertelen a beszúrás.

A 〈h(k, 0), h(k, 1), . . . , h(k,m−1)〉 sorozatot a azért nevezzük potenciális pró-basorozatnak, mert a beszúrás, keresés vagy törlés során ennek ténylegesencsak egy prexét állítjuk el®. A potenciális próbasorozatnak azt a prexét,amit egy beszúrás, keresés (vagy törlés) esetén ténylegesen el®állítunk, aktu-ális próbasorozatnak nevezzük. A potenciális próbasorozattal szemben meg-követeljük, hogy a 〈0, 1, . . . , (m − 1)〉 egy permutációja legyen, azaz, hogyaz egész hasító táblát lefedje (és így ne hivatkozzon kétszer vagy többszörugyanarra a résre). Ha a beszúrás a h(k, i − 1) próbánál áll meg, akkor (éscsak akkor) a beszúrás (azaz az aktuális próbasorozat) hossza i.

A k kulcsú adat keresésénél is a fenti potenciális próbasorozatot követjük,és akkor állunk meg, ha megtaláltuk a keresett kulcsú foglalt rést (sikereskeresés), illetve ha üres rést találunk vagy kimerítjük a potenciális próbaso-rozatot (sikertelen keresés). Ha a keresés a h(k, i−1) próbánál áll meg, akkor(és csak akkor) a keresés (azaz az aktuális próbasorozat) hossza i.

Ideális esetben egy tetsz®leges potenciális próbasorozat a 〈0, 1, . . . , (m− 1)〉sorozatnak mind az m! permutációját azonos valószín¶séggel állítja el®.Ilyenkor egyenletes hasításról beszélünk.

Amennyiben a táblában nincsenek törölt rések egyenletes hasítást és ahasító tábla 0 < α < 1 kitöltöttségét feltételezve , egy sikertelen keresésilletve egy sikeres beszúrás várható hossza legfeljebb

1

1− α

míg egy sikeres keresés illetve sikertelen beszúrás várható hossza legfeljebb

1

αln

1

1− α

Ez azt jelenti, hogy egyenletes hasítást feltételezve, pl. 50%-os kitöltöttségmellett egy sikertelen keresés (illetve egy sikeres beszúrás) várható hosszalegfeljebb 2, míg egy sikeres keresésé (illetve egy sikertelen beszúrásé) kisebb,mint 1,387; 90%-os kitöltöttség mellett pedig egy sikertelen keresés (illetveegy sikeres beszúrás) várható hossza legfeljebb 10, míg egy sikeres keresésé(illetve egy sikertelen beszúrásé) kisebb, mint 2,559 [4].

111

Page 112: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

12.5.2. Nyílt címzés¶ hasítótábla m¶veletei, ha van törlés is

A törlés egy sikeres keresést követ®en a megtalált i rés kulcsának törölt-re(D) állításából áll. Itt az üres-re (E) állítás helytelen lenne, mert ha példáulfeltesszük, hogy a k kulcsú adatot kulcsütközés miatt a Z[h(k, 1)] helyretettük, majd töröltük a h(k, 0) helyen lev® adatot (azaz a Z[h(k, 0)] réstüresre állítottuk), akkor egy ezt követ® keresés nem találná meg a k kulcsúadatot.

A keresésnél tehát átlépjük a törölt réseket is, és csak akkor állunk meg,- ha megtaláltuk a keresett kulcsú elemet (sikeres keresés),- ha üres rést találunk vagy kimerítjük a potenciális próbasorozatot (sikerte-len keresés).

A beszúrásnál is egy teljes keresést végzünk el, most a beszúrandó adatkulcsára, de ha közben találunk törölt rést, az els® ilyent megjegyezzük.- Ha a keresés sikeres, akkor a beszúrás sikertelen (hiszen duplikált kulcsotnem engedünk meg).- Ha a keresés sikertelen, és találtunk közben törölt rést, akkor a beszúrandóadatot az els®ként talált törölt résbe tesszük (hogy a jöv®beli keresések hosszaa lehet® legkisebb legyen).- Ha a keresés sikertelen, de nem talál törölt rést, viszont üres résen áll meg,akkor a beszúrandó adatot ebbe az üres résbe tesszük.- Ha a keresés sikertelen, de nem talál sem törölt, sem üres rést, és így azért állmeg, mert a potenciális próbasorozatot kimeríti, akkor a beszúrás sikertelen(mert a hasítótábla tele van).

Ha elég sokáig használunk egy nyílt címzés¶ hasító táblát, elszaporodhatnaka törölt rések, és elfogyhatnak az üres rések, holott a tábla esetleg közelsincs tele. Ez azt jelenti, hogy pl. a sikertelen keresések az egész táblátvégig fogják nézni. Ez ellen a tábla id®nkénti frissítésével védekezhetünk,amikor megszüntetjük a törölt réseket. (A legegyszer¶bb megoldás kimásoljaaz adatokat egy temporális területre, üresre inicializálja a táblát, majd akimentett adatokat egyesével újra beszúrja.)

12.5.3. Lineáris próba

Ebben és a következ® két alfejezetben áttekintünk három stratégiát〈h(k, 0), h(k, 1), . . . , h(k,m − 1)〉 próba sorozat (részleges) el®állítására.Mindegyiknél lesz egy els®dleges h1 : U → 0..(m−1) hasítófüggvény, ami-nek az értéke egyben az els® próba indexét, h(k, 0)-t adja. Ebb®l kiindul-va lépkedünk a réseken tovább, ha ez szükséges. Elvárás, hogy h1 egyszer¶

112

Page 113: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

egyenletes hasítás legyen. Mindhárom stratégiánál feltesszük, hogy a kulcsoktermészetes számok (így lehet pl. D = −1 és E = −2).

A próbák közül a legegyszer¶bb, a lineáris próba deníciója a következ®.

h(k, i) = (h1(k) + i) mod m (i ∈ 0..(m− 1))

ahol h1 : U → 0..m− 1 hasító függvény. Könny¶ implementálni, de összesencsak m db különböz® próba sorozat van, az egyenletes hasításhoz szükségesm! db próba sorozathoz képest, hiszen ha két kulcsra h(k1, 0) = h(k2, 0),akkor az egész próba sorozatuk megegyezik. Ráadásul a különböz® próbasorozatok összekapcsolódásával foglalt rések hosszú, összefügg® sorozatai ala-kulhatnak ki, megnövelve a várható keresési id®t. Ezt a jelenséget els®dlegescsomósodásnak nevezzük. Minél hoszabb egy ilyen csomó, annál valószí-n¶bb, hogy a következ® beszúráskor a hossza tovább fog növekedni. Ha pl.két szabad rés között (ciklikusan értve) i db foglalt rés van, akkor legalább(i + 2)/m a valószín¶sége, hogy a következ® sikeres beszúráskor ez a csomómég hosszabb lesz, és az is lehet, hogy közben összekapcsolódik egy másikcsomóval. Ez az egyszer¶ módszer csak akkor használható, ha a kulcsütközésvalószín¶sége elenyész®en kicsi.

12.5.4. Négyzetes próba

h(k, i) = (h1(k) + c1i+ c2i2) mod m (i ∈ 0..m− 1)

ahol h1 : U → 0..m−1 hasító függvény; c1, c2 ∈ R; c2 6= 0. A különböz® próbasorozatok nem kapcsolódnak össze, de itt is csak m db különböz® próba so-rozat van, az egyenletes hasításhoz szükséges m! db próba sorozathoz képest,hiszen ha két kulcsra h(k1, 0) = h(k2, 0), akkor az egész próba sorozatuk ittis megegyezik. Ezt a jelenséget másodlagos csomósodásnak nevezzük.

A négyzetes próba konstansainak megválasztása Annak érdekében,hogy a próba sorozat az egész táblát lefedje, a c1, c2 konstansokat körültekin-t®en kell megválasztani. Ha például a tábla m mérete kett® hatvány, akkorc1 = c2 = 1/2 jó választás. Ráadásul ilyenkor

h(k, i) =

(h1(k) +

i+ i2

2

)mod m (i ∈ 0..m− 1)

Ezért

(h(k, i+ 1)− h(k, i)) mod m =

((i+ 1) + (i+ 1)2

2− i+ i2

2

)mod m =

113

Page 114: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

(i+ 1) mod m

azazh(k, i+ 1) = (h(k, i) + i+ 1) mod m

12.1. Feladat. Készítsük el a fenti rekurzív képlet segítségével a négyzetespróba (c1 = c2 = 1/2) esetére a beszúrás, a keresés és a törlés struktogramjait!

12.5.5. Kett®s hasítás

h(k, i) = (h1(k) + ih2(k)) mod m (i ∈ 0..(m− 1))

ahol h1 : U → 0..(m−1) és h2 : U → 1..(m−1) hasító függvények. A próbasorozat pontosan akkor fedi le az egész hasító táblát, ha h2(k) és m relatívprímek. Ezt a legegyszer¶bb úgy biztosítani, ha a m kett® hatvány és h2(k)minden lehetséges kulcsra páratlan szám, vagy m prímszám. Például ha mprímszám (ami lehet®leg ne essen kett® hatvány közelébe) és m′ kicsit kisebb(mondjuk m′ = m− 1 vagy m′ = m− 2) akkor

h1(k) = k mod m

h2(k) = 1 + (k mod m′)

egy lehetséges választás.A kett®s hasításnál minden különböz® (h1(k), h2(k)) pároshoz különböz®

próbasorozat tartozik. Ezért itt Θ(m2) különböz® próbasorozat lehetséges. Akett®s hasítás, bár próbasorozatainak száma messze van az ideális m! számúpróbasorozattól, úgy t¶nik, hogy jól közelíti annak m¶ködését.

A kett®s hasítás m¶veleteinek szemléltetése: Mivelh(k, i) = (h1(k) + ih2(k)) mod m (i ∈ 0..(m−1)), azért h(k, 0) = h1(k) ésh(k, i + 1) = (h(k, i) + d) mod m, ahol d = h2(k). Az els® próba helyének(h1(k)) meghatározása után tehát mindig d-vel lépünk tovább, ciklikusan.

Legyen most m = 11 h1(k) = k mod 11 h2(k) = 1+(k mod 10).Az alábbi táblázat m¶veletek oszlopában, minden m¶veletnél az els®

karakter a m¶velet kódja, azaz i=insert, s=search, d=delete. Ezután jöna beszúrandó, vagy keresett, vagy törlend® adat kulcsa (ami értelemszer¶ensosem E vagy D). A táblázatban nem foglalkozunk a járulékos adatokkal,csak a kulcsokat jelöljük. Itt következik indexben a d = h2(k) érték, de csakakkor, ha ezt szükséges kiszámolni, azaz az aktuális próbasorozat hossza ≥ 2.Ezután jön maga a próbasorozat 〈〉 zárójelek között, és végül a m¶velet si-kerességének jelzése, ahol + = sikeres és x = sikertelen. A próbasorozat

114

Page 115: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

utolsó eleménél a megfelel® rés kulcsát is indexben odaírtuk, ezzel utalva am¶velet kimenetelének okára. Ha egy beszúrás közben törölt rést találtunk,az els® ilyen mellé indexben egy D bet¶t írtunk, utalva arra, hogy a beszúrásalgoritmusa megjegyzi az els® törölt rést, amit talál. (Ld. a 12.5.2. alfe-jezetet!) A táblázat els® 11 oszlopában az üres réseket egyszer¶en üresenhagytuk. Mindegyik foglalt résbe beírtuk a megfelel® kulcsot, míg a töröltréseket D bet¶vel jelöltük. Ebben a táblázatban a könnyebb érthet®ség ked-véért minden m¶velet után új sorba írtuk a táblának a m¶velet végrehajtásautáni tartalmát, kivéve a legutolsó keresést. (A zh-kon és a vizsgákon eléglesz akkor új sort nyitni, ha nemüres rés tartalma változik.)

0 1 2 3 4 5 6 7 8 9 10 m¶veleteki32〈10E〉 +

32 i40〈7E〉 +40 32 i37〈4E〉 +

37 40 32 i156〈4; 10; 5E〉 +37 15 40 32 i701〈4; 5; 6E〉 +37 15 70 40 32 s156〈4; 10; 515〉 +37 15 70 40 32 s1045〈5; 10; 4; 9E〉 x37 15 70 40 32 d156〈4; 10; 515〉 +37 D 70 40 32 s701〈4; 5; 670〉 +37 D 70 40 32 i701〈4; 5D; 670〉 x37 D 70 40 32 d37〈437〉 +D D 70 40 32 i1045〈5D; 10; 4; 9E〉 +D 104 70 40 32 s156〈4; 10; 5; 0E〉 x

12.2. Feladat. A kett®s hasítás programozása: Írja meg beszúrás, akeresés és a törlés struktogramjait, ahol x a beszúrandó adat, k a keresettkulcs, illetve a törlend® adat kulcsa.

A hasító tábla a Z[0..(m−1)], azaz hagyományosan (célszer¶en) nullátólindexeljük, és m a mérete, ahol m értékét a függvények Z.M-b®l nyerik ki.

Sikeres keresésnél a keresett kulcsú adat pozícióját adjuk vissza. A siker-telen keresést a −1 visszadásával jelezzük.

Sikeres beszúrásnál a beszúrás pozícióját adjuk vissza. Sikertelen beszúrás-nál két eset van. Ha nincs elég hely a táblában, azt a −(m+1) visszadásávaljelezzük. Ha a j index¶ résben megtaláljuk a táblában a beszúrandó adat kul-csát, azt a −(j + 1) visszaadásával jelezzük.

Vegyük észre, hogy a nyílt címzés¶ hasító tábla üresre inicializálásánakés az adott kulcsú foglalt rés törlésének struktogramja nem függ attól, hogy abeszúrást és a keresést milyen stratégiával hajtjuk végre. Sikeres törlésnél a

115

Page 116: Algoritmusok és adatszerkezetek I. el®adásjegyzetaszt.inf.elte.hu/~asvanyi/ad/ad1jegyzet.pdf · A T alapértelmezésben olyan ismert (de közelebbr®l meg nem nevezett) ... áltv

törölt adat pozícióját adjuk vissza. A sikertelen törlést a −1 visszadásávaljelezzük.

Megoldás: insert( Z:R[] ; x:R ):Z

k := x.k ; d := h2(k)

j := h1(k) ; i := 0

i < Z.M ∧ Z[j].k /∈ E,D

AAZ[j].k = k

return−(j + 1)

i+ +

j := (j + d) mod Z.M

AAi < Z.M

ide := j return −(Z.M + 1)

i < Z.M ∧ Z[j].k 6= E

AAZ[j].k = k

return−(j + 1)

i+ +

j := (j + d) mod Z.M

Z[ide] := x

return ide

search(Z:R[] ; k:U ):Z

i := 0 ; j := h1(k)

b := true ; d := h2(k)

b

AAZ[j].k = k

return j SKIP

i+ +

b := (Z[j].k 6= E∧ i < Z.M)

j := (j + d) mod Z.M

return −1

delete( Z:R[] ; k:U ):Z

j := search(Z, k)

AAj ≥ 0

Z[j].k := D SKIPreturn j

116