Ugrás a tartalomhoz

Adatstruktúrák és algoritmusok

Házy Attila, Nagy Ferenc (2009)

3. fejezet - Elemi dinamikus halmazok

3. fejezet - Elemi dinamikus halmazok

3.1. A tömb adatstruktúra

Egy adastruktúra számtalan adatot tartalmazhat. Mondhatjuk, hogy egy adathalmazt tárolunk egy struktúrában. Számunkra a dinamikus halmazok lesznek fontosak.

3.1. Definíció. Dinamikus halmaz Az olyan halmazt, amely az őt felhasználó algoritmus során változik (bővül, szűkül, módosul) dinamikus halmaznak nevezzük.

A dinamikus halmazok elemei tartalmazhatnak az információs adatmezőiken felül kulcsmezőt, és mutatókat (pointereket), amelyek a dinamikus halmaz más elemeire mutatnak. (pl: a következő elemre). Felsorolunk a dinamikus halmazokon néhány általánosságban értelmezett műveletet. Konkrét esetekben ezek közül egyesek el is maradhatnak, vagy továbbiak is megjelenhetnek. Az jelöli a szóban forgó halmazt, kulcsot ad meg és mutató a halmaz valamely elemére. Feltételezzük, hogy a kulcsok között értelmezett a kisebb, nagyobb, egyenlő reláció.

Az egyes műveletek végrehajtásukat tekintve lehetnek statikusak (passzívak), vagy dinamikusak (aktívak) aszerint, hogy a struktúrát változatlannak hagyják-e vagy sem. A módosító műveletek alapvetően dinamikusak, a lekérdezők általában statikusak, de nem ritkán lehetnek szintén dinamikusak. (A dinamikus lekérdezés olyan szempontból érdekes és fontos, hogy ha egy elemet a többitől gyakrabban keresnek, akkor azt a struktúrában a keresés folyamán a megtalálási útvonalon közelebbi helyre helyezi át a művelet, ezzel megrövidíti a későbbi keresési időt erre az elemre, vagyis a művelet változást eredményez a struktúrában.)

3.2. Definíció. A sorozat adatstruktúra

Sorozatnak nevezzük az objektumok (elemek) olyan tárolási módját (adatstruktúráját), amikor az elemek a műveletek által kijelölt lineáris sorrendben követik egymást. Tipikus műveletek: keresés, beszúrás, törlés.

A sorozat egyik lehetséges implementációja – gyakorlati megvalósítása, megvalósítási eszköze – a tömb. A tömb azonos felépítésű (típusú) egymást fizikailag követő memóriarekeszeket jelent. Egy rekeszben egy elemet, adatrekordot helyezünk el. Az egyes tömbelemek helyét az indexük határozza meg. Az elemek fontos része a kulcsmező, melyet révén kérdezhetünk le az A tömb x indexű eleme esetén. Számunkra lényegtelen lesz, de a gyakorlat szempontjából alapvetően fontos része az adatrekordnak az információs (adat) mezőkből álló rész. A tömböt szokás vektornak is nevezni. Ha a lineáris elhelyezésen kívül egyéb szempontokat is figyelembe veszünk, akkor ezt az egyszerű szerkezetet el lehet bonyolítani. Ha például az elemek azonosítására indexpárt használunk, akkor mátrixról vagy táblázatról beszélünk. Ilyen esetben az első index a sort, a második az oszlopot adja meg. (Itt tulajdonképpen olyan vektorról van szó, amelynek elemei maguk is vektorok.)

A struktúrának és így az implementációnak is lehetnek attributumai – jellemzői, hozzákapcsolt tulajdonságai. A tömb esetében ezeket az alábbi táblázatban adjuk meg.

Vizsgáljuk meg most a műveletek algoritmusait!

A keresési algoritmus. Az tömbben egy kulcsú elem keresési algoritmusa pszeudokóddal lejegyezve következik alább. Az algoritmus NIL-t ad vissza, ha a tömb üres, vagy a tömbben nincs benne a keresett kulcsú elem. A tömb elejétől indul a keresés. Ha a vizsgált elem egyezik a keresett elemmel, akkor azonnal viszatérünk az indexével. (Realizáció szempontjából úgy is elképzelhetjük a dolgot, hogy a tömb elemeinek indexelése 1-gyel kezdődik és a NIL eredményt a 0 indexszel jelezzük.) Ha nem egyezik, akkor az INC függvénnyel növeljük eggyel az index értékét (rátérünk a következő elemre) és újra vizsgálunk. Addig növeljük az indexet, míg az érvényes indextartományból ki nem lépünk vagy meg nem találjuk a keresett kulcsú elemet. A legrosszabb eset az, ha az elem nincs benne a tömbben, – ekkor ugyanis az összes elemet meg kell vizsgálni – így az algoritmus időigénye: , ahol , a tömbelemek száma.

