Bölüm 12: Özyineleme

Bilgisayar ve Programlamaya Giriş
Taban durumÇağrıYığınVerimlilikBellekleme
Sercan KÜLCÜ · Giresun Üniversitesi

Giriş

Öğrenme Çıktıları

Yapı

Taban durum ve özyinelemeli durumu belirlemek.

İzleme

Çağrı yığınını adım adım takip etmek.

Karşılaştırma

Özyineleme ile döngüyü değerlendirmek.

İyileştirme

Tekrarlı hesaplamayı bellekleme (memoization) ile azaltmak.

Temeller

Özyineleme Nedir?

  • Bir metodun problemi daha küçük bir örneğine indirip kendisini çağırmasıdır.
  • Her çağrı kendi parametre ve yerel değişkenleriyle ayrı bir yığın çerçevesi oluşturur.
  • Problem her çağrıda taban duruma yaklaşmalıdır; yoksa çağrılar sonlanmaz.
faktoriyel(4) = 4 × faktoriyel(3)faktoriyel(3) = 3 × faktoriyel(2)faktoriyel(2) = 2 × faktoriyel(1)faktoriyel(1) = 1taban durum

Temeller

İki Zorunlu Parça

Taban durum

Sonucun doğrudan bilindiği, yeni çağrı yapılmayan durum (ör. n ≤ 1 → 1).

Özyinelemeli durum

Problemi daha küçük örneğe dönüştüren çağrı (ör. n × f(n − 1)).

f(n) çağrısıtaban durummu?evetsonucu doğrudan döndürhayırküçült ve f(n − 1)'i çağıryeni çağrı

Örnekler

Özyinelemeli Faktöriyel

static long faktoriyel(int n) {
    if (n < 0)
        throw new IllegalArgumentException();
    if (n <= 1) return 1;
    return n * faktoriyel(n - 1);
}
Tanım

n! = n × (n − 1)!, 0! = 1! = 1

Taban durum

n ≤ 1 → 1; her çağrıda n bir azalır.

Negatif n

Faktöriyel tanımsız → hata fırlatılır.

-2-1012345hata (n < 0)taban: 1özyinelemeli: n × f(n − 1)her çağrıda n bir azalır → tabana ulaşır

Çağrı Yığını · Kodu adım adım çalıştır

Kod İzleme: faktoriyel(4)

main()main (yığının dibi) → yığın bu yönde büyür → üst

main, faktoriyel(4)'ü çağırır; sonucu bekler.

main()faktoriyel(4)n=4main (yığının dibi) → yığın bu yönde büyür → üst

Yeni çerçeve: n = 4. 4 < 0 → false, 4 <= 1 → false.

main()faktoriyel(4)n=4: 4 × ?main (yığının dibi) → yığın bu yönde büyür → üst

4 * faktoriyel(3): çarpma için önce alt sonuç gerekli → bu çerçeve bekler, yeni çağrı yapılır.

main()faktoriyel(4)n=4: 4 × ?faktoriyel(3)n=3main (yığının dibi) → yığın bu yönde büyür → üst

Yeni çerçeve: n = 3. 3 < 0 → false, 3 <= 1 → false.

main()faktoriyel(4)n=4: 4 × ?faktoriyel(3)n=3: 3 × ?main (yığının dibi) → yığın bu yönde büyür → üst

3 * faktoriyel(2): çarpma için önce alt sonuç gerekli → bu çerçeve bekler, yeni çağrı yapılır.

main()faktoriyel(4)n=4: 4 × ?faktoriyel(3)n=3: 3 × ?faktoriyel(2)n=2main (yığının dibi) → yığın bu yönde büyür → üst

Yeni çerçeve: n = 2. 2 < 0 → false, 2 <= 1 → false.

main()faktoriyel(4)n=4: 4 × ?faktoriyel(3)n=3: 3 × ?faktoriyel(2)n=2: 2 × ?main (yığının dibi) → yığın bu yönde büyür → üst

2 * faktoriyel(1): çarpma için önce alt sonuç gerekli → bu çerçeve bekler, yeni çağrı yapılır.

main()faktoriyel(4)n=4: 4 × ?faktoriyel(3)n=3: 3 × ?faktoriyel(2)n=2: 2 × ?faktoriyel(1)n=1main (yığının dibi) → yığın bu yönde büyür → üst

Yeni çerçeve: n = 1. 1 < 0 → false, 1 <= 1 → true: taban durum, return 1.

main()faktoriyel(4)n=4: 4 × ?faktoriyel(3)n=3: 3 × ?faktoriyel(2)n=2: 2 × ?faktoriyel(1)↩ return 1main (yığının dibi) → yığın bu yönde büyür → üst

faktoriyel(1) 1 döndürdü; çerçevesi silindi. Şimdi 2 × 1 = 2.

main()faktoriyel(4)n=4: 4 × ?faktoriyel(3)n=3: 3 × ?faktoriyel(2)↩ return 2main (yığının dibi) → yığın bu yönde büyür → üst

faktoriyel(2) 2 döndürdü; çerçevesi silindi. Şimdi 3 × 2 = 6.

main()faktoriyel(4)n=4: 4 × ?faktoriyel(3)↩ return 6main (yığının dibi) → yığın bu yönde büyür → üst

faktoriyel(3) 6 döndürdü; çerçevesi silindi. Şimdi 4 × 6 = 24.

main()faktoriyel(4)↩ return 24main (yığının dibi) → yığın bu yönde büyür → üst

En üstteki çerçeve de kapandı; main 24 yazdırır. En fazla 5 çerçeve açıktı → ek bellek O(n).

static long faktoriyel(int n) {
    if (n < 0)
        throw new IllegalArgumentException();
    if (n <= 1) return 1;
    return n * faktoriyel(n - 1);
}
System.out.println(faktoriyel(4));

Çağrı Yığını

faktoriyel(4): Açılım ve Dönüş

f(4) = 4 × f(3)f(3) = 3 × f(2)f(2) = 2 × f(1)f(1) = 1f(1) = 1f(2) = 2f(3) = 6f(4) = 24çağrılar açılır (yığın büyür) ↓↑ dönüşler (yığın küçülür)
  • Çağrılar taban duruma ulaşıncaya kadar yığında açılır; hiçbir çarpma henüz yapılmamıştır.
  • f(1) = 1 döndükten sonra bekleyen çarpımlar ters sırada tamamlanır: 2, 6, 24.

Örnekler · Kodu adım adım çalıştır

1'den n'e Toplam: toplam(3)

main()main (yığının dibi) → yığın bu yönde büyür → üst

