Algoritmusok és adatszerkezetek I. el adásjegyzet

Hasonló dokumentumok
Adatszerkezetek és algoritmusok

Algoritmusok és adatszerkezetek I. el adásjegyzet

Algoritmusok és adatszerkezetek I. el adásjegyzet

Algoritmusok és adatszerkezetek I. régebbi vizsgakérdések.

Nagyságrendek. Kiegészítő anyag az Algoritmuselmélet tárgyhoz. Friedl Katalin BME SZIT február 1.

Algoritmusok Tervezése. 6. Előadás Algoritmusok 101 Dr. Bécsi Tamás

Algoritmuselmélet 2. előadás

Függvények növekedési korlátainak jellemzése

A félév során előkerülő témakörök

Algoritmusok és adatszerkezetek gyakorlat 07

Adatszerkezetek és algoritmusok

Keresés és rendezés. A programozás alapjai I. Hálózati Rendszerek és Szolgáltatások Tanszék Farkas Balázs, Fiala Péter, Vitéz András, Zsóka Zoltán

Kupac adatszerkezet. A[i] bal fia A[2i] A[i] jobb fia A[2i + 1]

A számítástudomány alapjai. Katona Gyula Y. Számítástudományi és Információelméleti Tanszék Budapesti Műszaki és Gazdaságtudományi Egyetem

file:///d:/okt/ad/jegyzet/ad1/b+fa.html

Tartalom Keresés és rendezés. Vektoralgoritmusok. 1. fejezet. Keresés adatvektorban. A programozás alapjai I.

1. Alapfogalmak Algoritmus Számítási probléma Specifikáció Algoritmusok futási ideje

Rendezések. A rendezési probléma: Bemenet: Kimenet: n számot tartalmazó (a 1,a 2,,a n ) sorozat

Adatszerkezetek. Nevezetes algoritmusok (Keresések, rendezések)

Elemi adatszerkezetek

7. BINÁRIS FÁK 7.1. A bináris fa absztrakt adattípus 7.2. A bináris fa absztrakt adatszerkezet

B-fa. Felépítés, alapvető műveletek. Programozás II. előadás. Szénási Sándor.

Taylor-polinomok. 1. Alapfeladatok április Feladat: Írjuk fel az f(x) = e 2x függvény másodfokú Maclaurinpolinomját!

Amortizációs költségelemzés

A programozás alapjai 1 Rekurzió

Adatszerkezetek 7a. Dr. IványiPéter

1. ábra. Egy rekurzív preorder bejárás. Egy másik rekurzív preorder bejárás

Online algoritmusok. Algoritmusok és bonyolultságuk. Horváth Bálint március 30. Horváth Bálint Online algoritmusok március 30.

Programozás alapjai 9. előadás. Wagner György Általános Informatikai Tanszék

Edényrendezés. Futási idő: Tegyük fel, hogy m = n, ekkor: legjobb eset Θ(n), legrosszabb eset Θ(n 2 ), átlagos eset Θ(n).

1. A k-szerver probléma

A programozás alapjai előadás. [<struktúra változó azonosítók>] ; Dinamikus adatszerkezetek:

Algoritmusok és adatszerkezetek gyakorlat 06 Adatszerkezetek

1: Bevezetés: Internet, rétegmodell Alapok: aszimptótika, gráfok. HálózatokII, 2007

Programozás alapjai II. (7. ea) C++ Speciális adatszerkezetek. Tömbök. Kiegészítő anyag: speciális adatszerkezetek

Rendezések. Összehasonlító rendezések

22. GRÁFOK ÁBRÁZOLÁSA

Buborékrendezés: Hanoi Tornyai: Asszimptótikus fv.ek: Láncolt ábrázolás: For ciklussal:

Adatszerkezetek I. 7. előadás. (Horváth Gyula anyagai felhasználásával)

Speciális adatszerkezetek. Programozás alapjai II. (8. ea) C++ Tömbök. Tömbök/2. N dimenziós tömb. Nagyméretű ritka tömbök

Haladó rendezések. PPT 2007/2008 tavasz.

Függvények július 13. f(x) = 1 x+x 2 f() = 1 ()+() 2 f(f(x)) = 1 (1 x+x 2 )+(1 x+x 2 ) 2 Rendezés után kapjuk, hogy:

file:///d:/apa/okt/ad/jegyzet/ad1/b+fa.html

10. előadás Speciális többágú fák

Más szavakkal formálisan:, ahol olyan egész szám, hogy. Más szavakkal formálisan:, ahol olyan egész szám, hogy.

19. AZ ÖSSZEHASONLÍTÁSOS RENDEZÉSEK MŰVELETIGÉNYÉNEK ALSÓ KORLÁTJAI

Ellenőrző kérdések. 36. Ha t szintű indexet használunk, mennyi a keresési költség blokkműveletek számában mérve? (1 pont) log 2 (B(I (t) )) + t

15. tétel. Adatszerkezetek és algoritmusok vizsga Frissült: január 30.

Specifikáció. B logikai formula, a bemeneti feltétel, K logikai formula, a kimeneti feltétel, A az algoritmus, amelyre az állítás vonatkozik.

Számítógép hálózatok, osztott rendszerek 2009

Számláló rendezés. Példa

Feladat. Ternáris fa. Típusspecikáció. Reprezentáció. Absztrakt implementáció. Érdi Gerg EAF II. 4/3.

Programozás alapjai C nyelv 8. gyakorlat. Mutatók és címek (ism.) Indirekció (ism)

Programozás alapjai II. (7. ea) C++

Hierarchikus adatszerkezetek

Gyakori elemhalmazok kinyerése

Kiegészítő részelőadás 1. Az algoritmusok hatékonyságának mérése

Tömbök kezelése. Példa: Vonalkód ellenőrzőjegyének kiszámítása

Függvények határértéke, folytonossága

Általános algoritmustervezési módszerek

Kupac adatszerkezet. 1. ábra.

Mutatók és címek (ism.) Programozás alapjai C nyelv 8. gyakorlat. Indirekció (ism) Néhány dolog érthetőbb (ism.) Változók a memóriában

Információs Technológia

Függvények. Programozás alapjai C nyelv 7. gyakorlat. LNKO függvény. Függvények(2) LNKO függvény (2) LNKO függvény (3)

Programozás alapjai C nyelv 7. gyakorlat. Függvények. Függvények(2)

Adatszerkezetek 1. előadás

Módosítható Prioritási sor Binomiális kupaccal. Wednesday, March 21, 12

Bevezetés a programozásba I.

Specifikáció. B logikai formula, a bemeneti feltétel, K logikai formula, a kimeneti feltétel, A az algoritmus, amelyre az állítás vonatkozik.

Fák Témakörök. Fa definíciója. Rekurzív típusok, fa adatszerkezet Bináris keresőfa, bejárások Bináris keresőfa, módosítás B-fa

A programozás alapjai előadás. Amiről szólesz: A tárgy címe: A programozás alapjai

Algoritmuselmélet. 2-3 fák. Katona Gyula Y. Számítástudományi és Információelméleti Tanszék Budapesti Műszaki és Gazdaságtudományi Egyetem. 8.

Összetett programozási tételek Rendezések Keresések PT egymásra építése. 10. előadás. Programozás-elmélet. Programozás-elmélet 10.

Ugrólisták. RSL Insert Example. insert(22) with 3 flips. Runtime?

Algoritmusok és adatszerkezetek

Gráfok, definíciók. Gráfok ábrázolása. Az adott probléma megoldásához ténylegesen mely műveletek szükségesek. Ábrázolások. Példa:

Algoritmusok és adatszerkezetek II. régebbi vizsgakérdések.

26. MINIMÁLIS KÖLTSÉGŰ UTAK MINDEN CSÚCSPÁRRA

Dr. Schuster György február / 32

Programozási módszertan. Függvények rekurzív megadása "Oszd meg és uralkodj" elv, helyettesítő módszer, rekurziós fa módszer, mester módszer

Deníciók és tételek a beugró vizsgára

Web-programozó Web-programozó

Tartalomjegyzék. Köszönetnyilvánítás. 1. Az alapok 1

Tuesday, March 6, 12. Hasító táblázatok

mul : S T N 1 ha t S mul(s, t) := 0 egyébként Keresés Ezt az eljárást a publikus m veletek lenti megvalósításánál használjuk.