Az új elem beszúrásának algoritmusa az A tömb adott x indexű helyére szúrja be az új elemet. Az ott lévőt és a mögötte állókat egy hellyel hátrább kell tolni. Emiatt az időigény

Ezzel az algoritmussal nem tudunk az utolsó elem után beszúrni. A problémát egy erre a célra megírt külön algoritmussal is megoldhatjuk. Legyen ennek CSATOL_TÖMBHÖZ a neve. Az olvasóra bízzuk pszeudokódjának megírását. Írjunk pszeudokódot arra az esetre, amikor a beszúrás az adott indexű elem mögé történik! Ennek is van egy szépséghibája!

Egy adott indexű elem törlésének algoritmusa az elem felszabaduló helyének megfelelően az elem mögött állókat feltömöríti, egy hellyel előre lépteti. Az algoritmusban használni fogjuk a DEC függvényt, melynek hatása az, hogy csökkenti eggyel az argumentuma értékét. Ennek révén a hossz és a vége attributumokat korrigáljuk. Legrosszabb esetben az összes megmaradt elemet mozgatni kell, ezért az időigény

A lineáris törlési időt konstansra csökkenthetjük, ha a tömbelemek eredeti sorrendjének megörzése nem fontos azáltal, hogy a törlésre ítélt elem helyére a tömb utolsó elemét helyezzük és a tömböt lerövidítjük.

Az eddigiek során nem használtuk ki, hogy a tömbben a kulcsok rendezetten (növekvő sorrend, vagy csökkenő sorrend) követik egymást. Nem is használhattuk ki, hiszen ezt nem tételeztük fel. Ez ismeretlen volt a számunkra. Most azonban élünk azzal a feltételezéssel, hogy a tömbelemek kulcsai növekvő sorrendben vannak.

A keresés időigénye, amely rendezettlen tömbben lineáris volt, feljavítható logaritmikusra az úgynevezett bináris keresés révén. A bináris keresés alapötlete az, hogy a k kulcs keresésekor a kulcsösszehasonlítást a tömb középső elemének kulcsával kezdjük. Ha egyezést tapasztalunk, akkor az eljárás véget ér. Ha a k kulcs értéke kisebb, mint a megvizsgált elem kulcsa, akkor a tömb középső elemének indexétől kisebb indexű elemek között folytatjuk a keresést. Ha a k kulcs értéke nagyobb, mint a megvizsgált elem kulcsa, akkor a tömb középső elemének indexétől nagyobb indexű elemek között folytatjuk a keresést. A méret ezáltal feleződött. A további keresés ugyanilyen elv alapján megy tovább. Minden lépésben vagy megtaláljuk a keresett kulcsú elemet, vagy fele méretű résztömbben folytatjuk a keresést. Ha a résztömb mérete (hossza) zérusra zsugorodik, akkor a keresett kulcs nincs a tömbben.

Megadjuk a rekurzív algoritmust is és az iteratívat is. Mindkettőben a felezést nyílt index-intervallumra végezzük, ami azt jelenti, hogy az index-intervallum végek nem tartoznak a keresési index-intervallumhoz. Ez az ötlet jó hatással van az algoritmus szerkezetére. Az iteratív esetben az algoritmus bemenő paraméterei természetes módon a tömb és a keresett kulcs, az algoritmus az egész tömbben keres. A rekurzív algoritmus bemenő paraméterei a rekurzivitás sajátosságai révén szintén természetes módon a tömb, a keresési nyílt index-intervallum két vége valamint a keresett kulcs. Tehát alaphelyzetben ez az algoritmus csak a tömb egy összefüggő részében keres, nem a teljes tömbben. Ha a teljes tömbben akarunk keresni, akkor az algoritmust a

BINÁRIS_KERESÉS_TÖMBBEN ( A, fej[A] - 1, vége[A] + 1, k )

sorral kell aktivizálni. Itt feltételeztük, hogy a tömb nem üres.

A másik két művelet hatékonyságára a rendezettség nincs jótékony hatással. A beszúrásnál a helycsinálás továbbra is lineáris ideig fog tartani, és ez lesz a helyzet a törlésnél is a tömörítés idejével.