Tanım: toplam(n) = n + toplam(n − 1), toplam(0) = 0. main, toplam(3)'ü çağırır.

main()toplam(3)n=3main (yığının dibi) → yığın bu yönde büyür → üst

3 <= 0 → false.

main()toplam(3)n=3: 3 + ?main (yığının dibi) → yığın bu yönde büyür → üst

3 + toplam(2): toplama bekler, yeni çağrı.

main()toplam(3)n=3: 3 + ?toplam(2)n=2main (yığının dibi) → yığın bu yönde büyür → üst

2 <= 0 → false.

main()toplam(3)n=3: 3 + ?toplam(2)n=2: 2 + ?main (yığının dibi) → yığın bu yönde büyür → üst

2 + toplam(1): toplama bekler, yeni çağrı.

main()toplam(3)n=3: 3 + ?toplam(2)n=2: 2 + ?toplam(1)n=1main (yığının dibi) → yığın bu yönde büyür → üst

1 <= 0 → false.

main()toplam(3)n=3: 3 + ?toplam(2)n=2: 2 + ?toplam(1)n=1: 1 + ?main (yığının dibi) → yığın bu yönde büyür → üst

1 + toplam(0): toplama bekler, yeni çağrı.

main()toplam(3)n=3: 3 + ?toplam(2)n=2: 2 + ?toplam(1)n=1: 1 + ?toplam(0)n=0main (yığının dibi) → yığın bu yönde büyür → üst

0 <= 0 → true: taban durum, return 0.

main()toplam(3)n=3: 3 + ?toplam(2)n=2: 2 + ?toplam(1)n=1: 1 + ?toplam(0)↩ return 0main (yığının dibi) → yığın bu yönde büyür → üst

toplam(0) = 0 döndü → 1 + 0 = 1.

main()toplam(3)n=3: 3 + ?toplam(2)n=2: 2 + ?toplam(1)↩ return 1main (yığının dibi) → yığın bu yönde büyür → üst

toplam(1) = 1 döndü → 2 + 1 = 3.

main()toplam(3)n=3: 3 + ?toplam(2)↩ return 3main (yığının dibi) → yığın bu yönde büyür → üst

toplam(2) = 3 döndü → 3 + 3 = 6.

main()toplam(3)↩ return 6main (yığının dibi) → yığın bu yönde büyür → üst

6 = 3 + 2 + 1 + 0 yazdırılır.

static int toplam(int n) {
    if (n <= 0) return 0;
    return n + toplam(n - 1);
}
System.out.println(toplam(3));

Örnekler · Kodu adım adım çalıştır

Rakamlar Toplamı: rakamToplami(472)

main()main (yığının dibi) → yığın bu yönde büyür → üst

main, rakamToplami(472)'yi çağırır.

main()rakamToplami(472)main (yığının dibi) → yığın bu yönde büyür → üst

Negatif sayılar için mutlak değer alınır: m = 472 (long'a çevirmek en küçük int'te taşmayı önler).

main()rakamToplami(472)main (yığının dibi) → yığın bu yönde büyür → üst

Asıl işi yapan yardımcı metot çağrılır.

main()rakamToplami(472)rakamToplamiPozitif(472)main (yığının dibi) → yığın bu yönde büyür → üst

472 < 10 → false: son rakam 2, kalan 47.

main()rakamToplami(472)rakamToplamiPozitif(472)2 + ?main (yığının dibi) → yığın bu yönde büyür → üst

472 % 10 = 2 bekletilir; 472 / 10 = 47 ile yeni çağrı.

main()rakamToplami(472)rakamToplamiPozitif(472)2 + ?rakamToplamiPozitif(47)main (yığının dibi) → yığın bu yönde büyür → üst

47 < 10 → false: son rakam 7, kalan 4.

main()rakamToplami(472)rakamToplamiPozitif(472)2 + ?rakamToplamiPozitif(47)7 + ?main (yığının dibi) → yığın bu yönde büyür → üst

47 % 10 = 7 bekletilir; 47 / 10 = 4 ile yeni çağrı.

main()rakamToplami(472)rakamToplamiPozitif(472)2 + ?rakamToplamiPozitif(47)7 + ?rakamToplamiPozitif(4)main (yığının dibi) → yığın bu yönde büyür → üst

4 < 10 → true: tek basamak, return 4.

main()rakamToplami(472)rakamToplamiPozitif(472)2 + ?rakamToplamiPozitif(47)7 + ?rakamToplamiPozitif(4)↩ return 4main (yığının dibi) → yığın bu yönde büyür → üst

Alt çağrı 4 döndü → 7 + 4 = 11.

main()rakamToplami(472)rakamToplamiPozitif(472)2 + ?rakamToplamiPozitif(47)↩ return 11main (yığının dibi) → yığın bu yönde büyür → üst

Alt çağrı 11 döndü → 2 + 11 = 13.

main()rakamToplami(472)rakamToplamiPozitif(472)↩ return 13main (yığının dibi) → yığın bu yönde büyür → üst

Yardımcı metot 13 döndürdü; aynen döndürülür.

main()rakamToplami(472)↩ return 13main (yığının dibi) → yığın bu yönde büyür → üst

4 + 7 + 2 = 13 yazdırılır.

static int rakamToplami(int n) {
    long m = Math.abs((long) n);
    return rakamToplamiPozitif(m);
}
static int rakamToplamiPozitif(long n) {
    if (n < 10) return (int) n;
    return (int) (n % 10)
        + rakamToplamiPozitif(n / 10);
}
System.out.println(rakamToplami(472));

Örnekler · Kodu adım adım çalıştır

Palindrom: palindrom("kayak", 0, 4)

main()main (yığının dibi) → yığın bu yönde büyür → üst0k1a2y3a4k

main, palindrom("kayak", 0, 4)'ü çağırır.

main()palindrom(s, 0, 4)main (yığının dibi) → yığın bu yönde büyür → üst0k1a2y3a4k▲ sol▲ sag

0 >= 4 → false.

main()palindrom(s, 0, 4)main (yığının dibi) → yığın bu yönde büyür → üst0k1a2y3a4k▲ sol▲ sag

'k' != 'k' → false: uçlar eşit, içe doğru devam.

main()palindrom(s, 0, 4)main (yığının dibi) → yığın bu yönde büyür → üst0k1a2y3a4k▲ sol▲ sag

İki uç atılır: palindrom(s, 1, 3) çağrılır.

main()palindrom(s, 0, 4)palindrom(s, 1, 3)main (yığının dibi) → yığın bu yönde büyür → üst0k1a2y3a4k▲ sol▲ sag

