Özyinelemeli karmaşıklık: yineleme bağıntıları
Özyinelemeli bir metodun süresini T(n) bağıntısıyla yazmak ve açılım, özyineleme ağacı ve master teoremi ile çözmek. Sınavda “write the recurrence and give the Big-O” soruları gelir.
- haftada özyinelemeli metot yazdık. Şimdi soru şu: bu metotlar ne kadar hızlı? Döngüde tur sayısını sayıyorduk. Özyinelemede ise metodun süresi kendi süresine bağlıdır:
factorial(n)’in süresifactorial(n - 1)’in süresi artı biraz iş. Bunu bir yineleme bağıntısı (recurrence relation) olarak yazar, sonra çözeriz. Sınavda iki adım istenir: koddan bağıntıyı çıkar, bağıntıdan Big-O’yu bul. Bu sayfadaki on bağıntıyı tanırsan soruların çoğunu ezbere çözersin.
Altın kurallar:
- T(n) = (kaç çağrı) · T(her çağrının boyutu) + (çağrı dışında yapılan iş).
- n − 1 ile küçülen özyineleme doğrusal derinliktedir; n / 2 ile küçülen logaritmik.
- İki çağrı + n − 1 = üstel. İki çağrı + n / 2 = doğrusal ya da n log n.
- Özyineleme ağacında: seviye sayısı × seviye başına iş.
Koddan bağıntıya
Üç soruyu sor: taban durumda ne kadar iş var? Kaç özyinelemeli çağrı var ve her biri hangi boyutla? Çağrıların dışında ne kadar iş var?
| Kod | Bağıntı |
|---|---|
return n * f(n - 1); | |
for (i < n) ...; f(n - 1); | |
return f(n / 2) + 1; | |
for (i < n) ...; f(n / 2); | |
f(n / 2); f(n / 2); | |
f(left half); f(right half); merge (n); | |
f(n - 1); f(n - 1); |
Sabit iş (c) Big-O’yu etkilemez; çoğu zaman 1 yazılır.
Yöntem 1: açılım (unrolling)
Bağıntıyı kendi içine birkaç kez yerleştir, deseni gör, taban duruma kadar götür.
,
T(n) = T(n-1) + n
= T(n-2) + (n-1) + n
= T(n-3) + (n-2) + (n-1) + n
...
= T(1) + 2 + 3 + ... + n
= 1 + 2 + ... + n = n(n+1)/2 → O(n²)
,
T(n) = T(n/2) + 1
= T(n/4) + 2
= T(n/8) + 3
...
= T(n/2^k) + k n/2^k = 1 olduğunda k = log2 n
= 1 + log2 n → O(log n)
,
T(n) = 2T(n-1) + 1
= 4T(n-2) + 2 + 1
= 8T(n-3) + 4 + 2 + 1
...
= 2^n T(0) + (2^(n-1) + ... + 2 + 1)
= 2^n - 1 → O(2^n)
Programın doğrudan hesapladığı değerler (n iki katına çıkarken oranlar parantezde):
T(n-1)+1: T(10)=10 T(1000)=1000 (x100.00) T(2000)=2000 (x2.00) T(4000)=4000 (x2.00)
T(n-1)+n: T(10)=55 T(1000)=500500 (x9100.00) T(2000)=2001000 (x4.00) T(4000)=8002000 (x4.00)
T(n/2)+1: T(16)=5 T(1024)=11 (x2.20) T(2048)=12 (x1.09) T(1048576)=21 (x1.75)
2T(n-1)+1: T(5)=31 T(10)=1023 (x33.00) T(20)=1048575 (x1025.00) T(21)=2097151 (x2.00)
Son satırda dikkat: n sadece 1 artınca (20 → 21) değer iki katına çıkıyor. Üstel büyüme bu.
Yöntem 2: özyineleme ağacı (recursion tree)
Her düğüm bir çağrının kendi işini gösterir; toplam süre bütün düğümlerin toplamıdır.
seviye 0: n toplam n
seviye 1: n/2 n/2 toplam n
seviye 2: n/4 n/4 n/4 n/4 toplam n
...
seviye log n: 1 1 1 1 ... 1 (n yaprak) toplam n
Seviye sayısı , her seviyede n iş: . n = 8 için 8 · 3 + 8 = 32, n = 1024 için 11 264. Program aynısını buldu:
2T(n/2)+n: T(8)=32 T(1024)=11264 (x352.00) T(2048)=24576 (x2.18) T(4096)=53248 (x2.17)
ağacı tek dallıdır: n, n/2, n/4, … Toplam . Kökteki iş baskın: O(n).
ağacında seviye başına iş 1, 2, 4, …, n. Toplam . Yapraklar baskın: O(n).
İPUCU · Ağaçta kim baskın? Seviye başına iş aşağı doğru azalıyorsa kök baskındır (cevap kökteki iş). Sabitse seviye sayısıyla çarp (log n çarpanı). Artıyorsa yapraklar baskındır (cevap yaprak sayısı). Master teoremi bunun formül halidir.
Yöntem 3: master teoremi
biçimindeki bağıntılar için (, ). hesapla ve d ile karşılaştır:
| Durum | Sonuç | Sezgi |
|---|---|---|
| kökteki iş baskın | ||
| her seviye eşit | ||
| yapraklar baskın |
| Bağıntı | a, b, d | k | Sonuç |
|---|---|---|---|
| 1, 2, 0 | 0 | → | |
| 1, 2, 1 | 0 | → | |
| 2, 2, 0 | 1 | → | |
| 2, 2, 1 | 1 | → | |
| 2, 2, 2 | 1 | → | |
| 3, 3, 1 | 1 | → | |
| 4, 2, 1 | 2 | → | |
| 8, 2, 2 | 3 | → |
Programla kontrol (n iki katına çıkınca beklenen oran: n için x2, n² için x4, n³ için x8):
4T(n/2)+n: T(1024)=2096128 T(2048)=8386560 (x4.00) T(4096)=33550336 (x4.00)
T(n/2)+n: T(1024)=2047 T(2048)=4095 (x2.00) T(4096)=8191 (x2.00)
2T(n/2)+1: T(1024)=2047 T(2048)=4095 (x2.00) T(4096)=8191 (x2.00)
8T(n/2)+n^2: T(256)=33488896 T(512)=268173312 (x8.01) T(1024)=2146435072 (x8.00)
2T(n/2)+n^2: T(1024)=2096128 T(2048)=8386560 (x4.00) T(4096)=33550336 (x4.00)
TUZAK · Master teoremi n − 1’e uygulanmaz. ya da biçiminde bölme yok, çıkarma var. Bunları açılımla çöz.
Gerçek kodda sayım
Bağıntılar soyut kalmasın diye gerçek özyinelemeli kodlarda işlem saydık:
static void halfLoop(int n) { // T(n) = T(n/2) + n
if (n <= 1) return;
for (int i = 0; i < n; i++) ops++;
halfLoop(n / 2);
}
static void twoCalls(int n) { // T(n) = 2T(n-1) + 1
ops++;
if (n == 0) return;
twoCalls(n - 1);
twoCalls(n - 1);
}
static void splitLoop(int lo, int hi) { // T(n) = 2T(n/2) + n
if (hi - lo < 1) return;
for (int i = lo; i <= hi; i++) ops++;
int mid = (lo + hi) / 2;
splitLoop(lo, mid);
splitLoop(mid + 1, hi);
}
halfLoop n=1024 ops=2046 halfLoop n=2048 ops=4094 halfLoop n=4096 ops=8190
bsearch(miss) n=1024 calls=11 bsearch(miss) n=2048 calls=12 bsearch(miss) n=1048576 calls=21
twoCalls n=3 calls=15 twoCalls n=10 calls=2047 twoCalls n=11 calls=4095 twoCalls n=12 calls=8191
splitLoop n=1024 ops=10240 splitLoop n=2048 ops=22528 splitLoop n=4096 ops=49152
loopThenOne n=10 ops=55 loopThenOne n=1000 ops=500500 loopThenOne n=2000 ops=2001000
splitLoop için 1024 · 10 = 10 240: tam olarak n log₂ n. loopThenOne (n’lik döngü + f(n − 1)) için n(n+1)/2.
Bellek karmaşıklığı
Özyinelemenin gizli bir maliyeti var: her açık çağrı bir yığın çerçevesi tutar. Ek bellek, en derin çağrı zincirinin uzunluğu kadardır.
| Metot | Süre | Ek bellek (yığın) |
|---|---|---|
Özyinelemeli liste sum / size | O(n) | O(n) |
| Özyinelemeli ikili arama | O(log n) | O(log n) |
| Naif fib | O(2ⁿ) civarı | O(n) (derinlik n, genişlik değil) |
| Merge sort | O(n log n) | O(log n) yığın + O(n) yardımcı dizi |
Naif fib’de çağrı sayısı üstel ama aynı anda açık olan çağrı sayısı en fazla n’dir: bir dal bitmeden diğerine geçilmez.
Sık yapılan hatalar
| Yanlış | Doğrusu |
|---|---|
| İki özyinelemeli çağrı her zaman O(2ⁿ) | n − 1 ile ise evet; n / 2 ile ise O(n) ya da O(n log n) |
| → O(n log n) | Geometrik seri: O(n) |
| → O(n²) | Seviye başına n, log n seviye: O(n log n) |
| Master teoremini ‘e uygulamak | Açılım yöntemi kullan |
| ‘yı yanlış hesaplamak () | |
| Özyineleme ek bellek kullanmaz | Derinlik kadar çerçeve: liste için O(n) |
| Döngü içinde çağrı varsa sadece döngüyü saymak | Döngü işi + çağrıların işi, ikisi de bağıntıya girer |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
Write the recurrence for the running time of factorial(n) (if (n <= 1) return 1; return n * factorial(n - 1);) and give its Big-O.
Cevabı göster
Her çağrı sabit iş (karşılaştırma, çarpma) yapar ve kendini n − 1 ile bir kez çağırır. Açılım: . Doğrusal: O(n).
Solve , . What is and the Big-O?
Cevabı göster
Açılım: . T(10) = 55. Programla: T(1000) = 500 500, T(2000) = 2 001 000 (x4.00). Örnek kod: her çağrıda n'lik döngü yapıp n − 1 ile tekrar çağırmak (selection sort'un özyinelemeli hali).
Solve , for . What is the Big-O?
Cevabı göster
16 → 8 → 4 → 2 → 1: dört yarılama, her biri +1, artı T(1) = 1. Toplam 5 = . Ölçüm: T(1024) = 11, T(1 048 576) = 21. Bu, ikili aramanın (binary search) bağıntısı.
Solve , for . What is the Big-O?
Cevabı göster
Kapalı form : 8 · 3 + 8 = 32. Özyineleme ağacında her seviyede toplam iş n, seviye sayısı . Ölçüm: T(1024) = 11 264, T(2048) = 24 576 (x2.18). Merge sort'un bağıntısı.
Solve , . What is and the Big-O?
Cevabı göster
T(1) = 1, T(2) = 3, T(3) = 7, T(4) = 15, T(5) = 31: . n bir artınca süre iki katına çıkar (T(20) = 1 048 575, T(21) = 2 097 151). Hanoi kuleleri ve 10. haftadaki g(n) metodu bu bağıntıya uyar.
How many calls does f(10) make in total (including the first)? What is the Big-O?
static void f(int n) {
if (n == 0) return;
f(n - 1);
f(n - 1);
}Cevabı göster
Çağrı sayısı C(n) = 2C(n−1) + 1, C(0) = 1, yani . C(10) = 2047. Sayaçla: n = 10 → 2047, 11 → 4095, 12 → 8191. İki çağrının ikisi de n − 1 ile yapıldığı için ağaç dolu ve üstel.
Use the master theorem to give the Big-O of .
Cevabı göster
a = 4, b = 2, d = 1. . d = 1 < 2: yapraklar baskın, . Ölçüm: n iki katına çıkınca T x4.00.
What is the Big-O of this method? Write its recurrence first.
static void h(int n) {
if (n <= 1) return;
for (int i = 0; i < n; i++)
count++;
h(n / 2);
}Cevabı göster
Döngü n iş, sonra tek çağrı n/2 ile. Açılım: . Geometrik seri, toplam O(n). Sayaç: n = 1024 → 2046, 2048 → 4094 (x2). Master teoremi: a = 1, b = 2, d = 1 > log₂1 = 0 → Θ(n).
Give the Big-O for (a) (b) (c) .
Cevabı göster
(a) : eşit, . (b) : ; ölçümde n x2 → T x8.00. (c) : ; T(1024) = 2047, T(2048) = 4095.
What is the running time of recursive binary search on a sorted array of size n? About how many calls are made for an unsuccessful search when ?
Cevabı göster
Her çağrı sabit iş yapıp aralığın yarısıyla tek çağrı yapar: T(n) = T(n/2) + c → O(log n). 2²⁰ elemanda 20 yarılama ve boş aralık için bir çağrı daha: sayaç 21 dedi. Bir milyon elemanda yaklaşık 20 adım.
For , draw the recursion tree. How many levels are there, how much work is done on each level, and what is the total?
Cevabı göster
seviye 0: n = n
seviye 1: n/2 n/2 = n
seviye 2: n/4 n/4 n/4 n/4 = n
...
seviye k: 2^k parça, her biri n/2^k = n
son seviye: n tane T(1) = nn/2^k = 1 olduğunda dururuz: . Her seviye n iş, toplam → O(n log n).What is the space complexity (extra memory) of the recursive sum(Node p) (return p.data + sum(p.next);) on a list of n nodes, and of recursive binary search?
Cevabı göster
Ek bellek, aynı anda açık olan en fazla yığın çerçevesi kadardır. sum her düğüm için bir çerçeve açar ve hepsi taban duruma kadar bekler: n + 1 çerçeve, O(n). İkili aramada derinlik log n: O(log n). Döngüyle yazılırsa ikisi de O(1) bellek.
Doğrulama: Her yineleme bağıntısı verify/SEN2211/W11.java içinde doğrudan hesaplandı (java -Xss64m W11.java) ve kapalı formüllerle karşılaştırıldı (ör. 2T(n/2)+n için n log n + n: T(8) = 32, T(1024) = 11264). Ayrıca gerçek özyinelemeli kodlarda işlem/çağrı sayısı sayıldı; n iki katına çıkınca oranın (x2, x4, +1, ~x2.2) iddia edilen sınıfla uyuştuğu görüldü.