Tilbake
5.2

5.2 Rekursjon — spore og skrive

Forstå rekursjon gjennom de klassiske eksemplene — fakultet, gcd (Euklid), Fibonacci, tverrsum, potens — og både spore og skrive dem.

55 min
11 oppgaver
Rekursjonsporeskrive
Din fremgang i kapitlet
0 / 11 oppgaver
Kapitlets plass i kurset
Forkunnskaper

- kap. 2.1 — funksjoner: def, parametere, return og at return avslutter funksjonen umiddelbart. Uten dette gir rekursjon ingen mening.
- kap. 1.2if/else, som er det grunntilfellet skrives med.
- kap. 1.1// og %, som brukes i tverrsum og i Euklids algoritme.
- kap. 5.1 — sporingstavla. Rekursjonssporing er den samme metoden med en ekstra kolonne.

Vil du ha en mykere inngang til begrepet før du leser videre, dekker Rekursjon og algoritmekompleksitet det samme stoffet på et roligere nivå.

Du trenger ingen ny Python her. Rekursjon er ikke en ny konstruksjon — det er en måte å bruke def, if og return på.

Økt 1 — hva rekursjon er (~12 min)

Du står bakerst i en kø på et bibliotek og vil vite hvor mange som står foran deg. Du kan ikke se hele køen. Så du gjør det eneste du kan: du spør personen foran deg om det samme spørsmålet, og legger til én på svaret hun gir.

Hun gjør akkurat det samme. Og hun foran henne. Helt til noen står forrest og kan svare uten å spørre videre: «null foran meg». Da folder svarene seg tilbake gjennom køen — 0, 1, 2, 3 — til det når deg.

Det er hele rekursjonen. Én regel for det enkle tilfellet ingen trenger hjelp til, og én regel som løser det store problemet ved å stille det samme spørsmålet om et litt mindre problem.

Rekursjon

En rekursiv funksjon er en funksjon som kaller seg selv med et mindre argument, og som har en utgang der den svarer direkte uten å kalle seg selv igjen.

Den er ikke magi og ikke en ny språkkonstruksjon: hvert kall er et helt vanlig funksjonskall, med sine egne lokale variabler, som venter på at svaret fra det neste kallet skal komme tilbake.

Rekursjon løser problemer som kan sies slik: «svaret for n er noe enkelt gjort med svaret for n - 1».

📜Rekursjonsmalen — grunntilfelle og steg

Hver eneste rekursive funksjon i dette emnet har nøyaktig samme form. Skriv malen først, fyll inn etterpå:

def f(n):
    if <grunntilfelle>:
        return <enkelt svar>
    return <noe> f(<mindre argument>)

Blokken over er en mal, ikke kjørbar kode — den viser formen, ikke en ferdig funksjon.

Tre spørsmål, i denne rekkefølgen, gir deg alltid funksjonen:

1. Når er svaret opplagt? Det er grunntilfellet. For fakultet er det n <= 1; for tverrsum er det n == 0; for største felles divisor er det at resten er 0.
2. Hvordan blir problemet mindre? Argumentet må nærme seg grunntilfellet i hvert steg: n - 1, n // 10, a % b, x[1:].
3. Hva gjør du med svaret som kommer tilbake? Gang med n, legg til et siffer, summer to kall. Dette står FORAN eller RUNDT det rekursive kallet.

Rekkefølgen er ufravikelig: grunntilfellet skrives først. Skriver du det rekursive steget øverst, blir det aldri noen utgang.

Grunntilfelle

Det tilfellet funksjonen kan svare på direkte, uten å kalle seg selv. På engelsk kalles det base case, og det ordet møter du i mye kodelitteratur.

Grunntilfellet står alltid først i funksjonen, som en if med en return. Uten det får du en funksjon som kaller seg selv i det uendelige, og Python stopper den til slutt med en RecursionError.

Et godt grunntilfelle dekker også de rare inndataene: fakultet(0) skal gi 1, ikke krasje, og derfor skrives testen if n <= 1, ikke if n == 1.

Rekursivt steg

Linja der funksjonen kaller seg selv med et mindre argument, og gjør noe med svaret som kommer tilbake.

return n * fakultet(n - 1) er et rekursivt steg: kallet gjøres først, og multiplikasjonen skjer når svaret kommer tilbake.

To ting må være på plass, ellers virker ikke funksjonen: argumentet må faktisk bli mindre, og resultatet av kallet må brukes. Glemmer du return foran kallet, regner funksjonen riktig og returnerer None likevel.

Argumentet må nærme seg grunntilfellet