1 >= 3 → false.

main()palindrom(s, 0, 4)palindrom(s, 1, 3)main (yığının dibi) → yığın bu yönde büyür → üst0k1a2y3a4k▲ sol▲ sag

'a' != 'a' → false: uçlar eşit, içe doğru devam.

main()palindrom(s, 0, 4)palindrom(s, 1, 3)main (yığının dibi) → yığın bu yönde büyür → üst0k1a2y3a4k▲ sol▲ sag

İki uç atılır: palindrom(s, 2, 2) çağrılır.

main()palindrom(s, 0, 4)palindrom(s, 1, 3)palindrom(s, 2, 2)main (yığının dibi) → yığın bu yönde büyür → üst0k1a2y3a4k▲ solsag

2 >= 2 → true: ortada tek karakter kaldı → return true.

main()palindrom(s, 0, 4)palindrom(s, 1, 3)palindrom(s, 2, 2)↩ return truemain (yığının dibi) → yığın bu yönde büyür → üst0k1a2y3a4k▲ sol▲ sag

Alt çağrı true döndürdü; bu çerçeve de aynı değeri döndürür.

main()palindrom(s, 0, 4)palindrom(s, 1, 3)↩ return truemain (yığının dibi) → yığın bu yönde büyür → üst0k1a2y3a4k▲ sol▲ sag

Alt çağrı true döndürdü; bu çerçeve de aynı değeri döndürür.

main()palindrom(s, 0, 4)↩ return truemain (yığının dibi) → yığın bu yönde büyür → üst0k1a2y3a4k

Sonuç main'e ulaştı: true. "kayak" tersten de aynı okunur.

static boolean palindrom(String s,
                         int sol, int sag) {
    if (sol >= sag) return true;
    if (s.charAt(sol) != s.charAt(sag))
        return false;
    return palindrom(s, sol + 1, sag - 1);
}
System.out.println(palindrom("kayak", 0, 4));

Örnekler · Kodu adım adım çalıştır

Öklid ile EBOB: ebob(48, 18)

main()main (yığının dibi) → yığın bu yönde büyür → üst

main, ebob(48, 18)'i çağırır.

main()ebob(48, 18)main (yığının dibi) → yığın bu yönde büyür → üst

18 == 0 → false.

main()ebob(48, 18)main (yığının dibi) → yığın bu yönde büyür → üst

48 % 18 = 12 → ebob(18, 12) çağrılır (sayılar küçülüyor).

main()ebob(48, 18)ebob(18, 12)main (yığının dibi) → yığın bu yönde büyür → üst

12 == 0 → false.

main()ebob(48, 18)ebob(18, 12)main (yığının dibi) → yığın bu yönde büyür → üst

18 % 12 = 6 → ebob(12, 6) çağrılır (sayılar küçülüyor).

main()ebob(48, 18)ebob(18, 12)ebob(12, 6)main (yığının dibi) → yığın bu yönde büyür → üst

6 == 0 → false.

main()ebob(48, 18)ebob(18, 12)ebob(12, 6)main (yığının dibi) → yığın bu yönde büyür → üst

12 % 6 = 0 → ebob(6, 0) çağrılır (sayılar küçülüyor).

main()ebob(48, 18)ebob(18, 12)ebob(12, 6)ebob(6, 0)main (yığının dibi) → yığın bu yönde büyür → üst

0 == 0 → true: taban durum, return 6.

main()ebob(48, 18)ebob(18, 12)ebob(12, 6)ebob(6, 0)↩ return 6main (yığının dibi) → yığın bu yönde büyür → üst

Alt çağrı 6 döndürdü; aynen döndürülür.

main()ebob(48, 18)ebob(18, 12)ebob(12, 6)↩ return 6main (yığının dibi) → yığın bu yönde büyür → üst

Alt çağrı 6 döndürdü; aynen döndürülür.

main()ebob(48, 18)ebob(18, 12)↩ return 6main (yığının dibi) → yığın bu yönde büyür → üst

Alt çağrı 6 döndürdü; aynen döndürülür.

main()ebob(48, 18)↩ return 6main (yığının dibi) → yığın bu yönde büyür → üst

EBOB(48, 18) = 6: kalanlar küçülerek 0'a ulaştı; son sıfır olmayan kalan EBOB'dur.

static int ebob(int a, int b) {
    if (b == 0) return Math.abs(a);
    return ebob(b, a % b);
}
System.out.println(ebob(48, 18));

Örnekler · Kodu adım adım çalıştır

Hanoi Kuleleri: hanoi(3, 'A', 'C', 'B')

A321BChamle: 0

3 disk A'dan C'ye taşınacak; B yardımcı. Kural: büyük disk küçüğün üstüne konamaz.

A321BChamle: 0

hanoi(3, A, C, B): önce üstteki 2 disk A → B (yardımcıya) taşınmalı.

A321BChamle: 0

hanoi(2, A, B, C): önce üstteki 1 disk A → C (yardımcıya) taşınmalı.

A32BC1hamle: 1

Hamle 1: disk 1 A → C. (hanoi(0) çağrıları hemen döner)

A3B2C1hamle: 2

Hamle 2: disk 2 A → B.

A3B21Chamle: 3

Hamle 3: disk 1 C → B.

AB21C3hamle: 4

Hamle 4: disk 3 A → C.

AB21C3hamle: 4

hanoi(2, B, C, A): önce üstteki 1 disk B → A (yardımcıya) taşınmalı.

A1B2C3hamle: 5

Hamle 5: disk 1 B → A.

A1BC32hamle: 6

Hamle 6: disk 2 B → C.

ABC321hamle: 7

Hamle 7: disk 1 A → C.

ABC321hamle: 7

Bitti: 7 = 2³ − 1 hamle. n disk için 2ⁿ − 1 hamle gerekir (üstel).

static void hanoi(int n, char kaynak,
                  char hedef, char ara) {
  if (n == 0) return;
  hanoi(n - 1, kaynak, ara, hedef);
  System.out.print(kaynak + "->" + hedef + " ");
  hanoi(n - 1, ara, hedef, kaynak);
}
hanoi(3, 'A', 'C', 'B');

Örnekler · Kodu adım adım çalıştır

Özyinelemeli Üs: us(2, 4)

main()main (yığının dibi) → yığın bu yönde büyür → üst

2⁴ = 2 × 2³ tanımı: us(taban, n) = taban × us(taban, n − 1), us(taban, 0) = 1.

main()us(2, 4)2 × ?main (yığının dibi) → yığın bu yönde büyür → üst

4 == 0 → false: 2 × us(2, 3) için yeni çağrı.

main()us(2, 4)2 × ?us(2, 3)2 × ?main (yığının dibi) → yığın bu yönde büyür → üst

3 == 0 → false: 2 × us(2, 2) için yeni çağrı.

main()us(2, 4)2 × ?us(2, 3)2 × ?us(2, 2)2 × ?main (yığının dibi) → yığın bu yönde büyür → üst

2 == 0 → false: 2 × us(2, 1) için yeni çağrı.

main()us(2, 4)2 × ?us(2, 3)2 × ?us(2, 2)2 × ?us(2, 1)2 × ?main (yığının dibi) → yığın bu yönde büyür → üst

1 == 0 → false: 2 × us(2, 0) için yeni çağrı.

main()us(2, 4)2 × ?us(2, 3)2 × ?us(2, 2)2 × ?us(2, 1)2 × ?us(2, 0)n=0main (yığının dibi) → yığın bu yönde büyür → üst

0 == 0 → true: taban durum, return 1.

main()us(2, 4)2 × ?us(2, 3)2 × ?us(2, 2)2 × ?us(2, 1)2 × ?us(2, 0)↩ return 1main (yığının dibi) → yığın bu yönde büyür → üst

us(2, 0) = 1 döndü → 2 × 1 = 2.

main()us(2, 4)2 × ?us(2, 3)2 × ?us(2, 2)2 × ?us(2, 1)↩ return 2main (yığının dibi) → yığın bu yönde büyür → üst

us(2, 1) = 2 döndü → 2 × 2 = 4.

main()us(2, 4)2 × ?us(2, 3)2 × ?us(2, 2)↩ return 4main (yığının dibi) → yığın bu yönde büyür → üst

us(2, 2) = 4 döndü → 2 × 4 = 8.

main()us(2, 4)2 × ?us(2, 3)↩ return 8main (yığının dibi) → yığın bu yönde büyür → üst

us(2, 3) = 8 döndü → 2 × 8 = 16.

main()us(2, 4)↩ return 16main (yığının dibi) → yığın bu yönde büyür → üst

16: n + 1 = 5 çağrı → O(n).

static long us(long taban, int n) {
  if (n == 0) return 1;
  return taban * us(taban, n - 1);
}
System.out.println(us(2, 4));

Örnekler · Kodu adım adım çalıştır

Hızlı Üs: hizliUs(2, 10)

main()main (yığının dibi) → yığın bu yönde büyür → üst

Fikir: 2¹⁰ = (2⁵)², 2⁵ = (2²)² × 2. Üs her çağrıda yarıya iner.

main()hizliUs(2, 10)main (yığının dibi) → yığın bu yönde büyür → üst

us = 10 ≠ 0 → önce hizliUs(2, 5) (10 / 2 = 5).

main()hizliUs(2, 10)hizliUs(2, 5)main (yığının dibi) → yığın bu yönde büyür → üst

us = 5 ≠ 0 → önce hizliUs(2, 2) (5 / 2 = 2).

main()hizliUs(2, 10)hizliUs(2, 5)hizliUs(2, 2)main (yığının dibi) → yığın bu yönde büyür → üst

us = 2 ≠ 0 → önce hizliUs(2, 1) (2 / 2 = 1).

main()hizliUs(2, 10)hizliUs(2, 5)hizliUs(2, 2)hizliUs(2, 1)main (yığının dibi) → yığın bu yönde büyür → üst

us = 1 ≠ 0 → önce hizliUs(2, 0) (1 / 2 = 0).

main()hizliUs(2, 10)hizliUs(2, 5)hizliUs(2, 2)hizliUs(2, 1)hizliUs(2, 0)main (yığının dibi) → yığın bu yönde büyür → üst

us == 0: return 1.

main()hizliUs(2, 10)hizliUs(2, 5)hizliUs(2, 2)hizliUs(2, 1)hizliUs(2, 0)↩ return 1main (yığının dibi) → yığın bu yönde büyür → üst

yarim = 1; 1 tek → 1 × 1 × 2 = 2.

main()hizliUs(2, 10)hizliUs(2, 5)hizliUs(2, 2)hizliUs(2, 1)↩ return 2main (yığının dibi) → yığın bu yönde büyür → üst

yarim = 2; 2 çift → 2 × 2 = 4.

main()hizliUs(2, 10)hizliUs(2, 5)hizliUs(2, 2)↩ return 4main (yığının dibi) → yığın bu yönde büyür → üst

yarim = 4; 5 tek → 4 × 4 × 2 = 32.

main()hizliUs(2, 10)hizliUs(2, 5)↩ return 32main (yığının dibi) → yığın bu yönde büyür → üst

yarim = 32; 10 çift → 32 × 32 = 1024.

main()hizliUs(2, 10)↩ return 1024main (yığının dibi) → yığın bu yönde büyür → üst

1024, yalnızca 5 çağrıyla (düz yöntem 11 çağrı yapardı) → O(log n).

static long hizliUs(long taban, int us) {
  if (us == 0) return 1;
  long yarim = hizliUs(taban, us / 2);
  if (us % 2 == 0) return yarim * yarim;
  return yarim * yarim * taban;
}
System.out.println(hizliUs(2, 10));

Örnekler · Kodu adım adım çalıştır

Özyinelemeli İkili Arama: ara(a, 38, 0, 7)

021528312416523638756a

Döngü yerine özyineleme: her çağrı aralığın yarısıyla kendini çağırır.

021528312416523638756▲ lo▲ hi▲ mida

0 <= 7; mid = 3, a[mid] = 12.

021528312416523638756▲ lo▲ hia

12 < 38 → sağ yarı: ara(a, 38, 4, 7).

021528312416523638756▲ lo▲ hi▲ mida

4 <= 7; mid = 5, a[mid] = 23.

021528312416523638756▲ lo▲ hia

23 < 38 → sağ yarı: ara(a, 38, 6, 7).

021528312416523638756▲ lomid▲ hia

6 <= 7; mid = 6, a[mid] = 38.

021528312416523638756▲ lomid▲ hia

38 == 38 → return 6 (taban durum).

021528312416523638756▲ mida

Sonuç çerçevelerden aynen geri geçer: 6.

static int ara(int[] a, int x,
               int lo, int hi) {
  if (lo > hi) return -1;
  int mid = (lo + hi) / 2;
  if (a[mid] == x) return mid;
  if (a[mid] < x) return ara(a, x, mid + 1, hi);
  return ara(a, x, lo, mid - 1);
}
// a = {2, 5, 8, 12, 16, 23, 38, 56}
System.out.println(ara(a, 38, 0, 7));

