4  Problem Oturumu 1

Ders 1-2’nin uygulaması: asimptotik sıralama, sequence black box, çift uçlu dinamik dizi ve yerinde bağlı liste ters çevirme

NotOturum bilgisi

4.1 Bu Problem Oturumu Ne Hakkında?

6.006’da iki tür öğretim vardır: ders (lecture) temel materyali (veri yapıları, algoritmalar) sunar; problem oturumu (problem session) ise o materyalin uygulamasını gösterir. Jason Ku’nun deyişiyle, problemlerin “hissi” temel materyalden çok farklıdır — problemlere yaklaşmanın, ancak çalışarak öğrenilen püf noktaları vardır.

Bu ilk oturum, Ders 1-2’nin kavramlarını uygular: asimptotik analiz (Problem 1), sequence arayüzü ve özyineleme (Problem 2), dinamik dizi (Problem 3) ve bağlı liste manipülasyonu (Problem 4). Dört problem birer “İfade → Yaklaşım → Çözüm → Karmaşıklık” akışıyla işlenir. Ortak araçların haritası Şekil 4.1 içinde özetlenir.

flowchart TD
    R["Problem Oturumu 1<br/>(Ders 1-2 uygulaması)"] --> P1["Problem 1<br/>Asimptotik Sıralama"]
    R --> P2["Problem 2<br/>Sequence Black Box"]
    R --> P3["Problem 3<br/>Çift Uçlu Dinamik Dizi"]
    R --> P4["Problem 4<br/>Son Yarıyı Yerinde Ters"]
    P1 --> T1["Asimptotik karşılaştırma<br/>(log n)ᵃ ≪ nᵇ ≪ cⁿ · Stirling"]
    P2 --> T2["Black-box arayüz<br/>+ özyineleme + değişmez"]
    P3 --> T3["Charging argument<br/>(O(1) amortize)"]
    P4 --> T4["Yerinde işaretçi<br/>manipülasyonu (O(1) alan)"]

    classDef root fill:#fef3c7,stroke:#b45309,stroke-width:3px,color:#1f2937
    classDef prob fill:#f3f4f6,stroke:#374151,stroke-width:2px,color:#1f2937
    classDef tool fill:#ffffff,stroke:#9ca3af,stroke-width:1px,color:#1f2937
    class R root
    class P1,P2,P3,P4 prob
    class T1,T2,T3,T4 tool
Şekil 4.1: Problem Oturumu 1’in kavram haritası: dört problem (üst sıra) ve her birinin öğrettiği taşınabilir araç (alt sıra). Problem 1 asimptotik karşılaştırma (Stirling + hiyerarşi), Problem 2 black-box arayüz disiplini + özyineleme, Problem 3 charging argument (amortize), Problem 4 yerinde işaretçi manipülasyonu kazandırır. Hepsi Ders 1-2 temeline dayanır.

Bir genel uyarı tüm oturuma damga vurur: çözümü kod değil, kelimelerle anlat. Ku, kafasında kod derleyemediğini söyler; problem setlerinde de net sözel açıklama beklenir.

“the problem sets that you will work on… applications of that material… there’s usually a much different feel between those problems then the underlying foundational material.” — Ku, 1:04

“I want you to explain in words to me, and we want you to explain in words in your LaTeX submissions what it is the algorithm is doing.” — Ku, 30:37

4.2 Problem 1: Asimptotik Sıralama

İfade. Bir fonksiyon listesini asimptotik büyüme hızına göre (yavaştan hızlıya) sırala. Asimptotik olarak eşit olan fonksiyonlar küme parantezi {} içinde gruplanır. Örneğin \(n\), \(\sqrt{n}\), \(n + \sqrt{n}\) için cevap: \(\sqrt{n}\), sonra \(\{n,\ n + \sqrt{n}\}\).

İpucuYaklaşım — Tanıdık Forma İndirge, Sonra Hiyerarşiyi Uygula

