SEN2211 · 11. hafta · Recursive Complexity

Ö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.

recurrence relationT(n)unrolling (iteration method)recursion treemaster theoremdivide and conquerlogarithmic depthexponential growthspace complexitycall stack depth
  1. 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üresi factorial(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:

  1. T(n) = (kaç çağrı) · T(her çağrının boyutu) + (çağrı dışında yapılan iş).
  2. n − 1 ile küçülen özyineleme doğrusal derinliktedir; n / 2 ile küçülen logaritmik.
  3. İki çağrı + n − 1 = üstel. İki çağrı + n / 2 = doğrusal ya da n log n.
  4. Ö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?

KodBağıntı
return n * f(n - 1);T(n)=T(n−1)+cT(n) = T(n-1) + c
for (i < n) ...; f(n - 1);T(n)=T(n−1)+nT(n) = T(n-1) + n
return f(n / 2) + 1;T(n)=T(n/2)+cT(n) = T(n/2) + c
for (i < n) ...; f(n / 2);T(n)=T(n/2)+nT(n) = T(n/2) + n
f(n / 2); f(n / 2);T(n)=2T(n/2)+cT(n) = 2T(n/2) + c
f(left half); f(right half); merge (n);T(n)=2T(n/2)+nT(n) = 2T(n/2) + n
f(n - 1); f(n - 1);T(n)=2T(n−1)+cT(n) = 2T(n-1) + c

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)+nT(n) = T(n-1) + n, T(1)=1T(1) = 1

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)+1T(n) = T(n/2) + 1, T(1)=1T(1) = 1

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)+1T(n) = 2T(n-1) + 1, T(0)=0T(0) = 0

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.

T(n)=2T(n/2)+nT(n) = 2T(n/2) + n

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ı log⁡2n+1\log_2 n + 1, her seviyede n iş: T(n)=nlog⁡2n+nT(n) = n\log_2 n + n. 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)

T(n)=T(n/2)+nT(n) = T(n/2) + n ağacı tek dallıdır: n, n/2, n/4, … Toplam n(1+12+14+… )<2nn(1 + \frac12 + \frac14 + \dots) < 2n. Kökteki iş baskın: O(n).

T(n)=2T(n/2)+1T(n) = 2T(n/2) + 1 ağacında seviye başına iş 1, 2, 4, …, n. Toplam 2n−12n - 1. 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

T(n)=a T(n/b)+ndT(n) = a\,T(n/b) + n^d biçimindeki bağıntılar için (a≥1a \ge 1, b>1b > 1). k=log⁡bak = \log_b a hesapla ve d ile karşılaştır:

DurumSonuçSezgi
d>kd > kΘ(nd)\Theta(n^d)kökteki iş baskın
d=kd = kΘ(ndlog⁡n)\Theta(n^d \log n)her seviye eşit
d<kd < kΘ(nk)\Theta(n^k)yapraklar baskın
Bağıntıa, b, dkSonuç
T(n/2)+1T(n/2) + 11, 2, 00d=kd = k → Θ(log⁡n)\Theta(\log n)
T(n/2)+nT(n/2) + n1, 2, 10d>kd > k → Θ(n)\Theta(n)
2T(n/2)+12T(n/2) + 12, 2, 01d<kd < k → Θ(n)\Theta(n)
2T(n/2)+n2T(n/2) + n2, 2, 11d=kd = k → Θ(nlog⁡n)\Theta(n\log n)
2T(n/2)+n22T(n/2) + n^22, 2, 21d>kd > k → Θ(n2)\Theta(n^2)
3T(n/3)+n3T(n/3) + n3, 3, 11d=kd = k → Θ(nlog⁡n)\Theta(n\log n)
4T(n/2)+n4T(n/2) + n4, 2, 12d<kd < k → Θ(n2)\Theta(n^2)
8T(n/2)+n28T(n/2) + n^28, 2, 23d<kd < k → Θ(n3)\Theta(n^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. T(n)=T(n−1)+nT(n) = T(n-1) + n ya da 2T(n−1)+12T(n-1) + 1 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.

MetotSüreEk bellek (yığın)
Özyinelemeli liste sum / sizeO(n)O(n)
Özyinelemeli ikili aramaO(log n)O(log n)
Naif fibO(2ⁿ) civarıO(n) (derinlik n, genişlik değil)
Merge sortO(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)
T(n)=T(n/2)+nT(n) = T(n/2) + n → O(n log n)Geometrik seri: O(n)
T(n)=2T(n/2)+nT(n) = 2T(n/2) + n → O(n²)Seviye başına n, log n seviye: O(n log n)
Master teoremini T(n−1)T(n-1)‘e uygulamakAçılım yöntemi kullan
log⁡ba\log_b a‘yı yanlış hesaplamak (log⁡28=4\log_2 8 = 4)log⁡28=3\log_2 8 = 3
Özyineleme ek bellek kullanmazDerinlik kadar çerçeve: liste için O(n)
Döngü içinde çağrı varsa sadece döngüyü saymakDö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ç.

Soru 1kolay

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
T(n) = T(n-1) + c, O(n)

Her çağrı sabit iş (karşılaştırma, çarpma) yapar ve kendini n − 1 ile bir kez çağırır. Açılım: T(n)=T(n−1)+c=T(n−2)+2c=⋯=T(1)+(n−1)cT(n) = T(n-1) + c = T(n-2) + 2c = \dots = T(1) + (n-1)c. Doğrusal: O(n).

Soru 2orta

Solve T(n)=T(n−1)+nT(n) = T(n-1) + n, T(1)=1T(1) = 1. What is T(10)T(10) and the Big-O?

Cevabı göster
T(n) = n(n+1)/2, T(10) = 55, O(n^2)

Açılım: T(n)=n+(n−1)+⋯+2+1=n(n+1)2T(n) = n + (n-1) + \dots + 2 + 1 = \frac{n(n+1)}{2}. 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).