Örnekler

Böl ve Fethet: Birleştirme Sıralaması

static void sirala(int[] a, int lo, int hi) {
    if (lo >= hi) return;
    int mid = (lo + hi) / 2;
    sirala(a, lo, mid);
    sirala(a, mid + 1, hi);
    int[] t = new int[hi - lo + 1];
    int i = lo, j = mid + 1, k = 0;
    while (i <= mid && j <= hi)
        t[k++] = a[i] <= a[j] ? a[i++] : a[j++];
    while (i <= mid) t[k++] = a[i++];
    while (j <= hi) t[k++] = a[j++];
    System.arraycopy(t, 0, a, lo, t.length);
}

Her düzeyde toplam n öğe birleştirilir, düzey sayısı log₂ n: zaman O(n log n), ek bellek O(n).

[5, 2, 4, 1][5, 2][4, 1][5][2][4][1][2, 5][1, 4][1, 2, 4, 5]bölbirleştirgri ok: böl · renkli ok: birleştir

Örnekler · Kodu adım adım çalıştır

Dizgiyi Özyinelemeli Ters Çevirme

main()main (yığının dibi) → yığın bu yönde büyür → üst

ters(s) = ters(ilk karakter hariç kalan) + ilk karakter.

main()ters("abc")? + "a"main (yığının dibi) → yığın bu yönde büyür → üst

ters("bc") çağrılır; 'a' sona eklenmek üzere bekler.

main()ters("abc")? + "a"ters("bc")? + "b"main (yığının dibi) → yığın bu yönde büyür → üst

ters("c") çağrılır; 'b' sona eklenmek üzere bekler.

main()ters("abc")? + "a"ters("bc")? + "b"ters("c")s="c"main (yığının dibi) → yığın bu yönde büyür → üst

Uzunluk 1 ≤ 1 → taban durum, "c" döner.

main()ters("abc")? + "a"ters("bc")? + "b"ters("c")↩ return "c"main (yığının dibi) → yığın bu yönde büyür → üst

"c" + 'b' = "cb".

main()ters("abc")? + "a"ters("bc")↩ return "cb"main (yığının dibi) → yığın bu yönde büyür → üst

"cb" + 'a' = "cba".

main()ters("abc")↩ return "cba"main (yığının dibi) → yığın bu yönde büyür → üst

cba yazdırılır. Her çağrı substring ile yeni bir alt dizgi üretir.

static String ters(String s) {
  if (s.length() <= 1) return s;
  return ters(s.substring(1)) + s.charAt(0);
}
System.out.println(ters("abc"));

Örnekler · Kodu adım adım çalıştır

Kod İzleme: Alt Küme Üretimi

main()main (yığının dibi) → yığın bu yönde büyür → üst

main, boş bir liste ile altKume(a, 0, sec)'yi çağırır. Her öğe için iki seçim var: dahil et / hariç tut.

main()altKume(i=0)sec (tüm çerçevelerin ortak listesi)[]main (yığının dibi) → yığın bu yönde büyür → üst

i = 0: dizi bitmedi (0 ≠ 2). Önce 1 dahil dalı.

main()altKume(i=0)sec (tüm çerçevelerin ortak listesi)[1]main (yığının dibi) → yığın bu yönde büyür → üst

sec.add(1): liste artık [1].

main()altKume(i=0)altKume(i=1)sec (tüm çerçevelerin ortak listesi)[1]main (yığının dibi) → yığın bu yönde büyür → üst

Yeni çerçeve i = 1: 2 için karar; önce dahil et.

main()altKume(i=0)altKume(i=1)sec (tüm çerçevelerin ortak listesi)[1, 2]main (yığının dibi) → yığın bu yönde büyür → üst

sec.add(2): liste [1, 2].

main()altKume(i=0)altKume(i=1)altKume(i=2)sec (tüm çerçevelerin ortak listesi)[1, 2]main (yığının dibi) → yığın bu yönde büyür → üst

i = 2 = a.length: taban durum. Alt küme yazdırılır: [1, 2].

main()altKume(i=0)altKume(i=1)sec (tüm çerçevelerin ortak listesi)[1]main (yığının dibi) → yığın bu yönde büyür → üst

Geri dönüş: sec.remove(...) 2'yi çıkarır (geri izleme) → [1].

main()altKume(i=0)altKume(i=1)altKume(i=2)sec (tüm çerçevelerin ortak listesi)[1]main (yığının dibi) → yığın bu yönde büyür → üst

2'yi hariç tut dalı yaprağa ulaşır: [1].

main()altKume(i=0)sec (tüm çerçevelerin ortak listesi)[]main (yığının dibi) → yığın bu yönde büyür → üst

altKume(i=1) bitti; remove 1'i çıkarır → [].

main()altKume(i=0)altKume(i=1)sec (tüm çerçevelerin ortak listesi)[]main (yığının dibi) → yığın bu yönde büyür → üst

1'i hariç tut dalı: yeni çerçeve i = 1.

main()altKume(i=0)altKume(i=1)sec (tüm çerçevelerin ortak listesi)[2]main (yığının dibi) → yığın bu yönde büyür → üst

sec.add(2): liste [2].

main()altKume(i=0)altKume(i=1)altKume(i=2)sec (tüm çerçevelerin ortak listesi)[2]main (yığının dibi) → yığın bu yönde büyür → üst

Yaprak: [2] yazdırılır.

main()altKume(i=0)altKume(i=1)sec (tüm çerçevelerin ortak listesi)[]main (yığının dibi) → yığın bu yönde büyür → üst

remove 2'yi çıkarır → [].

main()altKume(i=0)altKume(i=1)altKume(i=2)sec (tüm çerçevelerin ortak listesi)[]main (yığının dibi) → yığın bu yönde büyür → üst

Son yaprak: boş küme [].

main()sec (tüm çerçevelerin ortak listesi)[]main (yığının dibi) → yığın bu yönde büyür → üst

Tüm çerçeveler kapandı. 2 öğe → 2² = 4 alt küme; çıktı sırası: [1, 2], [1], [2], [].

static void altKume(int[] a, int i,
                    List<Integer> sec) {
    if (i == a.length) {
        System.out.println(sec);
        return;
    }
    sec.add(a[i]);
    altKume(a, i + 1, sec);
    sec.remove(sec.size() - 1);
    altKume(a, i + 1, sec);
}
altKume(new int[]{1, 2}, 0, new ArrayList<>());

Çağrı Yığını