Her fonksiyonu tanıdık bir forma (polinom-benzeri) çevir, sonra şu hiyerarşiyi uygula: logaritmik faktörler polinom faktörlerden, polinomlar da üstellerden yavaş büyür. Karmaşık ifadeler (faktöriyel, binom katsayısı) için Stirling yaklaşımı kullanılır. Bir fonksiyonu sınıflandıramıyorsan, onu faktöriyel/binom tanımına aç, Stirling uygula, sadeleştir — ortaya polinom-benzeri bir form çıkar ve karşılaştırma kolaylaşır.

Çözüm. Üç temel araç bu problemin tüm varyantlarını çözer:

  • Log–polinom kimliği: Herhangi pozitif \(a\), \(b\) için \((\log n)^a \ll n^b\). Yani logaritmanın herhangi bir kuvveti, herhangi bir polinomdan kesinlikle (little-o anlamında) yavaştır. İspat yöntemi: iki fonksiyonun oranının limitini (kolaylık için logaritmasını) \(n \to \infty\) için al.

“this log n to any power is asymptotically less than any polynomial for any positive a and b.” — Ku, 7:11

  • Stirling yaklaşımı: \(n! \approx \sqrt{2\pi n}\,(n/e)^n\). Bu, asimptotik değil gerçek bir limit eşitliğidir. Logaritması alınınca \(\log(n!) = \Theta(n \log n)\) çıkar — sınıfta sık kullanılır.
  • Binom katsayısı: \(\binom{n}{k} = n! / (k!\,(n-k)!)\). İki örnek: \(C(n, 3) = n(n-1)(n-2)/6 = \Theta(n^3)\); \(C(n, n/2)\), Stirling ile sadeleştirilince \(\Theta(2^n / \sqrt{n})\) verir.

Bu araç kutusunun görsel özeti — kesin hiyerarşi ve Stirling’in \(\log(n!)\) üzerindeki etkisi — Şekil 4.2 içinde gösterilir.

Karmaşıklık. Bu bir analiz problemidir (çalışma süresi değil): araç, fonksiyonları kapalı, karşılaştırılabilir forma indirgemek. Sonuç bir sıralamadır, bir koşma zamanı değil.

Şekil 4.2: Problem 1 araç kutusu: log-y ekseninde kesin asimptotik hiyerarşi \((\log n)^2 \ll n^{0.5} \ll n \ll 2^n\). Logaritmik faktörler (en açık slate) her polinomun (\(n^{0.5}\), \(n\) — orta/koyu slate) altında kalır; polinomlar da üstelin (amber, en hızlı) altındadır — bu sıralama her pozitif \(a\), \(b\) ve \(c>1\) için geçerlidir. Üstel eğri log eksende düz bir doğru olur (\(\log 2^n = n\log 2\)) ve \(n\) büyüdükçe diğerlerinin hepsini ezici biçimde geride bırakır; üç yavaş eğri tabanda kümelenir çünkü hepsi üstelin yanında ihmal edilebilir kalır. Sağ-alttaki kutu Stirling sonucunu özetler: \(\log(n!) = \Theta(n\log n)\)\(\sqrt{2\pi n}\,(n/e)^n\) yaklaşımının logaritması \(n\log n - n\) baskın terimini verir; \(n=64\) için \(\ln(64!) = 205{,}17\) ile Stirling \(205{,}17\) yalnız %0,0006 sapar (asimptotik değil, gerçek bir limit eşitliği).

4.3 Problem 2: Sequence Arayüzünü Black Box Olarak Kullanma

İfade. Sana bir sequence veri yapısı veriliyor ve içini göremiyorsun (black box). Yalnızca dört işlemi var, hepsi sabit zaman: insert_first, insert_last, delete_first, delete_last (delete’ler sildikleri öğeyi döndürür). Bu işlemleri kullanarak (a) swap_ends ve (b) shift_left(D, K) yaz.

İpucuYaklaşım — Arayüzü Onurlandır, Özyinelemeyle Değişmez Koru

İçeriği değil, yalnızca dış işlemleri kullan — bu, arayüz/gerçekleştirim ayrımının pratiğidir. Sabit-zamandan uzun işlemler için döngü veya özyineleme kullan; Ku özyinelemeyi tercih eder çünkü her adımda yalnızca sabit miktarda bilgiyle (bir değişmez + küçük durum analizi) uğraşmak argümanı kolaylaştırır.

“if I break it down so that I solve a slightly smaller problem recursively, and then do a constant amount of work and maintain some invariant, then it’s very easy to argue things about it.” — Ku, 40:03

Çözüm.

(a) swap_ends — ilk ve son öğeyi değiştir: her ikisini sil, geçici değişkenlerde tut, ters yerlere geri ekle.

swap_ends(D):
    x1 = D.delete_first()
    x2 = D.delete_last()
    D.insert_first(x2)
    D.insert_last(x1)

(b) shift_left(D, K) — ilk \(K\) öğeyi sona taşı (\(K\)’inci öğe son olur, \(K+1\)’inci ilk olur). Özyinelemeli:

shift_left(D, K):
    if K < 1 or K > len(D) - 1:   # taban durum / sınır kontrolü
        return
    x = D.delete_first()
    D.insert_last(x)
    shift_left(D, K - 1)          # bir küçük örnek

Her çağrı bir öğeyi öne alır ve \(K\)’yi 1 azaltır; taban durumda (\(K < 1\)) durur. Değişmez korunduğu için doğruluk kolayca tümevarımla görülür. Bu black-box disiplini, bir sonraki problemde (Problem 3) tam tersini yapmanın — yani arayüzün altındaki veri yapısını tasarlamanın — neden değer taşıdığını da gösterir.

Karmaşıklık. swap_ends sabit sayıda \(O(1)\) işlem → \(O(1)\). shift_left, \(K\) kez sabit iş → \(O(K)\).

4.4 Problem 3: Çift Uçlu Dinamik Dizi

İfade. “İki dünyanın en iyisi” bir veri yapısı tasarla: en kötü durumda \(O(1)\) indeksleme (dizi gibi) ve sequence’in her iki ucunda \(O(1)\) amortize ekleme/silme. Tek başına dinamik dizi sonda amortize \(O(1)\) ama başta \(O(n)\)’dir; bağlı liste iki uçta \(O(1)\) ama indeksleme \(O(n)\)’dir.

İpucuYaklaşım — Sıfırdan Tasarla ya da Hazıra İndirge

İki yol vardır: (1) dinamik diziyi sıfırdan yeniden tasarla (çift uçlu); (2) hazır bir dinamik diziye indirgeme (reduction) yap. Her ikisinin de çekirdeği aynı sezgidir: pahalı bir yeniden-inşadan önce mutlaka lineer sayıda ucuz işlem garanti et — bu, charging argument’ın özüdür.

Çözüm.

Yöntem 1 — Çift uçlu dinamik dizi (dynamic deque). Diziyi her iki uçta da fazladan boş yerle (her birinde lineer miktarda) tut. Böylece hem önden hem sondan eklerken, lineer sayıda işlem yapana kadar yeniden boyutlandırma gerekmez. Yeniden boyutlandırırken (büyütme veya küçültme) her iki uçta yine lineer fazla yer bırak — bu, charging argument’ın çalışmasını garanti eder: bir pahalı yeniden-inşadan önce mutlaka lineer sayıda ucuz işlem yapılmış olur (silmede de israfı önlemek için \(\tfrac14\) doluluğa inince küçült).

“I have to do a linear number of cheap things before I have to do an expensive thing again.” — Ku, 1:00:09

Yöntem 2 — Dinamik diziye indirgeme. İki dinamik dizi kullan; birini ters yönde gör. Saklanacak sequence’i ortadan ikiye böl; her yarı bir dinamik dizidir (biri normal, biri ters). Önden/sondan işlemler artık birer dinamik dizi ucu işlemi olur (biraz indeks aritmetiğiyle). Tek incelik: bir dizi boşalırsa, diğerini ikiye böl ve iki yeni diziye yeniden dağıt — bu yeniden-inşanın maliyeti, biriken amortize bütçeden karşılanır.

Karmaşıklık. İndeksleme \(O(1)\) en kötü durum (saf ofset aritmetiği); her iki uçta ekleme/silme \(O(1)\) amortize; alan, saklanan öğe sayısında lineer (\(O(n)\)).

4.5 Problem 4: Bağlı Listenin Son Yarısını Ters Çevirme

İfade. \(2n\) düğümlü tek yönlü (singly) bir bağlı liste verilir. Son \(n\) düğümün sırasını yerinde (in-place) ters çevir. Yeni düğüm oluşturma yok; sabit (constant) ek alandan fazlasını kullanma — yani öğeleri bir diziye kopyalayıp geri yazamazsın, sadece mevcut düğümlerin işaretçilerini değiştirebilirsin.

İpucuYaklaşım — Üç Adıma Böl, Önceki Düğümü Hatırla

Üç adıma böl: (1) \(n\)’inci düğümü bul, (2) \(n+1\)’den \(2n\)’e kadar olan düğümlerin next işaretçilerini ters çevir, (3) uçları yeniden bağla. İşaretçi ters çevirirken, “önceki” düğümü hatırlamak gerekir; aksi halde liste kopar ve düğümlere erişim kaybolur (garbage-collected dillerde sızıntı, diğerlerinde memory leak). Adım-adım işaretçi izi Şekil 4.3 içinde gösterilir.

Çözüm.

  1. \(n\)’inci düğümü bul: \(n = \text{size} / 2\). Baştan \(n-1\) kez next takip et → a = \(n\)’inci düğüm. b = a.next (ters çevrilecek bloğun başı).
  2. İşaretçileri ters çevir: İki imleç tut — x (yeniden bağlanacak düğüm) ve x_p (ondan önceki). x_p, x = a, b ile başla. \(n\) kez: x_n = x.next (sonrakini sakla), x.next = x_p (geriye bağla), sonra x_p, x = x, x_n (kaydır).
  3. Uçları temizle: Döngü sonunda x_p ters bloğun yeni başı (c) olur. a.next = c (ilk blok yeni başa bağlanır), b.next = None (eski baş, artık son, sonlanır).
reorder(L):
    n = L.size // 2
    a = L.head
    for _ in range(n - 1):
        a = a.next
    b = a.next
    x_p, x = a, b
    for _ in range(n):
        x_n = x.next
        x.next = x_p
        x_p, x = x, x_n
    a.next = x_p      # x_p = c
    b.next = None

Şekil 4.3’in beş aşaması, bu pseudocode’un \(2n = 8\) düğümlük bir liste üzerinde nasıl yürüdüğünü — pivot a’nın bulunması, bloğun ters çevrilmesi ve uçların yeniden bağlanması — somut olarak izler.

Karmaşıklık. \(n\)’inci düğümü bulma \(O(n)\), ters çevirme \(O(n)\), temizlik \(O(1)\) → toplam \(O(n)\); ek alan \(O(1)\) (yalnızca birkaç imleç).

Şekil 4.3: Son yarıyı yerinde ters çevirme izi (\(2n=8\) düğüm, son \(n=4\)). (1) Orijinal liste \(1\to\cdots\to8\), head. (2) \(a=\) 4. düğüm (pivot), \(b=a.\text{next}=5\) (ters çevrilecek bloğun başı). (3) Bloğun (5,6,7,8) next işaretçileri tersine çevrilir — amber geri-oklar artık \(8\to7\to6\to5\to4\) yönünde; \(a\)’nın next’i geçici olarak \(\varnothing\). (4) Uçlar yeniden bağlanır: a.next = 8 ve b.next = None (eski baş artık son). (5) Sonuç \(1\to2\to3\to4\to8\to7\to6\to5\) — ek alan \(O(1)\) (yalnız birkaç imleç), zaman \(O(n)\). İmleç ters çevrilirken “önceki” düğümü hatırlamak şarttır, yoksa liste kopar.
İpucuBuilder Notu — Yerinde Algoritmalar ve Memory Leak

İşaretçi ters çevirmede bir düğüme referans kaybedersen, garbage-collected bir dilde (Python) GC onu toplar; C gibi dillerde bu bir memory leak’tir. Yerinde (in-place) algoritmalar, gömülü sistemler ve düşük-bellek ortamlarında kritiktir: ekstra dizi ayırmaya bütçe yoktur.

  • İleriye → ML / tensör işlemleri: PyTorch’taki _ son ekli işlemler (add_, relu_) tam olarak bu sezgidir — yeni tensör ayırmadan mevcut belleği yerinde değiştirmek; büyük modellerde bellek tasarrufunun anahtarı.
  • İleriye → embedded / firmware: Mikrodenetleyicilerde yığın (heap) ayırma çoğu zaman yasaktır; veri yapıları yerinde dönüştürülür.

Tek cümle: Yerinde dönüşüm, “yeni alan ayırma — var olanı yeniden bağla” disiplinidir.

4.6 Ne Öğrendik?

ÖnemliAltı Taşınabilir Araç

Bu oturum, Ders 1-2’nin teorisini dört somut problemde uyguladı ve altı taşınabilir araç kazandırdı:

  1. Asimptotik karşılaştırma araçları: \((\log n)^a \ll n^b \ll c^n\) hiyerarşisi; faktöriyel/binom için Stirling yaklaşımı (\(\log(n!) = \Theta(n \log n)\)).
  2. Black box / arayüz disiplini: Bir veri yapısını yalnızca dış işlemleriyle kullanıp üzerine yeni işlemler kurmak.
  3. Özyineleme stratejisi: Sabit iş + bir küçük örneğe indirgeme; değişmezle kolay doğruluk argümanı.
  4. Charging argument (amortize): Pahalı bir işlemden önce lineer sayıda ucuz işlem garanti ederek \(O(1)\) amortize elde etme.
  5. Yerinde işaretçi manipülasyonu: Sabit ek alanla bağlı liste yeniden bağlama; referans kaybı = sızıntı.
  6. İletişim kuralı: Algoritmayı koddan önce kelimelerle anlat.

Dört problemin zaman, ek alan ve anahtar sonuç açısından maliyet özeti Şekil 4.4 içinde tek bakışta toplanır.

Şekil 4.4: PS1’in dört probleminin maliyet özeti tek bakışta. Satırlar dört problem, sütunlar Zaman · Ek Alan · Anahtar Sonuç. Hücreler maliyet sınıfına göre renklenir: yeşilimsi hücreler \(O(1)\) sabit (en iyi), amber-çerçeveli açık hücreler \(O(1)\) amortize / yerinde (neredeyse en iyi), dolu amber hücreler \(O(n)\) / \(O(K)\) girdiye bağlı, nötr hücreler ise çalışma-süresi olmayan analiz. P1 bir araç-kutusudur (çalışma süresi yok): asimptotik hiyerarşi \((\log n)^a \ll n^b \ll c^n\) ve \(\log(n!)=\Theta(n\log n)\). P2 yalnız dört \(O(1)\) uç işlemiyle swap_ends’i \(O(1)\), shift_left’i özyinelemeyle \(O(K)\) kurar — \(O(1)\) ek alan. P3 ofset aritmetiğiyle \(O(1)\) en-kötü indeksleme ile charging argument’a dayanan iki-uçta \(O(1)\) amortize ekleme/silmeyi birleştirir (alan \(O(n)\)). P4 son yarıyı yalnız üç imleçle, yeni düğüm üretmeden çevirir: zaman \(O(n)\), ek alan \(O(1)\) yerinde. Amber çerçeve, amortize ve yerinde kazanımları işaretler.

4.7 Sonraki

UyarıDers 4 (L3) — Kümeler ve Sıralama

Sırada Ders 4 (L3): Kümeler ve Sıralama var — Justin Solomon ile küme (set) arayüzüne ve onu çözmenin ilk adımı olan sıralamaya (insertion, selection, merge sort) geçiyoruz. Bu oturumda gördüğün asimptotik karşılaştırma araçları, sıralama algoritmalarının çalışma sürelerini analiz ederken doğrudan işine yarayacak.