Menu
Coddy logo textTech

Özyineleme (Recursion)

Son güncelleme

Özyineleme, bir fonksiyonun aynı problemin daha küçük bir sürümü için kendini çağırmasıdır; bu, doğrudan yanıtlanabilecek kadar küçük bir duruma ulaşana dek sürer. Doğrudan yanıtlanabilen bu durum temel durumdur ve her özyinelemeli fonksiyonun bir tanesine ihtiyacı vardır: fib(n), fib(1) veya fib(0) değerine ulaşana kadar fib(n - 1) ve fib(n - 2) olarak bölünmeye devam eder; bu ikisi ise doğrudan kendi değerlerini döndürür. Yukarıdaki görselleştirme tam olarak bunu çalıştırır: Oynat'a basın ve çağrıların bir ağaç halinde dallanmasını, yapraklarda temel durumlara ulaşmasını, ardından değerlerini her seviyede birleştirerek yukarı döndürmesini izleyin.

Animasyonun gösterdiği ikinci şey çağrı yığınıdır: başlamış ama henüz geri dönmemiş her çağrı. Yığın, çağrılar derinleştikçe büyür, özyineleme derinliğinde zirve yapar ve sonuçlar geri geldikçe çözülür. Derin özyinelemenin yığın taşmasına yol açabilmesinin, yinelemeli bir döngünün ise yığını hiç büyütmemesinin nedeni budur. Aynı çağrı biçimi derinlik öncelikli arama, merge sort ve bir ikili ağaç üzerindeki işlemlerin çoğunun temelini oluşturur.

Zaman ve alan karmaşıklığı

Yukarıda gösterilen naif özyinelemeli Fibonacci ve iki standart iyileştirmesi için:

YaklaşımZamanAlanNotlar
Naif özyinelemeO(2^n)O(n)Çağrı ağacı her seviyede ikiye katlanır; alan, tüm ağaç değil en derin yığın kadardır.
Memoization ileO(n)O(n)Her fib(k) bir kez hesaplanıp önbelleğe alınır; tekrar eden alt ağaçlar tek bir aramaya iner.
Yinelemeli döngüO(n)O(1)İki döner değişken yığının yerini tamamen alır.
Genel olarak her özyinelemeçağrı sayısı × çağrı başına işO(max depth)Yığın, başlamış ama henüz geri dönmemiş her çağrı için bir çerçeve tutar.

Adım adım

AdımNe olur
1İlk çağrı fib(n) çağrı yığınına eklenir.
2Bu çağrının fib(n - 1) sonucuna ihtiyacı vardır, bu yüzden o çağrı da yığına eklenir; üst çağrı bekler.
3Çağrılar, biri n <= 1 durumunu sorana kadar iç içe geçmeyi sürdürür: temel durum daha derin bir çağrı olmadan hemen yanıt verir.
4Temel durumun değeri üst çağrıya döner; üst çağrı artık ikinci çağrısını, fib(n - 2), başlatabilir.
5Her iki alt çağrı da döndüğünde üst çağrı bunları toplar ve kendisi de döner; çerçevesi yığından çıkar.
6Bu dönüş ağaç boyunca yukarı doğru tekrarlanır; sonunda ilk çağrının çerçevesi nihai yanıtla yığından çıkar ve yığın boşalır.

Çözümlü örnek

Animasyonun oynattığı tam çağrı sırasıyla fib(4) hesaplanıyor:

ÇağrıO andaki yığınDöndürdüğü
fib(4)fib(4)alt çağrıları bekler
fib(3)fib(4) > fib(3)alt çağrıları bekler
fib(2)fib(4) > fib(3) > fib(2)alt çağrıları bekler
fib(1)fib(4) > fib(3) > fib(2) > fib(1)1 (temel durum)
fib(0)fib(4) > fib(3) > fib(2) > fib(0)0 (temel durum)
fib(2) birleştirirfib(4) > fib(3) > fib(2)1 + 0 = 1
fib(1)fib(4) > fib(3) > fib(1)1 (temel durum)
fib(3) birleştirirfib(4) > fib(3)1 + 1 = 2
fib(2) tekrarfib(4) > fib(2)1, sıfırdan yeniden hesaplanır
fib(4) birleştirirfib(4)2 + 1 = 3

Özyineleme ne zaman kullanılmalı

