Динамическое программирование последовательности / числа Фибоначчи - PullRequest
0 голосов
/ 15 июня 2019

Я пытаюсь улучшить свои навыки логики программирования, и я смотрел одно из видео о том, как приблизиться к числам Фибоначчи.

Посмотрев на псевдокод на 6:34, я написал это:

In [14]: def my_fib(x, memo=dict()):
    ...:     if memo.get(x):
    ...:         return memo[x]
    ...:     if x == 1 or x == 2:
    ...:         result = 1
    ...:     else:
    ...:         result = my_fib(x - 1, memo) + my_fib(x -2, memo)
    ...:     memo[x] = result
    ...:     return result

Что прекрасно работает, однако, когда я смотрел видео до конца, когда парень оскорблял свой код Python, яобнаружил, что он немного отличается от моего.

Код CS Dojo:

In [68]: def fib_dyn_2(x, memo):     
    ...:     if memo[x] is not None:
    ...:         return memo[x]
    ...:     if x == 1 or x == 2:
    ...:         result = 1
    ...:     else:
    ...:         result = fib_dyn_2(x-1, memo) + fib_dyn_2(x-2, memo)
    ...:     memo[x] = result
    ...:     return result
    ...: 
    ...: def fib_memo(x):
    ...:     memo = [None] * (x + 1)
    ...:     return fib_dyn_2(x, memo)

Существует небольшая разница: я использую словарь для кэширования, он использует список.

ЧтоЯ понял, что мой код выглядит немного быстрее.При наборе номеров в последовательности X >= 100, а также при запуске одного и того же номера последовательность повторяется более одного раза.

т.е. мой код:

In [4]: %time my_fib(100)
CPU times: user 70 µs, sys: 44 µs, total: 114 µs
Wall time: 92 µs
Out[4]: 354224848179261915075L

Код CS Dojo:

In [5]: %time fib_memo(100)
CPU times: user 99 µs, sys: 128 µs, total: 227 µs
Wall time: 187 µs
Out[5]: 354224848179261915075L

Вопрос в том, какой из них «лучше» или более желателен в качестве ответа?

Ответы [ 4 ]

1 голос
/ 15 июня 2019

Хотя запомненная версия вычисления чисел Фибоначчи намного лучше, чем наивный, рекурсивный подход, я рекомендую вам проверить решение на основе Матричной формы чисел Фибоначчи:

https://stackoverflow.com/a/23462371/1570854

0 голосов
/ 16 июня 2019

Немного поэкспериментировав, я нашел вариант вашего кода, который превосходит всех других кандидатов по времени @AlainT., Даже итерационный один. Есть два места, производительность которых теряется. Во-первых, это логика:

if memo.get(x):

медленнее, чем простой:

if x in memo:

Так как при попадании вы в конечном итоге ищите значение дважды , а не один раз в следующей строке. Однако более существенное улучшение происходит здесь:

result = my_fib(x - 1, memo) + my_fib(x - 2, memo)

Вы уже использовали аргумент memo по умолчанию, почему вы его пропускаете? Вы можете значительно ускорить время, выполнив:

result = my_fib(x - 1) + my_fib(x - 2)

Моя переработанная функция:

def my_fib(x, memo={1:1, 2:1}):
     if x in memo:
         return memo[x]

     memo[x] = result = my_fib(x - 1) + my_fib(x - 2)

     return result
0 голосов
/ 16 июня 2019

Интуитивно понятно, что запоминание на основе списка должно быть немного быстрее, чем на основе словаря.Я обнаружил, что алгоритм и порядок вызовов оказывают большое влияние на результат, поэтому для правильного сравнения требуется некоторая осторожность (например, использование предварительного распределения против добавления)

Я провел несколько сравнительных тестов, которые, кажется, подтверждают это.Вы также можете получить значительные изменения производительности с типом операции / логики, которую вы используете в алгоритме.

Вот результаты теста (для 100 повторений получения 900-го числа Фибоначчи):

my_fib(N)     0.0578 Original
fibo(N)       0.0089 Iterative algorithm
simpleFibo(N) 0.0248 Single recursion algorithm
dynaFibo(N)   0.0463 Double recursion with dictionary based memoization
dynaFibo2(N)  0.0440 Double recursion with list based memoization
binFibo(N)    0.0012 Iterative exponential algorithm
                     (this one responds in O(log(N)) time)

Вот реализации функции:

def my_fib(x, memo=dict()):
     if memo.get(x):
         return memo[x]
     if x == 1 or x == 2:
         result = 1
     else:
         result = my_fib(x - 1, memo) + my_fib(x -2, memo)
     memo[x] = result
     return result

def fibo(N):
    a = b = 1
    for _ in range(2,N): a,b = b,a+b
    return b

def simpleFibo(N,a=0,b=1):
    if N < 3: return a+b
    return simpleFibo(N-1,b,a+b)

def dynaFibo(N,memo={1:1,2:1}):
    if N not in memo:
        memo[N] = dynaFibo(N-1,memo) + dynaFibo(N-2,memo)
    return memo[N]

def dynaFibo2(N,memo=None):
    if not memo:    memo = [0,1,1]+[0]*N
    if not memo[N]: memo[N] = dynaFibo2(N-1,memo) + dynaFibo2(N-2,memo)
    return memo[N]

РЕДАКТИРОВАТЬ Добавлен экспоненциальный алгоритм, который отвечает за O (log (N)) времени

def binFibo(N):
    a,b   = 0,1
    f0,f1 = 1,1
    r,s   = (1,1) if N&1 else (0,1)
    N //=2
    while N > 0:
        a,b   = f0*a+f1*b, f0*b+f1*(a+b)
        f0,f1 = b-a,a
        if N&1: r,s = f0*r+f1*s, f0*s+f1*(r+s)
        N //= 2        
    return r

И процедура теста

from timeit import timeit
count = 100

N = 990

t= timeit(lambda:my_fib(N,dict()), number=count) # providing dict() to avoid reuse between repetitions
print("my_fib(N)",t)

t= timeit(lambda:fibo(N), number=count)
print("fibo(N)",t)

t= timeit(lambda:simpleFibo(N), number=count) 
print("simpleFibo(N)",t)

t= timeit(lambda:dynaFibo(N,{1:1,2:1}), number=count) # providing dict() to avoid reuse between repetitions
print("dynaFibo(N)",t) 

t= timeit(lambda:dynaFibo2(N), number=count) 
print("dynaFibo2(N)",t)

t= timeit(lambda:binFibo(N), number=count) 
print("binFibo(N)",t)

Кстати, я предполагаю, что ваша цельэто изучить динамическое программирование.В противном случае использование двойной рекурсии для функции Фибоначчи, безусловно, является худшим из возможных подходов.

0 голосов
/ 15 июня 2019

Я только что попытался проверить, есть ли заметная разница в производительности между dict и версией списка.Похоже, что разница между этими двумя методами очень мала.Btw.обратите внимание, что я также измерял создание списка кеша.Если я сравниваю время, которое печатает команда времени "unix", я не вижу никакой разницы, но, конечно, это также измеряет время, необходимое операционной системе для загрузки интерпретатора python, и, следовательно, не столь надежно.

from datetime import datetime
def fib_cached(n, cache=None):
    if n <= 2:
        return 1
    if cache[n] is None:
        fib_n= fib_cached(n-1, cache) + fib_cached(n-2, cache)
        cache[n]= fib_n
    else:
        fib_n= cache[n]
    return fib_n


n= 950

before= datetime.now()
print(fib_cached(n, cache=[None]*(n+1)))
print(datetime.now() - before)
...