Çağrı Yığını

  • Yeni çağrı yapıldığında mevcut metodun dönüş noktası ve yerel değişkenleri çerçevesinde saklanır.
  • Taban durumdan sonra çerçeveler ters sırada (son açılan ilk) kapanır.
  • Taban durum yoksa (sonsuz özyineleme) ya da çok derin özyinelemede yığın dolar ve yığın taşması olur: StackOverflowError.
main()sonsuz(0)dönüş noktası saklısonsuz(1)dönüş noktası saklısonsuz(2)dönüş noktası saklısonsuz(3)sonsuz(4)sonsuz(5)yığın sınırıStackOverflowError

Çağrı Yığını · Kodu adım adım çalıştır

Kod İzleme: StackOverflowError

main()main (yığının dibi) → yığın bu yönde büyür → üst

main, sonsuz(0)'ı çağırır.

main()sonsuz(0)main (yığının dibi) → yığın bu yönde büyür → üst

İlk çerçeve. Metotta taban durum yok: hiçbir koşul çağrıyı durdurmuyor, hemen sonsuz(1) çağrılır.

main()sonsuz(0)sonsuz(1)main (yığının dibi) → yığın bu yönde büyür → üst

İkinci çerçeve; önceki çerçeve hâlâ yığında.

main()sonsuz(0)sonsuz(1)sonsuz(2)main (yığının dibi) → yığın bu yönde büyür → üst

Üçüncü çerçeve. Hiçbir çağrı dönmediği için hiçbir çerçeve silinmez.

main()sonsuz(0)sonsuz(1)sonsuz(2)…main (yığının dibi) → yığın bu yönde büyür → üst

Bu böyle sürer; yığın belleği sınırlıdır (varsayılan boyutta genellikle on binlerce çağrı).

main()sonsuz(0)sonsuz(1)sonsuz(2)…sonsuz(k)yığın doldumain (yığının dibi) → yığın bu yönde büyür → üst

Yığın doldu: JVM StackOverflowError fırlatır, program biter; hata metni stderr'e yazılır.

static void sonsuz(int n) {
    sonsuz(n + 1);
}
sonsuz(0);

Verimlilik

Özyineleme mi Döngü mü?

  • Kendine benzeyen problemlerde (ağaçlar, böl-parçala) özyineleme tanıma yakın ve açıktır.
  • Basit doğrusal tekrarlarda döngü tek çerçevede çalışır, daha az yığın belleği kullanır.
  • Seçim yalnızca kod kısalığına değil; doğruluk, okunabilirlik ve kaynak kullanımına dayanmalıdır.
özyinelemelidöngülümain()faktoriyel(4)faktoriyel(3)faktoriyel(2)faktoriyel(1)main()faktoriyelDongu(4)i, sonuc aynı çerçevedeher turda güncellenir5 çerçeve: O(n) yığın2 çerçeve: O(1) ek yığın

Verimlilik

Faktöriyel Karşılaştırması

ÖzellikÖzyinelemeliDöngülü
Zaman karmaşıklığıO(n)O(n)
Ek yığınO(n)O(1)
AkışTanıma yakınAçık sayaç
Derinlik riskiVarYok
özyinelemeli faktoriyel(4)faktoriyel(1)faktoriyel(2)faktoriyel(3)faktoriyel(4)main()4 çerçeveek yığın: n çerçeve → O(n)döngülü faktoriyelDongu(4)i: 2 → 3 → 4, sonuc: 2 → 6 → 24faktoriyelDongu(4)n, i, sonucmain()ek yığın: tek çerçeve → O(1)

Verimlilik · Kodu adım adım çalıştır

Karşılaştırma: Döngülü faktoriyel(4)

main()main (yığının dibi) → yığın bu yönde büyür → üst

main, faktoriyelDongu(4)'ü çağırır.

main()faktoriyelDongu(4)n=4main (yığının dibi) → yığın bu yönde büyür → üst

Tek çerçeve açılır; sonuc = 1.

main()faktoriyelDongu(4)i=2, sonuc=1main (yığının dibi) → yığın bu yönde büyür → üst

2 <= 4 → true

main()faktoriyelDongu(4)i=2, sonuc=2main (yığının dibi) → yığın bu yönde büyür → üst

sonuc *= 2 → 1 × 2 = 2. Yeni çerçeve açılmadı.

main()faktoriyelDongu(4)i=3, sonuc=2main (yığının dibi) → yığın bu yönde büyür → üst

i++ → 3 <= 4 → true

main()faktoriyelDongu(4)i=3, sonuc=6main (yığının dibi) → yığın bu yönde büyür → üst

sonuc *= 3 → 2 × 3 = 6. Yeni çerçeve açılmadı.

main()faktoriyelDongu(4)i=4, sonuc=6main (yığının dibi) → yığın bu yönde büyür → üst

i++ → 4 <= 4 → true

main()faktoriyelDongu(4)i=4, sonuc=24main (yığının dibi) → yığın bu yönde büyür → üst

sonuc *= 4 → 6 × 4 = 24. Yeni çerçeve açılmadı.

main()faktoriyelDongu(4)i=5, sonuc=24main (yığının dibi) → yığın bu yönde büyür → üst

i++ → 5 <= 4 → false: döngü biter.

main()faktoriyelDongu(4)i=5, sonuc=24main (yığının dibi) → yığın bu yönde büyür → üst

return 24.

main()faktoriyelDongu(4)↩ return 24main (yığının dibi) → yığın bu yönde büyür → üst

24 yazdırılır. Yığın hiç 2 çerçeveyi aşmadı → ek yığın O(1).

static long faktoriyelDongu(int n) {
    long sonuc = 1;
    for (int i = 2; i <= n; i++)
        sonuc *= i;
    return sonuc;
}
System.out.println(faktoriyelDongu(4));

Verimlilik

Fibonacci Sorunu: fib(5) Özyineleme Ağacı

f(5)f(4)f(3)f(2)f(1)f(0)f(1)f(2)f(1)f(0)f(3)f(2)f(1)f(0)f(1)
  • fib(5) için 15 çağrı yapılır: f(3) 2 kez, f(2) 3 kez, f(1) 5 kez hesaplanır (kesikli turuncu = tekrar).
  • fib(n − 1) ve fib(n − 2) alt ağaçları büyük ölçüde örtüşür; çağrı sayısı n ile yaklaşık üstel büyür.

Verimlilik · Kodu adım adım çalıştır

Kod İzleme: fib(4)

main, fib(4)'ü çağırır.

f(4)

fib(4) çağrıldı: 4 <= 1 → false.

f(4)f(3)

