Vadovėlis/Rekursinės funkcijos
Vieniems šis skyrius gali pasirodyti naudingas, o kitiems - painus. Jeigu tau informacija pasirodys paini, tai nesuk galvos ir praleisk, prie šio skyriaus galėsi grįžti šiek tiek vėliau.
O dabar pabandom pasižiūrėti į klaidingai parašytą programą:
def manoFunkcija(): print("labas") manoFunkcija() # ojojoi! manoFunkcija()
Tokia programa keletą kartų parašys žodį labas
ir tada nuluš... tai yra, galiausiai išspausdins kokią nors mįslingą klaidą, kaip RecursionError: maximum recursion depth exceeded
. Kodėl?
Priežastis ta, kad manoFunkcija
kiekvieną kartą kviečia save, tai yra, kviečia funkciją manoFunkcija
. O tada manoFunkcija
vėl kviečia save, tai yra manoFunkcija
. Ir taip be galo!
Štai dar vienas panašus (irgi blogas) pavyzdys, pasižiūrėkim:
def pirmaFunkcija(): print("pirma") antraFunkcija() def antraFunkcija(): print("antra") pirmaFunkcija() pirmaFunkcija()
Vėl nulūš programa, vėl nutiks klaida RecursionError
. Priežastis ta pati: pirmaFunkcija
visuomet kviečia funkciją antraFunkcija
, o ši visuomet kviečia pirmaFunkcija
. Ir taip be galo! Nei iš vienos funkcijos niekuomet nėra grįžtama.
Aš paprastai labai atsargiai žiūriu į funkcijas, kurios kviečia pačios save (tiesiogiai arba netiesiogiai), kaip kad pavyzdžiuose aukščiau.
Tačiau iš tiesų net ir tokias funkcijas įmanoma panaudoti, nesugriaunant programos. Tiesiog tokia funkcija turi kažkada nustoti kviesti save, ir tiesiog grįžti (return
).
Štai nenulūžtančios programos pavyzdys:
def manoFunkcija(skaitiklis): skaitiklis = skaitiklis + 1 if skaitiklis > 3: return print("labas") manoFunkcija(skaitiklis) manoFunkcija(0)
Ši programa žodį "labas" išspausdins tik 3 kartus, ir tada baigsis. Jokios klaidos. Ar supratai, kodėl?
Parametras "skaitiklis" yra skirtas apsaugai nuo begalinio kartojimosi. Funkcija manoFunkcija
kiekvieną kartą stropiai pasitikslina, ar skaitiklis
neviršytas, ir kiekvieną kartą pasididina to skaitiklio reikšmę. Kai tik skaitiklis padidėjau daugiau, nei leidžiama, funkcija savęs nebekviečia - tiesiog grįžta.
Taigi, nors funkcija ir kviečia pati save, bet ne begalinį kartų skaičų.
Rekursija
Funkcijos, kurios kreipiasi pačios į save yra vadinamos rekursinėmis funkcijomis. Šio skyriaus pavyzdžiuose panagrinėsime tokias funkcijas. Tai palengvina programavimo užduočių spendimų įgyvendinimą, nes kartais pakanka apsvarstyti tik vieną problemos žingsnį, o ne visą problemą iš karto. Be to tai leidžia išreikšti kai kurias matematines sąvokas paprastu, lengvai skaitomu kodu.
Bet kokią problemą, kurią galime išspręsti naudojant rekursiją, gali būti išspręsta naudojant ciklus. Jie veikia greičiau, bet kartais ciklus sunku atlikti teisingai.
Turbūt intuityviausias „rekursijos“ apibrėžimas yra toks:
REKURSIJA Jei vis dar nesupranti, tai skaityk: REKURSIJA.
Pabandyk perskaityti dar kelis pavyzdžius.
Pavyzdžiai
atgalinis_skaičiavimas.py
def atgalinis_skaičiavimas(n): print(n) if n > 0: return atgalinis_skaičiavimas(n-1) atgalinis_skaičiavimas(5)
Rezultatas:
5 4 3 2 1 0
Šis pavyzdys labai panašus į ankstesnyjį, tik skaitiklis ne didėja, o mažėja. Rekursinė funkcija atgalinis_skaičiavimas
turi vidinę apsaugą nuo begalinio kartojimosi, tikrina kintamąjį n
ir jį mažina, o nustoja save kvietinėti, kai n
sumažėja iki nulio.
sudetingas_pavyzdys.py
def daug(a, b): # daug reiškia daugybą if b == 0: return 0 lik = daug(a, b - 1) #lik reiškia likutį reikšmė = a + lik return reikšmė rezultatas = daug(3, 2) print("3 * 2 = ", rezultatas)
Iš esmės ši programa - tai teigiamų sveikųjų skaičių daugyba (iš tikrųjų Python'o programavimo kalboje daugybos funkcionalumas yra integruotas ir jo išradinėti nebūtina, tiesiog mes pabandėme suprogramuoti, kaip daugybą galima būtų pakeisti sudėties ir atimties veiksmais).
- Kas nutinka, pirmą kartą iškvietus funkciją daug(3, 2)?
- Parametras
a
gauna jam priskirtą reikšmę 3, o parametrasb
- jam priskirtą reikšmę 2. - Kas nutinka tada?
- Vykdoma eilutė
if b == 0:
. Kadangib
reikšmė yra 2, todėl eilutėreturn 0
praleidžiama. - Kas vyksta toliau?
- Vykdoma eilutė
lik = daug(a, b - 1)
. Ši eilutė nustato lokalaus kintamojolik
reikšmę įdaug(a, b - 1)
reikšmę.a
reikšmė yra 3, ob
- 2, todėl funkcija kviečia funkcijądaug(3, 1)
- Taigi kokia yra
daug(3, 1)
reikšmė? - Turime iškviesti funkciją
daug()
su parametrais 3 ir 1. - Kas nutinka toliau?
- Funkcijos viduje
a
reikšmė būtų 3, ob
reikšmė – 1. Kadangi jie yra lokalūs kintamieji, todėl jie neturi įtakos ankstesnėmsa
irb
reikšmėms. - Kas įvyksta toliau?
- Kadangi
b
turi reikšmę 1, if sąlygos rezultatas yra neigiamas, todėl vykdoma kita eilutėlik = daug(a, b - 1)
. - Ką daro ši eilutė?
- Dabar kintamajam
lik
priskiriam funkcijosdaug(3, 0)
reikšmė. - Kokia ši reikšmė?
- Norėdami tai išsiaiškinti, turime dar kartą paleisti funkciją. Šį kartą
a
reikšmė yra 3, ob
– 0. - Kas vyksta toliau?
- Pirmoji vykdytinos funkcijos eilutė yra
if b == 0:
.b
reikšmė 0, todėl kita vykdoma eilutė yrareturn 0
- Ką daro
return 0
eilutė? - Ši eilutė grąžina funkcijos reikšmę lygią 0 į tą vietą, kur ji buvo iškviesta.
- Kas iš to?
- Dabar mes žinome, kad
daug(3, 0)
grąžina reikšmę 0. Dar žinome, ką daro eilutėlik = daug(a, b - 1)
, nes paleidžiame funkcijądaug()
su parametrais 3 ir 0. Baigiame vykdytidaug(3, 0)
ir dabar vėl pradedame vykdytidaug(3, 1)
. Kintamajamlik
priskiriama reikšmė yra 0. - Kurią eilutę kompiuteris skaito po to?
- Toliau vykdoma eilutė
reikšmė = a + lik
. Žinome, kada = 3
irlik = 0
todėl dabarreikšmė = 3
. - Kas nutinka toliau?
- Vykdoma eilutė
return reikšmė
, kuri grąžina reikšmę 3. Šis skaičius atsiranda iš funkcijosdaug (3, 1)
vykdymo. Iškvietusreturn
, grįžtame priedaug(3, 2)
. - Kur yra
daug(3, 2)
? - Mes turėjome kintamuosius
a = 3
irb = 2
ir nagrinėjome eilutęlik = daug(a, b - 1)
. - Kas įvyksta?
- Kintamajam
lik
priskiriama reikšmė 3. Kita eilutėreikšmė = a + lik
priskiria kintamajamreikšmė
reikšmę3 + 3
arba 6. - Kas įvyksta toliau?
- Pradedama vykdyti kita eilutė, kuri grąžina 6 iš funkcijos. Tuomet grįžtame prie eilutės
rezultatas = daug(3, 2)
, kur kintamajamrezultatas
dabar priskiriama reikšmė 6 - Kas nutinka toliau?
- Paleidžiama kita eilutė po funkcijos
print ("3 * 2 =", rezultatas)
. - Ką ji daro?
- Ji spausdina
3 * 2 =
irrezultatas
reikšmę, kuri yra 6. Visa išspausdinta eilutė yra3 * 2 = 6
. - Taigi, kas čia įvyko apskritai?
- Iš esmės panaudojome du skirtingus faktus, kad apskaičiuotume dviejų skaičių kartotinį. Pirmas, kad bet koks skaičius padauginus iš nulio yra nulis
(x * 0 = 0)
. Antras, kad skaičius padaugintas iš kito skaičiaus yra lygus pirmo skaičiaus ir pirmo bei vienetu mažesnio už antrąjį sandaugos sumai(x * y = x + x * (y - 1))
. Taigi ir čia3 * 2
pirmiausiai paverčiamas į3 + 3 * 1
. Tada3 * 1
paverčiamas į3 + 3 * 0
. Tuomet mes žinome, kad bet kuris skaičius padaugintas iš nulio yra nulis, todėl3 * 0
yra 0. Kai viskas surašoma vienoje eilutėje, gauname3 + 3 + 0
Štai kaip viskas veikia:
daug(3, 2) 3 + daug(3, 1) 3 + 3 + daug(3, 0) 3 + 3 + 0 3 + 3 6
faktorialas.py
def faktorialas(n): if n == 0: return 1 if n < 0: return "Klaida, neigiami skaičiai neturi faktorialo reikšmių!!" return n * faktorialas(n - 1) print("2! =", faktorialas(2)) print("3! =", faktorialas(3)) print("4! =", faktorialas(4)) print("5! =", faktorialas(5)) print("-3! =", faktorialas(-3))
Rezultatas:
2! = 2 3! = 6 4! = 24 5! = 120 -3! = Klaida, neigiami skaičiai neturi faktorialo reikšmių!!
Pratimai
1. Parašyk programą, kuri paprašytų vartotojo įvesti skaičių ir laipsnį. Naudodamas rekursija, atspausdink skaičių pakelta duotuoju laipsniu. (Pvz: 23=8)
Sprendimas | |
---|---|
rezultatas = int() def keltiLaipsniu(skaičius, laipsnis): if laipsnis == 0: return 1 return skaičius * keltiLaipsniu(skaičius, laipsnis - 1) rezultatas = keltiLaipsniu(2, 3) print(rezultatas) |
2. Parašyk programą, kuri prašo vartotojo įvesti skaičių ir grąžina sumą nuo 1 iki to skaičiaus. Pavyzdžiui, jei įvedamas skaičius 5, programa grąžina 5 + 4 + 3 + 2 + 1 = 15. Nepamiršk naudoti rekursijos.
Sprendimas | |
---|---|
def suma_iki_vieno(skaičius, suma): if skaičius == 0: return suma else: return skaičius + suma_iki_vieno(skaičius - 1, suma) įvestas_skaičius = int(input("Įvesk skaičių: ")) rezultatas = suma_iki_vieno(įvestas_skaičius, 0) print ("Suma nuo 1 iki", įvestas_skaičius, ':', rezultatas) |
3. Parašyk programą, kuri išveda Fibonačio seką iki įvesto skaičiaus n
. Fibonačio seka - tai tokia sveikųjų skaičių seka, kur kiekvienas skaičius yra lygus prieš jį buvusių skaičių sumai. Pirmieji sekos skaičiai: 0 1 1 2 3 5 8 13 21 ir t.t.
Sprendimas | |
---|---|
def fibonači(n): if n <= 1: return n else: return fibonači(n-1) + fibonači(n-2) įvestas_skaičius = int(input("Įvesk skaičių: ")) for i in range(įvestas_skaičius): print(fibonači(i)) |
4. Parašyk programą, kuri prašo vartotojo įvesti žodį ir grąžina tą žodį parašytą iš kitos pusės. Pvz “Labas” = “sabaL”. Šios užduoties sprendimui tau prireiks daugiau žinių apie sąrašus.
Sprendimas | |
---|---|
def apversk(žodis): if len(žodis) == 0: return žodis else: return apversk(žodis[1:]) + žodis[0] įvestas_žodis = input("Įvesk žodį: ") print(apversk(įvestas_žodis)) |