Rekursiivne vs iteratiivne meetod

Iteratiivne meetod tähendab, et kasutatakse tsükleid. Rekursiivset funktsiooni on alati võimalik ümber kirjutada nii, et see oleks iteratiivne.

Faktoriaali iteratiivne arvutamine

def factorial_iter(n):
    if n == 0:
        return 1

    answer = 1
    for i in range(1, n + 1): # range(1, n + 1) => [1, 2, 3, 4, 5, 6, ..., n - 1, n]
        answer = answer * i

    return answer

Sellel juhul on see meetod efektiivsem, kuna see suudab suurte arvude faktoriaali arvutada. Rekursiivne meetod viskab aga suurte arvude korral RecursionError, sest on liiga palju rekursiivseid väljakutseid.

Koodinäide: Fibonacci arvud

Meil on piisavalt teadmisi Fibobacci arvudest.

F[n] = F[n-1] + F[n-2]
F[0] = 0, F[1] = 1, F[2] = 1

Kirjutame funktsiooni.

Sisend: n - indeks Fibonacci arvujadas. Väljund: Fibonacci arv, mis vastab indeksile n.

def fib(n):
     if n == 0:
         return 0

     if n == 1 or n == 2:
         return 1

     return fib(n - 1) + fib(n - 2)

Iteratiivne funktsioon

Siin kasutame kahte muutujat, et hoida mälus kahte viimast liiget. Iga kord, kui arvutame uut väärtust cur, paneme prev2 väärtuse muutujasse prev1 ja cur väärtuse paneme muutujasse prev2. Võiks ka kasutada list-i, aga see võtab rohkem mälu.

def fib_iter(n):
    if n == 0:
        return 0

    if n == 1:
        return 1

    prev1 = 0 # num0
    prev2 = 1 # num1

    # if n < 2 answer is already given
    for i in range(2, n + 1):
        cur = prev1 + prev2 # new value (num2)
        prev1 = prev2 # num1 becomes num0
        prev2 = cur   # num2 becomes num1

    return prev2

Efektiivsus

Iteratiivne meetod töötab kiiremini kui rekursiivne, sest rekursiivne ei jäta meelde kahte viimast väärtust ja arvutab neid kogu aeg uuesti. Nüüd on meil idee, kuidas teha rekursiivset lahendust kiiremaks: jätame meelde vanu väärtusi ja anname need väljakutsel lisaargumendina.

def fib(n, cache={0: 0, 1: 1, 2: 1}): # cache already contains 3 known values
    if n in cache: # if value is in the cache, return it
        return cache[n]

    # if the value is not in the cache, add and return it
    # notice that we add new value to the cache and give it as the additional argument
    cache[n] = fib(n - 1, cache) + fib(n - 2, cache)
    return cache[n]

Parem kood:

def fib(n, cache=None):
    if cache is None:
        cache = {0: 0, 1: 1, 2: 1} # cache already contains 3 known values

    if n in cache: # if value is in the cache, return it
        return cache[n]

    # if the value is not in the cache, add and return it
    # notice that we add new value to the cache and give it as the additional argument
    cache[n] = fib(n - 1, cache) + fib(n - 2, cache)
    return cache[n]

Võrdleme kiirust. Selleks arvutame 500 väärtust ja vaatame, kui palju aega see kummalgi meetodil võtab.

Kood võrdlemiseks:

import time

if __name__ == '__main__':
    print("test for recursive method")
    started = time.time()
    for i in range(500):
        fib(i)

    print("Total: " + str(time.time() - started) + " secs")

    print("test for iterative method")
    started = time.time()
    for i in range(500):
        fib_iter(i)

    print("Total: " + str(time.time() - started) + " secs")
test for recursive method
Total: 0.0004994869232177734 secs
test for iterative method
Total: 0.021016836166381836 secs

Rekursiivne meetod, mis jätab väärtusi meelde, osutus kiiremaks kui iteratiivne. Kuid on arusaadav, et rekursiivne meetod kasutab ka palju mälu. Kui mälu ei kasuta, siis on arvutil juba raske 30. väärtust arvutada. Võite proovida sama asja faktoriaali lahenduse jaoks.