B. függelék: a rekurzió

Rekurzió az, amikor egy eljárás vagy függvény - közvetve vagy közvetlenül - önmagát hívja. Az ilyen eljárások és függvények rekurzívak. A végtelen rekurzió elkerülésére szükségünk van egy feltételre, amely valamilyen lépésszám után leállítja a rekurziót. A következõ eljárás ciklus helyett rekurziót alkalmaz adott számú * kiraj zolására.

Procedure Csillagok(mennyi:integer);
 Begin
    If mennyi>0 then begin
       write('*');
       Csillagok(mennyi-1);
    end;
 End;

Az elv: adott számú csillagot úgy rajzolunk, hogy kirajzolunk egy csillagot, majd kirajzoljuk a maradékot. mennyi paraméter azt jelzi, hogy hány csillagot kell még kirajzolni. A feladatot (sok csillag) így visszavezettük egy egyszerûbb feladatra (egy csillag).

A matematikában találkozhatunk rekurzív definíciókkal. Így pl. 0! (nulla faktoriális) értéke 1, n! pedig n·(n-1)!. Tehát egy szám faktoriálisát úgy számítjuk ki, hogy kiszámítjuk a nála eggyel kisebb szám faktoriálisát (egyszerûbb feladat!), és megszorozzuk a számmal. A rekurzió véget ér, ha eljutunk a 0-hoz.

Function Fakt(n:integer);
 Begin
    If n>0 then Fakt:=n*Fakt(n-1)
           else Fakt:=1;
 End;

A rekurzív eljárások igénybe veszik a stack-et. A második példában a függvény paramétere minden egyes függvényhíváskor eltárolásra kerül, s a hívott függvény saját változóként újból létrehozza. Mire sorozatos függvényhívásokkal eljutunk a 0-hoz, a veremben ott sorakoznak az eltárolt paraméterek:Fakt(5)-nél 5, 4, 3, 2, 1. Ebbõl is látszik, hogy memóriafoglalás, sõt, olykor sebesség szempontjából a rekurzió nem hatékony, viszont egyes feladatokat rekurzív szemlélettel nagyon könnyû megoldani (ne a programozó dolgozzon, hanem a gép!).

A közvetett rekurzió alábbi megvalósítása hibás:

Procedure Egyik(n:integer);
 Begin
    Masik(n-1);
 End;

Procedure Masik(n:integer);
 Begin
    If n>0 then begin
       writeln(n);
       Egyik(n-1);
    end;
 End;

Az Egyik eljárás fordításakor a Masik eljárás még nincs deklarálva, és ez fordítási hibához vezet. A megoldás a következõ. Az Egyik eljárás deklarálása elé be kell írni:

Procedure Masik(n:integer); forward;

A forward szó jelzi a fordítónak, hogy a megadott eljárás késõbb kerül deklarálásra, és itt csak az eljárás fejét kell megadni.

Rekurzív adatszerkezetek

A dinamikus memóriakezelésre és a Type deklaráció rekurzív használatára mutat példát a következõ program. Az itt megvalósított adatszerkezet neve lista. Minden eleme tartalmaz egy adatot (hiszen a lista feladata a tömbhöz hasonlóan adattárolás), és egy mutatót, amely megmutatja, hogy a következõ listaelem hol van a memóriában.

TYPE listamutato=^listaelem;
     listaelem=record
                adat:integer;
                mutato:listamutato;
               end;

A TYPE, mint látható, felismeri a közvetett rekurziót. A továbbiakban szükségünk van egy pointerre, amely megmutatja a lista elsõ elemének helyét a memóriában, és egy másikra, amely a lista aktuális elemére mutat.

VAR fej,p:listamutato;

A következõ példaprogram létrehoz egy háromelemû listát.

BEGIN
 New(fej);
 fej^.adat=567;

A memóriában létrejött egy rekord, melyre a fej pointer mutat. Maga a rekord elérhetõ mint fej^. A rekord .adat mezõje egy integer változó, a .mutato mezõje viszont egy pointer, mely a lista következõ elemére mutat (most még nem mutat sehová).

 New(fej^.mutato);

Itt hoztuk létre a lista második elemét, melyre fej^.mutato mutat. Hogy áttekinthetõbb legyen, bevezetjük a p mutatót ehelyett (bár használhatnánk akár a ((fej^.mutato)^.mutato)^.adat formát is).

 p:=fej^.mutato;
 p^.adat:=1288;
 New(p^.mutato);
 p:=p^.mutato;
 p^.adat:=17;
 p^.mutato:=NIL;
END.

Hogyan lehet felszabadítani a memóriát? Végig kell lépegetni a listán a fejétõl kezdve. Vigyázat, még a p által mutatott rekord megsemmisítése elõtt el kell tárolni, hová mutat a törlendõ listaelem!

p:=fej;
while p<>NIL do begin
   q:=p^.mutato;
   Dispose(p);
   p:=q;
end;

Természetesen a legelegánsabb a rekurzív megoldás, hívása: Felszabadit(fej)

Procedure Felszabadit(p:listamutato);
 Begin
    If p<>NIL then begin
       Felszabadit(p^.mutato);
       Dispose(p);
    End;
END;

(Ez az eljárás hátulról visszafelé szabadítja fel a listát, legutoljára a fejrekordot törli.)

Ha a fentieket megértetted, elmondhatod magadról, hogy konyítasz valamicskét a programozáshoz. Ha nem, ne szomorkodj: a B függelék csak a teljesség kedvéért került a füzetbe, és enélkül is ugyanolyan jó programokat írhatsz. Mert most már nincs más hátra: válassz egy feladatot, fogj neki, és írj rá programot! Sok sikert.

Elõzõ fejezet
Tartalomjegyzék
Honlap