Zur Haupt­na­vi­ga­ti­on sprin­gen [Alt]+[0] Zum Sei­ten­in­halt sprin­gen [Alt]+[1]

Re­kur­si­on

Re­kur­si­ve Funk­tio­nen zeich­nen sich da­durch aus, dass sie sich in ihrem Ver­lauf selbst auf­ru­fen kön­nen. Bei einem re­kur­si­ven Auf­ruf (Re­kur­si­ons­schritt) wird das zu lö­sen­de Pro­blem (d.h. die Pa­ra­me­ter des Auf­rufs) ty­pi­scher­wei­se ver­klei­nert, bis die Pro­blem­grö­ße ir­gend­wann so ge­ring ist, dass das Pro­blem di­rekt ge­löst wer­den kann (Re­kur­si­ons­ba­sis).

Eine re­kur­si­ve Funk­ti­on, die bei einem Durch­lauf höchs­tens einen re­kur­si­ven Auf­ruf aus­führt, wird li­nea­re Re­kur­si­on ge­nannt. Sind meh­re­re Re­kur­si­ons­auf­ru­fe mög­lich, spricht man von kas­ka­den­för­mi­ger Re­kur­si­on.

In bei­den Fäl­len muss der Com­pu­ter sich beim Auf­ruf einer re­kur­si­ven Funk­ti­on20 mer­ken, von wel­cher Stel­le im Pro­gramm­code der Sprung aus­ge­führt wurde, damit es mög­lich ist, nach Ab­schluss des Un­ter­pro­gramms wie­der an die Aus­gangs­adres­se zu­rück­zu­sprin­gen. Hier gilt die Regel, dass die zu­letzt ver­las­se­ne Funk­ti­on zu­erst wie­der fort­ge­setzt wer­den muss, was einem LIFO-Prin­zip ent­spricht. Es muss also ein Stack auf­ge­baut wer­den, der so­ge­nann­te „call stack“.

Zudem wer­den die lo­ka­len Va­ria­blen und Pa­ra­me­ter zwi­schen­ge­spei­chert, so dass sie beim Rück­sprung wie­der­her­ge­stellt wer­den kön­nen. Diese In­for­ma­tio­nen wer­den in so­ge­nann­te „Stack Frames“ zu­sam­men­ge­fasst und in einem ge­son­der­ten Be­reich des Ar­beits­spei­chers (dem „Stack-Seg­ment“) ab­ge­legt. Die­ser Spei­cher um­fasst bei Java (ab­hän­gig von Be­triebs­sys­tem und Pro­zes­sor­ar­chi­tek­tur) stan­dard­mä­ßig zwi­schen 64 und 1024 KB21. Bei re­kur­si­ven Funk­tio­nen kann es pas­sie­ren, dass die­ser Spei­cher voll­läuft. Dies liegt dann meist ent­we­der an einem Pro­gramm­feh­ler (feh­len­de Ab­bruch­be­din­gung) oder daran, dass die über­ge­be­nen Pa­ra­me­ter zu groß sind.

Dar­stel­lun­gen

Den Ab­lauf einer re­kur­si­ven Funk­ti­on kann man mit dem In­halt sei­nes Call-Stacks dar­stel­len. Wir be­trach­ten als Bei­spiel die Fi­bo­nac­ci-Funk­ti­on:

fib(n):
(1)  erg = n
(2)  falls n > 1:
(3)    erg  = fib(n-2)
(4)    erg += fib(n-1)
(5)  return erg

 

Ablauf einer rekursiven Funktion

Bild­quel­le: Ab­lauf einer re­kur­si­ven Funk­ti­on von ZPG In­for­ma­tik [CC BY-NC-SA 3.0 DE], aus 02_hin­ter­grun­d_adts.odt

Bei kas­ka­den­för­mi­ger Re­kur­si­on lässt sich der Ab­lauf auch an­hand eines Re­kur­si­ons­baums dar­stel­len22. Hier­bei steht jeder Kno­ten für einen Funk­ti­ons­auf­ruf, seine Kind­kno­ten sind die von die­sem Funk­ti­ons­durch­lauf aus­ge­hen­den re­kur­si­ven Auf­ru­fe.

Rekursionsbaum

Bild­quel­le: Re­kur­si­ons­baum von ZPG In­for­ma­tik [CC BY-NC-SA 3.0 DE], aus 02_hin­ter­grun­d_adts.odt

Eine sol­che Dar­stel­lung kann bei grö­ße­ren Ein­ga­be­wer­ten schnell un­über­sicht­lich wer­den, daher ist sie nur für klei­ne Ein­ga­ben oder grund­sätz­li­che Über­le­gun­gen sinn­voll.

Der Re­kur­si­ons­baum zeigt auch sehr schön, dass die re­kur­si­ve Im­ple­men­ta­ti­on der Fi­bo­nac­ci-Funk­ti­on wenig ef­fi­zi­ent ist, da immer wie­der die glei­chen Teil­bäu­me ent­ste­hen. Eine bes­se­re Tech­nik wäre ent­we­der eine ite­ra­ti­ve Im­ple­men­ta­ti­on oder die Ver­wen­dung von dy­na­mi­scher Pro­gram­mie­rung bzw. Me­moi­sa­ti­on23 :

cache = new int[…]
   // So viele Plätze, wie der erste Aufruf von fib festlegt
   fib(n):
     falls cache[n] > 0:
       return cache[n]
     erg = n
     falls n > 1:
       erg  = fib(n-2)
       erg += fib(n-1)
     cache[n] = erg
     return erg

 

20 Ge­nau­ge­nom­men ist dies bei jedem Un­ter­pro­gramm nötig. Wenn keine Re­kur­si­on ver­wen­det wird, kön­nen Un­ter­pro­gram­me prin­zi­pi­ell vom Com­pi­ler „ex­pan­diert“ wer­den und man kommt auf As­sem­bler-Ebene ohne Stack aus. Bei re­kur­si­ven Funk­tio­nen ist dies al­ler­dings prin­zi­pi­ell un­mög­lich, da die An­zahl der Funk­ti­ons­auf­ru­fe meist von den Daten ab­hängt.

21 Die Größe des Stackseg­ments kann beim Start der Java-VM ma­nu­ell ge­setzt wer­den: https://​docs.​ora­cle.​com/​cd/​E13150_​01/​jro­ckit_​jvm/​jro­ckit/​jr­docs/​ref­man/​op­ti­onX.​html#​wp999540

22 Die Dar­stel­lungs­form ist auch für li­nea­re Re­kur­sio­nen mög­lich, al­ler­dings ist der Er­kennt­nis­ge­winn re­la­tiv ge­ring.

23 https://​de.​wi­ki­pe­dia.​org/​wiki/​Me­moi­sa­ti­on

 

Hin­ter­grund­in­for­ma­tio­nen: Her­un­ter­la­den [odt][305 KB]

 

Wei­ter zu De­fi­ni­ti­on