Şu durumlarda kullanınŞu durumlarda kaçının
Problem kendine benzer yapıdaysa: ağaçlar, iç içe yapılar, böl ve fethetBasit bir döngü aynı şeyi yığın çerçevesi olmadan ifade ediyorsa
Derinlik sınırlı ve makulse, örneğin merge sort içindeki O(log n) gibiDerinlik çok büyük girdilerde girdi boyutuna ulaşabiliyor ve yığın taşması riski doğuyorsa
Geri izleme, nereden devam edeceğini hatırlamak için yığına ihtiyaç duyuyorsaAynı alt problemler tekrar ediyor ve bunları önbelleğe almıyorsanız
Özyinelemeli sürüm okumak ve doğrulamak için belirgin biçimde daha kolaysaÇağrı ek yükünün ölçülebilir biçimde önem taşıdığı sıcak bir döngüdeyseniz

Recursion kodu

Python, JavaScript, Java, C++, C dillerinde temiz ve çalıştırılabilir bir Recursion uygulaması. Bir dil seçin, kodu kopyalayın veya Coddy Playground'da hazır yüklenmiş olarak açın.

Python ile Recursion kodu

Python
1calls = 02
3def fib(n, depth=0):4    global calls5    calls += 16    # Print the call with its depth so the recursion is visible7    print("  " * depth + f"fib({n})")8    if n <= 1:9        return n10    return fib(n - 1, depth + 1) + fib(n - 2, depth + 1)11
12
13print("fib(5) =", fib(5))14print("calls made:", calls)
Bu kodu Python Playground'da çalıştır

Özyineleme SSS

Özyinelemede temel durum nedir?
Başka bir özyinelemeli çağrı olmadan yanıtlanabilecek kadar küçük olan girdidir. fib(n) için bu n <= 1 koşuludur ve doğrudan n değerini döndürür. Ulaşılabilir bir temel durum yoksa çağrılar hiç bitmez, yığın büyümeye devam eder ve program yığın taşmasıyla çöker.
Çağrı yığını nedir ve neden önemlidir?
Çalışma zamanı, başlamış ama henüz geri dönmemiş her çağrı için argümanlarını ve yerel değişkenlerini tutan bir çerçeve saklar. Özyineleme derinliği yığın yüksekliğine eşittir; bu yüzden n seviye derine inen bir özyineleme, her çağrı neredeyse hiç iş yapmasa bile O(n) bellek kullanır. Animasyonun altındaki rozet sırası tam olarak bu yığının büyümesini ve çözülmesini gösterir.
Özyinelemeli Fibonacci neden üstel zaman alır?
Çünkü aynı alt problemler tekrar tekrar hesaplanır: yukarıdaki çözümlü örnekte fib(2), fib(4) içinde iki kez değerlendirilir ve bu tekrar her seviyede kabaca ikiye katlanarak O(2^n) çağrıya yol açar. Her sonucu ilk hesaplandığında önbelleğe almak, yani memoization, ağacı O(n) düzeyine indirir.
Özyineleme yinelemeden daha mı iyidir?
Hiçbiri her durumda daha iyi değildir. Her özyineleme, açık bir yığın kullanan bir döngü olarak; her döngü de bir özyineleme olarak yeniden yazılabilir. Ağaç dolaşımı ve derinlik öncelikli arama gibi kendine benzer problemlerde özyineleme okunabilirlik açısından öne geçer; doğrusal geçişlerde ise yineleme bellek ve çağrı ek yükü açısından kazanır.
Özyinelemeli bir fonksiyonda yığın taşmasına ne yol açar?
Ya temel durum eksiktir veya hiç ulaşılamaz, bu yüzden çağrılar hiç bitmez; ya da özyineleme doğrudur ama derinliği çalışma zamanının yığın sınırı için fazla büyüktür, örneğin milyonlarca elemanlı bir girdide eleman başına bir çağrı yapmak gibi. Çözümler şunlardır: temel duruma ulaşılmasını garantilemek, derinliği sınırlamak veya yinelemeye dönüştürmek.
Hangi algoritmalar doğası gereği özyinelemelidir?
Böl ve fethet mantığındaki merge sort ve quicksort gibi sıralamalar, bir ikili ağaç ve graflar üzerindeki dolaşımlar, ikili arama, N vezir gibi geri izleme bulmacaları ve JSON ya da bir dosya sistemi gibi iç içe yapılar üzerinde tanımlanan her şey.
Coddy programming languages illustration

Coddy ile algoritmalarda ustalaş

BAŞLA