VII. Adatszerkezetek: fák

Elsõ nekifutás: a fák tulajdonságai

nak nevezzük az összefüggõ, körmentes egyszerû gráfokat. Érdemes felsorolnunk a fák néhány jellegzetes tulajdonságát.

Fában két pont között pontosan egy út létezik. Létezik, mert a gráf összefüggõ, és pontosan egy, mert ha kettõ lenne, kör is lenne.

Ha fához hozzáteszünk egy élt, már nem lehet fa. (i j) él hozzávételével ugyanis kört kapnánk, hiszen eddig is volt a két csúcs között út, és most lett még egy, tehát kör keletkezett.

Fából egy élt elhagyva már nem lehet fa. Ha ugyanis fa lenne, akkor abba az iménti élt visszatéve megint csak fát kapnánk (az eredetit), ami az elõzõ bekezdés szerint nem lehet.

Úgy is fogalmazhatnánk, hogy a fa a lehetõ legkevesebb élt tartalmazza, ami szükséges az összefüggõséghez.

Ha egy fában E él és P csúcs van, akkor P=E+1. Bizonyítás: vegyünk egy fát. Ebben biztosan van olyan pont, melynek foka 1. Ha ugyanis minden pont foka legalább 2 lenne, akkor el tudnánk indulni egy pontból, és ha a pont még nem került sorra, tovább tudnánk lépni egy másik, még fel nem használt élen. Mivel véges számú pont van, elõbb-utóbb olyan pontot érintünk, ami már volt. Ekkor viszont találtunk egy kört, ami ellentmondás. Miután van 1-fokú pont, vágjuk le a fáról az élével együtt. Ezzel nem keletkezett kör, és az összefüggõség sem sérült. A maradék-fát ismét megnyessük, egészen addig, míg egy pont (és nulla él) marad. Mivel a nyesegetés során ugyanannyi pontot vettünk el, mint élt, az eredeti fában is eggyel több pont volt, mint él.

Ha egy összefüggõ gráfban P=E+1, akkor az fa. Ha ugyanis nem lenne fa, elhagyhatnánk belõle éleket, hogy az legyen (nyilván körök voltak benne, azért nem volt fa). Az így kapott fában viszont P=E+1, pedig most kevesebb él van benne, mint eredetileg. Ez ellentmondás.

Mire jó ez? A körmentesség közvetlenül elég nehezen mutatható ki egy gráfról. Az összefüggõség egyszerû (ld. VI. fejezet). Ha viszont van egy összefüggõ gráfunk, csak meg kell számolni a pontjait és éleit, hogy eldönthessük, körmentes-e (vagyis fa).

Egy összefüggõ gráf feszítõfája az a fa, amely a gráf minden pontját, és éleinek egy részét tartalmazza. Feszítõfát többféleképpen kaphatunk (a következõ algoritmusokat bizonyítás nélkül közöljük. A bizonyítással meg lehet próbálkozni. A megértéshez célszerû rajzolni!):

A 3. algoritmus nehezen megvalósítható, mert a gráfban nem egyszerû kört keresni. A Kruskal-féle algoritmus legegyszerûbb megvalósítása, ha a csúcsokat megszámozzuk. Az algoritmus voltaképpen diszjunkt fákat kötöget össze. Ha egy éllel két fát összekötünk, kapja a két fa összes pontja ugyanazt a kódszámot (valamelyik pontjuk kódszámát). Így könnyû eldönteni, hogy a kérdéses él eddig még diszjunkt fákat köt-e össze (ez esetben betehetõ). Prim algoritmusa esetén a helyzet még egyszerûbb: vannak pontok, melyek már bekerültek a fába, és vannak, amelyek még nem.

1. feladat: írj programot, amely élmátrixszal megadott összefüggõ gráfhoz feszítõfát keres Prim algoritmusa segítségével!

Bonyolítja a helyzetet, ha a gráf élei súlyozottak. Ekkor a feladat a legolcsóbb/legkönnyebb feszítõfa megkeresése (élei súlyának összege minimális). Ez gyakorlati alkalmazás szempontjából is fontos: ha városok közötti elektromos hálózatot akarnak kiépíteni (ez fa lesz, újabb vezetékek már fölöslegesek), nem mindegy, mennyi vezetéket használnak.

Az elõzõ algoritmusok módosítása: az 1. és 2. algoritmusban az új él kiválasztásakor az összes szóba jövõ él közül (tehát amelyeket még nem tettünk be, és diszjunkt fákat, illetve fát és pontot kötnek össze) a legkisebb súlyút kell kiválasztani. A 3. algoritmusnál pedig a szóba jövõ élek közül mindig a legnehezebbet kell elhagyni.

2. feladat: írj programot, amely élmátrixszal megadott súlyozott gráfban legkönnyebb feszítõfát keres!

Megjegyezzük, hogy a mélységi keresés algoritmusa (VI.fejezet) éppen egy feszítõfa mentén haladva sorolja fel a pontokat.