Adatszerkezetek Adatszerkezet fogalma. Az értékhalmaz struktúrája

Gyakorló feladatok ZH-ra

Bánsághi Anna 2014 Bánsághi Anna 1 of 68

15. A VERSENYRENDEZÉS

5. SOR. Üres: S Sorba: S E S Sorból: S S E Első: S E

Érdekes informatika feladatok

Adatbázisok. 8. gyakorlat. SQL: CREATE TABLE, aktualizálás (INSERT, UPDATE, DELETE), SELECT október október 26. Adatbázisok 1 / 17

Algoritmusok, adatszerkezetek, objektumok

Skalárszorzat, norma, szög, távolság. Dr. Takách Géza NyME FMK Informatikai Intézet takach/ 2005.

Algoritmizálás. Horváth Gyula Szegedi Tudományegyetem Természettudományi és Informatikai Kar

1. Jelölje meg az összes igaz állítást a következők közül!

8. gyakorlat Pointerek, dinamikus memóriakezelés

21. Adatszerkezetek Az adattípus absztrakciós szintjei Absztrakt adattípus (ADT) Absztrakt adatszerkezet (ADS) Egyszerű adattípusok Tömbök

Adatszerkezetek 1. Dr. Iványi Péter

Adatszerkezetek 2. Dr. Iványi Péter

Átírás:

Algoritmusok és adatszerkezetek I. el adásjegyzet Ásványi Tibor asvanyi@inf.elte.hu 017. január. 1. Bevezetés Az itt következ el adásjegyzetekben a láncolt listákról, a rendezésekr l és a programok aszimptotikus m veletigényér l szóló fejezetek még nem teljesek; egyenl re csak a bináris és általános fákkal kapcsolatos részeket dolgoztam ki részletesen, illetve a B+ fák témakörben angol nyelv egyetemi tananyag fordítását bocsátom rendelkezésükre. Az el adásokon tárgyalt programok struktogramjait igyekeztem minden esetben megadni, a másolási hibák kiküszöbölése érdekében. Ebben a tananyagban sajnos még egyáltalán nincsenek a megértést segít ábrák. Ezek b ségesen megtalálhatók az ajánlott segédanyagokban. A vizsgára való készülésben els sorban az el adásokon és a gyakorlatokon készített jegyzeteikre támaszkodhatnak. További ajánlott források: Hivatkozások [1] Ásványi Tibor, Algoritmusok és adatszerkezetek I. el adásjegyzet (016) http://aszt.inf.elte.hu/ asvanyi/ad/ad1jegyzet.pdf [] Ásványi Tibor, AVL tree balancing rules (016) http://aszt.inf.elte.hu/ asvanyi/ad/avl-tree_balancing.pdf [3] Ásványi Tibor, Algoritmusok I. gyakorló feladatok (015) http://aszt.inf.elte.hu/ asvanyi/ad/ad1feladatok.pdf [4] Fekete István, Algoritmusok jegyzet http://people.inf.elte.hu/fekete/algoritmusok_jegyzet/ 1

[5] Fekete István, Algoritmusok jegyzet (régi) http://aszt.inf.elte.hu/ asvanyi/ad/feketeistvan/ [6] Carl Burch, Ásványi Tibor, B+ fák http://aszt.inf.elte.hu/ asvanyi/ad/b+ fa.pdf [7] Cormen, T.H., Leiserson, C.E., Rivest, R.L., Stein, C., magyarul: Új Algortimusok, Scolar Kiadó, Budapest, 003. ISBN: 963 9193 90 9 angolul: Introduction to Algorithms (Third Edititon), The MIT Press, 009. [8] Wirth, N., Algorithms and Data Structures, Prentice-Hall Inc., 1976, 1985, 004. magyarul: Algoritmusok + Adatstruktúrák = Programok, M szaki Könyvkiadó, Budapest, 198. ISBN 963 10 3858 0 [9] Weiss, Mark Allen, Data Structures and Algorithm Analysis, Addison-Wesley, 1995, 1997, 007, 01, 013. Saját jegyzeteiken kívül els sorban az ebben a jegyzetben ([1] a [] kiegészítéssel), illetve az itt hivatkozott helyeken [4, 5, 6, 7] leírtakra támaszkodhatnak. Wirth [8] és Weiss [9] klasszikus munkáinak megfelel fejezetei pedig értékes segítséget nyújthatnak a mélyebb megértéshez. Ennek a jegyzetnek a *-gal jelölt alfejezetei szintén a mélyebb megértést szolgálják, azaz nem részei a vizsga anyagának. A vizsgákon az elméleti kérdések egy-egy tétel bizonyos részleteire vonatkoznak. Lesznek még megoldandó feladatok, amiket [3] alapján állítok össze.

. Tematika Minden tételhez: Egy algoritmus, program, m velet bemutatásának mindig része a m veletigény elemzése. Hivatkozások: például a [4] 1, 14, 18 jelentése: a [4] sorszámú szakirodalom adott fejezetei. 1. Függvények aszimptotikus viselkedése (O, o, Ω, ω, Θ). Programok m - veletigénye (futási id nagyságrendje: T (n), mt (n), AT (n), M T (n)), példák: beszúró rendezés (insertion sort) és összefésül (összefuttató) rendezés (merge sort) ([1]; [4] 1, 14, 18; [5]; [7] 1-3.). Elemi, lineáris adatszerkezetek: tömbök, láncolt listák, láncolt listák típusai, listakezelés. ([1]; [4] 3, 6; [7] 10; [8] 4.1-4.3) 3. Elemi adattípusok: vermek, sorok ([1]; [4] 3-4; [7] 10.1), megvalósításuk tömbös és láncolt reprezentációk esetén (láncolt listás megvalósítás a gyakorlatokon). Vermek felhasználása: kifejezések lengyel formára hozása, lengyel forma kiértékelése (ld. gyakorlat). 4. Els bbségi (prioritásos) sorok megvalósítása rendezetlen illetve rendezett tömbbel [1], rendezetlen, illetve rendezett listával (HF), továbbá kupaccal ([1]; [4] 8; [7] 6.5). A reprezentációk és az implementációk összehasonlítása a m veletek hatékonysága szempontjából. 5. Fák, bináris fák, bejárások, láncolt reprezentáció, példák ([1]; [4] 7, 11..3; [7] 10.4, 1.1). 6. Speciális bináris fák, aritmetikai ábrázolás, kupac, kupac m veletei, kupacrendezés (heapsort) ([1]; [4] 8.3-8.5, 16; [7] 6). 7. Véletlen építés bináris keres fák és m veleteik ([1]; [4] 11; [7] 1; [8] 4.4). 8. AVL fák és m veleteik: kiegyensúlyozási sémák, programok. Az AVL fa magassága ([1, ], [4] 1; [5]; [8] 4.5). 9. Általános fák, bináris láncolt reprezentáció, bejárások ([1]; [8] 4.7 bevezetése). Egyéb reprezentációk? (HF) 10. B+ fák és m veleteik [6]. 11. Gyorsrendezés (quicksort) ([1]; [7] 7; [5]; [4] 17). 1. A beszúró, összefésül, kupac és gyors rendezés összehasonlítása. Az összehasonlító rendezések alsókorlát-elemzése ([4] 19; [7] 8.1). 3