Soru 3kolay

Solve T(n)=T(n/2)+1T(n) = T(n/2) + 1, T(1)=1T(1) = 1 for n=16n = 16. What is the Big-O?

Cevabı göster
5, O(log n)

16 → 8 → 4 → 2 → 1: dört yarılama, her biri +1, artı T(1) = 1. Toplam 5 = log⁡216+1\log_2 16 + 1. Ölçüm: T(1024) = 11, T(1 048 576) = 21. Bu, ikili aramanın (binary search) bağıntısı.

Soru 4orta

Solve T(n)=2T(n/2)+nT(n) = 2T(n/2) + n, T(1)=1T(1) = 1 for n=8n = 8. What is the Big-O?

Cevabı göster
32, O(n log n)

Kapalı form nlog⁡2n+nn\log_2 n + n: 8 · 3 + 8 = 32. Özyineleme ağacında her seviyede toplam iş n, seviye sayısı log⁡2n\log_2 n. Ölçüm: T(1024) = 11 264, T(2048) = 24 576 (x2.18). Merge sort'un bağıntısı.

Soru 5orta

Solve T(n)=2T(n−1)+1T(n) = 2T(n-1) + 1, T(0)=0T(0) = 0. What is T(5)T(5) and the Big-O?

Cevabı göster
31, O(2^n)

T(1) = 1, T(2) = 3, T(3) = 7, T(4) = 15, T(5) = 31: T(n)=2n−1T(n) = 2^n - 1. 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.

Soru 6orta

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
2047, O(2^n)

Çağrı sayısı C(n) = 2C(n−1) + 1, C(0) = 1, yani C(n)=2n+1−1C(n) = 2^{n+1} - 1. 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.

Soru 7orta

Use the master theorem to give the Big-O of T(n)=4T(n/2)+nT(n) = 4T(n/2) + n.

Cevabı göster
O(n^2)

a = 4, b = 2, d = 1. log⁡ba=log⁡24=2\log_b a = \log_2 4 = 2. d = 1 < 2: yapraklar baskın, T(n)=Θ(nlog⁡ba)=Θ(n2)T(n) = \Theta(n^{\log_b a}) = \Theta(n^2). Ölçüm: n iki katına çıkınca T x4.00.

Soru 8zor

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
T(n) = T(n/2) + n, O(n)

Döngü n iş, sonra tek çağrı n/2 ile. Açılım: n+n2+n4+⋯<2nn + \frac{n}{2} + \frac{n}{4} + \dots < 2n. Geometrik seri, toplam O(n). Sayaç: n = 1024 → 2046, 2048 → 4094 (x2). Master teoremi: a = 1, b = 2, d = 1 > log₂1 = 0 → Θ(n).

Soru 9zor

Give the Big-O for (a) T(n)=3T(n/3)+nT(n) = 3T(n/3) + n (b) T(n)=8T(n/2)+n2T(n) = 8T(n/2) + n^2 (c) T(n)=2T(n/2)+1T(n) = 2T(n/2) + 1.

Cevabı göster
(a) O(n log n) (b) O(n^3) (c) O(n)

(a) log⁡33=1=d\log_3 3 = 1 = d: eşit, Θ(nlog⁡n)\Theta(n\log n). (b) log⁡28=3>d=2\log_2 8 = 3 > d = 2: Θ(n3)\Theta(n^3); ölçümde n x2 → T x8.00. (c) log⁡22=1>d=0\log_2 2 = 1 > d = 0: Θ(n)\Theta(n); T(1024) = 2047, T(2048) = 4095.

Soru 10kolay

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 n=220n = 2^{20}?

Cevabı göster
O(log n), about 21 calls

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.

Soru 11orta

For T(n)=2T(n/2)+nT(n) = 2T(n/2) + n, 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
log2 n levels (plus leaves), n work per level, total n log n

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)                     = n
n/2^k = 1 olduğunda dururuz: k=log2nk = \\log_2 n. Her seviye n iş, toplam nlog2n+nn\\log_2 n + n → O(n log n).

Soru 12zor

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
sum: O(n); binary search: O(log n)

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ü.

Deftere not kartı

Özyinelemeli süre: T(n) = (çağrı sayısı) × T(alt problem) + (çağrı dışındaki iş) T(n) = T(n-1) + 1 → O(n) factorial, liste size/sum T(n) = T(n-1) + n → O(n²) n(n+1)/2; özyinelemeli selection sort T(n) = T(n/2) + 1 → O(log n) binary search, log2 n + 1 T(n) = T(n/2) + n → O(n) n + n/2 + n/4 + ... < 2n T(n) = 2T(n/2) + 1 → O(n) 2n - 1 T(n) = 2T(n/2) + n → O(n log n) merge sort; n log n + n T(n) = 2T(n-1) + 1 → O(2ⁿ) 2ⁿ - 1; Hanoi; naif fib ~ O(1.6ⁿ) MASTER: T(n) = aT(n/b) + n^d, k = log_b a d > k → n^d d = k → n^d log n d < k → n^k Özyineleme ağacı: seviye sayısı × seviye başına iş Bellek: en derin çağrı zinciri (liste O(n), binary search O(log n))