Fibonacci ve Memoization
Aynı alt problemleri tekrar tekrar çözen özyinelemeden, her değeri bir kez hesaplayan memoization ve tablo yöntemine.
#Nasıl çalışır?
Dinamik programlama (DP), bir problemi örtüşen alt problemlere bölüp her alt problemi yalnızca bir kez çözen bir tekniktir. Fibonacci sayıları (0, 1, 1, 2, 3, 5, 8, …) bunu görmenin en kolay yoludur: fib(n) = fib(n − 1) + fib(n − 2).
Tanımı doğrudan özyinelemeye çevirmek doğru sonucu verir ama aynı değerleri defalarca hesaplar. fib(5) için fib(2) üç kez, fib(3) iki kez hesaplanır; çağrı sayısı n ile üstel büyür ve fib(50) için milyarlarca çağrı gerekir.
Memoization (yukarıdan aşağı DP) özyinelemeyi korur ama her sonucu bir önbelleğe yazar; aynı değer tekrar istendiğinde hesaplamadan döndürür. Tablo yöntemi (alttan yukarı DP) ise özyinelemeyi tamamen bırakıp küçük değerlerden büyüğe doğru bir dizi doldurur.
Bir problemin DP ile çözülebilmesi için iki özellik gerekir: örtüşen alt problemler (aynı alt problem tekrar tekrar çıkar) ve optimal alt yapı (büyük problemin cevabı alt problemlerin cevaplarından kurulur).
#Kendin dene
Aynı fib(n) değerini üç farklı yolla hesapla. Düz özyinelemede kırmızı düğümler daha önce hesaplanmış değerlerin boşuna tekrar hesaplandığı çağrılardır.
#Adım adım
- 01Durumu tanımla: dp[i] = i. Fibonacci sayısı.
- 02Temel durumları yaz: dp[0] = 0, dp[1] = 1.
- 03Geçişi yaz: dp[i] = dp[i − 1] + dp[i − 2].
- 04Hesaplama sırasını seç: memoization (özyineleme + önbellek) ya da tablo (küçükten büyüğe döngü).
#Örnek kod
Aynı algoritma 8 dilde. Her program derlenip çalıştırılarak test edildi ve hepsi aynı çıktıyı veriyor.
def fib_naive(n: int) -> int:
"""Plain recursion: correct, but O(2^n) calls."""
return n if n < 2 else fib_naive(n - 1) + fib_naive(n - 2)
memo = {}
def fib_memo(n: int) -> int:
"""Top-down DP: every value is computed once and cached."""
if n < 2:
return n
if n not in memo:
memo[n] = fib_memo(n - 1) + fib_memo(n - 2)
return memo[n]
def fib_table(n: int) -> int:
"""Bottom-up DP with only the last two values: O(n) time, O(1) memory."""
a, b = 0, 1 # fib(0), fib(1)
for _ in range(n):
a, b = b, a + b
return a
print(fib_naive(10)) # fine for small n; fib_naive(40) would take seconds
print(fib_memo(40))
print(fib_table(50))
# Python can also memoize for you: functools.lru_cache on fib_naive.#Karmaşıklık
| İşlem | Süre |
|---|---|
| Düz özyineleme | O(2ⁿ) zaman |
| Memoization | O(n) zaman, O(n) bellek |
| Tablo | O(n) zaman, O(n) bellek |
| Son iki değerle | O(n) zaman, O(1) bellek |
Düz özyinelemenin çağrı sayısı 2·fib(n + 1) − 1'dir ve yaklaşık 1,6ⁿ hızında büyür. DP her değeri bir kez hesapladığı için doğrusaldır. Matris üssü yöntemiyle fib(n) O(log n) zamanda da hesaplanabilir.
#Ne zaman kullanılır?
Memoization, özyinelemeli çözümü kolay yazılan ama alt problemleri tekrar eden her problemde ilk denenecek tekniktir. Tablo yöntemi derin özyinelemeden (yığın taşması) kaçınmak ve belleği küçültmek için tercih edilir. Merdiven çıkma, yol sayma ve bozuk para sayısı gibi problemler aynı kalıptadır.
#Alıştırma
Soruları cevaplamak ve ilerlemeni kaydetmek için giriş yap.
Kolay
Düz özyinelemeli fib(n) neden yavaştır?
- 1Aynı değerleri defalarca yeniden hesaplar
- 2Toplama işlemi yavaştır
- 3Çok fazla bellek ayırır
- 4Döngü kullanmaz
Memoization nedir?
- 1Hesaplanan sonuçları önbelleğe yazıp tekrar istendiğinde oradan döndürmek
- 2Özyinelemeyi döngüye çevirmek
- 3Diziyi sıralamak
- 4Sonuçları dosyaya yazmak
Memoization ile fib(n)'in zaman karmaşıklığı nedir?
- 1O(n)
- 2O(2ⁿ)
- 3O(n²)
- 4O(log n)
Orta
Düz özyinelemeyle fib(5) hesaplanırken toplam kaç çağrı yapılır?
- 115
- 25
- 38
- 425
Zor
Bir problemin dinamik programlamayla çözülebilmesi için hangi iki özellik gerekir?
- 1Örtüşen alt problemler ve optimal alt yapı
- 2Sıralı girdi ve sabit boyut
- 3Özyineleme ve döngü
- 4Açgözlü seçim ve geri izleme
#Bundan sonra
Bu dersi bildiğini varsayan dersler: