Arcade-pelien ohjelmointi Pythonilla ja Pygamella
Chapter 19: Rekursio
Pieni lapsi ei saa unta, joten hänen äitinsä kertoo hänelle sadun pikku sammakosta,
joka ei saa nukuttua, joten sammakkoäiti kertoo hänelle sadun pienestä karhusta,
joka ei saa nukuttua, joten hänen äitinsä kertoo hänelle sadun pienestä lumikosta...
joka nukahtaa sikeään uneen.
...ja pikku karhu nukahtaa;
...ja pieni sammakko nukahtaa;
...ja pikku lapsi nukahtaa.
(Tekstin alkuperäinen lähde, josta käännetty: http://everything2.com/title/recursion)
Rekursio on ohjelmointitekniikka, jossa ongelman ratkaisussa algoritmia itseään kutsutaan. Matemaattiset tehtävät kuten kertoman laskeminen ja Fibonaccin lukujono ovat ratkaistavissa rekursiivisesti. Dokumentit, jotka voivat sisältää toisia dokumentteja ja tämä sisältää edelleen dokumentteja, ovat rekursiivisia. Fraktaalit (toistuvat kuviot) ja tietyt biologiaan liittyvät tapahtumat ovat toiminnaltaan rekursiivisia.
19.1 Missä rekursiota voidaan käyttää?
Web-sivut ovat tietyllä tapaa rekursiivisia. Esimerkkinä kuvassa 19.1 on esitettynä web-dokumentti.

Web-dokumentti voidaan sijoittaa “laatikkoon,” joka saa sivun asettelun helpommaksi.
Katso kuva 19.2.

Tämä toimii rekursiivisesti. Jokainen laatikko voi sisältää web-dokumentin, joka voi sisältää toisen
web-dokumentin. Katso tämän havainnollistaminen kuvassa 19.3.

Rekursiivisia algoritmeja käytetään edistyneissä haku- ja lajittelualgoritmeissa. Katsotaan tässä muutama esimerkki.
Rekursiivisen ratkaisun periaatteen tuntemisesta on hyötyä muillekin kuin ohjelmoijille. Esimerkiksi liike-elämässä käytetään rekursiivisia taulukointeja, dokumentteja, yms. ja näiden toimenpiteiden määrittäminen rekursiivisesti on hyvä hallita.
Esimerkiksi, websovellus tarvitsee reseptien tulostamiseen ominaisuuden, jossa reseptiin saadaan aineosat ja määrät. Rekursiivisesti tässä voidaan ajatella, että aineosat ovat sinänsä reseptejä, jossa on ainesosia ja määriä.
19.2 Miten rekursiivinen koodi kirjoitetaan?
Edellä käsittelimme funktioiden yhteydessä funktioita, jotka kutsuvat toisia funktioita. Esimerkiksi:
def f():
g()
print("f")
def g():
print("g")
f()
Funktio voi kutsua myös itseään. Itseään kutsuvaa funktioita kutsutaan rekursiiviseksi funktioksi. Esimerkiksi:
def f():
print("Hello")
f()
f()
Koodi tulostaa Hello ja kutsuu uudelleen itseään, eli
f() funktiota. Tämä saa aikaiseksi Hello tulostuksen uudelleen ja taas uuden
funktiokutsun f(). Tämä jatkuu, kunnes tietokoneen muisti täyttyy ja saa
aikaiseksi virheen stack overflow. Tässä tilanteessa Python generoi pitkän
virheilmoituksen:
RuntimeError: maximum recursion depth exceeded
Virheilmoitus tulee, kun tietokoneen suorituspuskuri täyttyy.
19.3 Rekursion kontrollointi
Rekursion kontrolloimiseksi tarvitaan jokin keino, jolla voidaan välttää loputon funktion itsensä kutsu uudelleen ja uudelleen. Alla esimerkissä kutsukertojen määrää tarkastellaan if lauseessa ja kymmenen kerran täytyttyä, if lohkoa ei enää suoriteta ja rekursio päättyy.
def f(level):
# Pring the level we are at
print("Recursion call, level",level)
# If we haven't reached level ten...
if level < 10:
# Call this function again
# and add one to the level
f(level+1)
# Start the recursive calls at level 1
f(1)
|
Tulostus:
Recursion call, level 1 Recursion call, level 2 Recursion call, level 3 Recursion call, level 4 Recursion call, level 5 Recursion call, level 6 Recursion call, level 7 Recursion call, level 8 Recursion call, level 9 Recursion call, level 10 |
19.4 Kertoman rekursiivinen laskeminen
Kaikki rekursiivisesti ratkaistavat ohjelmointiprobleemat, jotka voidaan ratkaista rekursiivisesti, voidaan ratkaista myös ilman rekursiota. Jotkut ohjelmoijat pitävät rekursiivista tapaa ymmärrettävämpänä.
Kertoman laskeminen on klassinen esimerkki rekursiosta. Kertomaa
käytetään esim. todennäköisyyslaskennassa.
Esimerkiksi:
$10! = 10 \cdot 9 \cdot 8 \cdot 7 \cdot 6 \cdot 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1$
Rekursiivisesti tämä voidaan määritellä:
$n! = \begin{cases} 1 & \text{if } n = 0, \\ n \cdot (n-1)! & \text{if } n > 0. \end{cases}$
Alla on kaksi esimerkkiä, joissa lasketaan $n!$. Ensinnä on ei-rekursiivinen esimerkki ja toinen on sitten rekursiivinen koodi.
# This program calculates a factorial
# WITHOUT using recursion
def factorial_nonrecursive(n):
answer = 1
for i in range(2, n + 1):
answer = answer * i
return answer
# This program calculates a factorial
# WITH recursion
def factorial_recursive(n):
if n == 1:
return n
else:
return n * factorial_recursive(n - 1)
Alla esimerkissä on molemmat koodit yhdistetty samaan tiedostoon. Koodiin on lisätty muutamia print lauseita, jotta funktioiden tapahtumat on helpompi havainnoida.
# This program calculates a factorial
# WITHOUT using recursion
def factorial_nonrecursive(n):
answer = 1
for i in range(2, n + 1):
print(i, "*", answer, "=", i * answer)
answer = answer * i
return answer
print("I can calculate a factorial!")
user_input = input("Enter a number:")
n = int(user_input)
answer = factorial_nonrecursive(n)
print(answer)
# This program calculates a factorial
# WITH recursion
def factorial_recursive(n):
if n == 1:
return n
else:
x = factorial_recursive(n - 1)
print( n, "*", x, "=", n * x )
return n * x
print("I can calculate a factorial!")
user_input = input("Enter a number:")
n = int(user_input)
answer = factorial_recursive(n)
print(answer)
|
Output:
I can calculate a factorial! Enter a number:7 2 * 1 = 2 3 * 2 = 6 4 * 6 = 24 5 * 24 = 120 6 * 120 = 720 7 * 720 = 5040 5040 I can calculate a factorial! Enter a number:7 2 * 1 = 2 3 * 2 = 6 4 * 6 = 24 5 * 24 = 120 6 * 120 = 720 7 * 720 = 5040 5040 |
19.5 Rekursiiviset suorakulmiot
Rekursio voidaan esittää visuaalisesti seuraavalla Pygame ohjelmalla,
19.4, jossa rekursiivisesti piirretään
sisäkkäisiä suorakulmioita. Seuraava suorakulmio on 20% pienempi kuin edellinen. Katso koodia
tarkemmin ja rekursiivinen funktiokutsu recursive_draw.

"""
Recursively draw rectangles.
Sample Python/Pygame Programs
Simpson College Computer Science
http://programarcadegames.com/
http://simpson.edu/computer-science/
"""
import pygame
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
def recursive_draw(x, y, width, height):
""" Recursive rectangle function. """
pygame.draw.rect(screen, BLACK,
[x, y, width, height],
1)
# Is the rectangle wide enough to draw again?
if(width > 14):
# Scale down
x += width * .1
y += height * .1
width *= .8
height *= .8
# Recursively draw again
recursive_draw(x, y, width, height)
pygame.init()
# Set the height and width of the screen
size = [700, 500]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My Game")
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# -------- Main Program Loop -----------
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# Set the screen background
screen.fill(WHITE)
# ALL CODE TO DRAW SHOULD GO BELOW THIS COMMENT
recursive_draw(0, 0, 700, 500)
# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Limit to 60 frames per second
clock.tick(60)
# Be IDLE friendly. If you forget this line, the program will 'hang'
# on exit.
pygame.quit()
19.6 Fraktaalit
Fraktaalit on määritelty rekursiivisesti. Tässä esimerkissä on yksinkertainen fraktaali, joka muuttuu
rekursion “syvyyden” mukaan.




"""
Sample fractal using recursion.
Sample Python/Pygame Programs
Simpson College Computer Science
http://programarcadegames.com/
http://simpson.edu/computer-science/
"""
import pygame
# Define some colors
black = (0, 0, 0)
white = (255, 255, 255)
green = (0, 255, 0)
red = (255, 0, 0)
def recursive_draw(x, y, width, height, count):
# Draw the rectangle
# pygame.draw.rect(screen,black,[x,y,width,height],1)
pygame.draw.line(screen,
black,
[x + width*.25, height // 2 + y],
[x + width*.75, height // 2 + y],
3)
pygame.draw.line(screen,
black,
[x + width * .25, (height * .5) // 2 + y],
[x + width * .25, (height * 1.5) // 2 + y],
3)
pygame.draw.line(screen,
black,
[x + width * .75, (height * .5) // 2 + y],
[x + width * .75, (height * 1.5) // 2 + y],
3)
if count > 0:
count -= 1
# Top left
recursive_draw(x, y, width // 2, height // 2, count)
# Top right
recursive_draw(x + width // 2, y, width // 2, height // 2, count)
# Bottom left
recursive_draw(x, y + width // 2, width // 2, height // 2, count)
# Bottom right
recursive_draw(x + width // 2, y + width // 2, width // 2, height // 2, count)
pygame.init()
# Set the height and width of the screen
size = [700, 700]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My Game")
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# -------- Main Program Loop -----------
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# Set the screen background
screen.fill(white)
# ALL CODE TO DRAW SHOULD GO BELOW THIS COMMENT
fractal_level = 3
recursive_draw(0, 0, 700, 700, fractal_level)
# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Limit to 20 frames per second
clock.tick(20)
# Be IDLE friendly. If you forget this line, the program will 'hang'
# on exit.
pygame.quit()
19.7 Rekursiivinen binaarihaku
Rekursiota voidaan käyttää binaarihaussa. Tässä on ensin esimerkki ei-rekursiivisesta ratkaisusta, joka esitettiin kappaleessa 15:
def binary_search_nonrecursive(search_list, key):
lower_bound = 0
upper_bound = len(search_list) - 1
found = False
while lower_bound < upper_bound and found == False:
middle_pos = (lower_bound + upper_bound) // 2
if search_list[middle_pos] < key:
lower_bound = middle_pos + 1
elif list[middle_pos] > key:
upper_bound = middle_pos
else:
found = True
if found:
print( "The name is at position",middle_pos)
else:
print( "The name was not in the list." )
binary_search_nonrecursive(name_list,"Morgiana the Shrew")
Sama binaarihaku on alla kirjoitettuna rekursiivisella ratkaisulla:
def binary_search_recursive(search_list, key, lower_bound, upper_bound):
middle_pos = (lower_bound + upper_bound) // 2
if search_list[middle_pos] < key:
binary_search_recursive(search_list,
key,
middle_pos + 1,
upper_bound)
elif search_list[middle_pos] > key:
binary_search_recursive(search_list,
key,
lower_bound,
middle_pos )
else:
print("Found at position", middle_pos)
lower_bound = 0
upper_bound = len(name_list) - 1
binary_search_recursive(name_list,
"Morgiana the Shrew",
lower_bound,
upper_bound)
19.8 Review
19.8.1 Short Answer Worksheet
Klikkaa tästä kappaleen kertaustehtäviin.
English version by Paul Vincent Craven
Spanish version by Antonio Rodríguez Verdugo
Russian version by Vladimir Slav
Turkish version by Güray Yildirim
Portuguese version by Armando Marques Sobrinho and Tati Carvalho
Dutch version by Frank Waegeman
Hungarian version by Nagy Attila
Finnish version by Jouko Järvenpää
French version by Franco Rossi
Korean version by Kim Zeung-Il
Chinese version by Kai Lin