fib(3) çağrıldı: 3 <= 1 → false.

f(4)f(3)f(2)

fib(2) çağrıldı: 2 <= 1 → false.

f(4)f(3)f(2)f(1)

fib(1) çağrıldı: 1 <= 1 → true, return 1.

f(4)f(3)f(2)f(1)=1f(0)

fib(0) çağrıldı: 0 <= 1 → true, return 0.

f(4)f(3)f(2)=1f(1)=1f(0)=0

İki alt sonuç hazır: 1 + 0 = 1, return.

f(4)f(3)f(2)=1f(1)=1f(0)=0f(1)

fib(1) çağrıldı: 1 <= 1 → true, return 1.

f(4)f(3)=2f(2)=1f(1)=1f(0)=0f(1)=1

İki alt sonuç hazır: 1 + 1 = 2, return.

f(4)f(3)=2f(2)=1f(1)=1f(0)=0f(1)=1f(2)

fib(2) çağrıldı: 2 <= 1 → false. Tekrar: f(2) yeniden hesaplanıyor!

f(4)f(3)=2f(2)=1f(1)=1f(0)=0f(1)=1f(2)f(1)

fib(1) çağrıldı: 1 <= 1 → true, return 1.

f(4)f(3)=2f(2)=1f(1)=1f(0)=0f(1)=1f(2)f(1)=1f(0)

fib(0) çağrıldı: 0 <= 1 → true, return 0.

f(4)f(3)=2f(2)=1f(1)=1f(0)=0f(1)=1f(2)=1f(1)=1f(0)=0

İki alt sonuç hazır: 1 + 0 = 1, return.

f(4)=3f(3)=2f(2)=1f(1)=1f(0)=0f(1)=1f(2)=1f(1)=1f(0)=0

İki alt sonuç hazır: 2 + 1 = 3, return.

f(4)=3f(3)=2f(2)=1f(1)=1f(0)=0f(1)=1f(2)=1f(1)=1f(0)=0

3 yazdırılır. Küçük bir n için bile 9 çağrı yapıldı.

static long fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}
System.out.println(fib(4));

İyileştirme

Bellekleme (Memoization)

  • Hesaplanan alt problem sonuçları bir dizide (önbellek) saklanır.
  • Aynı girdi tekrar gelince özyineleme ağacı yeniden açılmaz, kayıtlı değer döner.
  • fib(5) için 15 çağrı yerine 9 çağrı; büyük n'de fark çok büyür.
  • Zaman kazanımı karşılığında O(n) ek bellek kullanılır.
f(5)f(3) ✓f(4)f(2) ✓f(3)f(1)f(2)f(0)f(1)✓ = bellekten okundu, alt ağaç açılmadı

İyileştirme · Kodu adım adım çalıştır

Kod İzleme: Belleklemeli fib(5)

001020304050bellek0 ve 1 taban durum (kullanılmaz)çağrı: 0

6 hücreli bellek dizisi; 0 = henüz hesaplanmadı.

001020304050bellek0 ve 1 taban durum (kullanılmaz)çağrı: 1

fib(5): bellek[5] == 0 → henüz yok, hesaplanacak.

001020304050bellek0 ve 1 taban durum (kullanılmaz)çağrı: 2

fib(4): bellek[4] == 0 → henüz yok, hesaplanacak.

001020304050bellek0 ve 1 taban durum (kullanılmaz)çağrı: 3

fib(3): bellek[3] == 0 → henüz yok, hesaplanacak.

001020304050bellek0 ve 1 taban durum (kullanılmaz)çağrı: 4

fib(2): bellek[2] == 0 → henüz yok, hesaplanacak.

001020304050bellek0 ve 1 taban durum (kullanılmaz)çağrı: 5

fib(1): 1 <= 1 → true, return 1.

001020304050bellek0 ve 1 taban durum (kullanılmaz)çağrı: 6

fib(0): 0 <= 1 → true, return 0.

001021304050bellek0 ve 1 taban durum (kullanılmaz)çağrı: 6

bellek[2] = 1 + 0 = 1 kaydedilir ve döndürülür.

001021304050bellek0 ve 1 taban durum (kullanılmaz)çağrı: 7

fib(1): 1 <= 1 → true, return 1.

001021324050bellek0 ve 1 taban durum (kullanılmaz)çağrı: 7

bellek[3] = 1 + 1 = 2 kaydedilir ve döndürülür.

001021324050bellek0 ve 1 taban durum (kullanılmaz)✓ bellektençağrı: 8

fib(2): bellek[2] = 1 != 0 → hesaplamadan return 1.

001021324350bellek0 ve 1 taban durum (kullanılmaz)çağrı: 8

bellek[4] = 2 + 1 = 3 kaydedilir ve döndürülür.

001021324350bellek0 ve 1 taban durum (kullanılmaz)✓ bellektençağrı: 9

fib(3): bellek[3] = 2 != 0 → hesaplamadan return 2.

001021324355bellek0 ve 1 taban durum (kullanılmaz)çağrı: 9

bellek[5] = 3 + 2 = 5 kaydedilir ve döndürülür.

001021324355bellek0 ve 1 taban durum (kullanılmaz)çağrı: 9

5 yazdırılır. Toplam 9 çağrı (belleksiz 15) → zaman O(n).

static long fib(int n, long[] bellek) {
    if (n <= 1) return n;
    if (bellek[n] != 0) return bellek[n];
    bellek[n] = fib(n - 1, bellek)
              + fib(n - 2, bellek);
    return bellek[n];
}
long[] bellek = new long[6];
System.out.println(fib(5, bellek));

İyileştirme

Kuyruk Özyinelemesi

  • Özyinelemeli çağrı metodun son işlemidir; dönüşte yapılacak iş kalmaz.
  • Ara sonuç bir parametrede (birikim) taşınır.
  • Bazı diller bu çağrıları yığını büyütmeden optimize eder.
  • Java dili/JVM kuyruk çağrısı optimizasyonunu garanti etmez: çerçeveler yine birikir.
static long fakt(int n, long birikim) {
    if (n <= 1) return birikim;
    return fakt(n - 1, n * birikim);
}
// fakt(4, 1) → fakt(3, 4)
//   → fakt(2, 12) → fakt(1, 24) → 24
normalkuyrukfaktoriyel(4)fakt(4, 1)faktoriyel(3)fakt(3, 4)faktoriyel(2)fakt(2, 12)faktoriyel(1)fakt(1, 24)24 = 4 × 66 = 3 × 22 = 2 × 1dönüşte:taban durum 24'ü döndürür; dönüşte çarpma kalmaz, 24 aynen geri iletilir