Hvert rekursivt kall må flytte argumentet nærmere grunntilfellet, ellers stopper funksjonen aldri.

FunksjonGrunntilfelleSlik blir argumentet mindre
fakultetn <= 1n - 1
tverrsumn == 0n // 10
største felles divisorb == 0a % b
potensn == 0n - 1
sum av listetom listex[1:]

Dette er kjernen i feilkode #10: skriver du f(n + 1) i stedet for f(n - 1), går argumentet feil vei, og funksjonen når aldri grunntilfellet.

✏️Eksempel 1: fakultet, malen fylt ut

Fakultet av n er produktet av alle heltallene fra 1 til n: 4!=4×3×2×1=244! = 4 \times 3 \times 2 \times 1 = 24. Skriv den rekursivt, og vis hva fakultet(4) og fakultet(0) gir.

Steg 1 — når er svaret opplagt? For n lik 0 eller 1 er fakultet 1. Vi skriver testen som n <= 1, så begge dekkes.

Steg 2 — hvordan blir problemet mindre? n - 1.

Steg 3 — hva gjør vi med svaret? Ganger det med n.

def fakultet(n):
    if n <= 1:
        return 1
    return n * fakultet(n - 1)

print(fakultet(4))
print(fakultet(0))
print(fakultet(1))

Utskrift:

24
1
1

Margnotat: på eksamen gir denne funksjonen full uttelling slik den står. Sensor krever ikke feilhåndtering for negative tall med mindre oppgaven ber om det — men n <= 1 i stedet for n == 1 er et lite pluss, fordi den også svarer riktig på 0.

Den samme jobben med løkke. Begge gir full pott; oppgaveteksten avgjør hvilken du skal bruke, og ber den om «en rekursiv funksjon», er løkka feil svar selv om den regner riktig:

def fakultet_lokke(n):
    total = 1
    for i in range(2, n + 1):
        total = total * i
    return total

print(fakultet_lokke(4))

Utskrift:

24
📝Oppgave 1

(Innstegsoppgave, sjanger J — rekursjon, spore.) Funksjonen er den samme som i eksempelet over. Hva skriver programmet ut?

def fakultet(n):
    if n <= 1:
        return 1
    return n * fakultet(n - 1)

print(fakultet(3))

Økt 2 — å spore et rekursivt kall (~12 min)

Sporing av rekursjon er den vanligste eksamensformen, og den har sin egen faste metode. Den vanlige sporingstavla fra kap. 5.1 holder ikke, fordi flere kall er i gang samtidig og venter på hverandre.

📜Rekursjonssporing — utover, så innover
Alltid to lister. Aldri bare én.

1. Bygg kall-stakken utover. Skriv ett kall per linje, med argumentet, og hva kallet venter på. Fortsett nedover til du treffer grunntilfellet. Da vet du hvor dypt det går, og hvor mange kall som venter.

2. Fold returverdiene innover. Start på den NEDERSTE linja — den med grunntilfellet — og regn deg oppover. Hvert kall bruker svaret fra kallet under seg.

Dette er den ene tingen som skiller riktig fra galt svar på eksamen. Nesten alle som svarer feil, har hoppet over det ene av de to stegene og prøvd å regne alt i én retning.

Kontrollspørsmålet til slutt: hvor mange kall ble gjort? Er svaret ditt bygd av like mange ledd? For fakultet(5) er det fem kall og fire multiplikasjoner.

Kall-stakken

Rekka av kall som er startet, men ikke ferdige. Hvert kall har sine egne lokale variabler, og de ligger «oppå hverandre» — det siste startede kallet blir det første som blir ferdig.

På papiret tegner du den som en trapp nedover, med argumentet på hver linje. Dybden er antall linjer, og den er ofte det oppgaven egentlig spør om.

Foldingen innover

Andre halvdel av sporingen: når grunntilfellet har svart, regnes hvert ventende kall ferdig, nedenfra og opp.

Det er her rekkefølgen ofte går galt. fakultet(4) er ikke 4×34 \times 3; det er 4 ganger svaret fra fakultet(3), som igjen er 3 ganger svaret fra fakultet(2). Skriv hvert ledd på egen linje med tallverdien innsatt, så ser du kjeden.

`return` foran det rekursive kallet

Resultatet av et rekursivt kall forsvinner hvis du ikke gjør noe med det. fakultet(n - 1) alene på en linje regner ut hele kjeden og kaster svaret.

Derfor har hvert rekursivt steg i denne boka return foran seg. Glemmer du det, returnerer funksjonen None, og feilen er stum: koden krasjer ikke, den svarer bare None — som er nøyaktig det en sporingsoppgave er ute etter å teste.