3. Jelölések N = {0; 1; ; 3;...} a természetes számok halmaza. Z = {... 3;, 1; 0; 1; ; 3;...} az egész számok halmaza. R a valós számok halmaza. P a pozitív { valós számok halmaza. log n ha n > 0 lg n = 0 ha n = 0 fele(n) = n, ahol n N A képletekben és a struktogramokban alapértelmezésben (tehát ha a környezetb l nem következik más) az i, j, k, l, m, n, I, J, K, M, N bet k egész számokat (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 teljes rendezés (az =,, <, >,, összehasonlításokkal) és értékadás (pl. x := y) van értelmezve. Az x, y, z alapértelmezésben T típusú változókat jelölnek. A tömböket pl. így jelöljük: A[1..n], B[i..j], C[k..m]:T. Az alapértelmezett elemtípus a T, amit az A és a B tömbnél nem adtunk meg, míg a C tömbnél megadtuk. 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ási kö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. Ha a végletekig kívánunk optimalizálni, akkor bizonyos esetekben elhagyhatók. 3.1. A struktogramok paraméterlistái, érték és referencia módú paraméterek A struktogramokhoz mindig tartozik egy eljárásnév és általában egy paraméterlista is (ami esetleg üres, de a () zárójelpárt ott is, és a megfelel eljáráshí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. Ha egy paraméterlistával ellátott struktogramban olyan változónév szerepel, ami a paraméterlistán nem szerepel, akkor ez alapértelmezésben a struktogrammal leírt eljárás lokális változója. 4

A skalár típusú 1 paramétereket alapértelmezésben érték szerint vesszük át. A skalár típusú paraméterek referencia módú paraméterek is lehetnek, de akkor ezt a formális paraméter listán a paraméter neve el tt egy & jellel jelölni kell (pl. swap(&x, &y)). 3 A formális paraméter listán a felesleges &- prexek hibának tekintend k, mert a referencia módú skalár paraméterek kezelése az eljárás futása során a legtöbb implementációban lassúbb, mint az érték módú paramétereké. Az aktuális paraméter listán nem jelöljük külön a referencia módú paramétereket 4, mert egyrészt a formális paraméter listáról kiderül, hogy egy tetsz leges paraméter érték vagy referencia módú-e, másrészt az &-prex az aktuális paraméternél teljesen mást jelent (tudniillik, hogy a jelölt adatra mutató pointert szeretnénk a hívott eljárásnak az ott jelölt paramétermód szerint átadni). Ha az eljárásokra szövegben hivatkozunk, a paraméterek 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ár 5 típusú paraméterek csak referencia módú paraméterek lehetnek. (Pl. a tömbö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étermódot, hiszen az egyértelm. 3.. Tömb típusú paraméterek a struktogramokban Ha pl. az A[1..n] kifejezés egy struktogram formális paraméter listáján, vagy egy eljáráshívás aktuális paraméter listáján szerepel, akkor az egy progra- 1 A skalár típusok az egyszer típusok: a szám, a pointer és a felsorolás (pl. a logikai és a karakter) típusok. Az érték módú paraméterek 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 egy lokális változó, és ha értéket kap, ennek nincs hatása az aktuális paraméterre. 3 A referencia módú paraméterek esetén, az eljáráshíváskor az aktuális paraméter összekapcsolódik a megfelel formális paraméterrel, egészen a hívott eljárás futásának végéig, ami azt jelenti, hogy bármelyik megváltozik, 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 például a fenti swap(&x, &y) eljárás törzse {z:=x; x:=y; y:=z;}, akkor a swap(a, b) eljáráshívás hatására az a és b változók értéke megcserél dik. Ha az eljárásfej swap(x, &y) lenne, az eljáráshívás hatása b := a lenne, ha pedig az eljárásfej swap(x, y) lenne, az eljáráshívás logikailag ekvivalens lenne a SKIP utasítással. 4 Pl. a swap(&x, &y) eljárás egy lehetséges meghívása: swap(a[i], A[j]). 5 A 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 kulcsszavakkal deniált osztályok. 5

mozási nyelven két paramétert jelentene, az A-t és az n-et, azzal a plusz információval, hogy az A egy 1-t l n-ig indexelt vektor. Ha pl. az A[m..n] kifejezés egy struktogram formális paraméter listáján, vagy egy eljáráshívás aktuális paraméter listáján szerepel, akkor az egy programozási nyelven három paramétert jelentene, az A-t, az m-et és az n-et, azzal a plusz információval, hogy az A egy m-t l n-ig indexelt vektor. Ha tehát egy struktogram egy formális paramétere pl. az A[1..n], akkor a neki megfelel eljáráshívásban a megfelel aktuális paraméter lehet pl. B[1..m], de nem lehet pl. C[k..m], hiszen az el bbi kett két, az utóbbi pedig három paramétert jelöl, és nem lehet az aktuális paraméter pl. C[k..10] sem, mert ez ugyan csak két paramétert jelöl, de pl. a k nem feleltethet meg az n-nek, és nem adható értékül az 1-nek sem. 4. Algoritmusok Az algoritmus egy jól deniált kiszámítási eljárás, amely valamely adatok (bemenet vagy input) felhasználásával újabbakat (kimenet, eredmény vagy output) állít el. (Gondoljunk pl. két egész szám legnagyobb közös osztójára [lnko(x, y) függvény], a lineáris keresésre a maximum keresésre, az összegzésre stb.) Az algoritmus bemenete adott el feltételnek kell eleget tegyen. (Az lnko(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. Az utófeltétel az algoritmus bemenete és a kimenete közt elvárt kapcsolatot írja le. Maga az algoritmus számítási lépésekb l áll, amiket általában szekvenciák, elágazások, ciklusok, eljárás- és függvényhívások segítségével, valamely pszeudo-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. Így témánk egyik klasszikusa a rendezési feladat. Most megadjuk, a rendez algoritmusok bemenetét és kimenetét milyen el - és utófeltétel páros, ún. feladat speciká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ám vagy egy sztring.) Bemenet: n darab kulcs a 1, a,..., a n sorozata. Kimenet: A bemenet egy olyan a p1, a p,..., a pn permutációja, amelyre a p1 a p... a pn. A fenti feladat nagyon egyszer, ti. könnyen érthet, hatékony megoldására viszont kinomult algoritmusokat (és hozzájuk kapcsolódó adatszerkezeteket) dolgoztak ki, így e tárgynak is a szakirodalomban jól bevált bevezetése lett. 6

4.1. Az A[1..n] vektor monoton növekv rendezése Egy sorozatot legegyszer bben egy A[k..u] vektorban tárolhatunk, ahol k a sorozat kezd indexe u pedig az utolsó elem indexe. Az elemtípust általában nem adjuk meg, hangsúlyozva, hogy az algoritmus általános, bár a rendezéseknél feltesszük, hogy a vektor elemtípusára teljes rendezés deniált és az értékadó utasítás is értelmezve van. Ha mégis feltüntetjük a T elemtípust, akkor az A[k..u] vektort A[k..u] : T alakban adjuk meg. 4.1.1. Beszúró rendezés (Insertion sort) Ha valaki semmit sem tud a rendezésekr l, és megkapja azt a feladatot, hogy rakjon 10-30 dolgozatot nevek szerint sorba, jó eséllyel ösztönösen ezt az algoritmust 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 a megfelel helyre stb. Ha a rendezést egy számsorra kell alkalmaznunk, pl. a 5, 4,, 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 rendezett részben: 5 4,, 8, 3. Ezután beszúrjuk a rendezetlen szakasz els elemét a rendezett részbe a megfelel helyre, és ezt ismételgetjük, amíg a sorozat rendezetlen vége el nem fogy: 5 4,, 8, 3 4, 5, 8, 3, 4, 5 8, 3, 4, 5, 8 3, 3, 4, 5, 8, 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 a beszúrást úgy végezzük el, hogy a rendezetlen szakasz els elemét (legyen x) ö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 x helyére csúsztatjuk. Úgy képzelhetjük, hogy u régi helyén most egy lyuk keletkezett. 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 balra mozog. Ha a lyuk a helyére ér, azaz x beleillik, akkor bele is tesszük. (Ld. az alábbi struktogramot!) Tekintsük például a, 4, 5, 8, 3 tömböt, ami a 8-ig rendezett, és már csak a 3-at kell rendezetten beszúrni. El ször úgy találjuk, hogy 8 > 3, így a 3-at kivesszük x-be, majd a lyukat (jelölje _) a helyére mozgatjuk, és végül beletesszük a 3-at:, 4, 5, 8, 3, 4, 5, 8, _, x = 3, 4, 5, _, 8, x = 3, 4, _, 5, 8, x = 3, _, 4, 5, 8, x = 3, 3, 4, 5, 8. 7

beszúró_rendezés(a[1..n]) i :=..n A[i 1] > A[i] x := A[i] A[i] := A[i 1] j := i j > 0 A[j] > x SKIP A[j + 1] := A[j] j := j 1 A[j + 1] := x A fenti eljárás az A[1..n] vektort rendezi monoton növekv en az el bb ismertetett egyszer beszúró rendezéssel. A f ciklus invariánsa: i (n + 1) A[1..n] 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 n <, akkor az A[1..n] vektor üres, vagy egyelem, ezért rendezett, és a program f ciklusa egyszer sem fut le. Ha n, a rendezés meghívásakor csak annyit tudhatunk, hogy A[1..1] rendezett, azaz i = -re fennáll az invariáns. A f ciklus magja ezután mindig A[i]-t szúrja be a vektor rendezett szakaszába, i-t eggyel növeli és tartja az invariánst. Mikor tehát i eléri az n + 1 értéket, már a teljes A[1..n] vektor rendezett, és ekkor be is fejez dik az eljárás. 4.1.. 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 hatékony. Hatékonyság alatt az eljárás er forrás igényét, azaz futási idejét és tárigényét értjük. 6 Mivel ez az egyszer rendezés a rendezend vektoron kívül csak néhány segédváltozót igényel, extra tárigénye minimális. Így els sorban a futási idejére lehetünk kíváncsiak. Ezzel kapcsolatos nehézség, hogy nem ismerjük a leend programozási környezetet: sem a programozási nyelvet, amiben kódolni fogják, sem a fordítóprogramot vagy interpretert, sem a leend futtatási környezetet, sem a számítógépet, amin futni fog, így nyilván a futási idejét sem tudjuk meghatározni. 6 Nem különböztetjük meg most a különféle hardver komponenseket, hiszen ezeket az algoritmus szintjén nem is ismerjük. 8

Meghatározhatjuk, vagy legalább becslés(eke)t adhatunk viszont arra, hogy adott n méret input esetén az 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. Megkülönböztetjük a legrosszabb vagy maximális MT S (n), a várható vagy átlagos AT S (n) és a legjobb vagy minimális mt S (n) eseteket. A valódi maximális, átlagos és minimális futási id k általában ezekkel arányosak lesznek. Ha MT S (n) = mt S (n), akkor deníció szerint T S (n) a minden esetre vonatkozó m veletigény (tehát az eljáráshívások és a ciklusiterációk számának összege), azaz T S (n) = MT S (n) = AT S (n) = mt S (n) A továbbiakban, a programok futási idejével kapcsolatos számításoknál, a m veletigény és a futási id, valamint a költség kifejezéseket szinonímákként fogjuk használni, és ezek alatt az eljáráshívások és a ciklusiterációk összegére vonatkozó MT S (n), AT S (n), mt S (n) és ha létezik, T S (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 (insertion sort)! A rendezés során egyetlen eljáráshívás hajtódik végre, és ez a beszúró_rendezés(a[1..n]) eljárás hívása. Az eljárás f ciklusa minden esetben pontosan (n 1) -szer fut le. El ször adjunk becslést a beszúró rendezés minimális futási idejére, amit jelöljünk mt IS (n)-nel, ahol n a rendezend vektor mérete, általában a kérdéses kód által manipulált adatszerkezet mérete. 7 Lehet, hogy a bels ciklus egyet sem iterál, pl. ha a f ciklus mindig a jobboldali ágon fut le, mert A[1..n] eleve monoton növekv en rendezett. Ezért mt IS (n) = 1 + (n 1) = n (Egy eljáráshívás + a küls ciklus (n 1) iterációja.) Most adjunk becslést (MT IS (n)) a beszúró rendezés maximális futási idejére! Világos, hogy az algoritmus ciklusai akkor iterálnak 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 a vektor szigorúan monoton csökken en rendezett.) Végrehajtódik tehát egy eljáráshívás + a küls ciklus (n 1) iterációja, amihez a küls ciklus adott i értékkel való iterációjakor a bels ciklus maximum (i )-ször iterál. Mivel az i, -t l n-ig fut, a bels ciklus összesen legfeljebb n i= (i ) iterációt hajt végre. Innét MT IS (n) = 1 + (n 1) + n n (i ) = n + j = n + i= j=0 (n 1) (n ) MT IS (n) = 1 n 1 n + 1 7 Az IS a rendezés angol nevének (Insertion Sort) a rövidítése. 9

Látható, hogy a minimális futási id becslése az A[1..n] input vektor méreté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ábbiakban ezeket a következ képpen fejezzük ki: mt IS (n) Θ(n), MT IS (n) Θ(n ). A Θ(n) (T heta(n)) függvényosztály ugyanis tartalmazza az n összes, pozitív együtthatós lineáris függvényét, Θ(n ) pedig az n összes, pozitív együtthatós má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ály pontos denícióját a 7. fejezetben fogjuk megadni.) Mint a maximális futási id re vonatkozó példából látható, a Θ jelölés szerepe, hogy elhanyagolja egy polinom jelleg függvényben (1) a kisebb nagyságrend tagokat, valamint () a f tag pozitív együtthatóját. Az el bbi azért jogos, mert a futási id általában nagyméret inputoknál igazán érdekes, hiszen tipikusan ilyenkor lassulhat le egy egyébként logikailag helyes program. Elég nagy n-ekre viszont pl. az a n + b n + c polinomban a n mellett b n és c elhanyagolható. A f tag pozitív együtthatóját pedig egyrészt azért hanyagolhatjuk 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észt pedig azért, mert ha az a f(n) alakú f tag értéke n -et végtelenül növelve maga is a végtelenhez tart (ahogy az lenni szokott), elég nagy n-ekre az a konstans 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 rendez: Nagyságrendileg a lineáris m veletigénynél gyorsabb rendezés elvileg is lehetetlen, 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övekszik, 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, ami másodpercenként 10 9 elemi m veletet tud elvégezni. Jelölje most mt (n) az algoritmus által elvégzend elemi m veletek minimális, míg M T (n) a maximális számát! Vegyük gyelembe, hogy mt IS (n) = n, ami közelít leg a küls ciklus iterációinak száma, és a küls ciklus minden iterációja legalább 8 elemi m veletet jelent; továbbá, hogy MT IS (n) (1/) n, ami közelít leg a bels ciklus iterációinak száma, és itt minden iteráció legalább 1 elemi m veletet jelent. Innét a mt (n) 8 n és a MT (n) 6 n képletekkel számolva a következ táblázathoz jutunk: 10

n mt IS (n) in secs MT IS (n) in time 1000 8000 4 10 6 6 10 6 0.003 sec 10 6 8 10 6 0.004 6 10 1 50 min 10 7 8 10 7 0.04 6 10 14 3.5 days 10 8 8 10 8 0.4 6 10 16 347 days 10 9 8 10 9 4 6 10 18 95 years Világos, hogy már tízmillió rekord rendezésére is gyakorlatilag használhatatlan az algoritmusunk. (Az implementációs problémákat most gyelmen kívül hagytuk.) Látjuk azt is, hogy hatalmas a különbség a legjobb és a legrosszabb 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ául az inputok monoton növekv en el rendezettek, ami alatt azt értjük, hogy az input sorozat elemeinek a rendezés utáni helyükt l való távolsága általában egy 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üggetlen s konstanssal becsülhet felülr l, az algoritmus m veletigénye lineáris, azaz Θ(n) marad, mivel a bels ciklus legfeljebb (k + s) n -szer fut le. Ha viszont a bemenet monoton csökken en el rendezett, az algoritmus m 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 monoton növekv en el rendezett vektort kapunk.) Véletlenített input sorozat esetén, egy-egy újabb elemnek a sorozat már rendezett kezd szakaszába való beszúrásakor, átlagosan a rendezett szakaszban lév elemek fele lesz nagyobb a beszúrandó elemnél. A rendezés várható m veletigénye ilyenkor tehát: AT IS (n) 1 + (n 1) + n ( ) i = n + 1 n j = i= j=0 = n + 1 (n 1) (n ) = 1 4 n + 1 4 n + 1 Nagy n -ekre tehát AT IS (n) (1/4) n. Ez körülbelül a fele a maximális futási id nek, ami a rendezend adatok milliós nagyságrendje esetén már így is nagyon hosszú futási id ket jelent. Összegezve az eredményeinket: mt IS (n) Θ(n) AT IS (n), MT IS (n) Θ(n ) 11

Ehhez hozzátehetjük, hogy el rendezett inputok esetén (ami a programozási gyakorlatban egyáltalán nem ritka) a beszúró rendezés segítségével lineáris id ben tudunk rendezni, ami azt jelenti, hogy erre a feladatosztályra nagyságrendileg, és nem túl nagy k és s konstansok esetén valóságosan is az optimális megoldás a beszúró rendezés. 4.1.3. A futási id kre vonatkozó becslések magyarázata* Jelölje most a beszúró_rendezés(a[1..n]) eljárás tényleges maximális és minimális futási idejét sorban MT (n) és mt (n)! Világos, hogy a rendezés akkor fut le a leggyorsabban, ha a f ciklus minden elemet a végs helyén talál, azaz mindig a jobboldali ágon fut le. (Ez akkor áll el, ha a vektor már eleve monoton növekv en rendezett.) Legyen a a f ciklus jobboldali ága egyszeri végrehajtásának m veletigénye, b pedig az eljárás meghívásával, a f ciklus el készítésével és befejezésével, valamint az eljárásból való visszatéréssel kapcsolatos futási id k összege! Ekkor a és b nyilván pozitív konstansok, és mt (n) = a (n 1) + b. Legyen most p = min(a, b) és P = max(a, b); ekkor 0 < p P, és p (n 1) + p mt (n) = a (n 1) + b P (n 1) + P, ahonnét p n mt (n) P n, azaz p mt IS (n) mt (n) P mt IS (n) Most adjunk becslést a beszúró rendezés maximális futási idejére, (M T (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 a vektor szigorúan monoton csökken en rendezett.) Legyen most a 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, de hozzászámolva a bels ciklusból való kilépés futási idejét (amibe beleértjük a 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 n d (i ) = b + c (n 1) + d j = i= j=0 (n 1) (n ) = b + c (n 1) + d Legyen most q = min(b, c, d) és Q = max(b, c, d); ekkor 0 < q Q, és q + q (n 1) + q (n 1) (n ) MT (n) Q + Q (n 1) + Q (n 1) (n ) 1

q (n + (n 1) (n ) ) MT (n) Q (n + (n 1) (n ) ), azaz q MT IS (n) mt (n) Q MT IS (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ív konstansszorosaival alulról és felülr l becsülhet, azaz, pozitív konstans szorzótól eltekintve ugyanolyan nagyságrend. Könny meggondolni, hogy ez a megállapítás tetsz leges program minimális, átlagos és maximális futási idejeire is általánosítható. (Ezt azonban már az Olvasóra bízzuk.) 4.1.4. Összefésül rendezés (mergesort) Az összefésül rendezés (mergesort, rövidítve M S) nagy elemszámú sorozatokat is viszonylag gyorsan rendez. Ráadásul a legjobb és a legrosszabb eset között nem mutatkozik nagy eltérés. Els megközelítésben azt mondhatjuk, 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. MT MS (n), mt MS (n) Θ(n lg n) Ez alatt, a Θ jelölés fogalmát tovább tágítva, azt értjük, hogy úgy a maximális, mint a minimális futási id alulról és felülr l is becsülhet egy-egy a n lg n + δ(n) alakú függvénnyel, ahol a > 0 konstans, és az n -et növelve tetsz legesen közel kerül nullához, azaz nagy n -ekre az n lg n -hez képest a δ(n) értéke elhanyagolható. Kicsit formálisabban fogalmazva: a δ(n) n lg n Tegyük fel, hogy f, g : N R függvények; f aszimptotikusan nemnegatív (azaz valamely küszöbszámtól nemnegatív), g pedig aszimptotikusan pozitív (azaz valamely küszöbszámtól pozitív) függvény. Ebben az esetben f Θ(g) akkor és csak akkor teljesül 8, ha vannak olyan c, d > 0 konstansok és ϕ, ψ : N R függvények, hogy n N-re: ϕ(n) c g(n) + ϕ(n) f(n) d g(n) + ψ(n) lim n g(n) = 0 lim ψ(n) n g(n) = 0 8 Ez az állítás valójában a deníció egyik fontos következménye. Ld. b vebben a 7. fejezetben! 13

összefésül _rendezés(a[1..n]) B[0..( n 1)] létrehozása öfrend(a[1..n], B) B[] törlése, ha ez nem automatikus öfrend(a[u..v], B[]) u < v k := u+v 1 öfrend(a[u..k], B) öfrend(a[(k + 1)..v], B) összefésül(a[u..v], k, B) SKIP összefésül(a[u..v], k, B[]) // A[u..k] és A[(k + 1)..v] rendezett összefésülése A[u..v]-be A[m] := A[i] i := i + 1 i := u..k B[i u] := A[i] m := u i := k + 1 j := 0 vb := k u i v j vb A[i] < B[j] m := m + 1 j vb A[m] := B[j] m := m + 1 j := j + 1 A[m] := B[j] j := j + 1 El ször az összefésül(a[u..v], k, B[]) eljárás m veletigényét határozzuk meg. Bevezetjük az n = v u + 1 jelölést. mt merge (n) az eljárás minimális, MT merge (n) a maximális m veletigénye, amiket most is az eljáráshívások + a ciklusiterációk számával közelítünk. Most csak 1 eljáráshívás van + az 1. ciklus n/ iterációja + a. és 3. ciklus iterációi: a B vektorban lév n/ elemet biztosan vissszamásoljuk az A-ba, ami legalább n/ iteráció. Ha viszont a. és a 3. ciklus mindegyik elemet átmásolja, az összesen a maximális n iteráció. Innét mt merge (n) 1 + n + n n + n = n, valamint 14

MT merge (n) 1 + n + n n + n n 1, ahonnét n mt merge (n) MT merge (n) n 1. (Innét mt merge (n), MT merge (n) Θ(n) adódik.) Most a rekurzív eljárás, az öfrend(a[u..v], B[]) m veletigényét határozzuk meg. Jelölje sorban mt ms (n) és MT ms (n) a minimális és a maximális m - veletigényét! Kezdjük az MT ms (n) fels becslésével, majd folytatjuk az mt ms (n) alsó becslésével. Belátjuk, hogy nagyságrendben MT ms (n) legfeljebb n lg n -nel arányos, mt ms (n) pedig legalább n lg n -nel arányos, ahonnét adódik, hogy mindkett, és végs soron az egész rendezés m veletigénye is Θ(n lg n) nagyságrend. MT ms (n) fels becsléséhez el ször a rekurzió legnagyobb mélységét számoljuk ki. Világos, hogy a rekurzív hívások egy bináris fát alkotnak (ld. a 8. fejezet bevezet részét). A 0. szinten van a legküls (1 = 0 db.) rekurzív hívás, ami a teljes, n hosszú tömbre vonatkozik. Feltéve, hogy n, az 1. szinten = 1 rekurzív hívás van, amik n/, illetve n/ hosszú tömbökre vonatkoznak. Feltéve, hogy n 4, a. szinten 4 = rekurzív hívás van, amik sorban n/ /, n/ /, n/ /, illetve n/ / hosszú tömbökre vonatkoznak. Feltéve, hogy n i, az i-edik szinten i rekurzív hívás van, és mindegyik közelít leg n/ i hosszú tömbökre vonatkozik. A továbbiakban feltesszük, hogy n > 1 (hiszen nagy n-ekre érdekes a m veletigény). Az M = lg n jelöléssel M 1 < n M. Deniáljuk a F ele(n) = n/ függvényt! Ekkor i 0..M-re, az i. rekurziós szinten a leghosszabb részvektor, amire a rekurzív eljárást meghívjuk, F ele i (n) hosszú, és M i 1 < F ele i (n) M i. Innét az M. rekurziós szinten lesz a leghoszabb részvektor hossza 1, és legkés bb itt minden ágon leáll a rekurzió. Most az i. rekurziós szinten (ahol i 0..M) végrehajtott, legfeljebb i rekurzív öfrend hívás i. szintre vonatkozó m veletigényeinek összegére adunk fels becslést. Az i. szinten végrehajtott tetsz leges öfrend hívásnak az i. szintre vonatkozó m veletigénye alatt az eljáráshívás végrehajtását értjük a benne szerepl két rekurzív hívás nélkül, mert azokat már az (i + 1). rekurziós szint m veletigényénél vesszük gyelembe. Az i. szinten végrehajtott j. öfrend hívásban az A vektor aktuális részvektorának hosszát l i,j -vel jelölve, l i,j > 1 esetén a rekurzív eljárás bal ága hajtódik végre, és MT merge (l i,j ) l i,j 1, ahonnét az aktuális öfrend hívásnak az i. rekurziós szintre vonatkozó m veletigényére MT ms(i) (l i,j ) l i,j adódik. Ez a fels becslés nyilván l i,j = 1 esetén is igaz, hiszen ekkor az öfrend eljárás jobboldali ága hajtódik végre, és MT ms(i) (l i,j ) = 1. Az 15

i. szinten végrehajtott öfrend hívások számát jelölje h i (h i i )! Az összes, az i. szinten végrehajtott öfrend hívást tekintve, a teljes m - veletigény MT ms(i) (n) h i j=1 l i,j = h i j=1 l i,j n, hiszen az i. szinten az l i,j hosszú részvektorok az eredeti A[1..n] vektor diszjunkt szakaszait képezik. Mivel 0-tól M = lg n -ig M = lg n + 1 rekurziós szint van, MT ms (n) n( lg n + 1) n((lg n) + ) = n lg n + 4n, azaz MT ms (n) n lg n + 4n Az mt ms (n) alsó becsléshez csak a rendezett összefésülések m veletigényei összegének alsó becslését vesszük gyelembe. Ehhez meghatározzuk, hogy a rekurzív hívásokban milyen mélységig lesz az adott i. szinten minden rekurzív hívásban l i,j > 1, mert ezeken a szinteken minden rekurzív hívásban meghívódik az összefésülés, és h i j=1 l i,j = n, így az összefésülések összes m veletigényére az i. szinten mt merge(i) (n) h i j=1 l i,j = n, ahonnét mt ms(i) (n) mt merge(i) (n) n adódik. A fenti kritikus mélység meghatározásához, az m = lg n jelöléssel m+1 > n m. A fele(n) = n/ függvénnyel kapjuk, hogy i 0..m -re m+1 i > fele i (n) m i, ahol fele i (n) az l i,j részvektor-hosszok minimuma. Ebb l > fele m 1 (n) ; továbbá > fele m (n) 1, azaz fele m (n) = 1. Innét adódik, hogy i 0..(m 1)-re az i. szinten minden rekurzív hívásban legalább hosszú a rendezend részvektor, és így mt ms(i) (n) n, azaz mt ms (n) nm = n lg n n((lg n) 1) = n lg n n. Ahonnét mt ms (n) n lg n n. Ebb l a teljes rendezésre, ami a rekurzív öfrend eljáráshoz képest pontosan egy többlet eljáráshívást tartalmaz, azt kapjuk, hogy n lg n n mt MS (n) AT MS (n) MT MS (n) n lg n + 4n + 1 Tekintetbe véve, hogy lim n n n lg n = 0 lim 4n + 1 n n lg n = 0, a fenti struktogramok el tti állítással kapjuk, hogy mt MS (n), AT MS (n), MT MS (n) Θ(n lg n). 16

5. Láncolt listák 5.1. Egyirányú láncolt listák (egyszer listák (EL) és fejelemes listák (FL)) Az egyirányú láncolt listák elemeinek osztálya (az egyszer ség kedvéért): Elem +kulcs : valamilyen ismert típus +mut : Elem* fv EL_hossza(L) n := 0 p := L p n := n + 1 p := p mut return n fv FL_hossza(F ) return EL_hossza(F mut) A különböz listam veleteknél a listaelemekben lev kulcsok (és az esetleges egyé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 nem tudjuk, hogy egy gyakorlati alkalmazásban mennyi járulékos adatot tartalmaznak az egyes listaelemek. mögé(p, q) q mut := p mut p mut := q mögül(p, &q) q := p mut p mut := q mut [q mut := ] 17

szétvág(l, n1, &L) p := L n1 > 1 n1 := n1 1 p := p mut L := p mut p mut := FL_beolvasása(&F ) F := v := new Elem van még beolvasandó adat v := v mut := new Elem beolvas(v kulcs) v mut := 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 egy vagy több mutatóval hivatkozni a frissen létrehozott objektumra. Az objektumok dinamikus létrehozása egy speciálisan a dinamikus helyfoglalásra fenntartott memóriaszegmens szabad területének rovására történik. Fontos ezért, hogy ha már egy objektumot nem használunk, a memória neki megfelel darabja visszakerüljön a szabad memóriához. Erre fogjuk használni a delete 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. 9 Ugyanígy, a p mutató 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 helyfoglaló és felszabadító rutinokat; speciális esetekben akár úgy is, hogy a fenti m veletek konstans id t igényelnek. (Erre egy példa a gyakorlatokon is elhangzik.) Általános esetben azonban a dinamikus helyfoglalásra használt szabad memó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 viszonylag bonyolult könyvelést igényel, ha a feldarabolódásnak gátat akarunk vetni. Ezt a problémát a különböz rendszerek különböz hatékonysággal kezelik. Az absztrakt programokban az objektumokat dinamikusan létrehozó (new) és törl (delete) utasítások m veletigényeit konstans értékeknek, azaz Θ(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. 9 Az absztrakt programokban (struktogram, pszeudokód) automatikus deklarációt feltételezünk: az eljárás vagy függvény meghívásakor az összes benne használt lokális változó és formális paraméter automatikusan deklarálódik (egy változó alapértelmezésben lokális). 18

r := s FL_beszúró_rendezése(F ) r := F mut r s := r mut s r kulcs s kulcs r mut := s mut p := F ; q := F mut q kulcs s kulcs p := q ; q := q mut s mut := q ; p mut := s s := r mut EL_összefésül _rendezése(&l) n := EL_hossza(L) ELöfR(L, n) SKIP ELöfR(&L, n) n > 1 n1 := n szétvág(l, n1, L) ELöfR(L, n1) ELöfR(L, n n1) összefésül(l, L) SKIP 19

p := L q := L mut p := q összefésül(&l, L) q := q mut p mut := L L kulcs L kulcs p := L ; L := L mut q := L p mut := q ; L := p q L q kulcs L kulcs r := L ; L := L mut r mut := q ; p mut := r p := r L SKIP 5.. Kétirányú ciklikus láncolt listák (KCL) A KCL-ek elemeinek osztálya: Elem bmut, jmut : a bal illetve a jobb szomszédra mutat vagy this +kulcs : valamilyen ismert típus + Elem() { bmut := jmut := this } // egyelem KCL-t képez bel le + Elem() törlés el tt kif zi + kif z() // kif zi és egyelem KCL-t képez bel le + balra(q) // átf zi a q KCL elemt l balra + jobbra(q) // átf zi a q KCL elemt l jobbra + fv bal() { return bmut } // a bal szomszédja címe + fv jobb() { return jmut } // a jobb szomszédja címe Elem:: Elem() bmut jmut := jmut jmut bmut := bmut bmut := jmut := Elem::kif z bmut jmut := jmut jmut bmut := bmut bmut := jmut := this 0

Elem::balra(q) kif z() p := q bmut bmut := p ; jmut := q p jmut := q bmut := this Elem::jobbra(q) kif z() p := q jmut jmut := p ; bmut := q p bmut := q jmut := this 5..1. Fejelemes, kétirányú ciklikus láncolt listák (FKCL), mint a KCL-ek speciális esete FKCL_beolvasása(&F ) F := new Elem van még beolvasandó adat p := new Elem beolvas(p kulcs) p balra(f ) FKCL_beszúró_rendezése(F ) r := F jobb() ; s := r jobb() s F r kulcs s kulcs p := r bal() p F p kulcs > s kulcs r := s p := p bal() s jobbra(p) s := r jobb() 6. Absztrakt adattípusok (ADT-k) 6.1. Vermek A vermet most statikus tömb (A[1..n] : T ) segítségével reprezentáljuk, ahol n a verem maximális mérete, T a verem elemeinek típusa. 1

Verem - A[1..n] : T // T ismert típus, n globális konstans - m : 0..n // m a verem aktuális mérete + Verem() {m := 0} // üresre inicializálja a vermet // destruktort itt nem kell deniálni + verembe(x) // beteszi x-et a verembe, felülre + fv veremb l() // kiveszi és visszaadja a verem fels elemét + fv tet () // megnézi és visszaadja a verem fels elemét + fv tele_e(){return m = n} + fv üres_e(){return m = 0} Verem::verembe(x) m := m + 1 A[m] := x m < n HIBA ("Tele van a verem.") fv Verem::veremb l() m > 0 m := m 1 HIBA return A[m + 1] ("Üres a verem.") fv Verem::tet () m > 0 return A[m] HIBA ("Üres a verem.") Példa a verem egyszer használatára a gyakorlat anyagából: Az input adatok kiírása fordított sorrendben. (A vermet az alábbi struktogramban szokások ellenére azért deklaráltuk, hogy jelezzük: üresre inicializáltuk. Az egyszer - ség kedvéért feltesszük, hogy a verem elég nagy a teljes input befogadásához.) megfordít() v : Verem van még beolvasandó adat v.verembe(beolvas()) v.üres_e() kiír(v.veremb l())

6.. Absztrakt adattípusok (ADT-k): sorok A sort statikus tömb (A[0..n 1] : T ) segítségével reprezentáljuk, ahol az n globális konstans a sor zikai mérete, T a sor elemeinek típusa. Sor A[0..n 1] : T m : 0..n // m a sor aktuális mérete k : 0..n 1 // k a sor kezd pozíciója az A tömbben + Sor(){ m:=0 ; k := 0 } // üresre inicializálja a sort + sorba(x) // beteszi x-et a sor végére + fv sorból() // kiveszi és visszaadja a sor els elemét + fv els () // megnézi és visszaadja a sor els elemét + fv hossz(){return m} + fv üres_e(){return m = 0} Sor::sorba(x) m < n A[(k + m) mod n] := x m := m + 1 HIBA ("Tele van a sor.") fv Sor::sorból() m > 0 m := m 1 i := k k := (k + 1) mod n return A[i] HIBA ("Üres a sor.") fv Sor::els () m > 0 return A[k] HIBA ("Üres a sor.") 6..1. Sor reprezentálása dinamikus tömbbel* A sort dinamikusan létrehozott tömb (A := new T [0..n 1]) segítségével reprezentáljuk, ahol n a sor zikai mérete, T a sor elemeinek típusa. Ha nincs elég memória, a new m velet -t ad vissza. (A konstruktor paramétere a kezd méret, s ha kés bb az s.sorba(x) m velettel a tömb betelik, kétszer akkorát foglalunk, és az adatokat átmásoljuk.) 3

Sor A : T // a konstruktor létrehozza az A[0..n 1] : T tömböt n : 1.. // n a sor zikai mérete m : 0..n // m a sor aktuális mérete k : 0..n 1 // k a sor kezd pozíciója az A tömbben + Sor(n0) // üresre inicializálja a sort n0 zikai mérettel + Sor() {delete A} + üressor(){m := 0 ; k := 0} // kiüríti a sort + sorba(x) // beteszi x-et a sor végére + fv sorból() // kiveszi és visszaadja a sor els elemét + fv els () // megnézi és visszaadja a sor els elemét + fv hossz(){return m} + fv üres_e(){return m = 0} Sor::Sor(n0) A := new T [0..n0 1] A = HIBA ("Nincs elég szabad memória.") n := n0 ; m := 0 ; k := 0 A[(k + m) mod n] := x m := m + 1 Sor::sorba(x) m < n B := new T [0..n 1] i := k ; j := 0 j < m B[j] := A[i] B i := (i + 1) mod n j := j + 1 delete A ; A := B n := n ; k := 0 A[m] := x ; m := m + 1 HIBA ("Nincs elég szabad memória.") 4

fv Sor::sorból() m > 0 m := m 1 i := k k := (k + 1) mod n return A[i] HIBA ("Üres a sor.") fv Sor::els () m > 0 return A[k] HIBA ("Üres a sor.") HF: A vermet hasonlóan átírni. 6.3. Absztrakt adattípusok (ADT-k): Els bbségi (prioritásos) sorok PrSor - A[1..n] : T // T ismert típus, n globális konstans - m : 0..n // m a PrSor aktuális mérete + PrSor() {m := 0} // üresre inicializálja a PrSort // destruktort itt nem kell deniálni + prsorba(x) // beteszi x-et a prsorba + fv maxkivesz() // kiveszi és visszaadja a PrSor maximális elemét + fv max() // megnézi és visszaadja a PrSor maximális elemét + fv tele_e(){return m = n} + fv üres_e(){return m = 0} A PrSor aktuális elemeit az A[1..m] résztömb tartalmazza. 6.3.1. A[1..m] rendezetlen T prsorba (m) Θ(1), T maxkivesz (m) Θ(m), T max (m) Θ(m): m := m + 1 A[m] := x PrSor::prSorba(x) m < n HIBA ("Tele van a PrSor.") 5

fv PrSor::maxKivesz() m > 0 maxi := maxiker(a[1..m]) x := A[maxi] A[maxi] := A[m] m := m 1 return x HIBA ("Üres a PrSor.") fv PrSor::max() m > 0 maxi := maxiker(a[1..m]) return A[maxi] HIBA ("Üres a PrSor.") fv maxiker(a[1..m]) maxi := 1 ; i := i m A[i] > A[maxi] maxi := i SKIP i := i + 1 return maxi HF: Lássuk be, hogy elérhet T max (m) Θ(1), míg T prsorba (m) Θ(1) és T maxkivesz (m) Θ(m) marad! Módosítsuk a PrSor osztályt ennek megfelel en! (Ötlet: vegyünk fel egy új, privát adattagot az osztályba, ami nemüres prioritásos sor esetén a legnagyobb elem indexe. Módosítsuk ennek megfelel en a metódusokat!) 6.3.. A[1..m] monoton növekv en rendezett MT prsorba (m), AT prsorba (m) Θ(m); mt prsorba (m) Θ(1) T maxkivesz (m) Θ(1), T max (m) Θ(1): 6

PrSor::prSorba(x) i := m i > 0 A[i] > x A[i + 1] := A[i] i := i 1 A[i + 1] := x m := m + 1 m < n HIBA ("Tele van a PrSor.") fv PrSor::maxKivesz() m > 0 m := m 1 HIBA return A[m + 1] ("Üres a PrSor.") fv PrSor::max() m > 0 return A[m] HIBA ("Üres a PrSor.") Vegyük észre, hogy - ha A[1..m] rendezetlen, a prsorba(x) eljárás megfelel a Verem típus verembe(x) metódusának, míg - ha A[1..m] rendezett, a maxkivesz() és a max() tagfüggvények az ottani veremb l() illetve tet () m veletek hasonmásai. A "régi" m veletek így Θ(1) id ben futnak. Az "újak" viszont lineáris m - veletigény ek, ezért hatékonyabb megoldást keresünk. 7. 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ágrendjeivel kapcsolatos alapvet fogalmakat, és az ezekhez kapcsolódó függvényosztályok legfontosabb tulajdonságait. 7.1 Deníció Valamely P (n) tulajdonság elég nagy n -ekre pontosan akkor teljesül, ha N N, hogy n N -re igaz P (n). 7. Deníció Az f AN (aszimptotikusan nemnegatív) függvény, ha elég nagy n-ekre f(n) 0. 7

7.3 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, tetsz leges megfelel mértékbegységben (másodperc, perc, Mbyte stb.) mérve pozitív számérték. Amikor (alsó és/vagy fels ) becsléseket végzünk a futási id re vagy a tárigényre, legtöbbször az input adatszerkezetek méretének 10 függvényében végezzük a becsléseket. Így a becsléseket leíró függvények természetesen N R típusúak. Megkövetelhetnénk, hogy N P típusúak legyenek, 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 aszimptotikusan nemnegatívak, esetleg aszimptotikusan pozitívak legyenek. 7.4 Jelölések: Az f, g, h latin bet kr l ebben a fejezetben feltesszük, hogy N R típusú, aszimptotikusan nemnegatív (AN) 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. 7.5 Deníció Az O(g) függvényhalmaz olyan f függvényekb l áll, amiket elég nagy n helyettesítési értékekre, megfelelel pozitív konstans szorzóval jól becsül felülr l a g függvény: O(g) = {f : d P, hogy elég nagy n -ekre d g(n) f(n).} 7.6 Deníció Az Ω(g) függvényhalmaz olyan f függvényekb l áll, amiket elég nagy n helyettesítési értékekre, megfelelel pozitív konstans szorzóval jól becsül alulról a g függvény: Ω(g) = {f : c P, hogy elég nagy n -ekre c g(n) f(n).} 7.7 Deníció A Θ(g) függvényhalmaz olyan f függvényekb l áll, amiket elég nagy n helyettesítési értékekre, megfelelel pozitív konstans szorzókkal alulról és felülr l is jól 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).} 7.8 Tulajdonság Θ(g) = O(g) Ω(g) 7.9 Tétel Ha f AN, g pedig AP függvény, akkor f O(g) d P és ψ, hogy n N -re d g(n) + ψ(n) f(n) lim n ψ(n) g(n) = 0 10 vektor mérete, láncolt lista hossza, fa csúcsainak száma stb. 8

7.10 Tétel Ha f AN, g pedig AP függvény, akkor f Ω(g) c P és ϕ, hogy n N -re c g(n) + ϕ(n) f(n) lim n ϕ(n) g(n) = 0 7.11 Tétel Ha f AN, g pedig AP függvény, akkor f Θ(g) c, d P és ϕ, ψ, hogy n N -re c g(n) + ϕ(n) f(n) d g(n) + ψ(n) ϕ(n) lim n g(n) = 0 lim ψ(n) n g(n) = 0 Ld. még ezzel kapcsolatban az alábbi címen az 1.3. alfejezetet! [4] <http://people.inf.elte.hu/fekete/algoritmusok_jegyzet. /01_fejezet_Muveletigeny.pdf> 8. Fák, bináris fák Megállapítottuk, hogy a prioritásos sorok rendezetlen és rendezett tömbös reprezentációjával is lesz olyan m velet, ami lineáris m veletigény, hiszen rendezetlen tömb esetén a maxkivesz() megvalósításához maximumkeresés, rendezett tömb esetén a pedig a prsorba(x) implementációjához rendezett beszúrás szükséges. HF: (1) Igazoljuk, hogy a láncolt listás reprezentációk esetén hasonló eredményekre juthatunk! () Vegyük észre, hogy egy apró ötlettel a max() függvény m veletigényét rendezetlen tömb illetve lista esetén is Θ(1)-re csökkenthetjük! (A maximum helyét folyamatosan tartsuk nyilván!) Bináris fák segítségével viszont elérhetjük, hogy a maxkivesz() és a prsorba(x) metódusok maximális futási ideje Θ(lg n), a többi m veleté pedig Θ(1) legyen. Az egydimenziós tömbök és a láncolt listák esetében minden adatelemnek legfeljebb egy rákövetkez je van, azaz az adatelemek lineárisan kapcsolódnak egymáshoz a következ séma szerint: OOOOO A bináris fák esetében minden adatelemnek vagy szokásos nevén csúcsnak legfeljebb kett rákövetkez je van: egy bal és/vagy egy jobb rákövetkez je. Ezeket a csúcs gyerekeinek nevezzük. A csúcs a gyerekei szül je, ezek pedig egymás testvérei. Ha egy csúcsnak nincs gyereke, levélnek hívjuk, ha pedig nincs szül je, gyökér csúcsnak nevezzük. Bels csúcs alatt nem-levél 9

csúcsot értünk. A fában egy csúcs leszármazottai a gyerekei és a gyerekei leszármazottai. Hasonlóan, egy csúcs sei a szül je és a szül je sei. A fákat felülr l lefelé szokás lerajzolni: fent van a gyökér, lent a levelek. Egyszer bináris fák: O O O / \ / \ O O O O O Az Ω üres fának nincs csúcsa. Egy tetsz leges nemüres t fát a gyökércsúcsa ( t) határoz meg, mivel ennek a 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 bal illetve t jobb (szokás a bal(t) és jobb(t) jelölés is). Ha t-nek nincs bal/jobb gyereke, akkor t bal = Ω illetve t jobb = Ω. Ha a gyerek létezik, jelölése t bal illetve t jobb. A t bináris fának (t = Ω esetén is) részfája önmaga. Ha t Ω, részfái még a t bal és a t jobb részfái is. A t valódi részfája f, ha t részfája f, és t f Ω. A t-ben tárolt kulcs jelölése t kulcs (illetve kulcs(t)). Megjegyezzzük, hogy a gyakorlatban ugyanúgy, mint a tömbök és a láncolt listák elemeinél a kulcs általában csak a csúcsban tárolt adat egy kis része, vagy abból egy függvény segítségével számítható ki. Mi az egyszer ség kedvéért úgy tekintjük, mintha a kulcs az egész adat lenne, mert az adatszerkezetek m veleteinek lényegét így is be tudjuk mutatni. Ha g egy fa egy csúcsa, akkor a szül je a g sz, és a szül jéhez, mint gyökércsúcshoz tartozó fa a g sz (illetve sz(g)). Ha g a teljes fa gyökere, azaz nincs szül je, akkor g sz = Ω. A bináris fa fogalma általánosítható. Ha a fában egy tetsz leges csúcsnak legfeljebb r rákövetkez je van, r-áris fáról beszélünk. Egy csúcs gyerekeit és a 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-edik részfa üres. Így tehát a bináris fa és a -áris fa lényegében ugyanazt jelenti, azzal, hogy itt a bal 0 és a jobb 1 szelektor-megfeleltetést alkalmazzuk. Beszélhetünk a fa szintjeir l. A gyökér van a nulladik szinten. Az i-edik szint csúcsok gyerekeit az (i + 1)-edik szinten találjuk. A fa magassága egyenl a legmélyebben fekv levelei szintszámával. Az üres fa magassága h(ω) = 1. Így tetsz leges nemüres t bináris fára: h(t) = 1 + max(h(t bal), h(t jobb)) 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é vannak 30

irányítva, a gyökérb l minden csúcs pontosan egy úton érhet el, és valamely r N-re tetsz leges csúcs kimen élei a 0..r 1 szelektorok egy részhalmaza elemeivel egyértelm en 11 van címkézve. (Ezzel szemben a szabad fák összefügg, körmentes irányítatlan gráfok.) 8.1. (Bináris) fák bejárásai A (bináris) fákkal dolgozó programok gyakran kapcsolódnak a négy klasszikus bejá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 kapcsolatban megköveteljük, hogy futási ideje Θ(1) legyen (ami ett l még persze összetett m velet is lehet). A f csúcs feldolgozása lehet például f kulcs 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 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 fa gyö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ét dolgozza fel, majd sorban bejárja az 1..r 1-edik részfákat; - a szintfolytonos bejárás a csúcsokat a gyökért l kezdve szintenként, minden szintet balról jobbra bejárva dolgozza fel. 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ákra 1 : preorder(t) t Ω feldolgoz(t) preorder(t bal) preorder(t jobb) SKIP inorder(t) t Ω inorder(t bal) feldolgoz(t) inorder(t jobb) SKIP 11 Az egyértelm ség itt azt jelenti, hogy egyetlen csúcsnak sincs két azonos címkéj kimen éle. 1 A struktogramokban a t csúcs feldolgozását feldolgoz(t) jelöli. 31

postorder(t) t Ω postorder(t bal) postorder(t jobb) SKIP feldolgoz(t) szintfolytonos(t) t Ω s : Sor ; s.sorba(t) s.üres_e() f := s.sorból() feldolgoz(f) f bal Ω s.sorba(f bal) SKIP f jobb Ω s.sorba(f jobb) SKIP SKIP Állítás: T preorder (n), T inorder (n), T postorder (n), T szintfolytonos (n) Θ(n), ahol n a fa mé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 szerinti teljes indukcióval könyen belátható, hogy tetsz leges n csúcsú bináris fának n + 1 részfája van. A szintfolytonos bejárás pedig mindegyik csúcsot a ciklus egy-egy végrehajtásával dolgozza fel. A szintfolytonos bejárás helyessége azon alapszik, hogy egy fa csúcsainak szintfolytonos felsorolásában a korábbi csúcs gyerekei megel zik a kés bbi csúcs gyerekeit. Ez az állítás nyilvánvaló, akár egy szinten, akár különböz szinten van a két csúcs a fában. A preorder és az inorder bejárások hatékonységa konstans szorzóval javítható, ha a végrekurziókat ciklussá alakítjuk. (Mivel a t érték módú formális paraméter, az aktuális paraméter nem változik meg. [Általában nem célszer a referencia módú formális paramétereket ciklusváltozóként vagy segédváltozóként használni.] ) preorder(t) t Ω feldolgoz(t) preorder(t bal) t := t jobb inorder(t) t Ω inorder(t bal) feldolgoz(t) t := t jobb 3