İyileştirme · Kodu adım adım çalıştır

Kod İzleme: fakt(4, 1)

main()main, fakt(4, 1)'i çağırır; birikim başlangıçta 1main (yığının dibi) → yığın bu yönde büyür → üst

main, fakt(4, 1)'i çağırır: ikinci parametre birikim başlangıçta 1.

main()fakt(4, 1)birikim=1main (yığının dibi) → yığın bu yönde büyür → üst

Çerçeve açıldı. 4 <= 1 → false. Son işlem fakt(3, 4 * 1) yani fakt(3, 4) çağrısı; dönüşte yapılacak çarpma yok.

main()fakt(4, 1)birikim=1fakt(3, 4)birikim=4main (yığının dibi) → yığın bu yönde büyür → üst

Çarpma çağrıdan önce yapıldı: sıradaki çağrı fakt(2, 12). Eski çerçevenin bekleyecek işi yok; ama Java onu silmez.

main()fakt(4, 1)birikim=1fakt(3, 4)birikim=4fakt(2, 12)birikim=12main (yığının dibi) → yığın bu yönde büyür → üst

Sıradaki çağrı fakt(1, 24). Yığın yine her çağrıda bir çerçeve büyüyor.

main()fakt(4, 1)birikim=1fakt(3, 4)birikim=4fakt(2, 12)birikim=12fakt(1, 24)birikim=24main (yığının dibi) → yığın bu yönde büyür → üst

1 <= 1 → true: taban durum, return birikim → 24. Kuyruk özyinelemesi olsa da yığında 5 çerçeve (main + 4) birikti: Java'da kuyruk çağrısı optimizasyonu yok.

main()← 24 döndümain (yığının dibi) → yığın bu yönde büyür → üst

24 çerçevelerden geçerek main'e döner; çarpma yapılmadı, yalnızca sonuç taşındı. Bellek yine O(n).

static long fakt(int n, long birikim) {
    if (n <= 1) return birikim;
    return fakt(n - 1, n * birikim);
}
System.out.println(fakt(4, 1)); // 24

Doğrulama

Taşma ve Uç Durumlar

long taşması

faktoriyel(20) → 2432902008176640000 (doğru).
faktoriyel(21) → -4249290049419214848
Hata fırlatılmaz; değer sessizce sarar. long en çok 9223372036854775807 tutar.

Negatif üs

us(2, -1): taban durum n == 0 hiç sağlanmaz; n −1, −2, … diye iner → StackOverflowError. Önlem: n < 0 ise IllegalArgumentException fırlatılır.

Fibonacci: özyinelemeli ≈ O(2ⁿ), döngülü O(n)

nfib(n) özyinelemeli: çağrı sayısıfib(n) döngülü: tur sayısı
1017710
202189120
30269253730

Sayılar, her çağrıda sayaç artırılarak elde edildi; süre ölçümü değildir.

Doğrulama

Hata Ayıklama

  • Taban duruma her girdi için ulaşıldığı gösterilir.
  • Problemin her çağrıda hangi ölçüye göre küçüldüğü belirlenir.
  • Küçük bir girdi için çağrı ve dönüş sırası tabloya dökülür (sağda toplam(2)).
  • Özyineleme derinliği ve tekrarlanan alt problemler değerlendirilir.
DerinlikOlayDeğer
0toplam(2) çağrıldı
1  toplam(1) çağrıldı
2    toplam(0) çağrıldı
2    toplam(0) döndü0
1  toplam(1) döndü1
0toplam(2) döndü3

Özet

Bölüm Özeti

Yapı

Taban durum + probleme küçülten özyinelemeli durum.

Çağrı yığını

Her çağrı bir çerçeve açar; taban durumdan sonra çerçeveler ters sırada kapanır.

Maliyet

Doğrusal özyineleme O(n) yığın kullanır; döngü çoğu zaman O(1) ile aynı işi yapar.

Bellekleme

Tekrarlanan alt problem sonuçlarını saklamak fib'i üstelden O(n)'e indirir.

Özet

Kontrol Soruları

  1. Taban durum olmazsa ne olur?
  2. Çağrı yığını neden ters sırada çözülür?
  3. Özyinelemeli faktöriyel ne kadar ek yığın kullanır?
  4. Doğrudan Fibonacci neden yavaştır?
  5. Java'da kuyruk özyinelemesi neden yığın güvenliği sağlamaz?
Cevaplar
  1. Çağrılar sonlanmaz; yığın dolar ve StackOverflowError oluşur.
  2. Her çağrı, kendisinden sonra açılan çağrının sonucunu bekler; son açılan çerçeve ilk tamamlanır.
  3. n çerçeve → O(n).
  4. Aynı alt problemleri (f(2), f(3)…) tekrar tekrar hesaplar; çağrı sayısı üstel büyür.
  5. JVM kuyruk çağrılarını optimize etmeyi garanti etmez; her çağrı yine yeni çerçeve açar.

Özet

Alıştırmalar

  1. Bir dizinin öğeleri toplamını özyinelemeli yazın: toplam(a, i) = a[i] + toplam(a, i + 1).
  2. Bir sayının basamak sayısını özyinelemeli bulun (basamak(472) = 3).
  3. Onluk sayıyı özyinelemeyle ikiliye çeviren ikili(n) metodunu yazın (13 → "1101").
  4. Hanoi'de n = 4 için kaç hamle gerekir? Programla doğrulayın.
  5. kombinasyon(n, k) = kombinasyon(n − 1, k − 1) + kombinasyon(n − 1, k) için taban durumları yazın; belleklemeyle hızlandırın.
  6. Bir dizgideki bir karakterin kaç kez geçtiğini özyinelemeli sayın.

Özet

Kaynaklar

  • Roberts, Thinking Recursively with Java, Wiley, 2006.
  • Sedgewick ve Wayne, Computer Science: An Interdisciplinary Approach, Addison-Wesley, 2016: 2.3 Recursion.
  • Cormen, Leiserson, Rivest ve Stein, Introduction to Algorithms, 4. baskı, MIT Press, 2022.
  • Liang, Introduction to Java Programming and Data Structures, 13. baskı, Pearson, 2024.
  • Oracle, Java SE 21 API: StackOverflowError.
  • Downey ve Mayfield, Think Java, 2. baskı, O'Reilly, 2019 (ücretsiz çevrim içi sürüm).

Son

Özyineleme
Çalışma soruları · Simülatör: Özyineleme Görselleştirici · Terim sözlüğü
1 / 1Sercan KÜLCÜ, Tüm hakları saklıdır.