✏️Eksempel 2: tverrsum, sporet begge veier
Tverrsummen av et tall er summen av sifrene: tverrsummen av 4072 er 4 + 0 + 7 + 2 = 13. Skriv funksjonen rekursivt, og spor tverrsum(4072).

Siste siffer i et tall er n % 10. Resten av tallet, uten det siste sifferet, er n // 10. Det gir både steget og grunntilfellet: når det ikke er flere sifre igjen, er n blitt 0.

def tverrsum(n):
    if n == 0:
        return 0
    return n % 10 + tverrsum(n // 10)

print(tverrsum(4072))

Utskrift:

13

Kall-stakken utover:

tverrsum(4072)  ->  2 + tverrsum(407)
tverrsum(407)   ->  7 + tverrsum(40)
tverrsum(40)    ->  0 + tverrsum(4)
tverrsum(4)     ->  4 + tverrsum(0)
tverrsum(0)     ->  grunntilfellet: 0

Foldingen innover:

tverrsum(0)    = 0
tverrsum(4)    = 4 + 0  = 4
tverrsum(40)   = 0 + 4  = 4
tverrsum(407)  = 7 + 4  = 11
tverrsum(4072) = 2 + 11 = 13

Margnotat: fem kall og fire addisjoner — det stemmer med at tallet har fire sifre. Bruk den kontrollen hver gang.

Legg merke til at nullen i 4072 ikke er et spesialtilfelle. Den legger til 0, og 40 // 10 er 4, så kjeden fortsetter som normalt.

📝Oppgave 2
Eksamensnivå, sjanger J

Hva skriver programmet ut? Vis både kall-stakken og foldingen.

def tverrsum(n):
    if n == 0:
        return 0
    return n % 10 + tverrsum(n // 10)

print(tverrsum(915))
print(tverrsum(0))

Økt 3 — de fem klassikerne (~18 min)

Rekursjonsoppgavene på settet er nesten alltid en av fem funksjoner. Kjenner du dem igjen på formen, sporer du dem på et minutt. Du har allerede sett fakultet og tverrsum; her kommer de tre siste.

De fem oppslagene under er flashcard-stoff — hopp trygt over ved førstegangslesing og kom tilbake til dem når du pugger. Gjennomgangen fortsetter i eksempel 3 rett etterpå.

— naturlig pausepunkt —

Fakultet

Produktet av alle heltallene opp til n: 5!=5×4×3×2×1=1205! = 5 \times 4 \times 3 \times 2 \times 1 = 120.

def fakultet(n):
    if n <= 1:
        return 1
    return n * fakultet(n - 1)

Grunntilfellet er n <= 1 og gir 1. Vokser fort: fakultet(10) er over tre millioner. Blandes ofte med potens på eksamen — fakultet ganger med et synkende tall, potens ganger med det samme tallet hver gang.

Største felles divisor — Euklids algoritme

Det største tallet som deler begge tallene. Euklids observasjon er at største felles divisor av a og b er den samme som av b og resten a % b.

def sfd(a, b):
    if b == 0:
        return a
    return sfd(b, a % b)

Grunntilfellet er b == 0, og svaret er da a. På eksamen står funksjonen ofte under navnet gcd (engelsk for greatest common divisor) eller med et intetsigende navn som myst, og oppgaven spør hva den regner ut.

Fibonacci-tallene

Rekka 0, 1, 1, 2, 3, 5, 8, 13 …, der hvert tall er summen av de to foregående.

def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

To grunntilfeller i ett: fib(0) er 0 og fib(1) er 1, og begge dekkes av if n <= 1: return n. Dette er den eneste klassikeren med to rekursive kall i samme uttrykk, og derfor den mest krevende å spore.

Tverrsum

Summen av sifrene i et positivt heltall: tverrsummen av 915 er 9 + 1 + 5 = 15.

def tverrsum(n):
    if n == 0:
        return 0
    return n % 10 + tverrsum(n // 10)

Bygger på at n % 10 er siste siffer og n // 10 er tallet uten det. Grunntilfellet er n == 0, som er det du sitter igjen med når alle sifrene er brukt opp.

Rekursiv potens
grunntall opphøyd i eksponent, regnet ved å gange grunntallet med seg selv én gang mindre for hvert steg.

def potens(g, e):
    if e == 0:
        return 1
    return g * potens(g, e - 1)

Grunntilfellet er e == 0 og gir 1, fordi ethvert tall opphøyd i null er 1. Merk at det er eksponenten som telles ned, mens grunntallet står stille — det er forskjellen fra fakultet, der tallet som ganges selv blir mindre.

To rekursive kall i samme uttrykk

Når et rekursivt steg inneholder to kall, som fib(n - 1) + fib(n - 2), blir sporingen et tre i stedet for en trapp.

Metoden er den samme, men du må regne den ene grenen helt ferdig før du starter på den andre, og skrive delsvarene ned underveis. På papiret lønner det seg å regne små verdier først og bygge oppover: fib(0), fib(1), fib(2) … til du er framme.

Det er også grunnen til at rekursiv Fibonacci er treg: fib(5) gjør 15 kall, og de fleste av dem regner ut det samme om igjen.

✏️Eksempel 3: den navnløse funksjonen på eksamen

Dette er en typisk oppgaveform: en rekursiv funksjon uten sigende navn. Hva returnerer myst(84, 36), og hva regner funksjonen ut?

def myst(a, b):
    if b == 0:
        return a
    return myst(b, a % b)

Kall-stakken utover:

myst(84, 36)  ->  84 % 36 er 12  ->  myst(36, 12)
myst(36, 12)  ->  36 % 12 er 0   ->  myst(12, 0)
myst(12, 0)   ->  grunntilfellet: 12

Foldingen innover. Her er den kort: ingen av kallene gjør noe med svaret som kommer tilbake — de returnerer det uendret. Svaret fra bunnen bobler rett opp:

myst(12, 0)  = 12
myst(36, 12) = 12
myst(84, 36) = 12

def myst(a, b):
    if b == 0:
        return a
    return myst(b, a % b)

print(myst(84, 36))
print(myst(36, 84))

Utskrift:

12
12

Hva regner den ut? Største felles divisor — det største tallet som deler begge. 12 deler både 84 og 36, og ingen større gjør det.

Margnotat: legg merke til myst(36, 84) i det andre kallet. Med argumentene i «feil» rekkefølge gir første steg myst(84, 36 % 84), altså myst(84, 36) — funksjonen ordner rekkefølgen selv i én ekstra runde. Det er et yndet oppfølgingsspørsmål.

Margnotat 2: spørres du «forklar med én setning hva funksjonen gjør», er svaret «Returnerer største felles divisor av a og b.» Den sjangeren driller du i kap. 5.3.

📝Oppgave 3
Eksamensnivå, sjanger J

Hva skriver programmet ut?

def myst(a, b):
    if b == 0:
        return a
    return myst(b, a % b)

print(myst(48, 18))

✏️Eksempel 4: Fibonacci, treet i stedet for trappa

Fibonacci-rekka er 0, 1, 1, 2, 3, 5, 8 … Hva returnerer fib(5), og hvor mange kall gjøres?

Med to rekursive kall i samme uttrykk blir sporingen et tre. Den raskeste sikre metoden på papir er å regne nedenfra og opp, én verdi om gangen:

fib(0) = 0            (grunntilfelle)
fib(1) = 1            (grunntilfelle)
fib(2) = fib(1) + fib(0) = 1 + 0 = 1
fib(3) = fib(2) + fib(1) = 1 + 1 = 2
fib(4) = fib(3) + fib(2) = 2 + 1 = 3
fib(5) = fib(4) + fib(3) = 3 + 2 = 5

For å telle kallene legger vi inn en teller. Det er ikke noe du gjør på eksamen — det er for å vise hvor mye arbeid som gjøres om igjen:

antall = 0

def fib(n):
    global antall
    antall = antall + 1
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(5))
print(antall)

Utskrift:

5
15

Margnotat: 15 kall for å regne ut det sjette tallet i rekka. fib(2) regnes ut tre ganger, fib(1) fem ganger. Det er poenget når en teorioppgave spør hvorfor rekursiv Fibonacci er treg.

Margnotat 2: global brukes bare her, som et måleverktøy. Bokas vanlige funksjoner tar imot data som parametere og returnerer svar — se kap. 2.1.

📝Oppgave 4
Eksamensnivå, sjanger J

Hva skriver programmet ut?

def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(6), fib(1), fib(0))

`RecursionError` — når grunntilfellet aldri nås

Python setter en øvre grense for hvor dypt kall kan stables — som regel rundt tusen nivåer. Nås grensen, avbrytes programmet med meldingen RecursionError: maximum recursion depth exceeded.

Det er et stumt symptom, ikke en diagnose: feilen ligger alltid enten i grunntilfellet eller i at argumentet ikke blir mindre. På en sporingsoppgave er «programmet stopper aldri / gir RecursionError» et fullgodt svar når koden faktisk har den feilen.

Rekursjonsdybde

Antall nivåer i kall-stakken før grunntilfellet nås. For fakultet(5) er dybden 5; for tverrsum(4072) er den 5 (fire sifre pluss grunntilfellet).

Dybden er et vanlig oppfølgingsspørsmål på eksamen: «hvor mange ganger kalles funksjonen?». Svaret leser du rett av kall-stakken du allerede har tegnet — og husk å telle med kallet som treffer grunntilfellet.

Rekursjon eller løkke

Alt du kan gjøre rekursivt, kan du gjøre med en løkke, og omvendt. Valget er et spørsmål om hva oppgaven ber om, og hva som blir lesbart.

Rekursjon passer nårLøkke passer når
Problemet deler seg naturlig i «ett steg + samme problem»Du går gjennom en samling én gang
Du følger en kjede med ukjent lengdeDu vet hvor mange runder det er
Oppgaven ber eksplisitt om en rekursiv funksjonAlt annet

På eksamen er regelen enkel: ber oppgaveteksten om en rekursiv funksjon, gir en løkkeløsning lite eller ingen uttelling, selv om den regner riktig. Og motsatt — står det ingenting, velger du selv, og løkka er som regel den tryggeste.

Rekursjon over lister og strenger

Det samme mønsteret virker på sekvenser: det første elementet behandles direkte, og resten sendes videre som x[1:].

def total(x):
    if len(x) == 0:
        return 0
    return x[0] + total(x[1:])

Grunntilfellet er den tomme sekvensen, og x[1:] er «alt unntatt det første» — et utsnitt du kjenner fra kap. 3.1. Merk at x[1:] lager en ny liste for hvert kall; det er greit på eksamensstørrelser, men er grunnen til at en løkke er raskere i praksis.

Økt 4 — skrive rekursive funksjoner selv (~13 min)

Å skrive er lettere enn å spore, når du bruker malen. De tre spørsmålene fra rekursjonsmalen — når er svaret opplagt, hvordan blir problemet mindre, hva gjør du med svaret — gir deg funksjonen nesten mekanisk.

📝Oppgave 5
Eksamensnivå, sjanger J

Skriv en rekursiv funksjon potens(g, e) som regner ut g opphøyd i e for et ikke-negativt heltall e. Du skal ikke bruke ** eller noen løkke.

Vis også hva potens(2, 5) og potens(7, 0) gir.

📝Oppgave 6
Eksamensnivå, sjanger J

En strømleverandør lagrer forbruket for hver time i ei liste med tall. Skriv en rekursiv funksjon total(x) som summerer alle tallene i lista, uten å bruke sum og uten løkke. Funksjonen skal gi 0 for ei tom liste.

Vis hva funksjonen gir for [3, 1, 4, 1] og for [].

📝Oppgave 7
Eksamensnivå, sjanger J

Skriv en rekursiv funksjon antall_sifre(n) som teller hvor mange sifre et positivt heltall har. antall_sifre(4072) skal gi 4 og antall_sifre(7) skal gi 1.

Forklar til slutt i én setning hvorfor grunntilfellet ikke kan være n == 0.

📝Oppgave 8
Eksamensnivå, sjanger J

Funksjonen under skal regne ut fakultet, men den virker ikke. Hva skjer når du kaller fakultet(4), og hva er den minste endringen som retter feilen?

def fakultet(n):
    if n <= 1:
        return 1
    return n * fakultet(n + 1)

📝Oppgave 9
Eksamensnivå, sjanger J

Funksjonen skriver ut underveis. Hva skriver programmet ut?

def ned(n):
    if n == 0:
        print('start')
        return
    print(n)
    ned(n - 1)
    print(-n)

ned(3)

📝Oppgave 10
Eksamensnivå, sjanger J

To funksjoner med nesten samme kropp. Hva skriver programmet ut?

def a(n):
    if n <= 1:
        return 1
    return n * a(n - 1)

def b(n):
    if n <= 1:
        return 1
    return 2 * b(n - 1)

print(a(5), b(5))

📝Oppgave 11
Eksamensnivå, sjanger J

Skriv en rekursiv funksjon snu(s) som returnerer strengen s baklengs. Du skal ikke bruke s[::-1] og ingen løkke.

Vis hva snu('bussrute') gir, og bruk funksjonen til å teste om 'regninger' og setningen 'agnes i senga' er palindromer — altså like lest begge veier.

API- og konstruksjonsliste

Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.

Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Norges teknisk-naturvitenskapelige universitet. Dette er ikke offisielt studiemateriell. Les mer.