Miért is nevezik a fát fának? Válasszunk ki egy tetszõleges pontot, és nevezzük el a fa gyökerének. A gyökérbõl minden pont egyetlen úton érhetõ el. Az egy lépésben elérhetõket rajzoljuk a gyökér alá. A következõ szintre az ezekbõl egy, a gyökérbõl két lépésben elérhetõk kerülnek, és így tovább. Irányítsuk az éleket: egy pontból az alatta lévõ szomszédok felé mutasson a nyíl. A gyökér kiválasztásával így a fához tartozik egy irányítás is. (Az éleket ezután nem is szükséges irányítani, elég megadni a gyökeret.) Egy pontból több felé is mutathat nyíl lefelé (ezeket a szomszédokat közvetlen leszármazottaknak nevezzük), egy pontba viszont csak egyetlen pontból mutathat nyíl (ez a pont az elõzõ õse). Egyedül a gyökérbe nem mutat nyíl. Azok a csúcsok, melyeknek nincsenek leszármazottaik, a fa leveleinek nevezzük. A továbbiakban ilyen módon irányított fákról lesz szó.

Ez a szerkezet alkalmas hierarchiák (alá-fölérendeltségi viszonyok) ábrázolására, pl. egy vállalat struktúrájában a közvetlen beosztottak rendszere, a DOS könyvtár-szerkezete stb.

Megjegyzés: a fa irányításával egyszerûbb a P=E+1 tétel bizonyítása. Minden pontba egy él mutat, ráadásul minden pontba másik, kivéve a gyökeret, amibe nem mutat él. Így a gyökér kivételével a pontok összepárosíthatók az élekkel.

Második nekifutás: bináris fák

A bináris fák olyan fák, melyeknél egy pontnak legfeljebb két közvetlen leszármazottja lehet. Ha a két leszármazott sorrendje nem tetszõleges, beszélhetünk bal- és jobboldali leszármazottról. Gyakorlati megvalósításnál a fa csúcsai valamilyen adatot tartalmaznak, ezen kívül tárolnunk kell, hogy mely pontoknak melyek a közvetlen leszármazottai. Ez célszerûen N-elemû tömbben történik (N a fa pontjainak száma), ahol egy tömbelem tartalmazza az adatot, valamint a baloldali és a jobboldali leszármazott indexét. Pascal-példa:

CONST N=csúcsok száma;
TYPE Faelem:record
             elem:típus;
             bal,jobb:integer;
            end;
VAR FA:array[1..N] of Faelem;
    GY:integer;
{a gyökérelem indexe}

1. feladat: rajzold le a következõ tömbbel megadott fa struktúráját! (GY=1)

index

elem

 bal 

jobb

1

gerinces

2

5

2

hal

3

6

3

harcsa

0

0

4

galamb

0

0

5

madár

4

0

6

csuka

0

0

A bal és jobb mezõket mutatónak is hívják, mivel megmutatják, hol van a bal- és jobboldali leszármazott a tömbben. Ha egy mutató értéke 0, akkor abba az irányba nincs több leszármazott (levél bal- és jobbmutatója egyaránt 0).

A bináris fák bejárásakor figyelembe vesszük a fa által képviselt struktúrát. Háromféle fabejárási sorrend létezik:

A bal-közép-jobb (BKJ) bejárásnál egy csúcs esetén elõször a baloldali részfáját, utána az illetõ csúcsot, utána a jobboldali részfáját dolgozzuk fel. A részfák feldolgozása rekurzívan ugyanígy történik. Példaként nézzük a következõ fát:egy bináris fa

Az elemek felsorolása BKJ bejárással: 1, 2, 3, 4, 5, 6, 7.
Közép-bal-jobb (KBJ) bejárással: 4, 2, 1, 3, 6, 5, 7.
Végül bal-jobb-közép (BJK) bejárással: 1, 3, 2, 5, 7, 6, 4.

Rekurzív Pascal-példaprogram a BJK bejárásra (egy elem feldolgozása legyen most a kiírása). Az eljárás indítása: BJK(GY).

Procedure BJK(cs:integer);
 Begin
  if cs>0 then begin
     BJK(FA[cs].bal);
     BJK(Fa[cs].jobb);
     Write(Fa[cs].elem);
  end;
 End;

2. feladat: írd meg a BKJ és KBJ bejárás programját is, majd teszteld egy beolvasott fán!

rendezőfaA bináris fák jellegzetes felhasználása a rendezõfa. A rendezõfa olyan fa, melyben egy elem baloldali részfája (a baloldali leszármazott, és az abból leágazó elemek) mind nála kisebb, jobboldali részfája pedig nála nagyobb elemeket tartalmaz. A rendezõfa BKJ bejárásával az elemek sorba rendezve kerülnek feldolgozásra.

Ha egy elemet keresünk a rendezõfában, a felezéses kereséshez hasonlóan minden lépésben választunk az éppen vizsgált elem bal- és jobboldali részfája között. Ez a keresés akkor fejezõdik be a legkevesebb lépésben, ha a rendezõfa kiegyensúlyozott, vagyis az egyes elemek bal- és jobboldali részfái nagyjából ugyanannyi elemet tartal maznak. (Ellenpélda: a gyökér alma, és minden elemnek csak jobboldali leszármazottja van. A keresés ekkor a soros keresésre hasonlít.Az ilyen fákat degeneráltnak nevezzük.)

Következzék egy algoritmus E elem keresésére a bináris fában. MUT adja vissza a keresett elem indexét, vagy 0-t, ha nem létezik.

MUT:=GY
Ciklus amíg FA[MUT].elem<>E és MUT<>0
   Ha E<FA[MUT]   akkor mut:=FA[MUT].bal
               különben mut:=FA[MUT].jobb
Ciklus vége

3. feladat: írd meg a keresés algoritmusát rekurzív formában, ciklus nélkül!

A rendezõfa felépítése, vagyis a rendezés fával a beillesztéses rendezéshez (2. Pascal füzetke, A függelék) hasonló: az elemek sorban érkeznek, mindegyiknek megkeressük a helyét a fában, és beillesztjük. A fába való beillesztés részletesebb leírása:

4. feladat: írd meg a fával való rendezés programját! (Az adatokat folyamatosan írjuk be, vagy egy file-ból kerülnek be. A program a beérkezõ adatoknál kiírja, hogy van már olyan a fában, vagy beilleszti a fába.)

5. feladat: hogyan keletkezhet még degenerált fa? Adj általános szabályt az elemek érkezési sorrendjére! (Véletlenszerûen érkezõ elemeknél a fa általában kiegyensúlyozott lesz.)

6. feladat: milyen módszer szerint lehet elemet törölni a rendezõfából? (Olyan elem törlése, melynek valamelyik mutatója 0, könnyû.)

Most megmutatjuk a bináris fák és bináris (kétváltozós) mûveletek kapcsolatát. Egy kétváltozós mûveletet felír hatunk infix (a mûveleti jel, vagyis operátor a két érték között van, pl. 3 + 4), prefix (+ 3 4) és postfix (3 4 +) módon is. A postfix forma elõnyeirõl ld. az I. fejezetet.

bináris műveletek fánA következõ fával a (2+3)*(7-5) kiszámításához szükséges mûveletvégzési sorrendet ábrázoltuk. A zárójeles kiszámítási mód a BKJ bejáráshoz kapcsolódik. Ugyanezt a fát BJK módon bejárva (2 3 + 7 5 - *) a mûvelet postfix, azaz lengyel forma (PN) szerinti jelölését kapjuk, amely verem-géppel zárójelezés nélkül is elvégezhetõ. Minden, bináris mûveleteket tartalmazó kifejezés átírható bináris fa formára, és felírható PN-ben is.

7. feladat: rajzold le bináris fával az 1/(3*((4+7)-(2+1))) mûveletet, és írd fel PN-ben is!

8. feladat: írj programot, mely egy mûveleteket ábrázoló bináris fából elõállítja a zárójeles (hagyományos, infix operátoros) kifejezést! Ha túl sok zárójelet tartalmaz, nem baj.

9. feladat: írj programot, mely kiértékel egy mûveleteket tartalmazó bináris fát! Használj rekurziót (BJK sorrendû kiértékelés).

10. feladat: írj programot, amely szabályos PN-formából elõállítja a bináris fát! Ehhez célszerû a PN formát hátulról visszafelé feldolgozni.

Harmadik nekifutás: általános fák ábrázolása

Mivel ebben az esetben egy elemnek bárhány leszármazottja lehet, a bináris fáknál használt módszer közvetlenül nem alkalmazható. Ismerkedjünk meg elõször a lista fogalmával.

A lista olyan adatszerkezet, ahol egy elemnek legfeljebb egy rákövetkezõje (leszármazottja) lehet. A lista ábrázolása a bináris fához hasonló, de egy elemhez csak egy mutató tartozik. A listában lévõ elemek logikai sorrendjét nem a táblázatban elfoglalt fizikai helyük, hanem a mutatók határozzák meg.

Az általános fák ábrázolhatók elemenként két mutatóval. Az elsõ mutató (le) az elem elsõ közvetlen leszármazottjára mutat, míg a második (mellé) az elem szülõjének következõ közvetlen leszármazottjára (vagyis az elem következõ testvérére). A mellé mutatók voltaképpen az elsõ leszármazottból kiindulva listaszerkezetben sorolják fel egy elem összes leszármazottját. Az ábrában szaggatott vonalak jelzik a fa eredeti éleit, nyilak pedig a mutatókat.általános fa mutatói

1. feladat: készíts algoritmusokat ilyen módon tárolt fa kezelésére. A legfontosabb mûveletek: elem összes közvetlen leszármazottjának kilistázása, új leszármazott felvétele (legelsõ, legutolsó, illetve tetszõleges helyre a leszármazottak listájában).

2. feladat: készíts algoritmust ilyen fák rekurzív bejárására!

3. feladat: készíts algoritmust elem törlésére összes leszármazotttjával együtt!

4. feladat: készíts interaktív fakezelõ programot! Ez a DOS könyvtárkezelõ parancsainak mintájára készíthetõ el. A program ismerje fel a CD, MD, RD, DIR és TREE parancsokat.

Következõ fejezet
Elõzõ fejezet
Tartalomjegyzék
Honlap