Unele probleme sunt rezolvate mai natural prin recurs. De exemplu, o secvență ca secvența Fibonacci are o definiție recursivă. Fiecare număr din secvență este suma celor două numere anterioare din secvență. Problemele care necesită construirea sau traversarea unei structuri de date asemănătoare copacilor pot fi de asemenea rezolvate prin recurs. Antrenați-vă să gândiți recursiv vă va oferi o abilitate puternică de a ataca astfel de probleme.
În acest tutorial, voi trece prin mai multe funcții recursive pas cu pas pentru a vedea cum funcționează și vă arată tehnici pe care le puteți utiliza pentru a defini în mod sistematic funcțiile recursive.
O funcție definită recursiv este o funcție care este definită în termenii unei versiuni mai simple a acesteia. Acesta este un exemplu simplificat:
funcția doA (n) ... doA (n-1);
Pentru a înțelege modul în care funcționează recursionul conceptual, vom examina un exemplu care nu are nimic de-a face cu codul. Imaginați-vă că sunteți responsabil pentru a răspunde la apelurile telefonice la serviciu. Deoarece aceasta este o companie ocupată, telefonul dvs. are mai multe linii telefonice, astfel încât să puteți jongla mai multe apeluri în același timp. Fiecare linie telefonică este un buton de pe receptor și atunci când există un apel primit, butonul va clipi. Astăzi, când ajungeți la serviciu și porniți telefonul, există patru linii care clipeau simultan. Așa că veți ajunge să lucrați la toate apelurile.
Alegeți linia unu și spuneți-le "vă rugăm să țineți". Apoi, ridicați linia doi și puneți-le în așteptare. Apoi, ridicați linia trei și puneți-le în așteptare. În cele din urmă, linia a patra răspundeți și vorbiți cu apelantul. Când ați terminat cu cel de-al patrulea apelant, închideți și preluați al treilea apel. Când terminați cel de-al treilea apel, închideți și ridicați al doilea apel. Când terminați al doilea apel, închideți și ridicați primul apel. Când terminați apelul, puteți pune telefonul în jos.
Fiecare dintre apelurile telefonice din acest exemplu este ca un apel recursiv într-o funcție. Când primiți un apel, acesta este pus în stivă de apeluri (în vorbire de cod). Dacă nu puteți termina imediat un apel, puneți apelul în așteptare. Dacă aveți un apel pentru funcții care nu poate fi evaluat imediat, acesta rămâne în stackul de apeluri. Când puteți răspunde la un apel, acesta este preluat. Când codul dvs. este capabil să evalueze un apel de funcție, acesta este scos din teanc. Țineți cont de această analogie pe măsură ce priviți următoarele exemple de coduri.
Toate funcțiile recursive au nevoie de un caz de bază, astfel încât acestea să se termine. Cu toate acestea, adăugarea unui caz de bază la funcția noastră nu îl împiedică să ruleze infinit. Funcția trebuie să aibă un pas pentru a ne apropia de cazul de bază. Ultimul pas este recursiv. În pasul recursiv, problema este redusă la o versiune mai mică a problemei.
Să presupunem că aveți o funcție care va însuma numerele de la 1 la n. De exemplu, dacă n = 4, suma 1 + 2 + 3 + 4.
În primul rând, determinăm cazul de bază. De asemenea, găsirea cazului de bază poate fi considerată ca găsirea cazului în care problema poate fi rezolvată fără recurs. În acest caz, atunci când n este egal cu zero. Zero nu are părți, deci recursiunea noastră se poate opri când ajungem la 0.
La fiecare pas, veți scade unul din numărul curent. Care este cazul recursiv? Cazul recursiv este suma de funcții numită cu numărul redus.
Suma funcției (num) if (num === 0) return 0; altfel return num + sum (- num) suma (4); // 10
Aceasta este ceea ce se întâmplă la fiecare pas:
Acesta este un alt mod de a vedea modul în care funcția procesează fiecare apel:
sumă (4) 4 + sumă (3) 4 + (3 + sumă (2)) 4 + (3 + (2 + sumă (1))) )) 4 + (3 + (2 + (1 + 0))) 4 + (3 + (2 + 1)) 4 +
Argumentul ar trebui să se schimbe în cazul recursiv și să vă aducă mai aproape de cazul de bază. Acest argument ar trebui testat în cazul de bază. În exemplul precedent, deoarece scădem unul în cazul recursiv, testăm dacă argumentul este egal cu zero în cazul nostru de bază.
se multiplica (2,4)
va întoarce 8. Scrie ce se întâmplă la fiecare pas pentru se multiplica (2,4)
.Recurgerea pe o listă este similară cu recurența unui număr, cu excepția faptului că, în loc de a reduce numărul la fiecare pas, reducem lista la fiecare pas până când ajungem la o listă goală.
Luați în considerare funcția sumă care ia o listă ca intrare și returnează suma tuturor elementelor din listă. Aceasta este o implementare pentru suma funcției:
Suma funcției (l) if (gol (l)) return 0; altfel retur auto (l) + suma (cdr (l));
gol
funcția returnează adevărat dacă lista nu are elemente. mașină
funcția returnează primul element din listă. De exemplu, car ([1,2,3,4])
se întoarce 1. The cdr
funcția returnează lista fără primul element. De exemplu, cdr ([1,2,3,4])
returnează [2,3,4]. Ce se întâmplă când executăm suma ([1,2,3,4])
?
suma [[1,2,3,4]) 1 + suma ([2,3,4]) 1 + (2 + sumă [3,4]) 1 + ]))) 1 + (2 + (3 + (4 + suma ([])))) 1 + (2 + + (2 + 7) 1 + 9 10
Când repetați o listă, verificați dacă este goală. În caz contrar, faceți pasul recursiv pe o versiune redusă a listei.
lungime (['a', 'b', 'c', 'd'])
ar trebui să întoarceți 4. Scrieți ce se întâmplă la fiecare pas.În ultimul exemplu, revenim un număr. Dar să presupunem că vrem să întoarcem o listă. Asta ar însemna că, în loc să adăugăm un număr la pasul nostru recursiv, ar trebui să adăugăm o listă. Luați în considerare funcția elimina
, care ia un element și o listă ca intrare și returnează lista cu elementul eliminat. Numai primul element găsit va fi eliminat.
eliminați funcția (element, l) if (gol (l)) return []; altfel dacă (eq (mașină (l), element)) return cdr (l); altceva returnează (mașină (l), elimină (element, cdr (l))); șterge ('c', ['a', 'b', 'c', 'd']) // ['a', 'b', 'd']
Aici eq
funcția returnează adevărat dacă ambele intrări sunt aceleași. contra
funcția ia un element și o listă ca intrări și returnează o nouă listă cu elementul adăugat la începutul acesteia.
Vom verifica dacă primul element din listă este egal cu elementul pe care dorim să îl eliminăm. Dacă este, eliminați primul element din listă și returnați noua listă. Dacă primul element nu este egal cu elementul pe care dorim să îl eliminăm, luăm primul element din listă și îl adăugăm la pasul recursiv. Pasul recursiv va conține lista cu primul element eliminat.
Vom păstra elemente înlăturate până când ajungem la cazul de bază, care este o listă goală. O listă goală înseamnă că am traversat toate articolele din lista noastră. Ce face eliminați ("c", ['a', 'b', 'c', 'd'])
do?
eliminați ("c", ['a', 'b', 'c', 'd']) cons ('a', eliminați ('c', ['b', 'c', 'd') ) cons ('a', cons ('b', eliminați ('c', ['c', 'd' ('a', ['b', 'd']) ['a', 'b', 'd']
Într-o situație în care trebuie să construim o listă, luăm primul element și îl adăugăm în partea recursivă a listei noastre.
eliminați ("c", ['a', 'b', 'c', 'd', 'c']
returnează ['a', 'b', 'd']. Scrieți ce se întâmplă pas cu pas.Există trei părți la o funcție recursivă. Primul este cazul de bază, care este starea finală. Al doilea este pasul spre a ne apropia de cazul nostru de bază. Al treilea este pasul recursiv, în care funcția se numește cu intrarea redusă.
Recurgerea este ca o iterație. Orice funcție pe care o puteți defini recursiv poate fi de asemenea definită folosind bucle. Alte lucruri care trebuie luate în considerare atunci când se utilizează recursul sunt recurente pe liste imbricate și optimizarea apelurilor dumneavoastră recursive.
O resursă excelentă pentru a continua să înveți despre recursiune este cartea Little Schemer. Te învață să gândești recursiv folosind un format de întrebări și răspunsuri.