---
title: 'İleri Algoritmalar ve Problem Çözme'
source: 'https://academia.sh/tr/kurslar/ileri-algoritmalar'
language: tr
updated: '2026-08-17T04:39:22+00:00'
license: 'CC BY-SA 4.0'
lessons: 23
outcomes:
    - 'Böl–yönet, açgözlü ve dinamik programlama yaklaşımlarını ön koşullarıyla birlikte ayırır'
    - 'Bir problemi tanınmış bir kalıba indirger ve kalıbın ön koşulunu kâhinle sınar'
    - 'Optimal alt yapı ve örtüşen alt problem koşullarını sayıyla gerekçelendirir'
---

# İleri Algoritmalar ve Problem Çözme

Tasarım yaklaşımlarının ve problem kalıplarının ön koşullarıyla birlikte ölçülmesi; bir kalıbın kazandırdığı adımın yanında ön koşulu bozulduğunda verdiği yanlış yanıtların sayılması.

## Algoritma Tasarım Yaklaşımları

Kaba kuvvetin kâhin rolü, böl–yönet bağıntısı, açgözlü seçimin yanıldığı sistemler, not almanın örtüşme koşulu, budamanın kestiği arama alanı ve rastgeleleştirmenin beklenen başarımı.

### Kaba Kuvvet ve Sınırları

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/kaba-kuvvet-ve-sinirlari

Kaba kuvvetin bu kurstaki rolü bir yavaş seçenek değil bir kâhindir: tam sayım 40 girdide 2640 adım harcıyor, erken çıkan eleme 866 adımla aynı yanıtı veriyor, ilk altı konuma bakan örnekleme ise 406 adımla 14 girdide kâhinden ayrılıyor.

Algoritmalar kursu bir ölçüt kurarak kapandı: işlem sayımı, asimptotik gösterim, alt sınır.
O kursta ölçülen tek şey maliyetti, çünkü her algoritmanın **doğru olduğu varsayılmıştı**.
Bu kurs o varsayımı kaldırarak açılır.

Bir tasarım kalıbı seçmek yalnız hızlanmak değildir; bir **ön koşulu (precondition)** kabul
etmektir. Ön koşul bozulduğunda kalıp yavaşlamaz — **yanlış yanıt verir** ve yanlışlığı
çıktısına bakarak anlaşılmaz, çünkü yine bir sayı döndürür. Bu yüzden kursun merkezinde
**kaba kuvvet (brute force)** durur; ama burada "yavaş seçenek" olarak değil, **kâhin
(oracle)** olarak durur. Bir kalıbın sayısı kazandırdığı adım değil, ön koşulu bozulduğunda
verdiği yanlış yanıt sayısıdır; ön koşulu kâhinle sınanmayan kalıp ölçülmemiş sayılır.

- **TY1.** Ölçü **adımdır**, süre değil. Hiçbir derste gerçek zaman ölçülmez.
- **TY2.** Tohum **20260218**. Aynı tohum aynı dağarcığı verir; buradaki her sayı yeniden
  üretilebilir.
- **TY3.** **Dağarcık (input corpus)** 40 diziden oluşur; her dizi 12 değer taşır ve
  değerler -9 ile 20 arasındadır.
- **TY4.** Bir **adım**, iki değerin toplanıp hedefle karşılaştırılmasıdır. Sayaç her adımı
  bir sayar.
- **TY5.** **Kâhin her zaman kaba kuvvettir:** bütün olasılıkları gören, erken çıkmayan
  yordam. Kâhinin yanıtı tanım gereği doğru sayılır.
- **TY6.** Bir adayın doğruluğu ancak kâhinle karşılaştırılarak iddia edilir. **Kâhinsiz
  doğruluk iddiası yazılmaz.**
- **TY7.** **Ayrılan girdi**, kâhin ile adayın farklı yanıt verdiği girdidir; 40 üzerinden
  sayılır.
- **TY8.** Çözünürlük: 40 girdide 1 ayrılma **ölçülmemiş sayılır**, 3 ve üzeri anlamlıdır.
- **TY9.** Rastgelelik belirlenimci bir üreteçle modellenir; standart kitaplığın rastgele
  sayı üreteci kullanılmaz.
- **TY10.** Her ölçüm **20260219** tohumlu ikinci bir dağarcıkta da koşturulur.

#### Kaba Kuvvet Neden Kâhin Olabiliyor

Kaba kuvvet, çözüm uzayının tamamını sayan yordamdır: bütün ikilileri, bütün alt kümeleri,
bütün sıralamaları. Algoritmalar kursunun metin algoritmaları konusunda kaba kuvvet örüntü
arama bir **taban** olarak kurulmuştu; oradaki soru, daha akıllı yordamların o tabandan kaç
karşılaştırma kazandığıydı. Burada rol değişir.

Kaba kuvvetin bu kurstaki ayrıcalığı hızıyla değil, **ön koşulsuz olmasıyla** ilgilidir.
İki işaretçi dizinin sıralı olmasını ister, kayan pencere değerlerin negatif olmamasını,
açgözlü seçim yerel seçimin küresel çözüme uymasını. Tam sayımın böyle bir isteği yoktur:
bütün olasılıkları gördüğü için hiçbir varsayımı bozulamaz. Bozulacak varsayımı olmayan tek
yordam, başkalarının bozulmuş varsayımını ölçebilecek tek yordamdır.

Bunun bedeli her derste ödenir. Kâhin pahalıdır ve pahalılığı kusur değil, kâhinliğin
karşılığıdır. Bir kalıbın kâhinden hızlı olması bir başarı değildir; kâhinle **aynı yanıtı
verirken** hızlı olması bir başarıdır. Bu ayrımı sayıya çeviren şey, ayrılan girdi sayısıdır.

#### Ölçüm Çerçevesi

Kursun bütün ölçümleri aynı dağarcık ve aynı sayaç üzerinde yapılır. Dağarcık, ön koşulları
kasıtlı olarak **bozan** girdilerden kurulur; ön koşulu sağlayan öbekler ondan türetilir.

```python
# Olcum cercevesi: dagarcik ve adim sayaci. Sure degil ADIM sayilir.
TOHUM = 20260218
UZUNLUK = 12
DAGARCIK = 40


def uretec(tohum):
    """Belirlenimci uretec. Ayni tohum ayni dagarcigi verir."""
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum=TOHUM, n=DAGARCIK, uzunluk=UZUNLUK):
    r = uretec(tohum)
    kume = []
    for i in range(n):
        dizi = [r(30) - 9 for _ in range(uzunluk)]        # -9 .. 20
        kume.append({"no": i + 1, "dizi": dizi,
                     "negatif": any(x < 0 for x in dizi),
                     "sirali": dizi == sorted(dizi)})
    return kume


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self, n=1):
        self.adim += n


K = dagarcik()
print("dagarcik:", len(K), "dizi x", UZUNLUK, "deger")
print("sirali olan:", sum(1 for k in K if k["sirali"]),
      "| negatif iceren:", sum(1 for k in K if k["negatif"]))
print("ilk dizi:", K[0]["dizi"])
```

```
dagarcik: 40 dizi x 12 deger
sirali olan: 0 | negatif iceren: 40
ilk dizi: [-8, -5, 2, -1, 2, 5, 4, 1, 16, 17, 6, -1]
```

Kırk dizinin **sıfırı** sıralı, **kırkı** negatif değer içeriyor. Bu bir kaza değil,
tasarımdır: taban dağarcık, kursun ele alacağı ön koşulların çoğunu ihlal eder. Ön koşulu
sağlayan öbekler bu diziler sıralanarak ya da mutlak değerleri alınarak üretilir, böylece
iki öbek aynı üreteçten gelir ve aralarındaki tek fark ön koşulun kendisi olur.

#### Kâhinin Adımı ve İki Kısaltma

Ölçülen problem şudur: bir dizide, toplamı hedefe eşit **iki ayrı konum** var mı. Hedef 11
alınır. Üç yordam yazılır. İlki kâhindir: bütün ikilileri görür, uyum bulsa bile durmaz.
İkincisi bir **eleme** uygular, ilk uyumda çıkar. Üçüncüsü bir **örnekleme** uygular, yalnız
ilk altı konuma bakar.

```python
# Onceki blogun uzerine: dagarcik, Sayac ve K oradan gelir.
HEDEF = 11


def kahin_tam(dizi, hedef, s):
    """Butun ciftleri gorur, erken cikmaz. Hakem budur."""
    bulundu = False
    for i in range(len(dizi)):
        for j in range(i + 1, len(dizi)):
            s.say()
            if dizi[i] + dizi[j] == hedef:
                bulundu = True
    return bulundu


def erken_cikan(dizi, hedef, s):
    """ELEME: ilk uyumda durur. Atlanan ciftler yaniti degistiremez."""
    for i in range(len(dizi)):
        for j in range(i + 1, len(dizi)):
            s.say()
            if dizi[i] + dizi[j] == hedef:
                return True
    return False


def orneklemeli(dizi, hedef, s, bak=6):
    """ORNEKLEME: yalniz ilk 'bak' konuma bakar. Gorulmeyen cift bilinmez."""
    for i in range(min(bak, len(dizi))):
        for j in range(i + 1, min(bak, len(dizi))):
            s.say()
            if dizi[i] + dizi[j] == hedef:
                return True
    return False


def olc(aday, kahin, kume, hedef):
    """Adayi kahinle karsilastirir; ayrilan girdiyi sayar."""
    ayrilan, aa, ah = [], 0, 0
    for k in kume:
        s1, s2 = Sayac(), Sayac()
        if aday(k["dizi"], hedef, s1) != kahin(k["dizi"], hedef, s2):
            ayrilan.append(k["no"])
        aa += s1.adim
        ah += s2.adim
    return {"girdi": len(kume), "ayrilan": len(ayrilan), "ayrilan_no": ayrilan[:6],
            "aday_adim": aa, "kahin_adim": ah,
            "oran": round(ah / aa, 2) if aa else 0.0}


s = Sayac()
for k in K:
    kahin_tam(k["dizi"], HEDEF, s)
print("kahin (tam sayim) toplam adim:", s.adim)
print("eleme    ", olc(erken_cikan, kahin_tam, K, HEDEF))
print("ornekleme", olc(orneklemeli, kahin_tam, K, HEDEF))
```

```
kahin (tam sayim) toplam adim: 2640
eleme     {'girdi': 40, 'ayrilan': 0, 'ayrilan_no': [], 'aday_adim': 866, 'kahin_adim': 2640, 'oran': 3.05}
ornekleme {'girdi': 40, 'ayrilan': 14, 'ayrilan_no': [1, 2, 3, 4, 8, 9], 'aday_adim': 406, 'kahin_adim': 2640, 'oran': 6.5}
```

Üç sayı yan yana duruyor. **Kâhin** 40 girdide **2640 adım** harcıyor; bu, dizi başına
tam olarak 66 ikilidir ve girdiye göre değişmez. **Eleme** 866 adım harcıyor, yani kâhinin
**3,05 katı** azını ve 40 girdinin **hiçbirinde** kâhinden ayrılmıyor. **Örnekleme** 406
adım harcıyor, kâhinin **6,50 katı** azını ve 40 girdinin **14'ünde** kâhinden ayrılıyor.
Ayrılan girdi oranı 0,3500'dür ve çözünürlüğün çok üstündedir.

Bu üç satırın taşıdığı ders, kursun geri kalanının çerçevesidir. **Kısaltmanın iki türü
vardır ve ikisi çıktıya bakarak ayırt edilemez.** Eleme, atladığı ikililerin yanıtı
değiştiremeyeceğini bildiği için doğruluğu korur: uyum zaten bulunmuşsa geri kalan ikililer
yanıtı `True` olmaktan çıkaramaz. Örnekleme böyle bir bilgiye dayanmaz; görmediği ikililer
hakkında hiçbir güvencesi yoktur ve 14 girdide gerçekten yanılır.

Ayrılan girdilerin numaraları da bir örüntü taşır. Çıktıdaki ilk altısı 1, 2, 3, 4, 8 ve
9'dur; bunların ortak yanı, hedefi tutturan **her** ikilinin en az bir ucunun **ilk altı
konumun dışında** kalmasıdır. Örnekleme o ikilileri hiç görmez ve "yok" yanıtı verir. Yanılgının nedeni bir hesap hatası
değil, **görülmeyen bir bölgeyle ilgili varsayımdır**; bu, kursun bütün kalıp yanılgılarının
ortak biçimidir.

Sayıların sırası da anlamlıdır. **Yanlış olan, doğru olandan hızlıdır:** örnekleme 406,
eleme 866 adım. Hızlanma tek başına ölçüldüğünde örnekleme kazanır. Ölçüye ayrılan girdi
eklendiği anda sıralama tersine döner. Bu, kursun ikinci iddiasının ilk biçimidir:
**hızlandırma yanlışa yaklaştırır** ve yanlışlık ucuz olduğu için çekicidir.

#### Tam Aramanın Sınırı

Kaba kuvvetin ne zaman kabul edilebilir olduğu sorusu, bir adım bütçesiyle yanıtlanır: bir
koşumda göze alınan adım sayısı belirlenir ve her arama ailesinin o bütçeye hangi girdi
büyüklüğüne kadar sığdığı hesaplanır. Bütçe burada yüz milyon adım alınmıştır.

```python
from math import comb, factorial

BUTCE = 10**8          # bir kosumda goze alinan adim ust siniri


def sigan_n(sayim, ust=20000):
    """Adim butcesine sigan en buyuk n."""
    en = 0
    for n in range(1, ust + 1):
        if sayim(n) > BUTCE:
            break
        en = n
    return en


AILE = (("ikili secim", lambda n: comb(n, 2)),
        ("uclu secim", lambda n: comb(n, 3)),
        ("alt kume", lambda n: 2 ** n),
        ("siralama", factorial))

print("tam tarama ailesi  n=12 adim           n=20 adim  butceye sigan n")
for ad, f in AILE:
    print(f"  {ad:13s} {f(12):10d} {f(20):19d} {sigan_n(f):15d}")
print("adim butcesi:", BUTCE)
```

```
tam tarama ailesi  n=12 adim           n=20 adim  butceye sigan n
  ikili secim           66                 190           14142
  uclu secim           220                1140             844
  alt kume            4096             1048576              26
  siralama       479001600 2432902008176640000              11
adim butcesi: 100000000
```

Dört aile aynı bütçede dört ayrı dünyaya karşılık geliyor. İkili seçim on dört binin
üzerinde bir girdiyi kaldırıyor; üçlü seçim 844'te duruyor; bütün alt kümeleri sayan arama
**26'da**, bütün sıralamaları sayan arama **11'de** duruyor. Aradaki uçurum, bu kursun
neden yalnız hızlanma değil **kalıp seçimi** öğrettiğinin nedenidir: sıralama ailesinde
kalan bir çözüm, girdi bir eleman büyüdüğünde bütçesini bir daha aşar.

Sınırın kâhin rolünü ortadan kaldırmadığına dikkat etmek gerekir. Bir yordam üretimde
n=1000 ile çağrılacak olsa bile, kâhinle karşılaştırma **n=12'lik bir dağarcıkta** yapılır;
orada tam sayım 2640 adımdır ve hiçbir bütçeyi zorlamaz. Kaba kuvvet **çözüm olarak** küçük
girdilerde biter, **ölçüm aracı olarak** her derste kullanılabilir kalır.

#### Dağarcığın Boyu Kâhinin Bütçesidir

Bir önceki tablo, dağarcığın neden on iki elemanlı dizilerden kurulduğunu da açıklar. Kurs
boyunca kâhin yalnız ikili seçim ailesinde kalmayacak; bazı problemlerde bütün alt kümeleri
taramak gerekecek. O tarama, dağarcığın eleman sayısına doğrudan bağlıdır.

```python
# Onceki bloklarin uzerine: dagarcik, Sayac ve UZUNLUK oradan gelir.
SINIR = 30


def kahin_alt_kume(dizi, sinir, s):
    """Toplami siniri asmayan en buyuk alt kume toplami. Butun alt kumeler taranir."""
    en_iyi = 0
    for maske in range(1 << len(dizi)):
        s.say()
        toplam = sum(dizi[i] for i in range(len(dizi)) if maske >> i & 1)
        if toplam <= sinir and toplam > en_iyi:
            en_iyi = toplam
    return en_iyi


K = dagarcik()
s = Sayac()
for k in K:
    kahin_alt_kume(k["dizi"], SINIR, s)
print("dizi basina alt kume:", 2 ** UZUNLUK, "| 40 dizide kahin adimi:", s.adim)
for uzunluk in (12, 16, 20, 26):
    print(f"  uzunluk {uzunluk:2d} -> 40 dizide {40 * 2 ** uzunluk:12d} adim",
          "(butce icinde)" if 40 * 2 ** uzunluk <= 10**8 else "(butce disinda)")
```

```
dizi basina alt kume: 4096 | 40 dizide kahin adimi: 163840
  uzunluk 12 -> 40 dizide       163840 adim (butce icinde)
  uzunluk 16 -> 40 dizide      2621440 adim (butce icinde)
  uzunluk 20 -> 40 dizide     41943040 adim (butce icinde)
  uzunluk 26 -> 40 dizide   2684354560 adim (butce disinda)
```

On iki elemanlı bir dizide 4096 alt küme vardır; kırk dizide kâhin **163.840 adım** harcar
ve bütçenin binde ikisini kullanır. Aynı kâhin yirmi elemanlı dizilerde 41.943.040 adıma
çıkar — hâlâ sığar, ama bütçenin yüzde kırkını yer. Yirmi altı elemanda dağarcığın kendisi
bütçeyi **yirmi yedi kat** aşar.

Buradan çıkan kural, kursun ölçüm düzeninin temelidir: **dağarcığın eleman sayısı, ölçülecek
en pahalı kâhine göre seçilir.** On iki, kırk girdilik bir dağarcıkta alt küme taramasını
bile taşıyan en büyük rahat sayıdır. Dağarcığı büyütmek daha zengin girdi vermez; yalnız
kâhini ulaşılmaz kılar ve ölçümü ortadan kaldırır. Kalıbı hızlandırmanın bir sınırı olduğu
gibi, kalıbı **sınamanın** da bir sınırı vardır ve ikisi aynı bütçeden ödenir.

#### İkinci Dağarcık

Tek bir dağarcıkta ölçülen oranın dağarcığa bağlı olup olmadığı ayrıca sınanır.

```python
# Onceki bloklarin uzerine: dagarcik, olc ve uc yordam oradan gelir.
for ad, tohum in (("birinci dagarcik (20260218)", 20260218),
                  ("ikinci  dagarcik (20260219)", 20260219)):
    kume = dagarcik(tohum)
    e = olc(erken_cikan, kahin_tam, kume, HEDEF)
    o = olc(orneklemeli, kahin_tam, kume, HEDEF)
    print(ad)
    print("  eleme    : ayrilan", e["ayrilan"], "/ 40 | oran",
          round(e["ayrilan"] / 40, 4), "| aday adim", e["aday_adim"])
    print("  ornekleme: ayrilan", o["ayrilan"], "/ 40 | oran",
          round(o["ayrilan"] / 40, 4), "| aday adim", o["aday_adim"])
```

```
birinci dagarcik (20260218)
  eleme    : ayrilan 0 / 40 | oran 0.0 | aday adim 866
  ornekleme: ayrilan 14 / 40 | oran 0.35 | aday adim 406
ikinci  dagarcik (20260219)
  eleme    : ayrilan 0 / 40 | oran 0.0 | aday adim 970
  ornekleme: ayrilan 21 / 40 | oran 0.525 | aday adim 512
```

Elemenin ayrılan girdisi iki dağarcıkta da **sıfırdır**; bu beklenen sonuçtur, çünkü
elemenin doğruluğu girdiye değil, atlanan dalların yanıtı değiştiremeyeceği gözlemine
dayanır. Örneklemenin oranı **0,3500'den 0,5250'ye** çıkıyor. İki oran aynı büyüklük
düzenindedir — ikisi de girdilerin üçte biri ile yarısı arasında — ama aradaki fark
çözünürlüğün çok üstündedir. Okuma şudur: örneklemenin yanıldığı **girdi vardır ve
sayıcadır**; kaç girdide yanıldığı **dağarcığa bağlıdır** ve tek bir sayıyla bildirilemez.

#### Özet

- Kaba kuvvet bu kursta bir yavaş seçenek değil **kâhindir**; ayrıcalığı hızından değil,
  bozulacak bir ön koşulu olmamasından gelir.
- Aynı problemde kâhin 40 girdide **2640 adım**, eleme **866 adım** (3,05 kat az, **0
  ayrılan girdi**), örnekleme **406 adım** (6,50 kat az, **14 ayrılan girdi**) harcıyor.
- Kısaltmanın iki türü çıktıdan ayırt edilemez: eleme atladığı dalların yanıtı
  değiştiremeyeceğini bilir, örnekleme bilmez.
- Yanlış olan, doğru olandan hızlıdır; ölçüye ayrılan girdi eklenmedikçe örnekleme
  kazanmış görünür.
- Yüz milyon adımlık bütçede ikili seçim 14.142, üçlü seçim 844, alt küme araması 26,
  sıralama araması 11 elemana kadar sığar; kâhin rolü bu sınırdan etkilenmez çünkü ölçüm
  12 elemanlık dağarcıkta yapılır.
- İkinci dağarcıkta elemenin ayrılan girdisi yine 0, örneklemeninki 14 yerine 21; oran
  aynı büyüklük düzeninde kalıyor ama tam değeri **dağarcığa bağlıdır**.

#### Sonraki Adım

Bu derste kaba kuvvet bölünmeden, tek parça olarak koştu. İlk tasarım kalıbı problemi
parçalar: aynı problemi daha küçük iki kopyasına indirir, her birini çözer ve sonuçları
birleştirir. Sonraki ders bu kalıbın bağıntısını kurar ve iki soruyu sayar — birleştirme
adımı eksik kaldığında kaç girdide yanlış yanıt çıkıyor ve bölmenin adım kazandırmadığı
girdi büyüklüğü nerede bitiyor.

### Böl ve Yönet

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/bol-ve-yonet

Bölme ile birleştirmenin ayrı hesaplar olduğu: aynı bölme üzerinde doğrusal birleştirme kâhinin 1,16 katı az adım harcayıp 40 girdide de doğru kalırken, karesel birleştirme kâhinden 1,71 kat yavaşlıyor ve sınırı geçen çözümü atlayan eksik birleştirme 920 adımla 40 girdinin 40'ında yanlış yanıt veriyor.

Önceki derste kaba kuvvet tek parça olarak koştu ve bütün olasılıkları gördü. İlk tasarım
kalıbı bu bütünlüğü bozar: problemi aynı türden daha küçük iki kopyaya indirir, her birini
özyinelemeyle çözer ve iki çözümü **birleştirir**. Bu kalıbın adı **böl ve yönet (divide and
conquer)**.

Algoritmalar kursu bu kalıbın üç örneğini çoktan ölçtü: birleştirmeli sıralama, hızlı
sıralama ve ikili arama. Bu ders o yordamları tekrarlamaz. Ölçtüğü şey **bağıntının
kendisidir** ve sorduğu soru şudur: bölme ile birleştirme ayrı hesaplardır; birleştirme
adımı eksik kaldığında kaç girdide yanlış yanıt çıkar ve bölmenin adım kazandırmadığı
girdi büyüklüğü nerede biter.

- **TY11.** Ölçülen problem: bir dizide toplamı en büyük **bitişik alt dizi**. Alt dizi boş
  olamaz, yani yanıt en kötü durumda tek bir elemandır.
- **TY12.** Kâhin bütün bitişik alt dizileri görür; 12 elemanlı bir dizide 78 alt dizi vardır.
- **TY13.** Bir **adım**, bir elemanın toplama eklenip karşılaştırılmasıdır. **Özyineleme
  çağrısının kendisi de bir adımdır** ve ayrıca sayılır.
- **TY14.** Bölme kuralı bütün değişkelerde aynıdır: dizi ortadan ikiye ayrılır, yani
  bağıntıda $a = 2$ ve $b = 2$.
- **TY15.** Birleştirme üç değişkede ölçülür — **doğrusal**, **karesel**, **eksik**. Üçü de
  aynı bölmeyi kullanır; aralarındaki tek fark birleştirmedir.
- **TY16.** Kalıbın **ön koşulu**: birleştirme adımı, çözümün **sınırı geçen** biçimini de
  kapsamalıdır. Eksik birleştirme yalnız bu ön koşulu bozar.
- **TY17.** Uzunluk süpürmesi 2'den 128'e ikişer kat; her uzunlukta yine 40 dizi.
- **TY18.** M01/K04'ün sıralama ve arama yordamları **tekrarlanmaz**; buradaki ölçü bağıntının
  kendisidir.
- **TY19.** Her ölçüm **20260219** tohumlu ikinci dağarcıkta da koşturulur.

#### Bölmenin Bağıntısı

Bir böl ve yönet yordamı üç işten oluşur: problemi $a$ parçaya bölmek, her parçayı
özyinelemeyle çözmek ve parçaların çözümlerini birleştirmek. Parçalar özgün problemin
$1/b$ boyunda ise maliyet şu bağıntıyı sağlar:

$$
T(n) = a \cdot T(n/b) + f(n)
$$

Burada $f(n)$ **birleştirmenin** maliyetidir ve bölmenin kendisini de içerir. **Ana teorem**,
bu bağıntının çözümünü $f(n)$ ile $n^{\log_b a}$ arasındaki yarışa bakarak verir. Üç durum
vardır: birleştirme özyinelemeden ucuzsa maliyeti özyineleme belirler ve sonuç
$\Theta(n^{\log_b a})$ olur; ikisi aynı büyüklükteyse araya bir $\log n$ çarpanı girer ve
sonuç $\Theta(n^{\log_b a} \log n)$ olur; birleştirme baskınsa sonuç doğrudan
$\Theta(f(n))$'dir.

Bu dersin bölmesinde $a = 2$, $b = 2$ ve $\log_b a = 1$. Yani yarış, birleştirmenin doğrusal
olup olmadığı üzerinedir. Doğrusal birleştirme ikinci duruma düşer ve $\Theta(n \log n)$
verir; karesel birleştirme üçüncü duruma düşer ve $\Theta(n^2)$ verir — yani **kaba kuvvetle
aynı büyüklüğe**. Bölme değişmediği hâlde sonuç değişir; bu, bölme ile birleştirmenin ayrı
hesaplar olduğunun biçimsel ifadesidir. Ana teoremin bir sınırı da burada anılmalıdır:
parçalar eşit boyda değilse, $f(n)$ üç durumun hiçbirine düşmüyorsa — durumlar arasındaki
fark **polinomsal** olmak zorundadır — ya da üçüncü durumda $f(n)$ düzenlilik koşulunu
($a \cdot f(n/b) \le c \cdot f(n)$, bir $c < 1$ için) sağlamıyorsa teorem yanıt vermez ve
bağıntı doğrudan açılarak çözülür. Bu dersin bölmesi eşit olduğu için o durumla
karşılaşılmaz.

#### Kâhin ve Problem

Ölçülen problem, sınırı geçen çözümün var olduğu en yalın problemdir: en büyük toplamlı
bitişik alt dizi. Kâhin bütün alt dizileri sayar.

```python
# Ortak cerceve: dagarcik ve adim sayaci (onceki dersten aynen).
TOHUM = 20260218


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum=TOHUM, n=40, uzunluk=12):
    r = uretec(tohum)
    return [{"no": i + 1, "dizi": [r(30) - 9 for _ in range(uzunluk)]}
            for i in range(n)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self, n=1):
        self.adim += n


def kahin_en_buyuk(dizi, s):
    """Butun bitisik alt dizileri gorur. Hakem budur."""
    en_iyi = dizi[0]
    for i in range(len(dizi)):
        toplam = 0
        for j in range(i, len(dizi)):
            s.say()
            toplam += dizi[j]
            if toplam > en_iyi:
                en_iyi = toplam
    return en_iyi


K = dagarcik()
s = Sayac()
yanit = [kahin_en_buyuk(k["dizi"], s) for k in K]
print("dagarcik:", len(K), "dizi x 12 deger | kahin toplam adim:", s.adim)
print("dizi basina adim:", s.adim // len(K), "= 12 x 13 / 2")
print("ilk uc yanit:", yanit[:3])
```

```
dagarcik: 40 dizi x 12 deger | kahin toplam adim: 3120
dizi basina adim: 78 = 12 x 13 / 2
ilk uc yanit: [52, 55, 84]
```

Kâhin dizi başına **78 adım**, kırk dizide **3120 adım** harcıyor ve girdiye göre
değişmiyor. Yanıtların büyüklüğü de anlamlıdır: ilk üç dizide 52, 55 ve 84. Dağarcığın
değerleri en çok 20 olduğuna göre bu yanıtlar **birden çok elemanı** kapsıyor demektir; yani
çözüm neredeyse hiçbir zaman tek bir elemanda değil.

#### Üç Birleştirme, Tek Bölme

Aşağıdaki yordam üç değişkeyi tek gövdede taşır. Bölme her üçünde aynıdır; değişen tek şey
sınırı geçen çözümün nasıl hesaplandığıdır.

```python
# Onceki blogun uzerine: dagarcik, Sayac, kahin_en_buyuk ve K oradan gelir.
def bol_yonet(dizi, s, birlestirme="dogrusal"):
    """T(n) = 2 T(n/2) + birlestirme(n). Birlestirme SINIRI GECEN cozumu bulur."""
    def coz(bas, son):
        s.say()                                   # ozyineleme cagrisi da bir adimdir
        if son - bas == 1:
            return dizi[bas]
        orta = (bas + son) // 2
        sol = coz(bas, orta)
        sag = coz(orta, son)
        if birlestirme == "eksik":                # SINIRI GECEN COZUM ATLANIYOR
            return max(sol, sag)
        if birlestirme == "karesel":              # dogru ama pahali birlestirme
            sol_uc, t = [], 0
            for i in range(orta - 1, bas - 1, -1):
                s.say()
                t += dizi[i]
                sol_uc.append(t)
            sag_uc, t = [], 0
            for j in range(orta, son):
                s.say()
                t += dizi[j]
                sag_uc.append(t)
            en = None
            for a in sol_uc:                      # her sinir cifti ayri deneniyor
                for b in sag_uc:
                    s.say()
                    if en is None or a + b > en:
                        en = a + b
            return max(sol, sag, en)
        en_sol, t = None, 0                       # dogrusal birlestirme
        for i in range(orta - 1, bas - 1, -1):
            s.say()
            t += dizi[i]
            if en_sol is None or t > en_sol:
                en_sol = t
        en_sag, t = None, 0
        for j in range(orta, son):
            s.say()
            t += dizi[j]
            if en_sag is None or t > en_sag:
                en_sag = t
        return max(sol, sag, en_sol + en_sag)
    return coz(0, len(dizi))


def olc(birlestirme, kume):
    ayrilan, ak, ah = [], 0, 0
    for k in kume:
        s1, s2 = Sayac(), Sayac()
        if bol_yonet(k["dizi"], s1, birlestirme) != kahin_en_buyuk(k["dizi"], s2):
            ayrilan.append(k["no"])
        ak += s1.adim
        ah += s2.adim
    return {"ayrilan": len(ayrilan), "ayrilan_no": ayrilan[:6],
            "kalip_adim": ak, "kahin_adim": ah, "oran": round(ah / ak, 2)}


for ad in ("dogrusal", "karesel", "eksik"):
    print(f"{ad:9s} birlestirme:", olc(ad, K))
```

```
dogrusal  birlestirme: {'ayrilan': 0, 'ayrilan_no': [], 'kalip_adim': 2680, 'kahin_adim': 3120, 'oran': 1.16}
karesel   birlestirme: {'ayrilan': 0, 'ayrilan_no': [], 'kalip_adim': 5320, 'kahin_adim': 3120, 'oran': 0.59}
eksik     birlestirme: {'ayrilan': 40, 'ayrilan_no': [1, 2, 3, 4, 5, 6], 'kalip_adim': 920, 'kahin_adim': 3120, 'oran': 3.39}
```

Üç sayı yan yana. **Kâhin** 3120 adım. **Doğrusal birleştirme** 2680 adım harcıyor —
kâhinin 1,16 katı azı — ve 40 girdinin **hiçbirinde** ayrılmıyor. **Karesel birleştirme**
5320 adım harcıyor, yani kâhinden **1,71 kat yavaş** ve o da 40 girdide ayrılmıyor.
**Eksik birleştirme** 920 adım harcıyor, kâhinin 3,39 katı azı ve 40 girdinin
**40'ında** ayrılıyor.

İki okuma birlikte durur. Birincisi: **doğru olmak hızlı olmayı gerektirmez.** Karesel
birleştirme yanıtı hep doğru veriyor ama kaba kuvvetten yavaş; bölme burada hiçbir şey
kazandırmıyor, üstelik özyineleme çağrılarının kendi adımını da ekliyor. İkincisi: **hızlı
olmak doğru olmayı gerektirmez.** Eksik birleştirme en ucuz olanı ve tam olarak bu yüzden
kullanılası görünüyor; oysa yanıtı 40 girdide de yanlış.

Eksik değişkenin ne döndürdüğüne bakmak yanılgının yapısını gösterir. Sınırı geçen çözüm
atlandığında özyineleme yaprağa kadar iner ve her düzeyde iki yarının en iyisi alınır; geriye
kalan tek aday, dizinin **en büyük tek elemanıdır**. Kâhinin ilk üç yanıtı 52, 55 ve 84 iken
eksik birleştirmenin yanıtı en çok 20 olabilir. Yanılgı bir hesap hatası değil, **çözüm
uzayının bir bölgesinin hiç ziyaret edilmemesidir** — önceki dersin örnekleme yanılgısıyla
aynı biçim, bu kez özyineleme sınırında.

#### Bağıntı Sayıya Dönüşürken

Ana teoremin iki durumu, uzunluk büyütüldüğünde ölçülen adımda görünür hâle gelir.

```python
# Onceki bloklarin uzerine: dagarcik, Sayac, kahin_en_buyuk, bol_yonet oradan gelir.
print("uzunluk  kahin adim  dogrusal adim   oran  karesel adim   oran")
for uzunluk in (2, 4, 8, 16, 32, 64, 128):
    kume = dagarcik(uzunluk=uzunluk)
    s1, s2, s3 = Sayac(), Sayac(), Sayac()
    for k in kume:
        kahin_en_buyuk(k["dizi"], s1)
        bol_yonet(k["dizi"], s2, "dogrusal")
        bol_yonet(k["dizi"], s3, "karesel")
    print(f"{uzunluk:7d} {s1.adim:11d} {s2.adim:14d} {s1.adim / s2.adim:6.2f}"
          f" {s3.adim:14d} {s1.adim / s3.adim:6.2f}")
```

```
uzunluk  kahin adim  dogrusal adim   oran  karesel adim   oran
      2         120            200   0.60            240   0.50
      4         400            600   0.67            840   0.48
      8        1440           1560   0.92           2680   0.54
     16        5440           3800   1.43           8600   0.63
     32       21120           8920   2.37          28760   0.73
     64       83200          20440   4.07         101080   0.82
    128      330240          46040   7.17         371160   0.89
```

Doğrusal birleştirmenin oranı 0,60'tan 7,17'ye çıkıyor ve **1'i 8 ile 16 arasında** geçiyor.
Bunun anlamı açıktır: **bölme, 8 elemana kadar hiçbir şey kazandırmıyor, tersine kaybettiriyor.**
Uzunluk 2'de böl ve yönet kâhinin neredeyse iki katı adım harcıyor; kazanç ancak 16'da
başlıyor. Kayıp özyineleme çağrılarından gelir — $n$ elemanlı bir dizide $2n - 1$ çağrı
yapılır ve bunların hiçbiri kaba kuvvette yoktur. Bu, gerçek gerçeklenimlerin küçük
parçalarda özyinelemeyi kesip doğrudan çözüme geçmesinin ölçülmüş nedenidir.

Karesel birleştirmenin oranı ise 0,50 ile 0,89 arasında kalıyor ve **hiçbir uzunlukta 1'i
geçmiyor**. Oran yavaşça 1'e yaklaşıyor, çünkü iki yordam da $\Theta(n^2)$ büyüyor ve
aradaki fark sabit bir çarpandır. Ana teoremin üçüncü durumu tam olarak budur: birleştirme
baskın olduğunda bölmenin verdiği $\log n$ kazancı ortadan kalkar ve geriye yalnız
özyinelemenin ek yükü kalır. **Bölmek bir kazanç değildir; birleştirme ucuzsa kazançtır.**

Toplam adımı iki bileşene ayırmak, ana teoremin ikinci durumunu doğrudan görünür kılar.

```python
# Onceki bloklarin uzerine: dagarcik, Sayac ve bol_yonet oradan gelir.
from math import log2
print("uzunluk  cagri  birlestirme  dizi basina toplam   n x log2(n)")
for uzunluk in (8, 16, 32, 64, 128):
    kume = dagarcik(uzunluk=uzunluk)
    s = Sayac()
    for k in kume:
        bol_yonet(k["dizi"], s, "dogrusal")
    cagri = 2 * uzunluk - 1
    toplam = s.adim // len(kume)
    print(f"{uzunluk:7d} {cagri:6d} {toplam - cagri:12d} {toplam:20d}"
          f" {int(uzunluk * log2(uzunluk)):13d}")
```

```
uzunluk  cagri  birlestirme  dizi basina toplam   n x log2(n)
      8     15           24                   39            24
     16     31           64                   95            64
     32     63          160                  223           160
     64    127          384                  511           384
    128    255          896                 1151           896
```

Birleştirme sütunu ile son sütun **birebir aynı**: 24, 64, 160, 384, 896. Bağıntının çözümü
burada bir yaklaşıklık değil, sayılan bir eşitliktir — her düzeyde toplam $n$ adım harcanır
ve $\log_2 n$ düzey vardır. Çağrı sütunu ise $2n - 1$ olarak ayrı büyür ve doğrusaldır;
küçük $n$'de baskın, büyük $n$'de önemsiz olan bileşen budur. Uzunluk 8'de çağrılar toplamın
%38'ini yer, 128'de %22'sini. Bölmenin küçük girdide kaybetmesinin sayısal kaynağı tam
olarak bu orandır.

#### İkinci Dağarcık

```python
# Onceki bloklarin uzerine: dagarcik, Sayac ve bol_yonet oradan gelir.
IKINCI = 20260219
for ad, tohum in (("birinci dagarcik (20260218)", TOHUM),
                  ("ikinci  dagarcik (20260219)", IKINCI)):
    kume = dagarcik(tohum)
    print(ad)
    for birlestirme in ("dogrusal", "eksik"):
        o = olc(birlestirme, kume)
        print(f"  {birlestirme:9s} ayrilan {o['ayrilan']:2d} / 40 | oran",
              round(o["ayrilan"] / 40, 4), "| kalip adim", o["kalip_adim"])
```

```
birinci dagarcik (20260218)
  dogrusal  ayrilan  0 / 40 | oran 0.0 | kalip adim 2680
  eksik     ayrilan 40 / 40 | oran 1.0 | kalip adim 920
ikinci  dagarcik (20260219)
  dogrusal  ayrilan  0 / 40 | oran 0.0 | kalip adim 2680
  eksik     ayrilan 39 / 40 | oran 0.975 | kalip adim 920
```

Doğrusal birleştirmenin adımı iki dağarcıkta **birebir aynı**: 2680. Bu şaşırtıcı değildir,
çünkü bu yordamın adım sayısı yalnız uzunluğa bağlıdır, değerlere değil — kaba kuvvette de,
böl ve yönette de erken çıkış yoktur. Eksik birleştirmenin ayrılan girdisi 40'tan 39'a
iniyor; oran 1,0000'den 0,9750'ye. Fark bir girdidir ve **çözünürlüğün altındadır**, yani
ölçülmemiş sayılır. İkinci dağarcıkta ayrılmayan tek dizi dokuzuncudur: on iki değerinin
onu sıfır ya da negatiftir ve tek büyük değeri olan 20, hem en büyük elemandır hem de en
büyük alt dizi toplamıdır. Eksik birleştirme orada doğru yanıtı **yanlış nedenle** verir —
sınırı geçen çözümü yine hiç aramamış, yalnız aradığı şey bu kez yeterli olmuştur. Bir
kalıbın tek bir girdide kâhinle uyuşması, ön koşulunun sağlandığı anlamına gelmez; ölçünün
40 girdi üzerinden alınmasının nedeni budur.

#### Özet

- Böl ve yönet bağıntısı $T(n) = a \cdot T(n/b) + f(n)$'dir; bu derste $a = 2$, $b = 2$ ve
  sonucu belirleyen tek şey $f(n)$'dir.
- Aynı bölme üzerinde doğrusal birleştirme 2680 adımla kâhinin 1,16 katı azını harcıyor ve
  40 girdide de doğru; karesel birleştirme 5320 adımla kâhinden **1,71 kat yavaş** ama yine
  doğru.
- Sınırı geçen çözümü atlayan eksik birleştirme 920 adımla en ucuz olan ve 40 girdinin
  **40'ında** yanlış; döndürdüğü şey dizinin en büyük tek elemanıdır.
- Bölme 8 elemana kadar kazandırmıyor; oran 1'i 8 ile 16 arasında geçiyor ve kayıp
  $2n - 1$ özyineleme çağrısından geliyor.
- Karesel birleştirmede oran hiçbir uzunlukta 1'i geçmiyor; ana teoremin üçüncü durumunda
  bölmenin $\log n$ kazancı ortadan kalkar.
- İkinci dağarcıkta doğrusal birleştirmenin adımı birebir aynı, eksik birleştirmenin ayrılan
  girdisi 40 yerine 39; aradaki bir girdilik fark çözünürlüğün altındadır.

#### Sonraki Adım

Böl ve yönet, problemi parçalayıp **bütün parçaları** çözüyordu. Sonraki kalıp bundan daha
cesur bir şey yapar: her adımda tek bir seçim yapar ve **geri dönmez**. Bu cesaretin bazen
kanıtı vardır — Algoritmalar kursundaki üç açgözlü yordamda olduğu gibi — bazen yoktur.
Sonraki ders açgözlü seçimin ne zaman yanıldığını sayar: dört değerli para sistemlerinin
kaçında açgözlü, gerekenden fazla para veriyor.

### Açgözlü Algoritmalar

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/acgozlu-algoritmalar

Açgözlü seçimin kanıtlanmadıkça yordam olmadığı: dört değerli 969 para sisteminin 827'sinde açgözlü gerekenden fazla para veriyor, oran 0,8535 ve en büyük fazlalık 16 para; aynı yanılgı 50 yerine 10 tutara kadar sınandığında sistemlerin yalnız 85'inde görülüyor.

Böl ve yönet problemi parçalıyor ve **bütün parçaları** çözüyordu. Bu dersin kalıbı daha
cesur davranır: her adımda o an en iyi görünen seçimi yapar, yaptığı seçimi bir daha
sorgulamaz ve geriye hiç dönmez. Kalıbın adı **açgözlü (greedy)** ve çekiciliği açıktır —
geri dönüş olmadığı için adım sayısı küçüktür.

Cesaretin bir bedeli var. Böl ve yönette ön koşul birleştirmeye ilişkindi ve gözle
görülüyordu. Burada ön koşul çok daha örtüktür: **yerel olarak en iyi olan seçimin, küresel
olarak en iyi çözümün parçası olması** gerekir. Bu, girdiye bakarak anlaşılmaz; kanıtlanır
ya da yanlıştır. Bu ders kanıtın ne olduğunu kurar ve kanıt olmadığında ne kadar
yanıldığını sayar.

- **TY20.** Ölçülen problem: verilen tutarı bir para sisteminde **en az sayıda parayla**
  ödemek.
- **TY21.** Para sistemi $(1, a, b, c)$ biçimindedir. 1'in bulunması her tutarın ödenebilir
  olmasını güvenceye alır, yani "ödenemez" durumu ölçümü kirletmez.
- **TY22.** Açgözlü yordam en büyük paradan başlar ve **geri dönmez**. Bir adım, bir paranın
  kalandan düşülmesidir.
- **TY23.** **Kâhin kaba kuvvettir:** bütün para bileşimlerini sayar. Alttan yukarı çözüm
  kâhin yerine kullanılmadan önce kâhinle sınanır.
- **TY24.** Tarama, 2 ile 20 arasındaki bütün üçlüleri kapsar — **969 sistem** — ve her
  sistemde 1'den 50'ye kadar bütün tutarları dener.
- **TY25.** **Ayrılan girdi burada bir sistemdir:** açgözlünün en az bir tutarda fazla para
  verdiği sistem sayılır.
- **TY26.** **Fazlalık**, açgözlünün kullandığı para sayısından en azının çıkarılmasıdır.
- **TY27.** M01/K04'ün açgözlü yordamları — Dijkstra algoritması, minimum kapsayan ağaç
  algoritmaları ve Huffman kodlaması — **tekrarlanmaz**; yalnız neden yanılmadıkları yazılır.
- **TY28.** İkinci dağarcık **20260219** tohumuyla üretilen beş değerli sistemlerdir.

#### Açgözlü Seçim ve Kanıt Yükümlülüğü

Açgözlü bir yordam, çözümü adım adım kurar ve her adımda bir ölçüte göre en iyi görünen
adayı alır. Ölçüt sabittir, seçim geri alınmaz. Böyle bir yordamın doğru olması için tek bir
şeyin gösterilmesi yeterlidir: **her adımda yapılan seçimin, en iyi çözümlerden en az birinde
bulunması.** Buna açgözlü seçim özelliği denir ve gösterilme biçimi neredeyse her zaman
aynıdır — bir **değiş tokuş** akıl yürütmesi.

Algoritmalar kursu bu akıl yürütmenin üç örneğini kurdu ve kanıtladı. Minimum kapsayan ağaç
dersinde kesme özelliği, bir kesmeyi geçen en hafif kenarın bir en iyi ağaçta bulunduğunu
gösteriyordu: o kenarı içermeyen bir ağaca kenar eklenince oluşan çevrimden, aynı kesmeyi
geçen daha ağır bir kenar çıkarılabiliyordu. Huffman kodlaması dersinde aynı yapı, en seyrek
iki simgenin kardeş yapılabileceğini gösteriyordu. Dijkstra algoritmasında ise seçim,
kesinleşmemiş düğümler arasında en küçük tahminli olanın artık kesinleşmiş olmasıydı ve bu,
kenar ağırlıklarının negatif olmamasına dayanıyordu.

Üçünün ortak yanı, seçimin **güvenliğinin gösterilmiş** olmasıdır. Bu ders o yordamları
tekrarlamaz; sorduğu soru şudur: kanıt yoksa ne olur. Yanıt, sayılabilir bir şeydir.

#### Kâhini Doğrulamak

Para üstü probleminde en az para sayısını veren yordam, alttan yukarı çözümdür. Ama bu
kursun kuralı kâhinin **kaba kuvvet** olmasını ister. Bu yüzden alttan yukarı çözüm, geniş
taramada kullanılmadan önce kâhinle sınanır.

```python
# Ortak cerceve: adim sayaci (onceki derslerden aynen).
from itertools import combinations


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self, n=1):
        self.adim += n


def acgozlu_para(sistem, tutar, s):
    """En buyuk paradan baslar, geri donmez."""
    kalan, adet = tutar, 0
    for p in sorted(sistem, reverse=True):
        while kalan >= p:
            s.say()
            kalan -= p
            adet += 1
    return adet if kalan == 0 else None


def dp_para(sistem, tutar, s):
    """Alttan yukari tam cozum."""
    en_az = [0] + [10**9] * tutar
    for t in range(1, tutar + 1):
        for p in sistem:
            if p <= t:
                s.say()
                if en_az[t - p] + 1 < en_az[t]:
                    en_az[t] = en_az[t - p] + 1
    return en_az[tutar] if en_az[tutar] < 10**9 else None


def kahin_kaba_para(sistem, tutar, s):
    """Butun para bilesimlerini sayar. Hakem budur."""
    en_az = None

    def gez(i, kalan, adet):
        nonlocal en_az
        s.say()
        if kalan == 0:
            if en_az is None or adet < en_az:
                en_az = adet
            return
        if i == len(sistem):
            return
        p = sistem[i]
        for k in range(kalan // p + 1):
            gez(i + 1, kalan - k * p, adet + k)
    gez(0, tutar, 0)
    return en_az


ORNEK = [(1, 3, 7, 12), (1, 4, 9, 16), (1, 5, 10, 25), (1, 18, 19, 20)]
ayrilan, sk, sd = 0, Sayac(), Sayac()
for sistem in ORNEK:
    for tutar in range(1, 26):
        if kahin_kaba_para(sistem, tutar, sk) != dp_para(sistem, tutar, sd):
            ayrilan += 1
print("dogrulama: ", len(ORNEK), "sistem x 25 tutar =", len(ORNEK) * 25, "girdi")
print("kahin (kaba kuvvet) adim:", sk.adim, "| alttan yukari adim:", sd.adim,
      "| oran:", round(sk.adim / sd.adim, 2))
print("kahinden ayrilan girdi:", ayrilan)
```

```
dogrulama:  4 sistem x 25 tutar = 100 girdi
kahin (kaba kuvvet) adim: 13584 | alttan yukari adim: 2785 | oran: 4.88
kahinden ayrilan girdi: 0
```

Yüz girdinin **hiçbirinde** ayrılma yok; kaba kuvvet 13.584 adım, alttan yukarı çözüm 2785
adım harcıyor. Bu, alttan yukarı çözümün doğru olduğunu **kanıtlamaz** — sınamak kanıt
değildir — ama onu 969 sistemlik taramada kâhin yerine kullanmayı ölçülmüş bir zemine
oturtur. Kaba kuvvetin doğrudan kullanılmamasının nedeni de görünüyor: 4,88 katlık fark
100 girdide zararsızdır, 969 sistem ve 50 tutar için ise taramayı gereksiz yere büyütür.

#### Dokuz Yüz Altmış Dokuz Sistem

Asıl ölçüm, dört değerli bütün sistemleri tarar.

```python
# Onceki blogun uzerine: Sayac, acgozlu_para, dp_para ve combinations oradan gelir.
def acgozlu_tarama(ust=20, tutar_ust=50):
    """Butun {1,a,b,c} sistemleri: acgozlu kac sistemde ve kac tutarda fazla veriyor."""
    bozuk, toplam_sistem, en_buyuk_fark, ornek = 0, 0, 0, None
    for a, b, c in combinations(range(2, ust + 1), 3):
        sistem = (1, a, b, c)
        toplam_sistem += 1
        s = Sayac()
        kotu = 0
        fark_en = 0
        for t in range(1, tutar_ust + 1):
            ac = acgozlu_para(sistem, t, s)
            dp = dp_para(sistem, t, s)
            if ac is not None and dp is not None and ac > dp:
                kotu += 1
                if ac - dp > fark_en:
                    fark_en = ac - dp
        if kotu:
            bozuk += 1
            if fark_en > en_buyuk_fark:
                en_buyuk_fark = fark_en
                ornek = (sistem, kotu, fark_en)
    return {"sistem": toplam_sistem, "bozuk": bozuk,
            "oran": round(bozuk / toplam_sistem, 4),
            "en_buyuk_fark": en_buyuk_fark, "ornek": ornek}


T = acgozlu_tarama()
print("taranan sistem:", T["sistem"], "| acgozlunun fazla verdigi:", T["bozuk"],
      "| oran:", T["oran"])
print("en buyuk fazlalik:", T["en_buyuk_fark"], "| ornek sistem:", T["ornek"])
```

```
taranan sistem: 969 | acgozlunun fazla verdigi: 827 | oran: 0.8535
en buyuk fazlalik: 16 | ornek sistem: ((1, 18, 19, 20), 2, 16)
```

**969 sistemin 827'sinde** açgözlü, gerekenden fazla para veriyor. Oran **0,8535**, yani
dört değerli para sistemlerinin yaklaşık altıda beşi açgözlü için uygun değil. En büyük
fazlalık **16 paradır** ve $(1, 18, 19, 20)$ sisteminde görülüyor.

Bu sayının hakkını vermek gerekir. Açgözlü seçim, para üstü probleminde herkesin sezgisine
uyar: en büyük parayı ver, kalanı küçült. Sezgi yanlış değildir — 142 sistemde gerçekten
doğru yanıtı verir. Yanlış olan, sezginin **yordam yerine geçmesidir**. Açgözlü seçim
sezgiseldir; kanıtlanmadıkça yordam değildir.

#### Nerede ve Ne Kadar Yanılıyor

En büyük fazlalığın nerede ortaya çıktığı, yanılgının biçimini gösterir.

```python
# Onceki bloklarin uzerine: Sayac, acgozlu_para, dp_para oradan gelir.
SISTEM = (1, 18, 19, 20)
s = Sayac()
print("sistem", SISTEM, "- acgozlunun fazla verdigi tutarlar")
for tutar in range(1, 51):
    ac = acgozlu_para(SISTEM, tutar, s)
    dp = dp_para(SISTEM, tutar, s)
    if ac > dp:
        print(f"  tutar {tutar:2d}: acgozlu {ac:2d} para, en az {dp:2d} para,"
              f" fazlalik {ac - dp:2d}")
print()
print("tarama siniri  bozuk sistem  oran")
for tutar_ust in (10, 20, 50, 100):
    T = acgozlu_tarama(tutar_ust=tutar_ust)
    print(f"{tutar_ust:14d} {T['bozuk']:13d} {T['oran']:6.4f}")
```

```
sistem (1, 18, 19, 20) - acgozlunun fazla verdigi tutarlar
  tutar 36: acgozlu 17 para, en az  2 para, fazlalik 15
  tutar 37: acgozlu 18 para, en az  2 para, fazlalik 16

tarama siniri  bozuk sistem  oran
            10            85 0.0877
            20           466 0.4809
            50           827 0.8535
           100           827 0.8535
```

36 tutarında en az çözüm iki paradır: 18 artı 18. Açgözlü ise 20'yi alır, kalan 16'yı bir
tek birlerle ödeyebilir ve **17 para** kullanır. 37'de aynı şey olur: en az çözüm 18 artı
19, açgözlünün çözümü 20 artı on yedi birlik. Yanılgının kaynağı görünüyor — **en büyük
parayı almak, geriye ödenmesi pahalı bir kalan bırakıyor.** Açgözlü bu kalanı görmez, çünkü
seçimi yaparken yalnız o anki en büyük değere bakar.

Alt tablo daha rahatsız edici bir şey söylüyor. Aynı 969 sistem yalnız 10'a kadar
sınandığında **85 sistem** bozuk görünüyor, oran 0,0877. 20'ye kadar sınandığında 466
sistem, oran 0,4809. 50'ye kadar sınandığında 827, oran 0,8535. 100'e çıkmak sonucu
değiştirmiyor. Yani bu sistem ailesinde yanılgıların tamamı 50'nin altında ortaya çıkıyor,
ama **10'a kadar sınayan biri sistemlerin yüzde 91'ini temiz sanırdı**. Sınamanın kapsamı
sonucun kendisini belirliyor; bir kalıbın "sınandı" denmesi, **neye kadar** sınandığı
yazılmadıkça bir şey ifade etmez.

#### Sınanabilir Bir Yeter Koşul

Kanıtın yerini bir sezgi alamıyorsa, en azından **sınanabilir bir koşul** aranabilir. Para
sistemleri için akla ilk gelen aday, değerlerin bir bölünme zinciri oluşturmasıdır: her
değerin bir sonrakini bölmesi.

```python
# Onceki bloklarin uzerine: Sayac, acgozlu_para, dp_para, combinations oradan gelir.
zincir_saglam, zincir_bozuk, digeri_saglam, digeri_bozuk = 0, 0, 0, 0
saglam_ornek = []
for a, b, c in combinations(range(2, 21), 3):
    sistem = (1, a, b, c)
    s = Sayac()
    bozuk = any(acgozlu_para(sistem, t, s) > dp_para(sistem, t, s)
                for t in range(1, 51))
    zincir = b % a == 0 and c % b == 0            # her deger bir sonrakini boluyor mu
    if zincir and bozuk:
        zincir_bozuk += 1
    elif zincir:
        zincir_saglam += 1
    elif bozuk:
        digeri_bozuk += 1
    else:
        digeri_saglam += 1
        if len(saglam_ornek) < 4:
            saglam_ornek.append(sistem)
print("bolunme zinciri olan  : saglam", zincir_saglam, "| bozuk", zincir_bozuk)
print("bolunme zinciri olmayan: saglam", digeri_saglam, "| bozuk", digeri_bozuk)
print("zinciri olmayan saglam sistemlerden ornekler:", saglam_ornek)
```

```
bolunme zinciri olan  : saglam 13 | bozuk 0
bolunme zinciri olmayan: saglam 129 | bozuk 827
zinciri olmayan saglam sistemlerden ornekler: [(1, 2, 3, 4), (1, 2, 3, 5), (1, 2, 3, 6), (1, 2, 3, 7)]
```

Bölünme zinciri taşıyan **13 sistemin 13'ü de sağlam**; bu koşul altında açgözlü hiçbir
tutarda fazla vermiyor. Ama zincirin bulunmadığı sistemlerin **129'u da sağlam**. Koşul
**yeterlidir, gerekli değildir**: sağladığında güvence verir, sağlamadığında hiçbir şey
söylemez. $(1, 2, 3, 4)$ sistemi zincir taşımaz — 3, 2'yi bölmez — ama açgözlü orada da
yanılmaz.

Bu ayrım bu kursun neden kâhini bırakmadığını açıklar. Yeter koşul, 969 sistemin 13'ünde
ölçüme gerek bırakmaz. Kalan 956 sistem için tek bilgi kaynağı kâhindir; koşulun
sağlanmaması bir kalıbı ne aklar ne mahkûm eder. **Sınanabilir bir koşul, ölçümün yerine
değil önüne geçer.**

#### İkinci Dağarcık

Ölçüm, dört değerli sistemlerin ötesine taşınır: beş değerli ve daha geniş aralıklı
sistemler iki tohumla üretilir.

```python
# Onceki bloklarin uzerine: Sayac, acgozlu_para, dp_para oradan gelir.
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def bes_degerli_dagarcik(tohum, n=40):
    """Bes degerli {1,a,b,c,d} sistemler; degerler 2..40 arasinda ve artan."""
    r = uretec(tohum)
    kume = []
    while len(kume) < n:
        d = sorted({2 + r(39) for _ in range(4)})
        if len(d) == 4:
            kume.append((1, *d))
    return kume


for ad, tohum in (("birinci dagarcik (20260218)", 20260218),
                  ("ikinci  dagarcik (20260219)", 20260219)):
    sistemler = bes_degerli_dagarcik(tohum)
    bozuk, en_fark = 0, 0
    for sistem in sistemler:
        s = Sayac()
        kotu = max((acgozlu_para(sistem, t, s) - dp_para(sistem, t, s))
                   for t in range(1, 61))
        if kotu > 0:
            bozuk += 1
            en_fark = max(en_fark, kotu)
    print(ad, "| bozuk sistem", bozuk, "/ 40 | oran", round(bozuk / 40, 4),
          "| en buyuk fazlalik", en_fark)
```

```
birinci dagarcik (20260218) | bozuk sistem 39 / 40 | oran 0.975 | en buyuk fazlalik 21
ikinci  dagarcik (20260219) | bozuk sistem 40 / 40 | oran 1.0 | en buyuk fazlalik 21
```

Beş değerli sistemlerde oran 0,9750 ve 1,0000; iki dağarcık arasındaki bir sistemlik fark
çözünürlüğün altındadır. Değer sayısını artırmak açgözlüyü **düzeltmiyor, kötüleştiriyor** —
daha çok değer, en büyük parayı aldıktan sonra kalanı ödemenin daha çok yolu ve dolayısıyla
açgözlünün kaçırabileceği daha çok çözüm demektir. En büyük fazlalık da 16'dan 21'e çıkıyor.

Bu, K04'ün üç açgözlü yordamıyla arasındaki farkı tamamlar. Orada seçimin güvenli olduğu
kanıtlanmıştı ve kanıt, girdinin bir özelliğine dayanıyordu: kesmeyi geçen en hafif kenar,
en seyrek iki simge, negatif olmayan kenar ağırlığı. Para sisteminde böyle bir özellik
sistemin kendisine bağlıdır ve **çoğu sistemde yoktur**. Açgözlü kalıbı seçmek, o özelliğin
var olduğunu varsaymaktır; varsayım yazılmadığında yordam sessizce fazla para verir.

#### Özet

- Açgözlü yordam her adımda en iyi görüneni alır ve geri dönmez; doğru olması için seçimin
  en iyi çözümlerden birinde bulunduğunun **değiş tokuş akıl yürütmesiyle gösterilmesi** gerekir.
- Alttan yukarı çözüm, geniş taramada kullanılmadan önce kaba kuvvetle sınandı: 100 girdide
  **0 ayrılma**, kaba kuvvet 13.584 adım, alttan yukarı 2785 adım.
- Dört değerli **969 sistemin 827'sinde** açgözlü fazla para veriyor; oran **0,8535** ve en
  büyük fazlalık **16 para**, $(1, 18, 19, 20)$ sisteminde 37 tutarında; aynı sistemde 36
  tutarının fazlalığı **15**'tir.
- Yanılgının kaynağı, en büyük parayı almanın geriye ödenmesi pahalı bir kalan bırakmasıdır;
  açgözlü o kalanı seçim anında görmez.
- Aynı tarama 10'a kadar yapılsaydı bozuk sistem sayısı 827 değil **85** çıkardı; sınamanın
  kapsamı sonucu belirler ve "sınandı" demek **neye kadar** yazılmadıkça bir şey söylemez.
- Beş değerli sistemlerde oran 0,9750 ve 1,0000'e çıkıyor; değer eklemek açgözlüyü
  düzeltmiyor, en büyük fazlalığı 16'dan 21'e taşıyor.

#### Sonraki Adım

Açgözlü, kalanı görmediği için yanılıyordu. Bunun bir çaresi var: kalanı da çözmek, yani
her alt problemin en iyi çözümünü hesaplayıp saklamak. Bu dersteki alttan yukarı çözüm zaten
bunu yapıyordu ve hiç yanılmadı. Sonraki ders o yordamın adını koyar ve iki koşulunu ölçer —
alt problemlerin **örtüşmesi** ve en iyi çözümün alt problemlerin en iyi çözümlerinden
kurulabilmesi. Örtüşme olmadığında saklamanın hiçbir şey kazandırmadığı, ama yine de yer
tuttuğu sayıyla gösterilecek.

### Dinamik Programlama

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/dinamik-programlama

Not almanın iki koşulu ve ikisinin de sayılması: örtüşen alt problemde çağrı 21.891'den 39'a inerken örtüşmeyende ikisi de 39 kalıyor ve defter 19 girişi boşa tutuyor, defterin anahtarı durumun tamamını taşımadığında ise kalıp 40 girdinin 39'unda kâhinden ayrılıyor.

Açgözlü yordam, aldığı en büyük parayı bir daha sorgulamadığı için geriye pahalı bir kalan
bırakıyordu. Çare belliydi ve önceki derste zaten kullanıldı: kalanı da çözmek, yani her
alt problemin en iyi çözümünü hesaplayıp saklamak. O yordamın adı **dinamik programlama
(dynamic programming)**.

Kalıbın çekiciliği, üstel bir aramayı sayılabilir bir tabloya indirmesidir. Bedeli ise iki
ayrı ön koşuldur ve ikisi de sessizce bozulabilir. Birincisi **optimal alt yapı**: en iyi
çözümün, alt problemlerin en iyi çözümlerinden kurulabilmesi. İkincisi **örtüşen alt
problem**: aynı alt problemin birden çok kez sorulması. Birincisi bozulunca yanıt yanlış
çıkar; ikincisi bozulunca yanıt doğru kalır ama saklamak hiçbir şey kazandırmaz. Bu ders
ikisini de sayar.

- **TY29.** Ölçülen problem: bir dizide **bitişik olmayan** en çok üç eleman seçerek elde
  edilebilecek en büyük toplam. Boş seçim serbesttir, yani yanıt hiçbir zaman sıfırın
  altında olmaz.
- **TY30.** Kâhin bütün alt kümeleri görür: 12 elemanlı bir dizide **4096 alt küme**.
- **TY31.** Bir **adım**, not almada bir özyineleme çağrısı, alttan yukarıda bir hücrenin
  hesaplanması, kâhinde bir alt kümenin denenmesidir.
- **TY32.** **Not alma (memoization)** özyinelemeli çözüme bir defter ekler; **alttan
  yukarı** çözüm aynı bağıntıyı özyinelemesiz kurar.
- **TY33.** Kalıbın **ön koşulu**: defterin anahtarı, alt problemi tek anlamlı belirleyen
  durumun **tamamını** taşımalıdır. Eksik anahtar yalnız bu ön koşulu bozar.
- **TY34.** İkinci ön koşul **optimal alt yapıdır** ve bu derste bozulmadan tutulur; onun
  bozulduğu durum klasik problemler konusuna aittir.
- **TY35.** Örtüşme ölçümü ortak tanımdan aynen alınır ve yeniden tanımlanmaz.
- **TY36.** Defter boyu **giriş sayısıyla**, tablo boyu **hücre sayısıyla** ölçülür.
- **TY37.** Her ölçüm **20260219** tohumlu ikinci dağarcıkta da koşturulur.

#### Örtüşme Sayılır

Not almanın kazancı, alt problemlerin kaç kez tekrarlandığına eşittir. Bu, tahmin edilecek
bir şey değil sayılacak bir şeydir. Aşağıdaki iki yordam aynı $n$ için çalışır; birinde
her alt problem iki kez sorulur, ötekinde her çağrı yeni bir alt problem üretir.

```python
# Ortak tanimin ortusme olcumu: ayni n, iki farkli alt problem yapisi.
class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self, n=1):
        self.adim += n


def ortusen(n, notlu=True):
    """Ortusen alt problem: her deger iki kez cagriliyor."""
    s = Sayac()
    defter = {}

    def f(k):
        s.say()
        if k < 2:
            return k
        if notlu and k in defter:
            return defter[k]
        d = f(k - 1) + f(k - 2)
        defter[k] = d
        return d
    f(n)
    return {"cagri": s.adim, "deger": len(defter)}


def ortusmeyen(n, notlu=True):
    """Ortusmeyen alt problem: her cagri farkli bir alt problem."""
    s = Sayac()
    defter = {}

    def f(bas, son):
        s.say()
        if son - bas <= 1:
            return son - bas
        if notlu and (bas, son) in defter:
            return defter[(bas, son)]
        orta = (bas + son) // 2
        d = f(bas, orta) + f(orta, son)
        defter[(bas, son)] = d
        return d
    f(0, n)
    return {"cagri": s.adim, "deger": len(defter)}


print(" n  ortusen notlu  ortusen notsuz  ortusmeyen notlu  ortusmeyen notsuz  defter")
for n in (10, 15, 20):
    a, b = ortusen(n, True), ortusen(n, False)
    c, d = ortusmeyen(n, True), ortusmeyen(n, False)
    print(f"{n:2d} {a['cagri']:14d} {b['cagri']:15d} {c['cagri']:17d}"
          f" {d['cagri']:18d} {c['deger']:7d}")
```

```
 n  ortusen notlu  ortusen notsuz  ortusmeyen notlu  ortusmeyen notsuz  defter
10             19             177                19                 19       9
15             29            1973                29                 29      14
20             39           21891                39                 39      19
```

Örtüşen yapıda $n = 20$ için çağrı sayısı **21.891'den 39'a** iniyor; kazanç 561 kat.
Örtüşmeyen yapıda aynı $n$ için notlu ve notsuz sürüm **ikisi de 39** çağrı yapıyor —
defter bir kez bile isabet vermiyor — ama yine de **19 giriş** tutuyor. İkinci satırın
söylediği şey açıktır: **not almanın koşulu örtüşmedir.** Örtüşme yoksa defter yalnız yer
tutar; yanıt doğru kalır, kazanç sıfırdır ve bellek boşa gider.

İki yapı arasındaki fark, bağıntının biçimindedir. Örtüşen yapıda iki dal aynı değerlere
iniyor ve alt problem uzayı $n$ büyüklüğünde; örtüşmeyende her dal kendi aralığına iniyor ve
alt problem uzayı çağrı sayısı kadar. Bir kalıbı seçmeden önce sorulacak soru budur: **alt
problem uzayı, çağrı sayısından küçük mü.**

#### Defterin Anahtarı Durumun Tamamını Taşımalı

Not almanın ikinci ve daha sessiz ön koşulu deftere ilişkindir. Aşağıdaki problem iki
değişkenli bir duruma sahiptir: hangi konumdayız ve kaç seçim hakkımız kaldı. Aynı yordam
iki anahtarla koşturulur.

```python
# Onceki blogun uzerine: Sayac oradan gelir.
TOHUM = 20260218
SECIM = 3          # en cok kac eleman secilebilir


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum=TOHUM, n=40, uzunluk=12):
    r = uretec(tohum)
    return [{"no": i + 1, "dizi": [r(30) - 9 for _ in range(uzunluk)]}
            for i in range(n)]


def kahin_secim(dizi, k, s):
    """Butun alt kumeleri gorur; bitisik olmayan ve en cok k elemanlilari deger."""
    en_iyi = 0
    for maske in range(1 << len(dizi)):
        s.say()
        if maske & (maske << 1):                  # bitisik iki secim var
            continue
        if bin(maske).count("1") > k:
            continue
        en_iyi = max(en_iyi, sum(dizi[i] for i in range(len(dizi))
                                 if maske >> i & 1))
    return en_iyi


def not_alan(dizi, k, s, anahtar="tam"):
    """anahtar='tam' -> defter (konum, kalan) ile; 'eksik' -> yalniz konum ile."""
    defter = {}

    def f(i, kalan):
        s.say()
        if i >= len(dizi) or kalan == 0:
            return 0
        ad = (i, kalan) if anahtar == "tam" else i
        if ad in defter:
            return defter[ad]
        d = max(f(i + 1, kalan), dizi[i] + f(i + 2, kalan - 1))
        defter[ad] = d
        return d
    sonuc = f(0, k)
    return sonuc, len(defter)


def olc(anahtar, kume):
    ayrilan, ak, ah, giris = [], 0, 0, 0
    for k in kume:
        s1, s2 = Sayac(), Sayac()
        deger, boy = not_alan(k["dizi"], SECIM, s1, anahtar)
        if deger != kahin_secim(k["dizi"], SECIM, s2):
            ayrilan.append(k["no"])
        ak += s1.adim
        ah += s2.adim
        giris += boy
    return {"ayrilan": len(ayrilan), "ayrilan_no": ayrilan[:6], "kalip_adim": ak,
            "kahin_adim": ah, "oran": round(ah / ak, 2), "defter_girisi": giris}


K = dagarcik()
print("kahin: dizi basina", 2 ** 12, "alt kume")
for anahtar in ("tam", "eksik"):
    print(f"defter anahtari {anahtar:6s}:", olc(anahtar, K))
```

```
kahin: dizi basina 4096 alt kume
defter anahtari tam   : {'ayrilan': 0, 'ayrilan_no': [], 'kalip_adim': 2440, 'kahin_adim': 163840, 'oran': 67.15, 'defter_girisi': 1200}
defter anahtari eksik : {'ayrilan': 39, 'ayrilan_no': [1, 2, 3, 5, 6, 7], 'kalip_adim': 1000, 'kahin_adim': 163840, 'oran': 163.84, 'defter_girisi': 480}
```

Üç sayı yan yana. **Kâhin** 40 girdide **163.840 adım** harcıyor. **Tam anahtarlı not alma**
2440 adım harcıyor — kâhinin **67,15 katı** azı — ve 40 girdinin hiçbirinde ayrılmıyor.
**Eksik anahtarlı not alma** 1000 adım harcıyor, kâhinin **163,84 katı** azı ve 40 girdinin
**39'unda** kâhinden ayrılıyor.

Yanılgının mekanizması, önceki iki dersinkinden farklıdır ve daha tehlikelidir. Burada
atlanan bir bölge yok; yordam bütün dalları geziyor. Sorun, deftere yazılan değerin
**yanlış soruya ait olmasıdır.** Beşinci konumda üç seçim hakkıyla hesaplanan en iyi değer,
aynı konuma bir seçim hakkıyla gelindiğinde geri veriliyor. Defter isabet ediyor, hesap
atlanıyor, adım sayısı düşüyor — ve yanıt bozuluyor. Adım sayısının düşmesi burada bir
hızlanma değil, **yanlışın belirtisidir**: 2440 yerine 1000 adım, defterin 1200 yerine 480
giriş tutması, hesaplanması gereken alt problemlerin **yarıdan çoğunun hiç hesaplanmadığı**
anlamına gelir.

#### Not Alma ile Alttan Yukarı

Aynı bağıntı özyinelemesiz de kurulabilir. Bu, dinamik programlamanın ikinci biçimidir ve
farkı ölçülebilir.

```python
# Onceki bloklarin uzerine: dagarcik, Sayac, not_alan, kahin_secim ve K oradan gelir.
def alttan_yukari(dizi, k, s):
    """Ayni bagintiyi ozyinelemesiz kurar; butun (konum, kalan) hucreleri dolar."""
    n = len(dizi)
    tablo = [[0] * (k + 1) for _ in range(n + 2)]
    for i in range(n - 1, -1, -1):
        for kalan in range(1, k + 1):
            s.say()
            tablo[i][kalan] = max(tablo[i + 1][kalan],
                                  dizi[i] + tablo[i + 2][kalan - 1])
    return tablo[0][k], n * k


s1, s2, s3 = Sayac(), Sayac(), Sayac()
ayrilan, not_giris, tablo_hucre = 0, 0, 0
for k in K:
    a, boy = not_alan(k["dizi"], SECIM, s1, "tam")
    b, hucre = alttan_yukari(k["dizi"], SECIM, s2)
    kahin_secim(k["dizi"], SECIM, s3)
    if a != b:
        ayrilan += 1
    not_giris += boy
    tablo_hucre += hucre
print("kahin adim       :", s3.adim)
print("not alma adim    :", s1.adim, "| defter girisi:", not_giris)
print("alttan yukari    :", s2.adim, "| tablo hucresi:", tablo_hucre)
print("ikisinin ayrildigi girdi:", ayrilan, "/ 40")
```

```
kahin adim       : 163840
not alma adim    : 2440 | defter girisi: 1200
alttan yukari    : 1440 | tablo hucresi: 1440
ikisinin ayrildigi girdi: 0 / 40
```

İki biçim 40 girdide de aynı yanıtı veriyor, ama sayıları farklı. Alttan yukarı çözüm
**1440 adım** harcıyor, not alma **2440**; aradaki fark özyineleme çağrılarının kendisidir.
Buna karşılık not alma **1200 giriş** tutuyor, alttan yukarı **1440 hücre**. Yani dizi
başına not alma 30 giriş, tablo 36 hücre.

Ödünleşim tam olarak buradadır. Alttan yukarı çözüm bütün hücreleri doldurur —
erişilemeyecek olanları da — ve karşılığında çağrı yükü ödemez. Not alma yalnız gerçekten
sorulan alt problemleri hesaplar, bu problemde 36 hücrenin 30'unu ve karşılığında her
alt problem için bir çağrı ödemesi yapar. Hangisinin kazandığı **alt problem uzayının ne
kadarının erişilebilir olduğuna** bağlıdır; bu problemde uzayın altıda beşi erişilebilir
olduğu için alttan yukarı öne geçiyor. Erişilebilir oran düştükçe not alma öne geçer.

#### Optimal Alt Yapı Sınanabilir Bir İddiadır

Yukarıdaki iki biçim de aynı bağıntıyı kullanıyor: bir konumdaki en iyi değer, ya o konumu
atlayıp bir sonrakine geçmenin ya da o konumu seçip iki sonrakine geçmenin en iyisidir. Bu
bağıntının doğruluğu **optimal alt yapı** varsayımına dayanır ve varsayım sınanabilir —
bağıntının her hücresi ayrı ayrı kâhine sorulur.

```python
# Onceki bloklarin uzerine: dagarcik, Sayac, kahin_secim, SECIM ve K oradan gelir.
def kahin_sonek(dizi, bas, kalan, s):
    """Sonekin en iyisini kaba kuvvetle bulur."""
    return kahin_secim(dizi[bas:], kalan, s)


hucre, uyusan, s = 0, 0, Sayac()
for k in K:
    dizi = k["dizi"]
    for i in range(len(dizi)):
        for kalan in range(1, SECIM + 1):
            hucre += 1
            atla = kahin_sonek(dizi, i + 1, kalan, s)           # i secilmiyor
            sec = dizi[i] + kahin_sonek(dizi, i + 2, kalan - 1, s)   # i seciliyor
            if max(atla, sec, 0) == kahin_sonek(dizi, i, kalan, s):
                uyusan += 1
print("sinanan hucre:", hucre, "| bagintinin kahinle uyustugu hucre:", uyusan)
print("kahin adimi  :", s.adim)
```

```
sinanan hucre: 1440 | bagintinin kahinle uyustugu hucre: 1440
kahin adimi  : 1719960
```

**1440 hücrenin 1440'ında** bağıntı kâhinle uyuşuyor. Sınamanın bedeli görülmelidir:
1.719.960 adım, yani çözümün kendisinin bin katından fazlası. Optimal alt yapıyı sınamak,
problemi çözmekten pahalıdır ve bu şaşırtıcı değildir — her hücre için soneki baştan kaba
kuvvetle çözmek gerekir.

Buradan iki şey çıkar. Birincisi, **optimal alt yapı bir sezgi değil, sınanabilir bir
iddiadır**; bir bağıntı yazıldığında onun geçerliliği kâhine hücre hücre sorulabilir.
İkincisi, bu sınama üretimde değil **tasarım sırasında ve küçük girdide** yapılır. Bağıntı
bir kez doğrulandıktan sonra 40 girdide 1440 adımla çalışan bir çözüm kalır geriye; bir
milyon yedi yüz binlik sınama, o çözümün doğruluğunun bir defalık bedelidir.

#### İkinci Dağarcık

```python
# Onceki bloklarin uzerine: dagarcik, olc oradan gelir.
for ad, tohum in (("birinci dagarcik (20260218)", 20260218),
                  ("ikinci  dagarcik (20260219)", 20260219)):
    kume = dagarcik(tohum)
    print(ad)
    for anahtar in ("tam", "eksik"):
        o = olc(anahtar, kume)
        print(f"  anahtar {anahtar:6s} ayrilan {o['ayrilan']:2d} / 40 | oran",
              round(o["ayrilan"] / 40, 4), "| kalip adim", o["kalip_adim"],
              "| defter girisi", o["defter_girisi"])
```

```
birinci dagarcik (20260218)
  anahtar tam    ayrilan  0 / 40 | oran 0.0 | kalip adim 2440 | defter girisi 1200
  anahtar eksik  ayrilan 39 / 40 | oran 0.975 | kalip adim 1000 | defter girisi 480
ikinci  dagarcik (20260219)
  anahtar tam    ayrilan  0 / 40 | oran 0.0 | kalip adim 2440 | defter girisi 1200
  anahtar eksik  ayrilan 37 / 40 | oran 0.925 | kalip adim 1000 | defter girisi 480
```

Adım ve defter sayıları iki dağarcıkta **birebir aynı**: 2440 ile 1200, 1000 ile 480.
Beklenen sonuçtur, çünkü bu yordamların adımı dizinin değerlerine değil yalnız uzunluğuna
ve seçim hakkına bağlıdır. Eksik anahtarın ayrılan girdisi 39'dan 37'ye iniyor; oran
0,9750'den 0,9250'ye. İki girdilik fark, çözünürlüğün anlamlı saydığı üç girdinin altındadır
ve **ölçülmemiş sayılır**. Okuma iki dağarcıkta da aynıdır: eksik anahtar neredeyse her
girdide yanlış yanıt verir.

#### Özet

- Dinamik programlamanın iki ön koşulu vardır: **optimal alt yapı** ve **örtüşen alt
  problem**; birincisi bozulunca yanıt, ikincisi bozulunca kazanç yok olur.
- Örtüşen yapıda $n = 20$ için çağrı **21.891'den 39'a** iniyor; örtüşmeyen yapıda notlu ve
  notsuz sürüm **ikisi de 39** çağrı yapıyor ve defter **19 girişi boşa** tutuyor.
- Tam anahtarlı not alma kâhinin 67,15 katı az adım harcıyor ve **0 girdide** ayrılıyor;
  eksik anahtarlı sürüm 163,84 katı az harcıyor ve **39 girdide** ayrılıyor.
- Eksik anahtarda adım sayısının düşmesi bir hızlanma değil **yanlışın belirtisidir**;
  defter 1200 yerine 480 giriş tutuyor, yani alt problemlerin yarıdan çoğu hiç
  hesaplanmıyor.
- Alttan yukarı çözüm 1440 adımla not almanın 2440 adımından az harcıyor ama 1440 hücre
  tutuyor; not alma 1200 giriş tutuyor. Kazananı, alt problem uzayının erişilebilir oranı
  belirler.
- İkinci dağarcıkta adım ve defter sayıları birebir aynı; eksik anahtarın ayrılan girdisi
  39 yerine 37 ve bu iki girdilik fark ölçülmemiş sayılır.

#### Sonraki Adım

Dinamik programlama, alt problem uzayını **tümüyle** dolaşıp saklıyordu. Bazı problemlerde
uzay o kadar büyüktür ki tümünü dolaşmak seçenek değildir; orada tek yol, uzayın büyük
bölümünün **çözüm içermediğini kanıtlayıp kesmektir**. Sonraki ders bu kesmeyi ölçer:
budamalı arama yedi vezirlik tahtada 552 düğüm gezerken budamasız arama 960.800 düğüm
geziyor ve iki sayının oranı $n$ büyüdükçe artıyor.

### Geri İzleme

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/geri-izleme

Budamanın doğruluğu koruyan tek kısaltma olduğu ve ölçütü bozulduğunda ne olduğu: yedi vezirlik tahtada budamalı arama 552, budamasız arama 960.800 düğüm geziyor, ama komşu sütunu da kesen aşırı budama 82 düğümde bitip çözümlerin tamamını kaybediyor.

Dinamik programlama alt problem uzayını **tümüyle** dolaşıp saklıyordu ve bunu yapabiliyordu,
çünkü uzay tabloya sığıyordu. Bazı problemlerde uzay tabloya sığmaz: sekiz vezirlik bir
tahtada 16.777.216 yerleşim vardır ve bunların yalnız 92'si çözümdür. Böyle bir uzayda tek
yol, büyük bölümünün **çözüm içermediğini kanıtlayıp kesmektir**.

Kalıbın adı **geri izleme (backtracking)**: çözüm parça parça kurulur, kurulan parça bir
kısıtı çiğnediği anda o dal bırakılır ve bir önceki karara dönülür. Kesme işlemine
**budama (pruning)** denir. Budama, bu kursta görülen kısaltmalar arasında özel bir yerdedir
— ilk dersteki eleme gibi doğruluğu **korur**, ama yalnız ölçütü doğruysa. Bu ders o
"yalnız"ı sayar.

- **TY38.** Ölçülen problem: $n \times n$ tahtada birbirini tehdit etmeyen $n$ vezir
  yerleşimlerinin **sayısı**.
- **TY39.** Bir **adım**, arama ağacındaki bir düğümdür; yapraklar da düğüm sayılır.
- **TY40.** **Kâhin budamasız aramadır:** bütün yerleşimleri üretir ve geçerliliği **sonda**
  sınar. Hiçbir dalı kesmediği için hiçbir çözümü kaçıramaz.
- **TY41.** Kalıbın **ön koşulu**: budama ölçütü yalnız **kesin olarak çözümsüz** dalları
  kesmelidir.
- **TY42.** Üç ölçüt ölçülür — **doğru**, **aşırı** (çözüm içeren dalı da keser), **eksik**
  (çözümsüz dalı kesmez).
- **TY43.** Dağarcık burada tahtadır: altı vezirlik tahtada üç kare yasak, 40 tahta.
- **TY44.** **Ayrılan girdi**, kâhin ile kalıbın **çözüm sayısının** farklı olduğu tahtadır.
- **TY45.** Sekiz vezirlik tahtada budamasız aramanın tarayacağı 16.777.216 yerleşim
  **hesaplanır, koşturulmaz**; ilk dersin adım bütçesi bunu gerektirir.
- **TY46.** Her ölçüm **20260219** tohumlu ikinci dağarcıkta da koşturulur.

#### Kısmi Çözüm Ağacı

Geri izleme, çözümü bir dizi karar olarak görür. Her karar bir düğüm üretir, her düğümün
çocukları o karardan sonraki seçeneklerdir. Bu yapıya **kısmi çözüm ağacı** denir ve
yaprakları tam çözüm adaylarıdır.

Budamasız arama bu ağacın tamamını gezer ve geçerlilik sınamasını yalnız yapraklarda yapar.
Budamalı arama sınamayı **her düğümde** yapar: kısmi çözüm zaten bir kısıtı çiğniyorsa, o
düğümün altındaki bütün yapraklar da çiğneyecektir ve dal kesilebilir. Kesmenin
meşruluğu tam olarak bu çıkarıma dayanır — **alt ağaçta çözüm olmadığı kanıtlanmış olmalıdır.**

Kanıt zayıfladığında iki yönde hata yapılır. Ölçüt gereğinden **fazla** keserse çözüm içeren
dallar da gider ve yanıt eksik çıkar. Gereğinden **az** keserse çözümsüz dallar gezilmeye
devam eder; bu tek başına yanlış yanıt vermez, ama yaprakta yapılan sınama da zayıfsa
geçersiz yerleşimler çözüm sayılır.

İki hata biçiminin çıktıdan ayırt edilme olanağı yoktur. Eksik yerleşim listesi de fazla
yerleşim listesi de bir liste olarak dönmeyi sürdürür; hangisinin doğru olduğunu söyleyecek
tek şey, dalları hiç kesmeyen kâhinin ürettiği sayıdır.

#### Budamanın Kestiği Uzay

```python
# Ortak tanimin geri izleme olcumu: ayni arama, budamali ve budamasiz.
class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self, n=1):
        self.adim += n


def vezir(n, budama=True):
    """Budamasiz surum butun yerlesimleri dener; budamali surum catismada durur."""
    s = Sayac()
    cozum = []

    def gez(satir, yer):
        s.say()
        if satir == n:
            cozum.append(tuple(yer))
            return
        for sutun in range(n):
            if budama and any(sutun == y or abs(sutun - y) == satir - i
                              for i, y in enumerate(yer)):
                continue
            yer.append(sutun)
            gez(satir + 1, yer)
            yer.pop()
    gez(0, [])
    if not budama:
        cozum = [c for c in cozum
                 if all(c[i] != c[j] and abs(c[i] - c[j]) != j - i
                        for i in range(n) for j in range(i + 1, n))]
    return {"dugum": s.adim, "cozum": len(cozum)}


for n in (5, 6, 7):
    b, k = vezir(n, True), vezir(n, False)
    print(f"n={n} budamali dugum {b['dugum']:5d} cozum {b['cozum']:3d}"
          f" | budamasiz dugum {k['dugum']:7d} cozum {k['cozum']:3d}"
          f" | oran {k['dugum'] / b['dugum']:7.1f}")
b8 = vezir(8, True)
print(f"n=8 budamali dugum {b8['dugum']:5d} cozum {b8['cozum']:3d}"
      f" | budamasiz {8 ** 8} yerlesim taranirdi (oran {8 ** 8 / b8['dugum']:.1f})")
```

```
n=5 budamali dugum    54 cozum  10 | budamasiz dugum    3906 cozum  10 | oran    72.3
n=6 budamali dugum   153 cozum   4 | budamasiz dugum   55987 cozum   4 | oran   365.9
n=7 budamali dugum   552 cozum  40 | budamasiz dugum  960800 cozum  40 | oran  1740.6
n=8 budamali dugum  2057 cozum  92 | budamasiz 16777216 yerlesim taranirdi (oran 8156.2)
```

Çözüm sütunları üç satırda da aynı: 10, 4, 40. Budama hiçbir çözümü kaçırmıyor, yani
**ayrılan girdi sıfırdır**. Düğüm sütunları ise uçurumu gösteriyor: yedi vezirlik tahtada
**552'ye karşı 960.800 düğüm**, oran **1740,6**. Sekiz vezirlikte budamalı arama 2057
düğümde bitiyor; budamasız arama 16.777.216 yerleşim tarardı ve oran **8156,2** olurdu.

Oran $n$ ile büyüyor: 72,3 sonra 365,9 sonra 1740,6 sonra 8156,2 — her adımda yaklaşık beş
kat. **Budamanın kazancı ölçek arttıkça artıyor.** Ama ikinci bir okuma bunu dengeler:
budamalı düğüm sayısının kendisi de 54, 153, 552, 2057 diye gidiyor — adımların oranı
sırasıyla 2,83, 3,61 ve 3,73. **Budama üstel uzayı kesiyor ama üstelliği yok etmiyor.**
İki katı büyük
bir tahta, budamalı aramada da erişilmez kalır; budama erteler, çözmez.

#### Kesmenin Hangi Kısıttan Geldiği

Vezir probleminin iki kısıtı vardır — aynı sütun ve aynı köşegen — ve budama ikisini birden
kullanıyordu. Kısıtlar ayrı ayrı da uygulanabilir; her biri **kesin olarak çözümsüz** dalları
kestiği için üçü de doğruluğu korur.

```python
# Onceki bloklarin uzerine: Sayac oradan gelir.
def parcali_budama(n, olcut):
    """olcut: yok | sutun | kosegen | ikisi. Hepsinde yaprakta tam dogrulama var."""
    s = Sayac()
    cozum = 0

    def gez(satir, yer):
        nonlocal cozum
        s.say()
        if satir == n:
            if all(yer[i] != yer[j] and abs(yer[i] - yer[j]) != j - i
                   for i in range(n) for j in range(i + 1, n)):
                cozum += 1
            return
        for sutun in range(n):
            if olcut in ("sutun", "ikisi") and sutun in yer:
                continue
            if olcut in ("kosegen", "ikisi") and any(
                    abs(sutun - y) == satir - i for i, y in enumerate(yer)):
                continue
            yer.append(sutun)
            gez(satir + 1, yer)
            yer.pop()
    gez(0, [])
    return {"dugum": s.adim, "cozum": cozum}


print("n=7  olcut      dugum   cozum")
for olcut in ("yok", "sutun", "kosegen", "ikisi"):
    r = parcali_budama(7, olcut)
    print(f"     {olcut:9s} {r['dugum']:7d} {r['cozum']:7d}")
```

```
n=7  olcut      dugum   cozum
     yok        960800      40
     sutun       13700      40
     kosegen     10736      40
     ikisi         552      40
```

Çözüm sütunu dört satırda da **40**; dördü de doğru. Düğüm sütunu ise budamanın gücünün
neye bağlı olduğunu gösteriyor. Yalnız sütun kısıtı 960.800 düğümü 13.700'e indiriyor —
70,1 kat. Yalnız köşegen kısıtı 10.736'ya indiriyor — 89,5 kat. İkisi birlikte **552**,
yani 1740,6 kat. Birleşik kesme, iki tekil kesmenin toplamından çok daha fazlasını
kazandırıyor; çünkü bir kısıtın bıraktığı dalların çoğunu öteki kısıt kesiyor.

Buradan çıkan tasarım kuralı ölçülmüştür: **bir kısıt ne kadar erken kanıtlanabilirse o
kadar çok keser.** Kısıtları yaprakta sınamak bir doğrulamadır, düğümde sınamak bir
budamadır; aradaki fark bu problemde 960.800 ile 552 arasındaki farktır.

#### Budama Ölçütü Yanlış Olduğunda

Aynı arama, üç farklı ölçütle koşturulur. İkisi ön koşulu bozar.

```python
# Onceki blogun uzerine: Sayac ve vezir oradan gelir.
def vezir_kipli(n, kip):
    """kip: dogru | asiri (komsu sutun degerini de keser) | eksik (yalniz onceki satir)"""
    s = Sayac()
    cozum = []

    def catisma(sutun, satir, yer):
        if kip == "eksik":
            i = satir - 1
            return i >= 0 and (sutun == yer[i] or abs(sutun - yer[i]) == 1)
        temel = any(sutun == y or abs(sutun - y) == satir - i
                    for i, y in enumerate(yer))
        if kip == "asiri":                        # "vezirler yan yana sutunda olmasin"
            return temel or any(abs(sutun - y) == 1 for y in yer)
        return temel

    def gez(satir, yer):
        s.say()
        if satir == n:
            cozum.append(tuple(yer))
            return
        for sutun in range(n):
            if catisma(sutun, satir, yer):
                continue
            yer.append(sutun)
            gez(satir + 1, yer)
            yer.pop()
    gez(0, [])
    return {"dugum": s.adim, "cozum": len(cozum)}


print(" n  kahin cozum  dogru budama      asiri budama      eksik budama")
ayrilan = {"dogru": 0, "asiri": 0, "eksik": 0}
for n in (5, 6, 7):
    kahin = vezir(n, False)
    satir = f"{n:2d} {kahin['cozum']:12d}"
    for kip in ("dogru", "asiri", "eksik"):
        r = vezir_kipli(n, kip)
        if r["cozum"] != kahin["cozum"]:
            ayrilan[kip] += 1
        satir += f"  {r['cozum']:4d} ({r['dugum']:5d})"
    print(satir)
print("kahinden ayrilan tahta sayisi (3 uzerinden):", ayrilan)
```

```
 n  kahin cozum  dogru budama      asiri budama      eksik budama
 5           10    10 (   54)     0 (   20)   184 (  306)
 6            4     4 (  153)     0 (   39)  2642 ( 3747)
 7           40    40 (  552)     0 (   82)  45514 (59196)
kahinden ayrilan tahta sayisi (3 uzerinden): {'dogru': 0, 'asiri': 3, 'eksik': 3}
```

Üç sayı yan yana ve bu kez ikisi de kötü. **Doğru budama** üç tahtada da kâhinin çözüm
sayısını veriyor: 10, 4, 40. **Aşırı budama** üçünde de **sıfır çözüm** buluyor —
"vezirler yan yana sütunlarda durmasın" kuralı makul görünür, ama kesin olarak çözümsüz
olmayan dalları kestiği için geriye hiçbir şey bırakmıyor. **Eksik budama** yalnız bir
önceki satıra baktığı için uzak satırlardaki köşegen çatışmalarını göremiyor ve yedi
vezirlik tahtada 40 yerine **45.514** yerleşimi çözüm sayıyor.

Düğüm sütunları burada tuzaktır. Aşırı budama yedi vezirlikte **82 düğümde** bitiyor;
doğru budamanın 552 düğümüne göre 6,7 kat hızlı. Bir ölçüm yalnız düğüm sayarsa aşırı
budama **en iyi yordam** görünür. Eksik budama ise 59.196 düğüm gezerek en yavaşı; yani
**yanlış olmak her zaman hızlı değildir.** Yanlışlığın iki biçimi vardır ve düğüm sayısı
ikisini de ele vermez; ele veren tek şey kâhinin çözüm sayısıdır.

#### Yasaklı Karelerde Dağarcık

Tek bir tahta ailesi bir dağarcık değildir. Ölçüm, altı vezirlik tahtaya üçer yasak kare
eklenerek kırk ayrı örneğe taşınır ve iki tohumla koşturulur.

```python
# Onceki bloklarin uzerine: Sayac ve vezir_kipli oradan gelir.
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


N = 6
YASAK_SAYISI = 3


def tahta_dagarcigi(tohum, adet=40):
    """Her tahtada 3 kare yasak. Yasak kareler tohumdan gelir."""
    r = uretec(tohum)
    return [{"no": i + 1, "yasak": {(r(N), r(N)) for _ in range(YASAK_SAYISI)}}
            for i in range(adet)]


def yasakli_arama(n, yasak, kip, s):
    """kip: kahin (budamasiz, sonda dogrula) | dogru | asiri"""
    cozum = []

    def gecerli(c):
        if any((i, c[i]) in yasak for i in range(n)):
            return False
        return all(c[i] != c[j] and abs(c[i] - c[j]) != j - i
                   for i in range(n) for j in range(i + 1, n))

    def gez(satir, yer):
        s.say()
        if satir == n:
            if kip != "kahin" or gecerli(tuple(yer)):
                cozum.append(tuple(yer))
            return
        for sutun in range(n):
            if kip != "kahin":
                if (satir, sutun) in yasak:
                    continue
                if any(sutun == y or abs(sutun - y) == satir - i
                       for i, y in enumerate(yer)):
                    continue
                if kip == "asiri" and any(abs(sutun - y) == 1 for y in yer):
                    continue
            yer.append(sutun)
            gez(satir + 1, yer)
            yer.pop()
    gez(0, [])
    return len(cozum)


for ad, tohum in (("birinci dagarcik (20260218)", 20260218),
                  ("ikinci  dagarcik (20260219)", 20260219)):
    tahtalar = tahta_dagarcigi(tohum)
    sh, sd, sa = Sayac(), Sayac(), Sayac()
    ay_dogru, ay_asiri, kayip = 0, 0, 0
    for t in tahtalar:
        h = yasakli_arama(N, t["yasak"], "kahin", sh)
        d = yasakli_arama(N, t["yasak"], "dogru", sd)
        a = yasakli_arama(N, t["yasak"], "asiri", sa)
        ay_dogru += h != d
        ay_asiri += h != a
        kayip += h - a
    print(ad)
    print("  kahin dugum", sh.adim, "| dogru budama dugum", sd.adim,
          "| oran", round(sh.adim / sd.adim, 2))
    print("  ayrilan girdi: dogru budama", ay_dogru, "/ 40 | asiri budama",
          ay_asiri, "/ 40 | kaybolan cozum", kayip)
```

```
birinci dagarcik (20260218)
  kahin dugum 2239480 | dogru budama dugum 4736 | oran 472.86
  ayrilan girdi: dogru budama 0 / 40 | asiri budama 40 / 40 | kaybolan cozum 92
ikinci  dagarcik (20260219)
  kahin dugum 2239480 | dogru budama dugum 4452 | oran 503.03
  ayrilan girdi: dogru budama 0 / 40 | asiri budama 40 / 40 | kaybolan cozum 90
```

Kâhinin düğüm sayısı iki dağarcıkta **birebir aynı**: 2.239.480. Bu beklenen sonuçtur —
budamasız arama yasak karelere bakmadan bütün yerleşimleri üretir, yani düğüm sayısı
tahtadan bağımsızdır. Doğru budamanın düğümü 4736 ile 4452 arasında değişiyor, çünkü yasak
kareler dalları erkenden kesiyor; oran 472,86 ve 503,03.

Ayrılan girdi sayıları iki dağarcıkta da aynı yönü gösteriyor: doğru budama **0/40**, aşırı
budama **40/40**. Kaybolan çözüm sayısı 92 ile 90; aradaki iki çözümlük fark yasak karelerin
yerleşiminden gelir ve ölçünün anlamlı saydığı eşiğin altındadır. Okuma iki dağarcıkta da
değişmiyor: **doğru budama kırk tahtanın kırkında kâhinle aynı, aşırı budama kırkında da
farklı.**

#### Özet

- Geri izleme, kısmi çözüm ağacında bir dalın altında çözüm olmadığı **kanıtlandığında** o
  dalı keser; kesmenin meşruluğu bu kanıta bağlıdır.
- Doğru budama yedi vezirlik tahtada **552**, budamasız arama **960.800** düğüm geziyor
  (oran 1740,6) ve çözüm sayıları **aynı** kalıyor: 10, 4, 40.
- Oran $n$ ile yaklaşık beş kat büyüyor (72,3 · 365,9 · 1740,6 · 8156,2), ama budamalı düğüm
  sayısı da adım başına 2,83'ten 3,73'e çıkan bir katsayıyla büyüyor: **budama üstelliği
  kesmez, erteler.**
- Aşırı budama yedi vezirlikte 82 düğümde bitip **sıfır çözüm** buluyor; yalnız düğüm sayan
  bir ölçümde en iyi yordam görünür.
- Eksik budama 59.196 düğüm gezip 40 yerine **45.514** yerleşimi çözüm sayıyor; yanlışlık
  her zaman hızlı değildir.
- Kırk yasaklı tahtada doğru budama **0/40**, aşırı budama **40/40** ayrılıyor; ikinci
  dağarcıkta aynı sonuç, kaybolan çözüm 92 yerine 90.

#### Sonraki Adım

Şimdiye kadarki bütün yordamlar belirlenimciydi: aynı girdi her zaman aynı adımları ve aynı
yanıtı veriyordu. Son tasarım yaklaşımı bu güvenceyi gevşetir ve karşılığında bir şey ister
— bazen adım sayısının, bazen de **yanıtın kendisinin** rastgele olmasını. Sonraki ders iki
tür rastgeleleştirmeyi aynı dağarcıkta ölçer ve tek bir soruyu yanıtlar: rastgele bir
yordamın kaç girdide yanıldığı, tohum değiştiğinde değişiyor mu.

### Rastgeleleştirilmiş Algoritmalar

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/rastgelelestirilmis-algoritmalar

Rastgeleliğin iki ayrı yerde kullanılabileceği ve ikisinin ayrı ölçüldüğü: doğrulamasız örnekleme çoğunluğu olmayan 40 dizinin 40'ında yanlış değer döndürürken doğrulamalı örnekleme aynı dizilerde hiç yanılmıyor ve on altı vezirlik tahtada rastgele sütun sırası 10.053 düğümlük belirlenimci aramayı ortalama 360 düğüme indiriyor.

Buraya kadarki bütün yordamlar belirlenimciydi: aynı girdi her zaman aynı adımları ve aynı
yanıtı veriyordu. Son tasarım yaklaşımı bu güvenceyi gevşetir. Yordamın içine bir rastgele
seçim konur ve karşılığında bir şey umulur — ya arama alanının kötü bölgelerine takılmamak,
ya da tam sayım yapmadan bir yanıta ulaşmak.

Gevşetilen güvencenin **hangisi** olduğu belirleyicidir ve iki seçenek vardır. Birincisinde
yanıt kesin kalır, yalnız **adım sayısı** rastgeleleşir; yordam her zaman doğru yanıtı
verir, ne kadar adımda vereceği belli değildir. İkincisinde adım sayısı sınırlı kalır,
**yanıt** olasılıklı olur; yordam hızlıdır ama bazen yanılır. Bu ders ikisini de aynı ölçüyle
sayar ve rastgeleliği bu kursun kuralına göre modeller: **belirlenimci bir üreteçle**.

- **TY47.** Rastgelelik belirlenimci bir üreteçle modellenir; standart kitaplığın rastgele
  sayı üreteci kullanılmaz. Her koşum tohumuyla yeniden üretilebilir.
- **TY48.** İki tohum kullanılır: **20260218** ve **20260219**. Rastgele bir yordamın sonucu
  **tek tohumla bildirilmez**.
- **TY49.** Ölçülen ilk problem: 15 değerli bir dizide **çoğunluk elemanı** — uzunluğun
  yarısından çoğunu kaplayan değer. Yoksa doğru yanıt "yok"tur.
- **TY50.** Kâhin her değeri tek tek sayar; hiçbir örnekleme yapmaz.
- **TY51.** Dağarcık iki öbektir: çoğunluğu **olan** 40 dizi ve **olmayan** 40 dizi.
- **TY52.** Bir **adım**, bir örnek çekmek ya da bir değeri karşılaştırmaktır.
- **TY53.** Kalıbın **ön koşulu**: çoğunluk elemanının var olması. İkinci öbek bu ön koşulu
  bozar.
- **TY54.** Ölçülen ikinci problem: vezir yerleşiminde **ilk çözümü** bulmak; sütun sırası
  rastgeleleştirilir.
- **TY55.** Rastgele adım sayısı **en az, ortalama ve en çok** olarak bildirilir; yirmi
  koşum alınır ve tek bir sayı yeterli sayılmaz.
- **TY56.** **Beklenen başarım adımın ortalamasıdır**, süre değildir.

#### Rastgelelik Nasıl Modellenir

Bu kursta rastgelelik bir kütüphane çağrısı değil, tohumlu bir üreteçtir. Nedeni ölçmedir:
bir rastgele yordamın ayrılan girdi sayısı ancak koşum yeniden üretilebilirse bildirilebilir.

```python
# Rastgelelik belirlenimci uretecle modellenir; standart kitapligin ureteci kullanilmaz.
UZUNLUK = 15


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self, n=1):
        self.adim += n


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def cogunluklu(tohum, adet=40, cogunluk=True):
    """cogunluk=True -> bir deger uzunlugun yarisindan cok yer tutar."""
    r = uretec(tohum)
    kume = []
    for i in range(adet):
        baskin = r(6)
        pay = 8 + r(3) if cogunluk else 5 + r(3)      # 8..10 ya da 5..7
        dizi = [baskin] * pay + [6 + r(6) for _ in range(UZUNLUK - pay)]
        for j in range(len(dizi) - 1, 0, -1):          # belirlenimci karistirma
            k = r(j + 1)
            dizi[j], dizi[k] = dizi[k], dizi[j]
        kume.append({"no": i + 1, "dizi": dizi})
    return kume


def kahin_cogunluk(dizi, s):
    """Her degeri tek tek sayar. Hakem budur."""
    for i in range(len(dizi)):
        sayi = 0
        for j in range(len(dizi)):
            s.say()
            if dizi[j] == dizi[i]:
                sayi += 1
        if sayi * 2 > len(dizi):
            return dizi[i]
    return None


VAR = cogunluklu(20260218)
YOK = cogunluklu(20260218, cogunluk=False)
s1, s2 = Sayac(), Sayac()
v = sum(1 for k in VAR if kahin_cogunluk(k["dizi"], s1) is not None)
y = sum(1 for k in YOK if kahin_cogunluk(k["dizi"], s2) is not None)
print("cogunluklu dagarcik: 40 dizi x", UZUNLUK, "deger | cogunlugu olan:", v)
print("cogunluksuz dagarcik:", 40, "dizi | cogunlugu olan:", y)
print("kahin adimi:", s1.adim, "(cogunluklu) |", s2.adim, "(cogunluksuz)")
```

```
cogunluklu dagarcik: 40 dizi x 15 deger | cogunlugu olan: 40
cogunluksuz dagarcik: 40 dizi | cogunlugu olan: 0
kahin adimi: 780 (cogunluklu) | 9000 (cogunluksuz)
```

İki öbek de kurulmuş: birinde 40 dizinin 40'ında çoğunluk var, ötekinde hiçbirinde yok.
Kâhinin adım sayısındaki fark da anlamlıdır — **780'e karşı 9000**. Çoğunluk varsa kâhin
genellikle ilk denediği değerde bulur ve durur; çoğunluk yoksa 15 değerin hepsini sonuna
kadar saymak zorunda kalır, yani dizi başına tam 225 adım. **Bir yordamın en pahalı hâli,
yanıtın "yok" olduğu hâldir.**

#### Olasılıklı Yanıt ve Doğrulamanın Rolü

Çoğunluk elemanı için rastgele bir yordam tek cümleyle kurulur: rastgele bir konum seç, oradaki
değeri aday say. Çoğunluk varsa aday, yarıdan çok olasılıkla doğrudur. Aşağıdaki iki
değişke bu fikri paylaşır ve tek bir noktada ayrılır — biri adayı **doğrular**, öteki
doğrulamaz.

```python
# Onceki blogun uzerine: Sayac, uretec, cogunluklu, kahin_cogunluk, VAR oradan gelir.
def orneklemeli_cogunluk(dizi, deneme, r, s, dogrulama=True):
    """deneme kadar rastgele konum secer. dogrulama=True ise adayi sayarak sinar."""
    if dogrulama:
        for _ in range(deneme):
            aday = dizi[r(len(dizi))]
            s.say()
            sayi = 0
            for x in dizi:
                s.say()
                if x == aday:
                    sayi += 1
            if sayi * 2 > len(dizi):
                return aday
        return None
    ornek = []                                    # dogrulama yok: ornegin en siki
    for _ in range(deneme):
        s.say()
        ornek.append(dizi[r(len(dizi))])
    en, en_sik = None, -1
    for a in ornek:
        c = ornek.count(a)
        if c > en_sik:
            en, en_sik = a, c
    return en


def olc(kume, deneme, tohum, dogrulama):
    r = uretec(tohum)
    ayrilan, sk, sh = 0, Sayac(), Sayac()
    for k in kume:
        a = orneklemeli_cogunluk(k["dizi"], deneme, r, sk, dogrulama)
        if a != kahin_cogunluk(k["dizi"], sh):
            ayrilan += 1
    return {"ayrilan": ayrilan, "kalip_adim": sk.adim, "kahin_adim": sh.adim}


print("deneme  tohum      dogrulamali ayrilan/adim   dogrulamasiz ayrilan/adim")
for deneme in (1, 3, 5):
    for tohum in (20260218, 20260219):
        a = olc(VAR, deneme, tohum, True)
        b = olc(VAR, deneme, tohum, False)
        print(f"{deneme:6d}  {tohum}  {a['ayrilan']:12d} / {a['kalip_adim']:5d}"
              f" {b['ayrilan']:16d} / {b['kalip_adim']:4d}")
```

```
deneme  tohum      dogrulamali ayrilan/adim   dogrulamasiz ayrilan/adim
     1  20260218            10 /   640               10 /   40
     1  20260219            17 /   640               17 /   40
     3  20260218             0 /   944                6 /  120
     3  20260219             1 /   928               13 /  120
     5  20260218             0 /   944                6 /  200
     5  20260219             0 /  1056                7 /  200
```

Üç sayı yan yana. **Kâhin** 780 adım harcıyor. **Doğrulamalı örnekleme** beş denemede
944 ile 1056 adım harcıyor ve iki tohumda da **0 girdide** ayrılıyor. **Doğrulamasız
örnekleme** 200 adım harcıyor — kâhinden neredeyse dört kat az — ve iki tohumda **6 ve 7**
girdide ayrılıyor.

Deneme sayısının etkisi iki sütunda farklı. Doğrulamalı sütunda ayrılan girdi 10 ve 17'den
üç denemede 0 ile 1'e, beş denemede 0 ile 0'a iniyor: her yeni deneme, önceki denemelerin
hepsinin başarısız olma olasılığını yarıdan çok azaltır. Doğrulamasız sütunda ise ayrılan
girdi 10 ve 17'den 6 ve 7'ye iniyor ve **orada kalıyor**; örneklem büyüdükçe iyileşiyor ama
sıfıra ulaşmıyor.

Aradaki asıl fark sayının büyüklüğü değil **hatanın yönüdür**. Doğrulamalı sürüm yanlış bir
değer **döndüremez**; adayı saymadan kabul etmediği için ya doğru değeri verir ya da "yok"
der. Bir denemede gördüğümüz 10 ayrılma, on girdide yanlış değer verdiği anlamına gelmez —
on girdide **bulamadığı** anlamına gelir. Doğrulamasız sürüm ise gerçekten yanlış değer
döndürür. **Doğrulama adımı, hatayı iki yönlüden tek yönlüye çevirir** ve bu, 200 adıma
karşı 944 adımın satın aldığı şeydir.

#### Ön koşul Bozulduğunda

Bu ayrımın önemi, çoğunluk elemanı **bulunmadığında** görünür hâle gelir.

```python
# Onceki bloklarin uzerine: olc, VAR ve YOK oradan gelir.
print("on_kosul  tohum      dogrulamali ayrilan  dogrulamasiz ayrilan")
for ad, kume in (("saglaniyor", VAR), ("bozuk     ", YOK)):
    for tohum in (20260218, 20260219):
        a = olc(kume, 5, tohum, True)
        b = olc(kume, 5, tohum, False)
        print(f"{ad}  {tohum}  {a['ayrilan']:16d}  {b['ayrilan']:20d}")
```

```
on_kosul  tohum      dogrulamali ayrilan  dogrulamasiz ayrilan
saglaniyor  20260218                 0                     6
saglaniyor  20260219                 0                     7
bozuk       20260218                 0                    40
bozuk       20260219                 0                    40
```

Çoğunluğu olmayan 40 dizide doğrulamasız örnekleme **40 girdinin 40'ında** kâhinden
ayrılıyor; doğrulamalı örnekleme **hiçbirinde** ayrılmıyor. Sonuç iki tohumda da aynı, yani
dağarcığa bağlı değil.

Nedeni açıktır ve kursun genel biçimine uyar. Doğrulamasız yordam, "çoğunluk vardır"
varsayımını kodun içine gömmüştür: örneklemin en sık değerini döndürür ve o değerin gerçekten
çoğunluk olup olmadığını hiç sormaz. Ön koşul bozulduğunda yavaşlamaz — yine 200 adımda
biter — ama her seferinde var olmayan bir yanıtı bildirir. Doğrulama, ön koşulu kodun içinden
çıkarıp **çalışma anında sınanan bir koşula** dönüştürür; bedeli dizi başına bir tam
tarama, kazancı ön koşul bozulduğunda sessiz kalmamaktır.

#### Kesin Yanıt, Rastgele Adım

Rastgeleliğin ikinci kullanımı yanıta hiç dokunmaz. Önceki dersin budamalı vezir araması
sütunları her zaman soldan sağa deniyordu; sıra rastgeleleştirildiğinde bulunan çözüm yine
geçerlidir, değişen tek şey ona kaç düğümde ulaşıldığıdır.

```python
# Onceki bloklarin uzerine: Sayac ve uretec oradan gelir.
def vezir_ilk_cozum(n, r=None):
    """Ilk cozumde durur. Sutun sirasi rastgele ise adim degisir, yanit degismez."""
    s = Sayac()
    bulunan = None

    def gez(satir, yer):
        nonlocal bulunan
        s.say()
        if satir == n:
            bulunan = tuple(yer)
            return
        sutunlar = list(range(n))
        if r:
            for j in range(n - 1, 0, -1):
                k = r(j + 1)
                sutunlar[j], sutunlar[k] = sutunlar[k], sutunlar[j]
        for sutun in sutunlar:
            if any(sutun == y or abs(sutun - y) == satir - i
                   for i, y in enumerate(yer)):
                continue
            yer.append(sutun)
            gez(satir + 1, yer)
            yer.pop()
            if bulunan is not None:
                return
    gez(0, [])
    return {"dugum": s.adim, "gecerli": bulunan is not None}


print(" n  belirlenimci  tohum      en az  ortalama  en cok  gecerli cozum")
for n in (8, 12, 16):
    b = vezir_ilk_cozum(n)
    for tohum in (20260218, 20260219):
        r = uretec(tohum)
        kosum = [vezir_ilk_cozum(n, r) for _ in range(20)]
        d = [k["dugum"] for k in kosum]
        print(f"{n:2d} {b['dugum']:13d}  {tohum}  {min(d):6d} {sum(d) / 20:9.1f}"
              f" {max(d):7d}  {sum(k['gecerli'] for k in kosum):9d} / 20")
```

```
 n  belirlenimci  tohum      en az  ortalama  en cok  gecerli cozum
 8           114  20260218       9      35.7      89         20 / 20
 8           114  20260219      16      35.0     101         20 / 20
12           262  20260218      15      98.7     491         20 / 20
12           262  20260219      17      90.1     356         20 / 20
16         10053  20260218      19     359.6    3379         20 / 20
16         10053  20260219      19     355.8    1237         20 / 20
```

Son sütun bütün satırlarda **20 / 20**: kırk koşumun hepsi geçerli bir yerleşim buluyor.
Yanıt rastgele değildir; rastgele olan yalnız adımdır. On altı vezirlik tahtada belirlenimci
sıra **10.053 düğüm** geziyor, rastgele sıra ortalama **359,6 ve 355,8 düğüm** — yaklaşık
28 kat az. İki tohumun ortalamaları birbirine çok yakın, yani **beklenen başarım dağarcığa
bağlı değil**.

Uç değerler bu tabloyu tamamlar ve tek bir ortalamanın neden yeterli olmadığını gösterir.
En az 19 düğüm, en çok 3379; aradaki fark 178 kat ve iki tohumun en çok değerleri de
birbirinden uzak, 3379 ile 1237. Rastgele bir yordamın başarımı tek bir sayıyla bildirilemez;
**ortalama bir güvence değil, bir beklentidir.** Belirlenimci sıranın 10.053 düğümü ise bir
kaza değildir: sabit sıra, çözüm içermeyen aynı bölgeyi her koşumda aynı derinlikte gezer ve
kötü bir sıranın bedeli hiçbir koşumda azalmaz. Rastgeleleştirmenin kazandırdığı şey,
**aynı kötü sırayı iki kez denememektir**.

#### Uzun Kuyruğu Kesmek

3379 düğümlük uç değer, rastgele yordamın asıl sorunudur: koşumların çoğu kısa, birkaçı çok
uzun. Bu kuyruk bir bütçeyle kesilebilir — arama belirli bir düğüm sayısını aşarsa
bırakılır ve yeni bir rastgele sırayla baştan başlanır.

```python
# Onceki bloklarin uzerine: Sayac, uretec ve vezir_ilk_cozum oradan gelir.
def butceli_arama(n, r, butce):
    """Butce asilirsa vazgecer. Vazgecmek yanlis yanit degildir - yanit yoktur."""
    s = Sayac()
    bulunan = None

    def gez(satir, yer):
        nonlocal bulunan
        s.say()
        if s.adim > butce or bulunan is not None:
            return
        if satir == n:
            bulunan = tuple(yer)
            return
        sutunlar = list(range(n))
        for j in range(n - 1, 0, -1):
            k = r(j + 1)
            sutunlar[j], sutunlar[k] = sutunlar[k], sutunlar[j]
        for sutun in sutunlar:
            if any(sutun == y or abs(sutun - y) == satir - i
                   for i, y in enumerate(yer)):
                continue
            yer.append(sutun)
            gez(satir + 1, yer)
            yer.pop()
            if bulunan is not None or s.adim > butce:
                return
    gez(0, [])
    return s.adim, bulunan


print("butce  tohum      yeniden baslatma  toplam dugum  en cok  gecerli")
for butce in (100, 400, 100000):
    for tohum in (20260218, 20260219):
        r = uretec(tohum)
        toplam, deneme, en_cok, gecerli = 0, 0, 0, 0
        for _ in range(20):
            kosum = 0
            while True:
                adim, sonuc = butceli_arama(16, r, butce)
                kosum += adim
                deneme += 1
                if sonuc is not None:
                    gecerli += 1
                    break
            toplam += kosum
            en_cok = max(en_cok, kosum)
        print(f"{butce:6d}  {tohum}  {deneme:16d} {toplam / 20:13.1f} {en_cok:7d}"
              f" {gecerli:8d} / 20")
```

```
butce  tohum      yeniden baslatma  toplam dugum  en cok  gecerli
   100  20260218                54         225.8     940       20 / 20
   100  20260219                57         240.8    1229       20 / 20
   400  20260218                27         228.8    1731       20 / 20
   400  20260219                29         276.1     835       20 / 20
100000  20260218                20         359.6    3379       20 / 20
100000  20260219                20         355.8    1237       20 / 20
```

Yüz düğümlük bütçeyle yirmi çözüm için **54 ve 57 deneme** yapılıyor, yani çözüm başına
2,70 ile 2,85 arası baştan başlama. Buna karşılık toplam düğüm ortalaması 359,6'dan **225,8'e**,
en kötü koşum 3379'dan **940'a** iniyor. Son satırlar bütçesiz koşumu tekrar veriyor ve
farkı doğruluyor.

Yanıt yine bozulmuyor: son sütun altı satırda da 20 / 20. Vazgeçmek yanlış yanıt vermek
değildir — bütçe aşıldığında yordam bir şey **söylemez** ve söylemediği için kâhinden
ayrılmaz. Kursun ilk dersindeki ayrım burada son biçimini alır: **bilmediğini bildiren bir
yordam ölçülebilir, bilmediğini uyduran bir yordam ölçülemez.**

#### Özet

- Rastgeleleştirme iki ayrı güvenceyi gevşetebilir: yanıt kesin kalıp adım rastgeleleşebilir,
  ya da adım sınırlı kalıp yanıt olasılıklı olabilir. İkisi ayrı ölçülür.
- Çoğunluk elemanında kâhin 780 adım harcıyor; doğrulamalı örnekleme beş denemede 944 ile
  1056 adım ve **0 ayrılan girdi**, doğrulamasız örnekleme 200 adım ve **6 ile 7 ayrılan
  girdi** veriyor.
- Doğrulama adımı **hatayı tek yönlü yapar**: doğrulamalı sürüm yanlış değer döndüremez,
  yalnız "yok" diyebilir; doğrulamasız sürüm gerçekten yanlış değer döndürür.
- Ön koşul bozulduğunda doğrulamasız örnekleme **40 girdinin 40'ında** ayrılıyor,
  doğrulamalı örnekleme hiçbirinde; sonuç iki tohumda da aynı.
- On altı vezirlik tahtada belirlenimci sıra 10.053 düğüm, rastgele sıra ortalama 359,6 ve
  355,8 düğüm geziyor ve kırk koşumun kırkı da geçerli çözüm buluyor; uç değerler 19 ile
  3379 arasında, yani **ortalama bir güvence değil beklentidir**.
- Yüz düğümlük bütçeyle yeniden başlatma ortalamayı 225,8'e, en kötü koşumu 3379'dan
  **940'a** indiriyor ve yanıt yine 20 / 20 geçerli kalıyor.

#### Sonraki Adım

Bu konu altı tasarım yaklaşımını aynı ölçüyle geçirdi ve hepsinde aynı şey çıktı: bir
kalıbın değeri kazandırdığı adımda değil, ön koşulu bozulduğunda verdiği yanlış yanıtta
görünüyor. Sonraki konu bu ölçüyü **problem kalıplarına** taşır. Orada kalıplar daha dar ve
daha tanınabilirdir — sıralı dizide karşılıklı tarama, bitişik alt dizide artımlı hesap,
ızgarada bağlı bileşen arama — ve her birinin ön koşulu tek bir cümleyle yazılabilir. İlk
ders sıralı girdi isteyen iki işaretçi kalıbıyla açılır ve sorusu buradakiyle aynıdır:
sıralama bozulduğunda kaç girdide yanlış yanıt çıkıyor.

## Problem Çözme Kalıpları

İki işaretçi, kayan pencere, hızlı ve yavaş işaretçi, aralık birleştirme, döngüsel yerleştirme, iki yığın, seçim ve ızgara gezinmesi kalıplarının ön koşullarıyla ve ön koşul bozulduğunda verdikleri yanlış yanıtlarla ölçülmesi.

### İki İşaretçi

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/iki-isaretci

Sıralı dizide karşılıklı tarama; ön koşul bozulduğunda ortaya çıkan 25 yanlış yanıt ve ön koşulu sağlamanın adım bedeli.

Önceki konu bir algoritmanın doğruluğunun **olasılıkla** ifade edilebileceğini gösterdi:
beklenen başarım bir dağılımdır, tek koşum bir örnektir. Bu konu farklı bir belirsizlik
kaynağıyla açılıyor ve bu kaynak olasılıksal değil **yapısaldır**. Bir problem çözme kalıbı
seçmek bir hızlanma satın almak değil, bir **ön koşulu kabul etmektir**.

Ön koşul sağlandığı sürece kalıp hem doğru hem ucuzdur. Sağlanmadığında kalıp durmaz,
uyarmaz, yavaşlamaz — **yanlış yanıt verir** ve yanlışlığı çıktısına bakarak anlaşılmaz.
Bu yüzden konunun sekiz dersinin sekizi de aynı çerçeveyi kurar: bir **kâhin** (kaba
kuvvet, her zaman doğru, her zaman pahalı), bir **kalıp** ve ikisinin **ayrıldığı girdi
sayısı**. İlk kalıp iki işaretçidir; ön koşulu tek cümledir: **dizi sıralı olmalı**.

#### Problem, Kâhin ve Kalıp

Problem şudur: bir dizide **toplamı hedefe eşit iki ayrı konum** var mı. Kâhin bütün
çiftleri dener; n değerli dizide $n(n-1)/2$ çift vardır ve kâhin gerektiğinde hepsine
bakar. Kalıp iki işaretçiyi dizinin iki ucuna koyar, toplam hedeften küçükse soldakini
sağa, büyükse sağdakini sola kaydırır.

**PK1.** Ölçü **adımdır**, süre değildir. Sayaç her karşılaştırmayı bir adım sayar ve
gerçek zaman hiçbir yerde ölçülmez.
**PK2.** Dağarcık belirlenimci bir üreteçten gelir; tohum `20260218`. Aynı tohum aynı 40
diziyi verir.
**PK3.** Her dizi 12 değer taşır, değerler −9 ile 20 arasındadır.
**PK4.** Kâhin kaba kuvvettir ve **her zaman doğru** sayılır. Kalıbın doğruluğu ancak
kâhinle karşılaştırılarak iddia edilir.
**PK5.** Ön koşulu sağlayan öbek, aynı 40 dizinin **sıralanmış** biçimidir; başka bir
üreteçten gelmez. İki öbeğin tek farkı sıradır.

```python
TOHUM, UZUNLUK, DAGARCIK = 20260218, 12, 40


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum=TOHUM, n=DAGARCIK, uzunluk=UZUNLUK):
    r = uretec(tohum)
    return [{"no": i + 1, "dizi": [r(30) - 9 for _ in range(uzunluk)]}
            for i in range(n)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self, n=1):
        self.adim += n


def kahin_ciftler(dizi, hedef, s):
    """Butun ciftleri dener. Her zaman dogru, her zaman pahali."""
    for i in range(len(dizi)):
        for j in range(i + 1, len(dizi)):
            s.say()
            if dizi[i] + dizi[j] == hedef:
                return True
    return False


def kalip_iki_isaretci(dizi, hedef, s):
    """ONKOSUL: dizi sirali olmali."""
    sol, sag = 0, len(dizi) - 1
    while sol < sag:
        s.say()
        t = dizi[sol] + dizi[sag]
        if t == hedef:
            return True
        if t < hedef:
            sol += 1
        else:
            sag -= 1
    return False


def olc(kume, hedef):
    ayrilan, ak, ah = [], 0, 0
    for k in kume:
        s1, s2 = Sayac(), Sayac()
        a = kalip_iki_isaretci(k["dizi"], hedef, s1)
        b = kahin_ciftler(k["dizi"], hedef, s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        if a != b:
            ayrilan.append(k["no"])
    return {"ayrilan": len(ayrilan), "ilk_ayrilan": ayrilan[:6],
            "kalip_adim": ak, "kahin_adim": ah, "oran": round(ah / ak, 2)}


K = dagarcik()
S = [dict(k, dizi=sorted(k["dizi"])) for k in K]
print("dagarcik:", len(K), "dizi x", UZUNLUK, "deger | kendiliginden sirali:",
      sum(1 for k in K if k["dizi"] == sorted(k["dizi"])))
for ad, kume in (("on_kosul saglaniyor", S), ("on_kosul bozuk    ", K)):
    print(f"  {ad}", olc(kume, 11))
```

```
dagarcik: 40 dizi x 12 deger | kendiliginden sirali: 0
  on_kosul saglaniyor {'ayrilan': 0, 'ilk_ayrilan': [], 'kalip_adim': 154, 'kahin_adim': 972, 'oran': 6.31}
  on_kosul bozuk     {'ayrilan': 25, 'ilk_ayrilan': [1, 2, 3, 6, 8, 9], 'kalip_adim': 372, 'kahin_adim': 866, 'oran': 2.33}
```

Üç sayı yan yana duruyor. Ön koşul sağlandığında kalıp **40 girdinin 40'ında** kâhinle aynı
yanıtı veriyor ve **154** adım harcıyor; kâhin **972** adım harcıyor, oran **6,31**.
Ön koşul bozulduğunda ayrılan girdi **25** oluyor ve oran **2,33'e** düşüyor.

İkinci satırın en önemli tarafı ayrılan girdi sayısı değil, ikisinin birlikte olmasıdır.
**Yanlışlık ucuz da değildir:** kalıbın adımı 154'ten 372'ye çıkıyor, çünkü sırasız dizide
işaretçiler doğru çifti bulamadan uçlarda buluşuyor ve kalıp erken çıkamıyor. Hızlanma
6,31 kattan 2,33 kata düşerken doğruluk da gidiyor.

#### Ayrılan Girdide Ne Oluyor

Ayrılmanın nedeni tek bir yerdedir. Kalıp, "toplam küçükse soldakini büyüt" kuralını
uygularken **soldaki değerin sağa doğru büyüdüğünü** varsayar. Sıralı olmayan dizide bu
varsayım yanlıştır; işaretçi bir kez yanlış yöne kaydırıldığında atlanan konumlara bir daha
dönülmez.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum):
    r = uretec(tohum)
    return [[r(30) - 9 for _ in range(12)] for _ in range(40)]


def kahin_ciftler(dizi, hedef):
    for i in range(len(dizi)):
        for j in range(i + 1, len(dizi)):
            if dizi[i] + dizi[j] == hedef:
                return True, (dizi[i], dizi[j])
    return False, None


def kalip_iki_isaretci(dizi, hedef):
    sol, sag = 0, len(dizi) - 1
    while sol < sag:
        t = dizi[sol] + dizi[sag]
        if t == hedef:
            return True
        if t < hedef:
            sol += 1
        else:
            sag -= 1
    return False


ilk = dagarcik(20260218)[0]
print("girdi 1  :", ilk)
print("  kalip  :", kalip_iki_isaretci(ilk, 11))
print("  kahin  :", kahin_ciftler(ilk, 11))
print("  sirali :", sorted(ilk), "-> kalip", kalip_iki_isaretci(sorted(ilk), 11))
print()
print("tohum      hedef  on_kosul     ayrilan/40")
for tohum in (20260218, 20260219):
    for hedef in (11, 25):
        K = dagarcik(tohum)
        for ad, kume in (("saglaniyor", [sorted(d) for d in K]), ("bozuk     ", K)):
            ayrilan = sum(1 for d in kume
                          if kalip_iki_isaretci(d, hedef) != kahin_ciftler(d, hedef)[0])
            print(f"{tohum}  {hedef:5d}  {ad}  {ayrilan:8d}"
                  f"    oran {ayrilan / 40:.4f}")
```

```
girdi 1  : [-8, -5, 2, -1, 2, 5, 4, 1, 16, 17, 6, -1]
  kalip  : False
  kahin  : (True, (-5, 16))
  sirali : [-8, -5, -1, -1, 1, 2, 2, 4, 5, 6, 16, 17] -> kalip True

tohum      hedef  on_kosul     ayrilan/40
20260218     11  saglaniyor         0    oran 0.0000
20260218     11  bozuk             25    oran 0.6250
20260218     25  saglaniyor         0    oran 0.0000
20260218     25  bozuk             22    oran 0.5500
20260219     11  saglaniyor         0    oran 0.0000
20260219     11  bozuk             24    oran 0.6000
20260219     25  saglaniyor         0    oran 0.0000
20260219     25  bozuk             16    oran 0.4000
```

Birinci girdide kâhin `(-5, 16)` çiftini buluyor; kalıp `False` döndürüyor. Aynı dizi
sıralandığında kalıp da `True` döndürüyor. **Girdi değişmedi, yalnız sırası değişti** —
kalıbın yanıtını değiştiren şey verinin içeriği değil, ön koşulun sağlanıp sağlanmadığıdır.

**PK6.** Ayrılan girdi sayısı 40 üzerindendir. 40 girdide 1 ayrılma 0,0250'dir; 1 girdilik
fark **ölçülmemiş sayılır**, 3 ve üzeri anlamlıdır.
**PK7.** İkinci dağarcık `20260219` tohumundan gelir ve yalnız oranın **büyüklük düzenini**
sınamak için kullanılır.

İkinci dağarcıkta hedef 11 için ayrılan girdi **24**, birincide **25**. Hedef 25 için
**16** ve **22**. Dört ölçümün dördünde de oran 0,40 ile 0,63 arasında, yani **aynı
büyüklük düzeninde**; sonuç dağarcığa bağlı değildir. Ön koşulun sağlandığı dört satırda
ayrılan girdi **sıfırdır** ve bu da dağarcıktan bağımsızdır.

#### Ön koşulu Sağlamanın Bedeli

Buraya kadarki ölçüm bir soruyu açıkta bırakıyor: dizi sıralı değilse **sıralanabilir**.
O hâlde kalıp yine kullanılabilir. Ama sıralamanın kendisi bir adım harcar ve bu adım
kalıbın hanesine yazılmalıdır.

**PK8.** Ön koşulu sağlamanın bedeli ölçülürken sıralama **karşılaştırmalı** bir yordamla
yapılır ve karşılaştırmaları adım olarak sayılır. Sıralama yordamlarının kendisi
Algoritmalar kursunda ölçüldü; burada tekrarlanmaz, yalnız adımı sayılır.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum=20260218):
    r = uretec(tohum)
    return [[r(30) - 9 for _ in range(12)] for _ in range(40)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_ciftler(dizi, hedef, s):
    for i in range(len(dizi)):
        for j in range(i + 1, len(dizi)):
            s.say()
            if dizi[i] + dizi[j] == hedef:
                return True
    return False


def kalip_iki_isaretci(dizi, hedef, s):
    sol, sag = 0, len(dizi) - 1
    while sol < sag:
        s.say()
        t = dizi[sol] + dizi[sag]
        if t == hedef:
            return True
        if t < hedef:
            sol += 1
        else:
            sag -= 1
    return False


def sirala_sayarak(dizi, s):
    """Onkosulu saglamanin bedeli. Karsilastirmalar adim olarak sayilir."""
    a = list(dizi)
    for i in range(1, len(a)):
        j = i
        while j > 0:
            s.say()
            if a[j - 1] <= a[j]:
                break
            a[j - 1], a[j] = a[j], a[j - 1]
            j -= 1
    return a


print("hedef  dogru  yalniz kalip  siralama dahil  kahin   oran")
for hedef in (11, 25):
    yalniz, dahil, kahin, dogru = 0, 0, 0, 0
    for dizi in dagarcik():
        sa, sk, sh = Sayac(), Sayac(), Sayac()
        y = kalip_iki_isaretci(sirala_sayarak(dizi, sa), hedef, sk)
        h = kahin_ciftler(dizi, hedef, sh)
        yalniz += sk.adim
        dahil += sa.adim + sk.adim
        kahin += sh.adim
        dogru += (y == h)
    print(f"{hedef:5d}  {dogru:2d}/40  {yalniz:12d}  {dahil:14d}  {kahin:5d}"
          f"  {kahin / dahil:5.2f}")
```

```
hedef  dogru  yalniz kalip  siralama dahil  kahin   oran
   11  40/40           154            1737    866   0.50
   25  40/40           352            1935   1564   0.81
```

Doğruluk geri geldi: **40/40**. Ama oran **1'in altına düştü**. Hedef 11 için sıralama
dahil toplam **1737** adım, kâhin **866** adım; kalıp kâhinin **iki katı** iş yapıyor.
Hedef 25 için 1935'e karşı 1564, oran 0,81.

Bu, konunun ikinci iddiasının ilk ödemesidir: **hızlandırma bazen hızlandırmaz.** Bu
büyüklükte bir girdide iki işaretçi, ön koşulu kendisi sağlamak zorunda kaldığında kaba
kuvvetten pahalıdır. Kalıbın kazandığı yer, sıralamanın **bir kez** yapılıp **çok kez**
sorgulandığı ya da verinin zaten sıralı geldiği durumdur. Dizi uzadıkça denge değişir —
sıralama $n \log n$, kâhin $n^2$ büyür — ama bu dağarcıkta 12 değer, dengeyi kaba kuvvetin
lehine bırakacak kadar küçüktür.

#### Adım Sayısı Doğruluk Hakkında Bir Şey Söylemez

Yukarıdaki iki ölçümde ayrılma, adımda bir değişiklikle birlikte geldi: 154'ten 372'ye.
Bu bir kural değildir ve buna güvenmek tehlikelidir. Aynı kalıbın sayan biçimi bunu
gösterir. Problem şimdi "toplamı hedeften küçük **kaç çift** var" sorusudur; kalıp, sağ
uçta bir çift sayıldığında aradaki bütün çiftlerin de sayılacağını kullanır.

**PK9.** Sayan biçimde kalıp erken çıkamaz; her koşumda işaretçiler tam olarak n−1 adım
atar. Adım sayısı bu yüzden girdinin içeriğinden bağımsızdır.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum=20260218):
    r = uretec(tohum)
    return [[r(30) - 9 for _ in range(12)] for _ in range(40)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_kucuk_cift(dizi, hedef, s):
    """Toplami hedeften kucuk cift sayisi. Butun ciftler denenir."""
    say = 0
    for i in range(len(dizi)):
        for j in range(i + 1, len(dizi)):
            s.say()
            if dizi[i] + dizi[j] < hedef:
                say += 1
    return say


def kalip_kucuk_cift(dizi, hedef, s):
    """ONKOSUL: dizi sirali olmali. Sag ucta bir cift sayilirsa aradakiler de sayilir."""
    sol, sag, say = 0, len(dizi) - 1, 0
    while sol < sag:
        s.say()
        if dizi[sol] + dizi[sag] < hedef:
            say += sag - sol
            sol += 1
        else:
            sag -= 1
    return say


print("on_kosul     ayrilan/40  kalip  kahin   oran")
for ad, hazirla in (("saglaniyor", sorted), ("bozuk     ", list)):
    ayrilan, ak, ah = 0, 0, 0
    for dizi in dagarcik():
        d = hazirla(dizi)
        s1, s2 = Sayac(), Sayac()
        a = kalip_kucuk_cift(d, 6, s1)
        b = kahin_kucuk_cift(d, 6, s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        ayrilan += (a != b)
    print(f"{ad}  {ayrilan:8d}  {ak:5d}  {ah:5d}  {ah / ak:5.2f}")
```

```
on_kosul     ayrilan/40  kalip  kahin   oran
saglaniyor         0    440   2640   6.00
bozuk             39    440   2640   6.00
```

İki satırın adım sütunları **birebir aynı**: kalıp 440, kâhin 2640, oran 6,00. Ayrılan
girdi sütunu ise 0'dan **39'a** çıkıyor — 40 girdinin 39'unda kalıp yanlış bir sayı
döndürüyor. Kalıp yine bir tam sayı veriyor, yine altı kat hızlı, yine hiçbir uyarı
üretmiyor.

Bu, konunun üçüncü iddiasının neden gerekli olduğunu gösteriyor. Adım sayısı bir başarım
ölçüsüdür ve **doğruluk hakkında hiçbir şey söylemez**. Kaba kuvvet burada bir "yavaş
seçenek" değil, 39 yanlış yanıtı görünür kılan **tek** araçtır. Kâhin olmasaydı bu tablonun
iki satırı ayırt edilemezdi.

#### Üç Sayı

| Ölçüt | Kâhin | Kalıp | Ayrılan girdi |
|---|---|---|---|
| Ön koşul sağlanıyor (hedef 11) | 972 adım | 154 adım | **0/40** |
| Ön koşul bozuk (hedef 11) | 866 adım | 372 adım | **25/40** |
| Ön koşulu sağlayarak (hedef 11) | 866 adım | 1737 adım | **0/40** |
| Sayan biçim, ön koşul bozuk | 2640 adım | 440 adım | **39/40** |

Üç satır üç ayrı karar noktasıdır. Birinci satır kalıbın vaadidir. İkinci satır ön koşul
denetlenmediğinde ne olduğunu gösterir: yanıtların yüzde altmış ikisi bozuk ve hızlanma
üçte iki oranında erimiş. Üçüncü satır ön koşulu kendi elinle sağlamanın faturasıdır.

Kalıbın bir denetim eklenerek güvenli kılınabileceği düşünülebilir: dizinin sıralı olup
olmadığına bakılır, değilse kâhine dönülür. Bu denetim n−1 karşılaştırmadır, yani 40 dizi
için 440 adım. Denetim **doğruluğu kurtarır** ama **hızlanmayı kurtarmaz**: sırasız
girdide iş yine kâhine kalır. Ölçülmesi gereken şey denetimin maliyeti değil, girdilerin
kaçının ön koşulu sağladığıdır. Bu dağarcıkta o sayı **sıfırdır**.

#### Özet

- Bir kalıp seçmek bir ön koşulu kabul etmektir; iki işaretçinin ön koşulu dizinin sıralı
  olmasıdır.
- Ön koşul sağlandığında kalıp 40 girdinin 40'ında kâhinle aynı yanıtı veriyor ve 972 yerine
  154 adım harcıyor; oran 6,31.
- Ön koşul bozulduğunda kalıp **25 girdide** kâhinden ayrılıyor ve hızlanma 2,33 kata
  düşüyor; yanlışlık ucuz değildir.
- İkinci dağarcıkta ayrılan girdi 24; oran aynı büyüklük düzeninde kaldığı için sonuç
  dağarcığa bağlı değildir.
- Ön koşulu sıralayarak sağlamak doğruluğu geri getiriyor ama toplam adımı 1737'ye çıkarıyor
  ve kalıbı kâhinden pahalı kılıyor.

#### Sonraki Adım

İki işaretçi diziyi iki uçtan sıkıştırıyordu ve ön koşulu sıraydı. Sonraki kalıp
işaretçileri aynı yönde tutar ve aralarındaki bölgeyi bir **pencere** gibi büyütüp
küçültür; kazancı, pencere kaydıkça toplamı sıfırdan hesaplamak yerine artımlı
güncellemesinden gelir. Ön koşulu da farklıdır ve sıraya hiç bakmaz: **hiçbir değer negatif
olmamalıdır**. Sonraki ders bu ön koşulun bozulduğu 10 girdiyi kâhinle sayacak.

### Kayan Pencere

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/kayan-pencere

Bitişik alt dizide artımlı hesap; negatif değerin küçültme kuralını bozduğu 10 girdi ve ön koşulun pencereye değil kurala ait olduğu.

Önceki ders iki işaretçiyi dizinin iki ucuna koyuyor ve ortada buluşturuyordu; ön koşulu
sıraydı. Bu dersin kalıbı işaretçileri **aynı yönde** tutar. Aralarındaki bölge bir
**pencere**dir: sağ işaretçi ilerledikçe pencere büyür, bir koşul sağlandığında sol
işaretçi ilerleyerek pencereyi küçültür.

Kazanç, pencere her kaydığında toplamın sıfırdan hesaplanmamasından gelir; giren değer
eklenir, çıkan değer düşülür. Ön koşulu ise sıraya hiç bakmaz: **hiçbir değer negatif
olmamalıdır**. Bu ders o ön koşulun bozulduğu girdileri kâhinle sayar ve ön koşulun aslında
pencerenin değil **küçültme kuralının** ön koşulu olduğunu gösterir.

#### Problem, Kâhin ve Kalıp

Problem şudur: toplamı hedeften küçük olmayan **en kısa bitişik alt dizinin uzunluğu**
nedir. Kâhin her başlangıç konumundan başlayıp sağa doğru genişler ve hedefi ilk aştığı
yerde durur; bütün başlangıçları dener. Kalıp tek geçişte ilerler: sağ uç değeri toplama
katar, toplam hedefi aştığı sürece sol uçtan değer düşerek pencereyi daraltır.

**PK10.** Dağarcık, önceki dersin dağarcığıdır: tohum `20260218`, 40 dizi, her biri 12
değer, değerler −9 ile 20 arasında. Kırk dizinin **kırkı** negatif değer içerir.
**PK11.** Ön koşulu sağlayan öbek, aynı dizilerin değerlerinin **mutlak değeridir**. İki
öbeğin tek farkı işarettir; uzunluk, konum ve üreteç aynıdır.
**PK12.** Hedef 25'tir ve bir alt dizi hedefi tam olarak tutturmak zorunda değildir;
"hedeften küçük olmamak" yeterlidir.
**PK13.** Kâhin kaba kuvvettir ve her zaman doğru sayılır. Yanıt bir uzunluktur; hiçbir
alt dizi hedefi tutturamıyorsa yanıt tanımsızdır.

```python
TOHUM = 20260218


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum=TOHUM, n=40, uzunluk=12):
    r = uretec(tohum)
    return [{"no": i + 1, "dizi": [r(30) - 9 for _ in range(uzunluk)]}
            for i in range(n)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_en_kisa(dizi, hedef, s):
    """Butun bitisik alt dizileri dener. Her zaman dogru."""
    en_iyi = None
    for i in range(len(dizi)):
        toplam = 0
        for j in range(i, len(dizi)):
            s.say()
            toplam += dizi[j]
            if toplam >= hedef and (en_iyi is None or j - i + 1 < en_iyi):
                en_iyi = j - i + 1
                break
    return en_iyi


def kalip_kayan_pencere(dizi, hedef, s):
    """ONKOSUL: butun degerler negatif olmamali."""
    sol, toplam, en_iyi = 0, 0, None
    for sag in range(len(dizi)):
        s.say()
        toplam += dizi[sag]
        while toplam >= hedef:
            if en_iyi is None or sag - sol + 1 < en_iyi:
                en_iyi = sag - sol + 1
            toplam -= dizi[sol]
            sol += 1
            s.say()
    return en_iyi


def olc(kume, hedef):
    ayrilan, ak, ah = [], 0, 0
    for k in kume:
        s1, s2 = Sayac(), Sayac()
        a = kalip_kayan_pencere(k["dizi"], hedef, s1)
        b = kahin_en_kisa(k["dizi"], hedef, s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        if a != b:
            ayrilan.append(k["no"])
    return {"ayrilan": len(ayrilan), "ilk_ayrilan": ayrilan[:6],
            "kalip_adim": ak, "kahin_adim": ah, "oran": round(ah / ak, 2)}


K = dagarcik()
P = [dict(k, dizi=[abs(x) for x in k["dizi"]]) for k in K]
print("dagarcik:", len(K), "dizi | negatif iceren:",
      sum(1 for k in K if any(x < 0 for x in k["dizi"])))
for ad, kume in (("on_kosul saglaniyor", P), ("on_kosul bozuk    ", K)):
    print(f"  {ad}", olc(kume, 25))
```

```
dagarcik: 40 dizi | negatif iceren: 40
  on_kosul saglaniyor {'ayrilan': 0, 'ilk_ayrilan': [], 'kalip_adim': 867, 'kahin_adim': 2396, 'oran': 2.76}
  on_kosul bozuk     {'ayrilan': 10, 'ilk_ayrilan': [2, 12, 13, 15, 24, 26], 'kalip_adim': 761, 'kahin_adim': 2462, 'oran': 3.24}
```

Ön koşul sağlandığında kalıp **867** adımda 40 girdinin 40'ında kâhinle aynı yanıtı veriyor;
kâhin **2396** adım harcıyor, oran **2,76**. Negatif değer içeren dağarcıkta ayrılan girdi
**10** oluyor.

Buradaki sayı, önceki dersin 25'inden düşük ve bu düşüklük dersin en tehlikeli tarafıdır.
Kalıp girdilerin dörtte üçünde hâlâ doğru yanıt veriyor. Bir sınama kümesi rastgele
seçilseydi temiz sonuç verme olasılığı yüksekti; kusur, gözlemle değil **kâhinle** ortaya
çıkar.

İkinci satırın oranı da dikkat çekicidir: **3,24**, yani ön koşul sağlandığındaki 2,76'dan
**yüksek**. Kalıp bozuk girdide daha az adım harcıyor (761'e karşı 867), çünkü negatif
değerler toplamı düşürüyor ve küçültme döngüsü daha seyrek çalışıyor. **Daha az adım daha
iyi yanıt demek değildir.**

#### Küçültme Kuralı Neye Dayanıyor

Kalıbın tek riskli satırı `while toplam >= hedef` döngüsüdür. Bu döngü, soldan bir değer
düşüldüğünde toplamın **azalacağını** varsayar. Negatif olmayan değerlerde bu doğrudur;
toplam, sol uç ilerledikçe tekdüze azalır ve döngü ilk kez koşulu bozduğunda o
başlangıç için en kısa pencere bulunmuş olur.

Negatif bir değer düşüldüğünde toplam **artar**. O anda pencere daralmış ama toplam
büyümüştür; kalıp bunu bir ilerleme sayar ve sol ucu bir daha geri almaz. Atlanan
başlangıçlar arasında daha kısa bir çözüm varsa görülmez.

**PK14.** Kalıp sol ucu **geri almaz**; her konum en çok bir kez sol uçtan çıkar. Kalıbın
doğrusal adım sayısı bu geri almama kuralından gelir, dolayısıyla kural gevşetilemez.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum):
    r = uretec(tohum)
    return [[r(30) - 9 for _ in range(12)] for _ in range(40)]


def kahin_en_kisa(dizi, hedef):
    en_iyi = None
    for i in range(len(dizi)):
        toplam = 0
        for j in range(i, len(dizi)):
            toplam += dizi[j]
            if toplam >= hedef and (en_iyi is None or j - i + 1 < en_iyi):
                en_iyi = j - i + 1
                break
    return en_iyi


def kalip_kayan_pencere(dizi, hedef):
    sol, toplam, en_iyi = 0, 0, None
    for sag in range(len(dizi)):
        toplam += dizi[sag]
        while toplam >= hedef:
            if en_iyi is None or sag - sol + 1 < en_iyi:
                en_iyi = sag - sol + 1
            toplam -= dizi[sol]
            sol += 1
    return en_iyi


ikinci = dagarcik(20260218)[1]
print("girdi 2:", ikinci)
print("  kalip:", kalip_kayan_pencere(ikinci, 25),
      "| kahin:", kahin_en_kisa(ikinci, 25))
print("  mutlak degerle:", [abs(x) for x in ikinci])
print("  kalip:", kalip_kayan_pencere([abs(x) for x in ikinci], 25),
      "| kahin:", kahin_en_kisa([abs(x) for x in ikinci], 25))
print()
print("tohum      hedef  on_kosul     ayrilan/40   oran")
for tohum in (20260218, 20260219):
    for hedef in (25, 15):
        K = dagarcik(tohum)
        obek = (("saglaniyor", [[abs(x) for x in d] for d in K]), ("bozuk     ", K))
        for ad, kume in obek:
            ayrilan = sum(1 for d in kume
                          if kalip_kayan_pencere(d, hedef) != kahin_en_kisa(d, hedef))
            print(f"{tohum}  {hedef:5d}  {ad}  {ayrilan:8d}   {ayrilan / 40:.4f}")
```

```
girdi 2: [-6, 15, 10, 11, 6, -5, -4, 3, 2, 17, -8, 5]
  kalip: 3 | kahin: 2
  mutlak degerle: [6, 15, 10, 11, 6, 5, 4, 3, 2, 17, 8, 5]
  kalip: 2 | kahin: 2

tohum      hedef  on_kosul     ayrilan/40   oran
20260218     25  saglaniyor         0   0.0000
20260218     25  bozuk             10   0.2500
20260218     15  saglaniyor         0   0.0000
20260218     15  bozuk              6   0.1500
20260219     25  saglaniyor         0   0.0000
20260219     25  bozuk              8   0.2000
20260219     15  saglaniyor         0   0.0000
20260219     15  bozuk              6   0.1500
```

İkinci girdide kâhin **2** diyor, kalıp **3**. Doğru yanıt `15 + 10` çiftidir; kalıp bu
çifti göremiyor, çünkü sol uç `-6` değerini düşürdüğünde toplam artmış ve pencere yanlış
yerde sabitlenmiş. Kalıbın çıktısı yine bir uzunluk, yine akla yatkın, yine yanlış.

**PK15.** İkinci dağarcık `20260219` tohumundan gelir. Ayrılan girdi oranı aynı büyüklük
düzeninde kalmazsa sonuç dağarcığa bağlıdır ve öyle yazılır.

İkinci dağarcıkta hedef 25 için ayrılan girdi **8**, birincide **10**; hedef 15 için ikisi
de **6**. Dört ölçümün dördü de 0,15 ile 0,25 arasında, yani **aynı büyüklük düzeninde**.
Sonuç dağarcığa bağlı değildir. Ön koşulun sağlandığı dört satırda ayrılan girdi
**sıfırdır**.

#### Ön koşul Pencerenin Değil, Kuralın

Kayan pencere adı iki ayrı kalıbı birden anar ve ikisinin ön koşulu aynı değildir. Yukarıdaki
biçim **değişken boyutludur**: pencere bir koşul sağlanana kadar büyür, sağlandığında
küçülür. Bir de **sabit boyutlu** biçim vardır: pencere hep k geniştir, sağdan bir değer
girer, soldan bir değer çıkar.

Sabit boyutlu biçimde küçültme kuralı yoktur; pencere bir koşula bakarak daralmaz,
yalnızca kayar. O hâlde toplamın tekdüze azalması da gerekmez.

**PK16.** Sabit boyutlu pencere yalnız **artımlı toplam** kullanır: bir toplama, bir
çıkarma. Kalıbın adımı k'dan bağımsızdır; kâhinin adımı k ile büyür.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum=20260218):
    r = uretec(tohum)
    return [[r(30) - 9 for _ in range(12)] for _ in range(40)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_sabit(dizi, k, s):
    """Her pencerenin toplamini sifirdan hesaplar."""
    en_iyi = None
    for i in range(len(dizi) - k + 1):
        toplam = 0
        for j in range(i, i + k):
            s.say()
            toplam += dizi[j]
        if en_iyi is None or toplam > en_iyi:
            en_iyi = toplam
    return en_iyi


def kalip_sabit(dizi, k, s):
    """Pencere sabit boyutlu: bir deger girer, bir deger cikar. Isaret on_kosulu yok."""
    toplam, en_iyi = 0, None
    for sag in range(len(dizi)):
        s.say()
        toplam += dizi[sag]
        if sag >= k:
            toplam -= dizi[sag - k]
        if sag >= k - 1 and (en_iyi is None or toplam > en_iyi):
            en_iyi = toplam
    return en_iyi


print("k  girdi           ayrilan/40  kalip  kahin   oran")
for k in (3, 4, 6):
    for ad, hazirla in (("negatifsiz", lambda d: [abs(x) for x in d]),
                        ("negatifli ", list)):
        ayrilan, ak, ah = 0, 0, 0
        for dizi in dagarcik():
            d = hazirla(dizi)
            s1, s2 = Sayac(), Sayac()
            ayrilan += (kalip_sabit(d, k, s1) != kahin_sabit(d, k, s2))
            ak, ah = ak + s1.adim, ah + s2.adim
        print(f"{k}  {ad}  {ayrilan:12d}  {ak:5d}  {ah:5d}  {ah / ak:5.2f}")
```

```
k  girdi           ayrilan/40  kalip  kahin   oran
3  negatifsiz             0    480   1200   2.50
3  negatifli              0    480   1200   2.50
4  negatifsiz             0    480   1440   3.00
4  negatifli              0    480   1440   3.00
6  negatifsiz             0    480   1680   3.50
6  negatifli              0    480   1680   3.50
```

Altı satırın altısında ayrılan girdi **sıfır**. Negatif değerler sabit boyutlu pencereyi
hiç etkilemiyor; kalıbın adımı üç k değerinde de **480**, kâhinin adımı k ile büyüyor ve
oran 2,50'den 3,50'ye çıkıyor.

Bu, dersin yapısal sonucudur: **ön koşul kalıbın adına değil, kalıbın içindeki tek bir
kurala aittir.** "Kayan pencere negatif değerle çalışmaz" cümlesi yanlıştır; doğru cümle,
"toplamın tekdüze azaldığı varsayımına dayanan küçültme kuralı negatif değerle çalışmaz"
cümlesidir. Bir kalıbı ön koşuluyla birlikte öğrenmek, kalıbın adını değil o kuralı
bilmek demektir.

#### Üç Sayı

| Ölçüt | Kâhin | Kalıp | Ayrılan girdi |
|---|---|---|---|
| Değişken pencere, ön koşul sağlanıyor | 2396 adım | 867 adım | **0/40** |
| Değişken pencere, ön koşul bozuk | 2462 adım | 761 adım | **10/40** |
| Sabit pencere (k=4), negatifli girdi | 1440 adım | 480 adım | **0/40** |

İkinci satır kalıbın en az adımı harcadığı satırdır ve tek bozuk satırdır. Üçüncü satır
aynı girdiyle sıfır ayrılma veriyor, çünkü kalıp değişti.

#### Denetim Doğruluğu Geri Alır, Hızlanmayı Almaz

Ön koşulu denetlemek ucuzdur: bir dizide negatif değer aramak en çok n karşılaştırmadır ve
ilk negatifte durur. Denetim başarısızsa iş kâhine bırakılır. Bu düzenlemenin doğruluğu
tamdır; sorulması gereken, geriye ne kadar hızlanma kaldığıdır.

**PK17.** Denetimli kalıp, denetimin adımlarını da kendi hanesine yazar; kâhine
devredildiğinde kâhinin adımı da kalıbın adımına eklenir.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum=20260218):
    r = uretec(tohum)
    return [[r(30) - 9 for _ in range(12)] for _ in range(40)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_en_kisa(dizi, hedef, s):
    en_iyi = None
    for i in range(len(dizi)):
        toplam = 0
        for j in range(i, len(dizi)):
            s.say()
            toplam += dizi[j]
            if toplam >= hedef and (en_iyi is None or j - i + 1 < en_iyi):
                en_iyi = j - i + 1
                break
    return en_iyi


def kalip_kayan_pencere(dizi, hedef, s):
    sol, toplam, en_iyi = 0, 0, None
    for sag in range(len(dizi)):
        s.say()
        toplam += dizi[sag]
        while toplam >= hedef:
            if en_iyi is None or sag - sol + 1 < en_iyi:
                en_iyi = sag - sol + 1
            toplam -= dizi[sol]
            sol += 1
            s.say()
    return en_iyi


def denetimli(dizi, hedef, s):
    """Onkosulu sinar; saglanmiyorsa kahine birakir."""
    for x in dizi:
        s.say()
        if x < 0:
            return kahin_en_kisa(dizi, hedef, s)
    return kalip_kayan_pencere(dizi, hedef, s)


for ad, hazirla in (("negatifsiz", lambda d: [abs(x) for x in d]),
                    ("negatifli ", list)):
    ayrilan, ad_denetimli, ad_kahin = 0, 0, 0
    for dizi in dagarcik():
        d = hazirla(dizi)
        s1, s2 = Sayac(), Sayac()
        ayrilan += (denetimli(d, 25, s1) != kahin_en_kisa(d, 25, s2))
        ad_denetimli += s1.adim
        ad_kahin += s2.adim
    print(f"{ad}  ayrilan {ayrilan}/40  denetimli kalip {ad_denetimli:5d}"
          f"  kahin {ad_kahin:5d}  oran {ad_kahin / ad_denetimli:.2f}")
```

```
negatifsiz  ayrilan 0/40  denetimli kalip  1347  kahin  2396  oran 1.78
negatifli   ayrilan 0/40  denetimli kalip  2568  kahin  2462  oran 0.96
```

Ayrılan girdi iki satırda da **sıfır**. Ama ikinci satırın oranı **0,96**: negatifli
dağarcıkta denetimli kalıp, kâhinden **daha çok** adım harcıyor. Bunun nedeni açıktır —
kırk dizinin kırkı denetimden geçemiyor, iş kırk kez kâhine düşüyor ve denetimin 106 adımı
üstüne biniyor. Ön koşulun sağlandığı dağarcıkta oran 1,78'de kalıyor, yani denetim orada
bile 2,76'dan 1,78'e bir kayıp yazdırıyor.

Buradan çıkan okuma şudur: **ön koşul denetimi bir doğruluk aracıdır, bir başarım aracı
değildir.** Girdilerin hangi oranda ön koşulu sağladığı bilinmeden denetimli kalıbın
kazandıracağı söylenemez; bu dağarcıkta o oran sıfırdır ve kalıp bütünüyle kâhine
dönüşmüştür.

#### Özet

- Kayan pencere iki işaretçiyi aynı yönde tutar ve toplamı sıfırdan hesaplamak yerine
  artımlı günceller.
- Değişken boyutlu biçimde küçültme kuralı, soldan değer düşüldüğünde toplamın azalacağını
  varsayar; bu varsayım yalnız negatif olmayan değerlerde doğrudur.
- Negatif içeren dağarcıkta kalıp **10 girdide** kâhinden ayrılıyor ve bunu **daha az
  adımla** yapıyor; az adım doğruluk göstergesi değildir.
- İkinci dağarcıkta ayrılan girdi 8; oran aynı büyüklük düzeninde kaldığı için sonuç
  dağarcığa bağlı değildir.
- Sabit boyutlu pencere küçültme kuralı taşımadığı için negatif değerden etkilenmez:
  altı ölçümün altısında ayrılan girdi sıfırdır.

#### Sonraki Adım

İki kalıp da tek bir dizide, konum sayısı bilinerek çalıştı. Sonraki kalıp işaretçileri
yine aynı yönde ilerletir ama **farklı hızlarda** ve uzunluğun bilinmediği bir yapıda:
her düğümün bir ardılı vardır, sonu olup olmadığı bilinmez. Sonraki ders bu yapıda döngü
tespitini ve orta elemanı ölçer; ön koşulu, ilerlemenin gerçekten tek yönlü olmasıdır ve o
ön koşul bozulduğunda kalıp yalnız yanlış yanıt vermez, hiç durmayabilir.

### Hızlı ve Yavaş İşaretçi

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/hizli-ve-yavas-isaretci

Farklı hızda ilerleyen iki işaretçiyle döngü tespiti ve orta eleman; ikinci kenarın görünmez kıldığı 14 döngü ve durmama bedeli.

Önceki iki kalıp bir dizide çalıştı: uzunluk biliniyordu, her konuma dizin üzerinden
erişilebiliyordu. Bu dersin yapısı farklıdır. Elde bir **başlangıç düğümü** ve her düğüm
için bir **ardıl** vardır; uzunluk bilinmez, geriye gidilemez, yapının bir sonu olup
olmadığı önceden bilinmez.

Kalıp iki işaretçiyi aynı yönde ilerletir ama **farklı hızlarda**: yavaş olan bir adım,
hızlı olan iki adım atar. Ön koşulu tek cümledir: **ilerleme tek yönlü olmalı**, yani her
düğümün tam bir ardılı bulunmalıdır. Bu ders o ön koşulun bozulduğu girdileri sayar ve
kalıbın asıl kazancının adımda değil **tutulan düğüm sayısında** olduğunu gösterir.

#### Döngü Tespiti

İlk problem şudur: başlangıçtan ilerleyen bir gezinme sonsuza kadar sürer mi. Kâhin, Veri
Yapıları kursunun derine aramasıyla döngü arar; oradaki "ziyaret edildi" ile "hâlâ açık"
ayrımı **tekrarlanmaz, doğrudan kullanılır**. Kalıp hiçbir küme tutmaz: iki işaretçi
farklı hızda ilerler ve döngü varsa er geç aynı düğümde buluşurlar.

**PK18.** Yapı 12 düğümlüdür; düğümler bir yol üzerinde dizilir ve yolun sonu ya biter ya
geriye bağlanır. Tohum `20260218`.
**PK19.** Ön koşulu bozan tek şey bazı düğümlere eklenen **ikinci bir geri kenardır**.
Ön koşulu sağlayan öbek, aynı yapıların ikinci kenarları atılmış biçimidir.
**PK20.** Kalıp her zaman **ilk ardılı** izler; ikinci kenarın varlığını göremez, çünkü
gördüğü tek şey `ardil[d][0]` değeridir.
**PK21.** Adımın yanında ikinci bir ölçü tutulur: **aynı anda tutulan düğüm sayısı**.
Kâhin için ziyaret kümesinin en büyük boyu, kalıp için iki işaretçidir.

```python
TOHUM, DUGUM, DAGARCIK = 20260218, 12, 40


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def zincir_dagarcik(tohum=TOHUM, n=DAGARCIK, dugum=DUGUM):
    """Her yapida bir yol var. Yolun sonu ya biter ya geriye baglanir; ayrica bazi
    dugumlere IKINCI bir geri kenar eklenir."""
    r = uretec(tohum)
    kume = []
    for i in range(n):
        yol = list(range(dugum))
        for j in range(dugum - 1, 0, -1):
            k = r(j + 1)
            yol[j], yol[k] = yol[k], yol[j]
        ardil = {yol[j]: [yol[j + 1]] for j in range(dugum - 1)}
        ardil[yol[-1]] = [yol[r(dugum)]] if r(100) < 40 else [-1]
        if r(100) < 60:
            p = 2 + r(dugum - 3)
            ardil[yol[p]].append(yol[r(p)])
        kume.append({"no": i + 1, "bas": yol[0], "ardil": ardil})
    return kume


def tek_yonlu(ardil):
    """Ikinci kenarlar atilir: her dugumun tam bir ardili kalir."""
    return {d: [a[0]] for d, a in ardil.items()}


class Sayac:
    def __init__(self):
        self.adim, self.tutulan = 0, 0

    def say(self, tutulan=0):
        self.adim += 1
        self.tutulan = max(self.tutulan, tutulan)


def kahin_dongu(bas, ardil, s):
    """Butun kenarlari gezer. Derine aramanin dongu tespiti dogrudan kullanilir."""
    ziyaret, acik = set(), set()

    def gez(d):
        ziyaret.add(d)
        acik.add(d)
        s.say(len(ziyaret))
        for k in ardil.get(d, []):
            if k == -1:
                continue
            if k in acik:
                return True
            if k not in ziyaret and gez(k):
                return True
        acik.discard(d)
        return False
    return gez(bas)


def kalip_hizli_yavas(bas, ardil, s):
    """ONKOSUL: her dugumun TAM BIR ardili olmali. Kalip ilk ardili izler."""
    yavas = hizli = bas
    while True:
        s.say(2)
        for _ in range(2):
            hizli = ardil[hizli][0]
            if hizli == -1:
                return False
        yavas = ardil[yavas][0]
        if yavas == hizli:
            return True


def olc(kume, tek):
    ayrilan, ak, ah, tk, th = [], 0, 0, 0, 0
    for k in kume:
        ardil = tek_yonlu(k["ardil"]) if tek else k["ardil"]
        s1, s2 = Sayac(), Sayac()
        a = kalip_hizli_yavas(k["bas"], tek_yonlu(k["ardil"]), s1)
        b = kahin_dongu(k["bas"], ardil, s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        tk, th = max(tk, s1.tutulan), max(th, s2.tutulan)
        if a != b:
            ayrilan.append(k["no"])
    return {"ayrilan": len(ayrilan), "ilk_ayrilan": ayrilan[:6],
            "kalip_adim": ak, "kahin_adim": ah, "oran": round(ah / ak, 2),
            "kalip_tutulan": tk, "kahin_tutulan": th}


K = zincir_dagarcik()
print("dagarcik:", len(K), "yapi x", DUGUM, "dugum | ikinci kenari olan:",
      sum(1 for k in K if any(len(a) > 1 for a in k["ardil"].values())))
for ad, tek in (("on_kosul saglaniyor", True), ("on_kosul bozuk    ", False)):
    print(f"  {ad}", olc(K, tek))
```

```
dagarcik: 40 yapi x 12 dugum | ikinci kenari olan: 23
  on_kosul saglaniyor {'ayrilan': 0, 'ilk_ayrilan': [], 'kalip_adim': 299, 'kahin_adim': 480, 'oran': 1.61, 'kalip_tutulan': 2, 'kahin_tutulan': 12}
  on_kosul bozuk     {'ayrilan': 14, 'ilk_ayrilan': [2, 8, 11, 12, 16, 19], 'kalip_adim': 299, 'kahin_adim': 480, 'oran': 1.61, 'kalip_tutulan': 2, 'kahin_tutulan': 12}
```

İki satırın adım sütunları **birebir aynı**: kalıp 299, kâhin 480, oran 1,61. Tutulan düğüm
sütunları da aynı: kalıp **2**, kâhin **12**. Değişen tek sütun ayrılan girdidir — 0'dan
**14'e** çıkıyor.

Adım oranı 1,61 zaten mütevazıdır ve kalıbın satış noktası da orası değildir. Kalıp
**tutulan düğüm sayısında** kazanır: kâhin yapının bütün düğümlerini bir kümede tutmak
zorundadır, kalıp iki işaretçiden başka hiçbir şey tutmaz. Düğüm sayısı büyüdükçe kâhinin
tuttuğu şey büyür, kalıbınki **2'de kalır**. Ölçülen kazanç budur ve ön koşul bozulduğunda
bu kazanç **hiç değişmez** — yalnız yanıt bozulur.

Buluşmanın neden güvenceli olduğu da tek ardıl varsayımına dayanır. İki işaretçi döngüye
girdikten sonra aralarındaki uzaklık her turda **tam bir azalır**, çünkü hızlı olan iki,
yavaş olan bir adım atar. Uzaklık bir tam sayıdır ve döngü uzunluğu kadar değerden birini
alır; her turda bir azaldığı için sonlu sayıda turda sıfıra iner. Uzaklığın bir azalması,
her düğümden **tek bir yol** çıkması demektir. İkinci bir ardıl varken "aralarındaki
uzaklık" diye tek bir sayı yoktur ve gerekçe düşer.

#### İkinci Kenar Neden Görünmüyor

Kalıbın tek bilgi kaynağı `ardil[d][0]` değeridir. Bir düğümün ikinci bir ardılı varsa
kalıp onu sorgulamaz; sorgulayamaz da, çünkü kalıbın tanımı "tek ardıl" varsayımı üzerine
kuruludur. Yapıda yalnız ikinci kenar üzerinden kapanan bir döngü varsa, kalıp o döngüye
hiç girmez ve `False` döndürür.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def zincir_dagarcik(tohum, n=40, dugum=12):
    r = uretec(tohum)
    kume = []
    for i in range(n):
        yol = list(range(dugum))
        for j in range(dugum - 1, 0, -1):
            k = r(j + 1)
            yol[j], yol[k] = yol[k], yol[j]
        ardil = {yol[j]: [yol[j + 1]] for j in range(dugum - 1)}
        ardil[yol[-1]] = [yol[r(dugum)]] if r(100) < 40 else [-1]
        if r(100) < 60:
            p = 2 + r(dugum - 3)
            ardil[yol[p]].append(yol[r(p)])
        kume.append({"no": i + 1, "bas": yol[0], "ardil": ardil})
    return kume


def tek_yonlu(ardil):
    return {d: [a[0]] for d, a in ardil.items()}


def kahin_dongu(bas, ardil):
    ziyaret, acik = set(), set()

    def gez(d):
        ziyaret.add(d)
        acik.add(d)
        for k in ardil.get(d, []):
            if k == -1:
                continue
            if k in acik:
                return True
            if k not in ziyaret and gez(k):
                return True
        acik.discard(d)
        return False
    return gez(bas)


def kalip_hizli_yavas(bas, ardil):
    yavas = hizli = bas
    while True:
        for _ in range(2):
            hizli = ardil[hizli][0]
            if hizli == -1:
                return False
        yavas = ardil[yavas][0]
        if yavas == hizli:
            return True


K = zincir_dagarcik(20260218)
ornek = next(k for k in K
             if kalip_hizli_yavas(k["bas"], tek_yonlu(k["ardil"]))
             != kahin_dongu(k["bas"], k["ardil"]))
print("ayrilan ilk girdi no:", ornek["no"], "| baslangic:", ornek["bas"])
print("  ardil:", {d: a for d, a in sorted(ornek["ardil"].items())})
print("  kalip (ilk ardil):", kalip_hizli_yavas(ornek["bas"], tek_yonlu(ornek["ardil"])))
print("  kahin (butun kenarlar):", kahin_dongu(ornek["bas"], ornek["ardil"]))
print()
print("tohum      on_kosul     dongulu (kahin)  ayrilan/40   oran")
for tohum in (20260218, 20260219):
    K = zincir_dagarcik(tohum)
    for ad, tek in (("saglaniyor", True), ("bozuk     ", False)):
        dongulu = ayrilan = 0
        for k in K:
            ardil = tek_yonlu(k["ardil"]) if tek else k["ardil"]
            b = kahin_dongu(k["bas"], ardil)
            dongulu += b
            ayrilan += (kalip_hizli_yavas(k["bas"], tek_yonlu(k["ardil"])) != b)
        print(f"{tohum}  {ad}  {dongulu:15d}  {ayrilan:8d}   {ayrilan / 40:.4f}")
```

```
ayrilan ilk girdi no: 2 | baslangic: 0
  ardil: {0: [8], 1: [2], 2: [7], 3: [-1], 4: [1, 8], 5: [10], 6: [5], 7: [6], 8: [9], 9: [11], 10: [3], 11: [4]}
  kalip (ilk ardil): False
  kahin (butun kenarlar): True

tohum      on_kosul     dongulu (kahin)  ayrilan/40   oran
20260218  saglaniyor               16         0   0.0000
20260218  bozuk                    30        14   0.3500
20260219  saglaniyor               18         0   0.0000
20260219  bozuk                    32        14   0.3500
```

İkinci girdide düğüm 4'ün iki ardılı var: `1` ve `8`. Kalıp yalnız `1`'i görüyor ve
`0 → 8 → 9 → 11 → 4 → 1 → 2 → 7 → 6 → 5 → 10 → 3 → son` yolunu izleyip `False` diyor.
Kâhin `4 → 8` kenarını da deniyor ve `8 → 9 → 11 → 4` döngüsünü buluyor.

**PK22.** İkinci dağarcık `20260219` tohumundan gelir. İki dağarcıkta da ayrılan girdi
**14/40**, oran **0,3500**; sonuç dağarcığa bağlı değildir.

#### Orta Eleman ve Durmama

İkinci problem, aynı kalıbın en sık kullanılan ikinci biçimidir: yapının **orta düğümünü**
bulmak. Kâhin düğümleri sırayla toplar, sonra ortadakini seçer — iki geçiş ve bir küme.
Kalıp tek geçişte biter: hızlı işaretçi sona ulaştığında yavaş işaretçi ortadadır.

Burada ön koşul değişir. Orta eleman ancak yapının bir **sonu** varsa tanımlıdır; döngü
varsa "orta" diye bir düğüm yoktur. Kâhin bunu görür ve `tanimsiz` döndürür. Kalıbın
göreceği bir şey yoktur: hızlı işaretçi döngü içinde sonsuza kadar döner.

**PK23.** Bu ölçümde ikinci kenarlar yoktur; yapılar tek ardıllıdır. Bozulan ön koşul
**döngüsüzlüktür**.
**PK24.** Kalıba bir **adım sınırı** konur (48 adım). Sınıra dayanan koşum `durmadi`
döndürür ve kâhinden ayrılmış sayılır. Sınır olmasaydı ölçüm hiç bitmezdi.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def yol_dagarcik(tohum=20260218, n=40, dugum=12):
    """`son` yolun son dugumu; oradan geriye baglanti varsa yapida dongu var."""
    r = uretec(tohum)
    kume = []
    for i in range(n):
        yol = list(range(dugum))
        for j in range(dugum - 1, 0, -1):
            k = r(j + 1)
            yol[j], yol[k] = yol[k], yol[j]
        ardil = {yol[j]: yol[j + 1] for j in range(dugum - 1)}
        ardil[yol[-1]] = yol[r(dugum)] if r(100) < 40 else -1
        r(100)
        kume.append({"no": i + 1, "bas": yol[0], "son": yol[-1], "ardil": ardil})
    return kume


class Sayac:
    def __init__(self):
        self.adim, self.tutulan = 0, 0

    def say(self, tutulan=0):
        self.adim += 1
        self.tutulan = max(self.tutulan, tutulan)


def kahin_orta(bas, ardil, s):
    """Once butun dugumleri toplar, sonra ortadakini secer. Ziyaret kumesi tutar."""
    ziyaret, sira, d = set(), [], bas
    while d != -1 and d not in ziyaret:
        ziyaret.add(d)
        sira.append(d)
        s.say(len(ziyaret))
        d = ardil[d]
    if d != -1:
        return "tanimsiz"
    for _ in range(len(sira) // 2 + 1):
        s.say(len(ziyaret))
    return sira[len(sira) // 2]


def kalip_orta(bas, ardil, s, sinir=48):
    """ONKOSUL: yapida dongu olmamali. Tek gecis, yalniz iki isaretci tutulur."""
    yavas = hizli = bas
    while True:
        s.say(2)
        if s.adim > sinir:
            return "durmadi"
        if hizli == -1 or ardil[hizli] == -1:
            return yavas
        hizli = ardil[ardil[hizli]]
        yavas = ardil[yavas]


K = yol_dagarcik()
A = [dict(k, ardil={**k["ardil"], k["son"]: -1}) for k in K]
print("dagarcik:", len(K), "yapi | dongulu olan:",
      sum(1 for k in K if k["ardil"][k["son"]] != -1))
print("on_kosul     ayrilan/40  kalip  kahin   oran  kalip tutulan  kahin tutulan")
for ad, kume in (("saglaniyor", A), ("bozuk     ", K)):
    ayrilan, ak, ah, tk, th = 0, 0, 0, 0, 0
    for k in kume:
        s1, s2 = Sayac(), Sayac()
        a = kalip_orta(k["bas"], k["ardil"], s1)
        b = kahin_orta(k["bas"], k["ardil"], s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        tk, th = max(tk, s1.tutulan), max(th, s2.tutulan)
        ayrilan += (a != b)
    print(f"{ad}  {ayrilan:8d}  {ak:5d}  {ah:5d}  {ah / ak:5.2f}"
          f"  {tk:13d}  {th:13d}")
```

```
dagarcik: 40 yapi | dongulu olan: 14
on_kosul     ayrilan/40  kalip  kahin   oran  kalip tutulan  kahin tutulan
saglaniyor         0    280    760   2.71              2             12
bozuk             14    868    662   0.76              2             12
```

Ön koşul sağlandığında kalıp **280** adımda 40 girdinin 40'ında kâhinle aynı düğümü
buluyor; kâhin **760** adım harcıyor, oran **2,71**. Döngülü yapılar geldiğinde ayrılan
girdi **14** oluyor ve oran **0,76'ya** düşüyor — kalıp kâhinden **pahalı** hâle geliyor.

Bu satır, önceki iki dersin göstermediği bir şeyi gösteriyor. Orada ön koşul bozulunca kalıp
yanlış bir yanıt veriyor ve normal adım sayısında bitiyordu. Burada kalıp **bitmiyor**:
868 adımın büyük bölümü, on dört yapının her birinde sınıra dayanana kadar dönen
işaretçilerdir. Sınır konmasaydı ölçüm hiç tamamlanmayacaktı. **Ön koşulun bedeli her zaman
yanlış yanıt değildir; bazen yanıtın hiç gelmemesidir.**

#### Üç Sayı

| Ölçüt | Kâhin | Kalıp | Ayrılan girdi |
|---|---|---|---|
| Döngü tespiti, ön koşul sağlanıyor | 480 adım / 12 düğüm | 299 adım / 2 düğüm | **0/40** |
| Döngü tespiti, ikinci kenarlı | 480 adım / 12 düğüm | 299 adım / 2 düğüm | **14/40** |
| Orta eleman, ön koşul sağlanıyor | 760 adım / 12 düğüm | 280 adım / 2 düğüm | **0/40** |
| Orta eleman, döngülü yapı | 662 adım / 12 düğüm | 868 adım / 2 düğüm | **14/40** |

Dört satırın hepsinde kalıbın tuttuğu düğüm sayısı **2**, kâhinin tuttuğu **12**. Bu sütun
kalıbın neden var olduğunu açıklar ve dört satırda da değişmez. Ayrılan girdi sütunu ise
iki kez sıfır, iki kez on dörttür — ve o sütunu okumadan diğerlerine bakmak, kalıbın on
dört girdide yanlış yanıt verdiğini görmemek demektir.

#### Özet

- Hızlı ve yavaş işaretçi, uzunluğu bilinmeyen tek yönlü bir yapıda döngüyü ve orta
  düğümü hiçbir küme tutmadan bulur.
- Ön koşul her düğümün **tam bir ardılı** olmasıdır; ikinci bir kenar eklendiğinde kalıp o
  kenarı hiç sorgulamaz ve **14 girdide** kâhinden ayrılır.
- Ayrılma adım sayısına yansımaz: iki öbekte de kalıp 299, kâhin 480 adım harcar.
- Kalıbın ölçülen kazancı adımda değil tutulan düğüm sayısındadır: **2'ye karşı 12** ve bu
  oran ön koşul bozulsa da değişmez.
- Orta eleman probleminde ön koşul döngüsüzlüktür; döngülü yapıda kalıp yanlış yanıt
  vermekle kalmaz, adım sınırına dayanır ve oran 2,71'den 0,76'ya düşer.

#### Sonraki Adım

Buraya kadarki üç kalıp tek bir geçişte, tek bir yapı üzerinde çalıştı. Sonraki kalıp
girdiyi önce **yeniden düzenler**: örtüşen aralıkları birleştirmek, aralıkları bir ölçüte
göre sıralamakla başlar. Ön koşul artık verinin bir özelliği değil, **seçilen ölçütün
kendisidir** — ve yanlış ölçütle sıralamak kalıbı bozar. Sonraki ders üç ayrı sıralama
ölçütünü aynı kâhinle karşılaştırıp hangisinin kaç girdide yanlış birleştirdiğini sayacak.

### Aralık Birleştirme

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/aralik-birlestirme

Örtüşen aralıkların tek geçişle indirgenmesi; yanlış sıralama anahtarınün 29 girdide bozduğu birleştirme ve eşitlik kuralının 8 girdide değiştirdiği yanıt.

Önceki üç kalıp girdiyi olduğu gibi aldı: iki işaretçi sıralı gelmesini bekliyordu, kayan
pencere işaretlere bakıyordu, hızlı ve yavaş işaretçi ardıl bağlantısını izliyordu. Bu
dersin kalıbı girdiyi **önce yeniden düzenler**. Örtüşen aralıkları birleştirmek, aralıkları
bir ölçüte göre sıralamakla başlar ve sıralama bittikten sonra tek bir geçiş yeter.

Ön koşul artık verinin bir özelliği değildir; **seçilen ölçütün kendisidir**. Ölçüt yanlış
seçildiğinde kalıp yine tek geçişte biter, yine bir aralık listesi döndürür ve yine hiçbir
uyarı vermez. Bu ders üç yanlış ölçütü aynı kâhinle karşılaştırıp her birinin kaç girdide
yanlış birleştirdiğini sayar.

#### Problem, Kâhin ve Kalıp

Problem şudur: verilen aralık kümesinde **örtüşen aralıkları birleştirip** en sade listeyi
üretmek. Kâhin hiçbir sıralama yapmaz; örtüşen bir çift buldukça birleştirir ve hiçbir çift
kalmayana kadar yineler. Bu, sonucu tanımı gereği doğru kılar ama her birleştirmede taramayı
baştan başlatır.

Kalıp bir kez sıralar, sonra listeyi soldan sağa tarar: her aralık ya son birleşik aralığın
sağ ucunu uzatır ya da yeni bir birleşik aralık açar.

**PK25.** Dağarcık 40 kümedir; her küme 12 aralık taşır. Başlangıç 0–60, uzunluk 1–9;
tohum `20260218`.
**PK26.** Aralıklar **kapalıdır**: `(4, 20)` aralığı 4 ile 20 arasındaki bütün noktaları
içerir ve `(20, 25)` ile örtüşür.
**PK27.** Kâhinin ve kalıbın yanıtı, karşılaştırmadan önce **sıralanır**; ayrılma bir sıra
farkından değil, yalnız içerik farkından doğar.
**PK28.** Sıralamanın adımı kalıbın hanesine yazılır. Karşılaştırmalı bir yordam kullanılır
ve her karşılaştırma bir adım sayılır; sıralama yordamlarının kendisi Algoritmalar
kursunda ölçüldü ve burada tekrarlanmaz.

```python
TOHUM, ARALIK, DAGARCIK = 20260218, 12, 40


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def aralik_dagarcik(tohum=TOHUM, n=DAGARCIK, adet=ARALIK):
    r = uretec(tohum)
    kume = []
    for i in range(n):
        liste = []
        for _ in range(adet):
            bas = r(61)
            liste.append((bas, bas + 1 + r(9)))
        kume.append({"no": i + 1, "aralik": liste})
    return kume


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_birlestir(araliklar, s):
    """Ortusen bir cift buldukca birlestirir, degisiklik bitene kadar yineler."""
    kalan = [list(a) for a in araliklar]
    degisti = True
    while degisti:
        degisti = False
        for i in range(len(kalan)):
            for j in range(i + 1, len(kalan)):
                s.say()
                if kalan[i][0] <= kalan[j][1] and kalan[j][0] <= kalan[i][1]:
                    kalan[i] = [min(kalan[i][0], kalan[j][0]),
                                max(kalan[i][1], kalan[j][1])]
                    kalan.pop(j)
                    degisti = True
                    break
            if degisti:
                break
    return sorted(tuple(a) for a in kalan)


def sirala_sayarak(liste, anahtar, s):
    a = list(liste)
    for i in range(1, len(a)):
        j = i
        while j > 0:
            s.say()
            if anahtar(a[j - 1]) <= anahtar(a[j]):
                break
            a[j - 1], a[j] = a[j], a[j - 1]
            j -= 1
    return a


def kalip_birlestir(araliklar, anahtar, s):
    """ONKOSUL: siralama olcutu BASLANGIC olmali. Tek gecis yeter."""
    sonuc = []
    for bas, son in sirala_sayarak(araliklar, anahtar, s):
        s.say()
        if sonuc and bas <= sonuc[-1][1]:
            sonuc[-1] = (sonuc[-1][0], max(sonuc[-1][1], son))
        else:
            sonuc.append((bas, son))
    return sorted(sonuc)


OLCUT = (("baslangica gore", lambda a: a[0]),
         ("bitise gore    ", lambda a: a[1]),
         ("uzunluga gore  ", lambda a: a[1] - a[0]),
         ("siralamasiz    ", lambda a: 0))

K = aralik_dagarcik()
print("dagarcik:", len(K), "kume x", ARALIK, "aralik")
print("olcut            ayrilan/40  kalip  kahin   oran   ilk ayrilanlar")
for ad, anahtar in OLCUT:
    ayrilan, ak, ah = [], 0, 0
    for k in K:
        s1, s2 = Sayac(), Sayac()
        a = kalip_birlestir(k["aralik"], anahtar, s1)
        b = kahin_birlestir(k["aralik"], s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        if a != b:
            ayrilan.append(k["no"])
    print(f"{ad}  {len(ayrilan):8d}  {ak:5d}  {ah:5d}  {ah / ak:5.2f}"
          f"   {ayrilan[:5]}")
```

```
dagarcik: 40 kume x 12 aralik
olcut            ayrilan/40  kalip  kahin   oran   ilk ayrilanlar
baslangica gore         0   2154   2974   1.38   []
bitise gore            29   2145   2974   1.39   [2, 3, 4, 6, 7]
uzunluga gore          40   2041   2974   1.46   [1, 2, 3, 4, 5]
siralamasiz            40    920   2974   3.23   [1, 2, 3, 4, 5]
```

Dört satır dört ayrı ölçüt, aynı kâhin, aynı dağarcık. Yalnız birinci satır doğrudur:
başlangıca göre sıralayan kalıp 40 girdinin 40'ında kâhinle aynı listeyi üretiyor.

Son satır dersin en keskin sayısıdır. **Sıralamasız kalıp en hızlısıdır** — 920 adım, oran
**3,23** — ve **40 girdinin 40'ında yanlıştır**. Sıralamayı atlamak kalıbı üç kat
hızlandırıyor ve tümüyle bozuyor. Sıralamanın adımı doğrudan ölçülebilir: 2154 ile 920
arasındaki **1234 adım**, ön koşulu sağlamanın bedelidir ve kalıbın kazancının büyük
bölümünü yiyor.

Kâhinin 2974 adımı da açıklama ister. Kâhin her birleştirmeden sonra taramayı **baştan**
başlatır, çünkü yeni oluşan geniş aralık daha önce bakılmış bir çiftle örtüşebilir. Bu,
kâhini bilerek pahalı kılan bir seçimdir ve amacı hızlanmak değil, **hiçbir örtüşmeyi
kaçırmamaktır**. Kalıbın tek geçişte aynı sonucu vermesi, sıralamanın bu geriye dönüşü
gereksiz kılmasından gelir: başlangıca göre sıralı bir listede, kapanmış bir birleşik
aralığa sonradan dokunacak bir aralık kalmaz.

Ortadaki iki satır arasındaki fark da öğreticidir. Uzunluğa göre sıralamak **40 girdinin
40'ında** yanlış; bitişe göre sıralamak **29'unda**. İkisi de yanlış ölçüttür, ama biri
kırk girdinin **on birinde** doğru yanıt üretir — ve o on bir girdi, ölçütün doğru
sanılmasına yeter.

#### Bitişe Göre Sıralamak Neden Bozuyor

Kalıbın tek karar kuralı `bas <= sonuc[-1][1]` karşılaştırmasıdır: yeni aralığın
başlangıcı, açık duran birleşik aralığın sağ ucunu geçmiyorsa birleştir. Bu kural, sonraki
her aralığın başlangıcının **öncekinden küçük olmadığını** varsayar. Başlangıca göre
sıralamak tam olarak bunu güvenceye alır.

Bitişe göre sıralandığında geç başlayan ama erken biten bir aralık öne geçebilir; onun
arkasından gelen, **daha erken başlayan** bir aralık artık birleşme koşulunu sağlamaz ve
yeni bir birleşik aralık açar. Sonuç listesinde birbirini kapsayan iki kayıt kalır.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def aralik_dagarcik(tohum, n=40, adet=12):
    r = uretec(tohum)
    kume = []
    for i in range(n):
        liste = []
        for _ in range(adet):
            bas = r(61)
            liste.append((bas, bas + 1 + r(9)))
        kume.append({"no": i + 1, "aralik": liste})
    return kume


def kahin_birlestir(araliklar):
    kalan = [list(a) for a in araliklar]
    degisti = True
    while degisti:
        degisti = False
        for i in range(len(kalan)):
            for j in range(i + 1, len(kalan)):
                if kalan[i][0] <= kalan[j][1] and kalan[j][0] <= kalan[i][1]:
                    kalan[i] = [min(kalan[i][0], kalan[j][0]),
                                max(kalan[i][1], kalan[j][1])]
                    kalan.pop(j)
                    degisti = True
                    break
            if degisti:
                break
    return sorted(tuple(a) for a in kalan)


def kalip_birlestir(araliklar, anahtar):
    sonuc = []
    for bas, son in sorted(araliklar, key=anahtar):
        if sonuc and bas <= sonuc[-1][1]:
            sonuc[-1] = (sonuc[-1][0], max(sonuc[-1][1], son))
        else:
            sonuc.append((bas, son))
    return sorted(sonuc)


kucuk = [(4, 20), (6, 8), (10, 12)]
print("kucuk ornek        :", kucuk)
print("  kahin            :", kahin_birlestir(kucuk))
print("  baslangica gore  :", kalip_birlestir(kucuk, lambda a: a[0]))
print("  bitise gore      :", kalip_birlestir(kucuk, lambda a: a[1]),
      "  <- bitise gore siralama:", sorted(kucuk, key=lambda a: a[1]))
print()
print("tohum      olcut            ayrilan/40   oran")
for tohum in (20260218, 20260219):
    K = aralik_dagarcik(tohum)
    for ad, anahtar in (("baslangica gore", lambda a: a[0]),
                        ("bitise gore    ", lambda a: a[1]),
                        ("uzunluga gore  ", lambda a: a[1] - a[0]),
                        ("siralamasiz    ", lambda a: 0)):
        ayrilan = sum(1 for k in K
                      if kalip_birlestir(k["aralik"], anahtar)
                      != kahin_birlestir(k["aralik"]))
        print(f"{tohum}  {ad}  {ayrilan:8d}   {ayrilan / 40:.4f}")
```

```
kucuk ornek        : [(4, 20), (6, 8), (10, 12)]
  kahin            : [(4, 20)]
  baslangica gore  : [(4, 20)]
  bitise gore      : [(6, 8), (10, 20)]   <- bitise gore siralama: [(6, 8), (10, 12), (4, 20)]

tohum      olcut            ayrilan/40   oran
20260218  baslangica gore         0   0.0000
20260218  bitise gore            29   0.7250
20260218  uzunluga gore          40   1.0000
20260218  siralamasiz            40   1.0000
20260219  baslangica gore         0   0.0000
20260219  bitise gore            32   0.8000
20260219  uzunluga gore          40   1.0000
20260219  siralamasiz            40   1.0000
```

Üç aralıklı küçük örnek mekanizmayı bütünüyle gösteriyor. Doğru yanıt tek aralıktır:
`(6, 8)` ve `(10, 12)` tümüyle `(4, 20)` içindedir. Bitişe göre sıralanınca `(4, 20)`
listenin **sonuna** düşüyor; kalıp önce `(6, 8)` ile başlıyor, `(10, 12)` birleşmiyor ve
yeni bir kayıt açılıyor, sonra `(4, 20)` gelip onu `(10, 20)` yapıyor. Geriye `(6, 8)`
kaydı **kapsanmış olduğu hâlde** ayrı duruyor.

**PK29.** İkinci dağarcık `20260219` tohumundan gelir. Bitişe göre sıralamada ayrılan girdi
**32/40**, birincide **29/40**; oran 0,7250 ile 0,8000, aynı büyüklük düzeninde. Sonuç
dağarcığa bağlı değildir. Uzunluğa göre ve sıralamasız ölçütler iki dağarcıkta da
**40/40**'tır.

#### Ölçüt Yalnız Anahtar Değil, Eşitlik Kuralıdır

Sıralama ölçütü seçildikten sonra bir soru daha kalır ve çoğu zaman sorulmaz: **aynı
değere sahip iki kayıt hangi sırayla gelecek.** Aralıklarla çalışan ikinci bir problem bu
soruyu ölçülebilir kılar: bir noktada **en çok kaç aralık aynı anda açık**.

Kalıp aralıkları olaylara çevirir — her başlangıç bir açılış, her bitiş bir kapanış — ve
olayları koordinata göre sıralayıp tek geçişte sayar. Bir aralığın bittiği koordinatta
başka bir aralık başlıyorsa, kapanışın açılıştan **önce** işlenmesi gerekir; kapalı sayılan
bir uç iki kez sayılırsa açık aralık sayısı şişer.

**PK30.** Bu ölçümde aralıklar **yarı açıktır**: bir aralık başlangıcını içerir, bitişini
içermez. Kâhin her başlangıç noktasında kaç aralığın açık olduğunu tek tek sayar.
**PK31.** Dağarcığın **32 kümesinde** bir aralığın bitişi başka bir aralığın başlangıcıyla
çakışır; eşitlik kuralının etkisi ancak bu kümelerde görülebilir.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def aralik_dagarcik(tohum=20260218, n=40, adet=12):
    r = uretec(tohum)
    kume = []
    for i in range(n):
        liste = []
        for _ in range(adet):
            bas = r(61)
            liste.append((bas, bas + 1 + r(9)))
        kume.append({"no": i + 1, "aralik": liste})
    return kume


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_ortusme(araliklar, s):
    """Her baslangic noktasinda kac aralik acik. Butun ciftler denenir."""
    en_cok = 0
    for bas, _ in araliklar:
        acik = 0
        for b, y in araliklar:
            s.say()
            if b <= bas < y:
                acik += 1
        en_cok = max(en_cok, acik)
    return en_cok


def kalip_ortusme(araliklar, once_bitis, s):
    """ONKOSUL: ayni koordinatta BITIS olayi baslangictan once islenmeli."""
    olay = []
    for bas, son in araliklar:
        olay.append((bas, 1 if once_bitis else 0, +1))
        olay.append((son, 0 if once_bitis else 1, -1))
    en_cok = acik = 0
    for _, _, delta in sorted(olay):
        s.say()
        acik += delta
        en_cok = max(en_cok, acik)
    return en_cok


K = aralik_dagarcik()
carpisan = sum(1 for k in K
               if {b for b, _ in k["aralik"]} & {y for _, y in k["aralik"]})
print("dagarcik: 40 kume x 12 aralik | bir bitisi bir baslangicla cakisan kume:",
      carpisan)
print("olay sirasi     ayrilan/40  kalip  kahin   oran   ilk ayrilanlar")
for ad, once_bitis in (("bitis once    ", True), ("baslangic once", False)):
    ayrilan, ak, ah = [], 0, 0
    for k in K:
        s1, s2 = Sayac(), Sayac()
        a = kalip_ortusme(k["aralik"], once_bitis, s1)
        b = kahin_ortusme(k["aralik"], s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        if a != b:
            ayrilan.append(k["no"])
    print(f"{ad}  {len(ayrilan):10d}  {ak:5d}  {ah:5d}  {ah / ak:5.2f}"
          f"   {ayrilan[:5]}")
```

```
dagarcik: 40 kume x 12 aralik | bir bitisi bir baslangicla cakisan kume: 32
olay sirasi     ayrilan/40  kalip  kahin   oran   ilk ayrilanlar
bitis once               0    960   5760   6.00   []
baslangic once           8    960   5760   6.00   [5, 7, 8, 15, 31]
```

İki satırın adım sütunları birebir aynı: kalıp **960**, kâhin **5760**, oran **6,00**.
Değişen tek şey aynı koordinattaki iki olayın hangisinin önce işlendiğidir ve bu tek karar
**8 girdide** yanıtı değiştiriyor.

Sekiz sayısı 32'nin çeyreğinden azdır ve bu, ölçütün neden gözden kaçtığını açıklar.
Çakışmanın olmadığı 8 kümede eşitlik kuralının hiçbir etkisi yoktur; çakışmanın olduğu 32
kümenin çoğunda da en çok örtüşme başka bir yerde oluştuğu için sonuç değişmez. Kural
yalnız 8 girdide görünür — ama o 8 girdide **yanlıştır** ve hangi 8 girdi olduğu ancak
kâhinle bilinir.

#### Üç Sayı

| Ölçüt | Kâhin | Kalıp | Ayrılan girdi |
|---|---|---|---|
| Başlangıca göre sıralı | 2974 adım | 2154 adım | **0/40** |
| Bitişe göre sıralı | 2974 adım | 2145 adım | **29/40** |
| Sıralamasız | 2974 adım | 920 adım | **40/40** |
| Örtüşme sayımı, bitiş önce | 5760 adım | 960 adım | **0/40** |
| Örtüşme sayımı, başlangıç önce | 5760 adım | 960 adım | **8/40** |

Beş satırın hiçbirinde adım sütunu doğruluk hakkında bilgi taşımıyor; üçüncü satırda en
düşük adımla en yüksek yanlış sayısı bir arada duruyor. Kalıbın ön koşulu tek bir cümleyle
söylenebilir: **sıralama anahtarı, kalıbın tek karar kuralının varsaydığı şeyi güvenceye
almalıdır** — ne fazlası ne eksiği. Ölçütü "doğal görünen" bir alana bakarak seçmek,
karar kuralına bakmadan seçmektir.

#### Özet

- Aralık birleştirme, girdiyi önce sıralayan ilk kalıptır; ön koşulu verinin değil
  **seçilen ölçütün** bir özelliğidir.
- Başlangıca göre sıralamak 40 girdinin 40'ında doğru; bitişe göre sıralamak **29**,
  uzunluğa göre sıralamak ve sıralamamak **40** girdide kâhinden ayrılıyor.
- Sıralamayı atlamak kalıbı 2154 adımdan 920 adıma indiriyor ve **bütün girdilerde**
  yanlış yapıyor; en hızlı satır en bozuk satırdır.
- İkinci dağarcıkta bitişe göre ayrılan girdi 32; oran aynı büyüklük düzeninde kaldığı için
  sonuç dağarcığa bağlı değildir.
- Örtüşme sayımında aynı koordinattaki olay sırası tek başına **8 girdide** yanıtı
  değiştiriyor; adım sayısı iki durumda da birebir aynı kalıyor.

#### Sonraki Adım

Bu kalıp sıralamanın bedelini ödeyip karşılığında tek geçiş aldı. Sonraki kalıp sıralamayı
bütünüyle atlar: değerlerin **kendi konumlarını bildiği** bir aralıkta, her değer doğrudan
gideceği yere yerleştirilir ve karşılaştırma hiç yapılmaz. Ön koşulu ağırdır — değerler
1 ile n arasında ve tekrarsız olmalıdır — ve bozulduğunda kalıp yalnız yanlış yanıt
vermez, dönüp durur. Sonraki ders bu iki kusuru ayrı ayrı sayacak.

### Döngüsel Yerleştirme

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/donguscel-yerlestirme

Sınırlı değer aralığında karşılaştırmasız yerinde yerleştirme; tekrarlı değerde durmayan kalıp, aralık dışı değerde 40 yanlış yanıt ve korumanın neyi çözmediği.

Önceki kalıp sıralamanın bedelini ödeyip karşılığında tek geçiş aldı. Bu dersin kalıbı
sıralamayı bütünüyle atlar. Fikri tek cümledir: **değer, gideceği yeri kendisi söylüyorsa
karşılaştırmaya gerek yoktur.** Değerler 1 ile n arasındaysa, `v` değerinin yeri `v-1`
konumudur; her değer doğrudan evine gönderilir ve evden çıkan değer sıradaki gönderiyi
belirler.

Karşılığında ödenen şey ağır bir ön koşuldur ve aslında **iki ayrı** ön koşuldur: değerler
**1 ile n arasında** olmalı ve değerler **tekrarsız** olmalı. Bu ders ikisini ayrı ayrı
bozar, çünkü bozulmalarının sonucu da ayrıdır: biri yanlış yanıt üretir, öbürü **hiç yanıt
üretmez**.

#### Kalıbın Fikri ve İki Ön koşulu

Kalıp bir konumda durur ve oradaki değere bakar. Değer evinde değilse, değeri evine
gönderir; evden çıkan yeni değer aynı konuma düşer ve aynı işlem yinelenir. Değer evindeyse
bir sonraki konuma geçilir. Her takas **en az bir değeri kalıcı olarak evine** koyduğu
için toplam takas sayısı n'i aşamaz.

Bu sayının güvencesi doğrudan ön koşuldan gelir. Bir değer evine gönderildiğinde orada başka
bir değer varsa, o değer **farklı** olmalıdır; aksi hâlde takas hiçbir şeyi ilerletmez ve
aynı iki değer sonsuza kadar yer değiştirir.

**PK32.** Dizi 12 değerlidir ve hedef aralık 1..12'dir; tohum `20260218`.
**PK33.** Üç öbek vardır ve üçü de aynı üreteçten gelir: **1..n yerleşimi** (tekrarsız ve
aralık içi), **tekrarlı değerler** ve iki değeri **aralık dışına** taşınmış diziler.
**PK34.** Kâhin, Algoritmalar kursunda ölçülen karşılaştırmalı yordamlardan birini kullanır
ve her karşılaştırmayı bir adım sayar. Yordamın kendisi burada tekrarlanmaz.
**PK35.** Kalıba bir **adım sınırı** konur (400 adım). Sınıra dayanan koşum `durmadi`
döndürür ve kâhinden ayrılmış sayılır.

```python
TOHUM, UZUNLUK, DAGARCIK = 20260218, 12, 40


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def deger_dagarcik(tohum=TOHUM, n=DAGARCIK, uzunluk=UZUNLUK):
    """Uc obek: 1..n yerlesimi; tekrarli degerler; aralik disi deger tasiyanlar."""
    r = uretec(tohum)
    kume = []
    for i in range(n):
        yerlesim = list(range(1, uzunluk + 1))
        for j in range(uzunluk, 1, -1):
            k = r(j)
            yerlesim[j - 1], yerlesim[k] = yerlesim[k], yerlesim[j - 1]
        tekrarli = [r(uzunluk) + 1 for _ in range(uzunluk)]
        disarili = list(yerlesim)
        for _ in range(2):
            disarili[r(uzunluk)] = uzunluk + 2 + r(uzunluk)
        kume.append({"no": i + 1, "yerlesim": yerlesim,
                     "tekrarli": tekrarli, "disarili": disarili})
    return kume


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_yerlesim(dizi, s):
    """Karsilastirmali secmeli yerlestirme. Her zaman dogru, her zaman pahali."""
    a = list(dizi)
    for i in range(len(a)):
        en = i
        for j in range(i + 1, len(a)):
            s.say()
            if a[j] < a[en]:
                en = j
        a[i], a[en] = a[en], a[i]
    return a


def kalip_yalin(dizi, s, sinir=400):
    """ONKOSUL: degerler 1..n araliginda ve TEKRARSIZ olmali."""
    a, n = list(dizi), len(dizi)
    i = 0
    while i < n:
        s.say()
        if s.adim > sinir:
            return "durmadi"
        h = a[i] - 1
        if 0 <= h < n and h != i:
            a[i], a[h] = a[h], a[i]
        else:
            i += 1
    return a


def kalip_korumali(dizi, s, sinir=400):
    """Tekrar korumasi eklendi: evdeki deger ayniysa takas edilmez."""
    a, n = list(dizi), len(dizi)
    i = 0
    while i < n:
        s.say()
        if s.adim > sinir:
            return "durmadi"
        h = a[i] - 1
        if 0 <= h < n and a[i] != a[h]:
            a[i], a[h] = a[h], a[i]
        else:
            i += 1
    return a


OBEK = (("1..n yerlesimi     ", "yerlesim"),
        ("tekrarli deger     ", "tekrarli"),
        ("aralik disi deger  ", "disarili"))

K = deger_dagarcik()
print("dagarcik:", len(K), "dizi x", UZUNLUK, "deger | ornekler:")
print("  yerlesim:", K[0]["yerlesim"])
print("  tekrarli:", K[0]["tekrarli"])
print("  disarili:", K[0]["disarili"])
print()
print("kalip      obek                 ayrilan/40  kalip  kahin   oran")
for kad, kalip in (("yalin    ", kalip_yalin), ("korumali ", kalip_korumali)):
    for ad, anahtar in OBEK:
        ayrilan, ak, ah = 0, 0, 0
        for k in K:
            s1, s2 = Sayac(), Sayac()
            a = kalip(k[anahtar], s1)
            b = kahin_yerlesim(k[anahtar], s2)
            ak, ah = ak + s1.adim, ah + s2.adim
            ayrilan += (a != b)
        print(f"{kad}  {ad}  {ayrilan:8d}  {ak:5d}  {ah:5d}  {ah / ak:5.2f}")
```

```
dagarcik: 40 dizi x 12 deger | ornekler:
  yerlesim: [5, 9, 3, 4, 7, 10, 1, 12, 11, 2, 6, 8]
  tekrarli: [3, 4, 1, 2, 3, 4, 5, 6, 7, 12, 9, 2]
  disarili: [5, 9, 3, 4, 7, 18, 1, 12, 11, 2, 6, 16]

kalip      obek                 ayrilan/40  kalip  kahin   oran
yalin      1..n yerlesimi              0    826   2640   3.20
yalin      tekrarli deger             40  16040   2640   0.16
yalin      aralik disi deger          40    801   2640   3.30
korumali   1..n yerlesimi              0    826   2640   3.20
korumali   tekrarli deger             39    750   2640   3.52
korumali   aralik disi deger          40    801   2640   3.30
```

Birinci satır kalıbın vaadidir: 40 girdinin 40'ında kâhinle aynı yerleşim, **826** adıma
karşı **2640**, oran **3,20**. Karşılaştırma hiç yapılmadığı hâlde sonuç doğrudur.

İkinci satır, bu konudaki en uç sonucu taşıyor. Tekrarlı değerlerde yalın kalıp
**16.040** adım harcıyor ve oran **0,16'ya** düşüyor — kâhinden **altı kat pahalı**. Bu
sayı bir yavaşlama değil, bir **durmama** ölçüsüdür: 40 girdinin 40'ı da 400 adımlık
sınıra dayanıyor. Sınır konmasaydı ilk girdide ölçüm biterdi ve hiç sonuç alınamazdı.

Üçüncü satır bambaşka bir kusuru gösteriyor. Aralık dışı değerlerde kalıp **801** adımda
biriyor, oran **3,30** — birinci satırdan bile **hızlı** — ve **40 girdinin 40'ında**
yanlış. Aralık dışı bir değerin evi yoktur; kalıp onu olduğu yerde bırakıp ilerler ve
geriye kalan yerleşim kâhinin ürettiğinden farklı olur. Hiçbir uyarı, hiçbir gecikme,
hiçbir belirti yoktur.

#### Koruma Durmamayı Çözer, Yanlışı Çözmez

Sonsuz takası durduran değişiklik tek bir karşılaştırmadır: değeri evine göndermeden önce
evde **aynı değerin** olup olmadığına bakmak. Aynıysa gönderim anlamsızdır ve konum
ilerletilir.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def deger_dagarcik(tohum, n=40, uzunluk=12):
    r = uretec(tohum)
    kume = []
    for i in range(n):
        yerlesim = list(range(1, uzunluk + 1))
        for j in range(uzunluk, 1, -1):
            k = r(j)
            yerlesim[j - 1], yerlesim[k] = yerlesim[k], yerlesim[j - 1]
        tekrarli = [r(uzunluk) + 1 for _ in range(uzunluk)]
        disarili = list(yerlesim)
        for _ in range(2):
            disarili[r(uzunluk)] = uzunluk + 2 + r(uzunluk)
        kume.append({"yerlesim": yerlesim, "tekrarli": tekrarli,
                     "disarili": disarili})
    return kume


def kahin_yerlesim(dizi):
    a = list(dizi)
    for i in range(len(a)):
        en = i
        for j in range(i + 1, len(a)):
            if a[j] < a[en]:
                en = j
        a[i], a[en] = a[en], a[i]
    return a


def kalip(dizi, koruma, sinir=400):
    a, n, i, adim = list(dizi), len(dizi), 0, 0
    while i < n:
        adim += 1
        if adim > sinir:
            return "durmadi"
        h = a[i] - 1
        if 0 <= h < n and ((a[i] != a[h]) if koruma else (h != i)):
            a[i], a[h] = a[h], a[i]
        else:
            i += 1
    return a


ornek = deger_dagarcik(20260218)[0]
print("tekrarli girdi:", ornek["tekrarli"])
print("  yalin kalip   :", kalip(ornek["tekrarli"], False))
print("  korumali kalip:", kalip(ornek["tekrarli"], True))
print("  kahin         :", kahin_yerlesim(ornek["tekrarli"]))
print()
print("tohum      kalip     obek       ayrilan/40   oran")
for tohum in (20260218, 20260219):
    K = deger_dagarcik(tohum)
    for kad, koruma in (("yalin   ", False), ("korumali", True)):
        for anahtar in ("yerlesim", "tekrarli", "disarili"):
            ayrilan = sum(1 for k in K
                          if kalip(k[anahtar], koruma) != kahin_yerlesim(k[anahtar]))
            print(f"{tohum}  {kad}  {anahtar:9s}  {ayrilan:8d}   {ayrilan / 40:.4f}")
```

```
tekrarli girdi: [3, 4, 1, 2, 3, 4, 5, 6, 7, 12, 9, 2]
  yalin kalip   : durmadi
  korumali kalip: [1, 2, 3, 4, 5, 6, 7, 4, 9, 2, 3, 12]
  kahin         : [1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 9, 12]

tohum      kalip     obek       ayrilan/40   oran
20260218  yalin     yerlesim          0   0.0000
20260218  yalin     tekrarli         40   1.0000
20260218  yalin     disarili         40   1.0000
20260218  korumali  yerlesim          0   0.0000
20260218  korumali  tekrarli         39   0.9750
20260218  korumali  disarili         40   1.0000
20260219  yalin     yerlesim          0   0.0000
20260219  yalin     tekrarli         40   1.0000
20260219  yalin     disarili         39   0.9750
20260219  korumali  yerlesim          0   0.0000
20260219  korumali  tekrarli         40   1.0000
20260219  korumali  disarili         39   0.9750
```

Örnek girdi mekanizmayı açıkça gösteriyor. Yalın kalıp `durmadi` döndürüyor. Korumalı kalıp
duruyor ve `[1, 2, 3, 4, 5, 6, 7, 4, 9, 2, 3, 12]` üretiyor; kâhinin yanıtı
`[1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 9, 12]`. İkisi aynı değil — **koruma durmamayı çözdü, yanlışı
çözmedi.** Ayrılan girdi 40'tan **39'a** indi; bu, çözünürlük kuralına göre **ölçülmemiş
sayılacak** bir fark bile değildir, çünkü aynı büyüklükte kalmıştır.

**PK36.** İkinci dağarcık `20260219` tohumundan gelir. On iki satırın on ikisinde ayrılan
girdi ya 0 ya 39–40'tır; sonuç dağarcığa bağlı değildir.
**PK37.** 40 girdide 39 ile 40 arasındaki fark **ölçülmemiş sayılır**; iki dağarcık
arasındaki yer değiştirmeler (tekrarlıda 39/40, aralık dışında 40/39) bu nedenle bir
eğilim değildir.

#### Ön koşul Kalıba Değil, Kalıp–Soru Çiftine Aittir

Buraya kadar tek bir soru soruldu: **yerleşimin kendisi ne olacak.** Aynı kalıp, aynı bozuk
girdilerle, başka bir soruya yanıt vermek için de kullanılır: **1..n aralığından hangi
değerler eksik.** Bu soruda kalıp yerleşimi bir amaç olarak değil, bir **ara ürün** olarak
kullanır; evinde olmayan konumlar doğrudan eksik değerleri verir.

**PK38.** Bu ölçümde kalıp **korumalı** biçimdir ve yanıt, evinde olmayan konumların
listesidir. Kâhin her değeri diziyi tarayarak arar.
**PK39.** Soru değişti; kalıp, kâhin, dağarcık ve tohum değişmedi.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def deger_dagarcik(tohum, n=40, uzunluk=12):
    r = uretec(tohum)
    kume = []
    for i in range(n):
        yerlesim = list(range(1, uzunluk + 1))
        for j in range(uzunluk, 1, -1):
            k = r(j)
            yerlesim[j - 1], yerlesim[k] = yerlesim[k], yerlesim[j - 1]
        tekrarli = [r(uzunluk) + 1 for _ in range(uzunluk)]
        disarili = list(yerlesim)
        for _ in range(2):
            disarili[r(uzunluk)] = uzunluk + 2 + r(uzunluk)
        kume.append({"yerlesim": yerlesim, "tekrarli": tekrarli,
                     "disarili": disarili})
    return kume


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_eksik(dizi, s):
    """1..n arasindaki her degeri diziyi tarayarak arar."""
    eksik = []
    for v in range(1, len(dizi) + 1):
        bulundu = False
        for x in dizi:
            s.say()
            if x == v:
                bulundu = True
                break
        if not bulundu:
            eksik.append(v)
    return eksik


def kalip_eksik(dizi, s, sinir=400):
    """Once yerlestirir, sonra evinde olmayan konumlari toplar."""
    a, n = list(dizi), len(dizi)
    i = 0
    while i < n:
        s.say()
        if s.adim > sinir:
            return "durmadi"
        h = a[i] - 1
        if 0 <= h < n and a[i] != a[h]:
            a[i], a[h] = a[h], a[i]
        else:
            i += 1
    eksik = []
    for j in range(n):
        s.say()
        if a[j] != j + 1:
            eksik.append(j + 1)
    return eksik


print("tohum      obek       ayrilan/40  kalip  kahin   oran")
for tohum in (20260218, 20260219):
    K = deger_dagarcik(tohum)
    for anahtar in ("yerlesim", "tekrarli", "disarili"):
        ayrilan, ak, ah = 0, 0, 0
        for k in K:
            s1, s2 = Sayac(), Sayac()
            a = kalip_eksik(k[anahtar], s1)
            b = kahin_eksik(k[anahtar], s2)
            ak, ah = ak + s1.adim, ah + s2.adim
            ayrilan += (a != b)
        print(f"{tohum}  {anahtar:9s}  {ayrilan:8d}  {ak:5d}  {ah:5d}"
              f"  {ah / ak:5.2f}")
print()
K = deger_dagarcik(20260218)
print("ornek aralik disi dizi:", K[0]["disarili"])
print("  kalip eksik:", kalip_eksik(K[0]["disarili"], Sayac()))
print("  kahin eksik:", kahin_eksik(K[0]["disarili"], Sayac()))
```

```
tohum      obek       ayrilan/40  kalip  kahin   oran
20260218  yerlesim          0   1306   3120   2.39
20260218  tekrarli          0   1230   3531   2.87
20260218  disarili          0   1281   3536   2.76
20260219  yerlesim          0   1313   3120   2.38
20260219  tekrarli          0   1233   3525   2.86
20260219  disarili          0   1294   3548   2.74

ornek aralik disi dizi: [5, 9, 3, 4, 7, 18, 1, 12, 11, 2, 6, 16]
  kalip eksik: [8, 10]
  kahin eksik: [8, 10]
```

Altı satırın altısında ayrılan girdi **sıfır**. Aynı kalıp, aynı tekrarlı ve aralık dışı
girdiler, iki dağarcık — ve hiçbir ayrılma yok. Bir önceki tabloda 39 ve 40 yazan satırlar
burada 0 yazıyor.

Değişen tek şey **soru**dur. "Yerleşim ne olacak" sorusu, dizinin bütün değerlerinin bir
eve sahip olmasını gerektirir; "hangi değerler eksik" sorusu bunu gerektirmez, çünkü evsiz
bir değerin nerede durduğu yanıtı etkilemez. Kalıbın ön koşulu, kalıbın kendi kodunda değil,
**kalıbın hangi soruya yanıt verdiğinde** yatar.

Buradan çıkan kural, konunun tamamı için geçerlidir: **bir kalıbın ön koşulu, kalıbın adı
sorulduğunda değil, sorulan soru sabitlendiğinde tanımlanır.** "Döngüsel yerleştirme
tekrarlı değerle çalışmaz" cümlesi bu tabloda yanlıştır; doğru cümle, "döngüsel yerleştirme
tekrarlı değerle **yerleşim sorusuna** doğru yanıt vermez" cümlesidir.

#### Üç Sayı

| Ölçüt | Kâhin | Kalıp | Ayrılan girdi |
|---|---|---|---|
| Yerleşim, 1..n değerleri | 2640 adım | 826 adım | **0/40** |
| Yerleşim, tekrarlı, yalın kalıp | 2640 adım | 16.040 adım | **40/40** |
| Yerleşim, tekrarlı, korumalı kalıp | 2640 adım | 750 adım | **39/40** |
| Yerleşim, aralık dışı | 2640 adım | 801 adım | **40/40** |
| Eksik değer, aralık dışı | 3536 adım | 1281 adım | **0/40** |

İkinci satır durmamanın, üçüncü satır sessiz yanlışın, beşinci satır ise sorunun
değişmesinin ölçüsüdür. Beş satırın hiçbirinde adım sütununa bakarak doğru satırlar seçilemez.

#### Özet

- Döngüsel yerleştirme hiç karşılaştırma yapmaz; her değeri doğrudan `v-1` konumuna
  gönderir ve toplam takas sayısı n'i aşmaz.
- Kalıbın iki ayrı ön koşulu vardır ve bozulmalarının sonucu ayrıdır: tekrarlı değer
  **durmamaya**, aralık dışı değer **sessiz yanlışa** yol açar.
- Yalın kalıp tekrarlı girdide 40 girdinin 40'ında adım sınırına dayanıyor ve oran 0,16'ya
  düşüyor; aralık dışı girdide 801 adımda biriyor ve 40 girdinin 40'ında yanlış.
- Tekrar koruması durmamayı ortadan kaldırıyor ama yerleşim sorusunda ayrılan girdiyi
  40'tan yalnız 39'a indiriyor.
- Aynı kalıp "hangi değerler eksik" sorusuna yanıt verdiğinde altı ölçümün altısında
  ayrılan girdi sıfırdır; ön koşul kalıba değil, kalıp–soru çiftine aittir.

#### Sonraki Adım

Buraya kadarki beş kalıp girdinin tamamını elinde tuttu: diziyi baştan sona görebiliyor,
istediği konuma dönebiliyordu. Sonraki kalıp bu olanağı kaybeder. Veriler **tek tek akar**
ve her yeni değerden sonra bir soru yanıtlanmalıdır: o ana kadar görülenlerin ortancası
nedir. Kalıp iki yığın tutar ve ön koşulu bu iki yığının **dengede** kalmasıdır. Sonraki
ders dengeyi bozunca kaç adımda kaç yanlış ortanca çıktığını sayacak.

### İki Yığın

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/iki-yigin

Akan veride ortanca izleme; denge adımı kaldırıldığında 480 ortancanın 295'inin bozulması ve kalıbın kazancının akış uzadıkça büyümesi.

Önceki beş kalıp girdinin tamamını elinde tuttu: dizi baştan sona görülebiliyor, istenen
konuma dönülebiliyordu. Bu dersin kalıbı o olanağı kaybeder. Değerler **tek tek akar** ve
her yeni değerden sonra bir soru yanıtlanmalıdır: o ana kadar görülenlerin **ortancası**
nedir.

Ortancayı bulmanın doğrudan yolu sıralı bir liste tutmaktır; her yeni değer doğru yere
sokulur ve ortadaki okunur. Kalıp bunun yerine **iki yığın** tutar: küçük yarı bir **en
büyük yığında**, büyük yarı bir **en küçük yığında**. Ortanca, iki yığının tepesinden
okunur. Yığın koşulu, süzme işlemleri ve dizi gösterimi Veri Yapıları kursunun Yığınlar
dersinde kuruldu; burada **tekrarlanmaz**, doğrudan kullanılır.

Kalıbın ön koşulu iki yığının **dengede** kalmasıdır. Bu ders dengeyi bozup kaç ortancanın
yanlış çıktığını sayar.

#### Kalıp ve Denge Ön koşulu

Her yeni değer önce yerine konur: küçük yarının tepesinden büyük değilse en büyük yığına,
değilse en küçük yığına. Bu tek başına yetmez, çünkü değerler bir yana yığılabilir. İkinci
adım **denge adımıdır**: yığınların boyu birden fazla ayrıldığında büyük olanın tepesi
öbürüne aktarılır.

Ortanca ancak iki yığının boyu doğruyken okunabilir. Toplam tek sayıdaysa büyük olan
yığının tepesi ortancadır; çift sayıdaysa iki tepenin ortalaması. Denge bozuksa bu iki
kural da yanlış yeri gösterir — kalıp yine bir sayı döndürür.

**PK40.** Dağarcık 40 akıştır; her akış 12 değer taşır ve değerler −9 ile 20 arasındadır.
Tohum `20260218`. Her akışta her değerden sonra bir ortanca sorulur; toplam **480** soru.
**PK41.** Kâhin her değeri **doğrusal tarama** ile sıralı listeye sokar ve ortadakini okur.
Her karşılaştırma bir adım sayılır.
**PK42.** Kalıbın adımı, yığın işlemlerinin yaptığı **gerçek karşılaştırmalardır**; değerler
karşılaştırmayı sayan bir sarmalayıcıya konur, adım tahmin edilmez.
**PK43.** Ön koşulu bozan tek şey **denge adımının kaldırılmasıdır**. Değer yerleştirme
kuralı, ortanca okuma kuralı, dağarcık ve tohum aynıdır.

```python
import heapq

TOHUM, UZUNLUK, DAGARCIK = 20260218, 12, 40


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def akis_dagarcik(tohum=TOHUM, n=DAGARCIK, uzunluk=UZUNLUK):
    r = uretec(tohum)
    return [{"no": i + 1, "akis": [r(30) - 9 for _ in range(uzunluk)]}
            for i in range(n)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


class Olcu:
    """Yiginin her karsilastirmasini sayan sarmalayici. yon=-1 en buyuk yigin verir."""
    __slots__ = ("d", "yon", "s")

    def __init__(self, d, yon, s):
        self.d, self.yon, self.s = d, yon, s

    def __lt__(self, o):
        self.s.say()
        return self.d * self.yon < o.d * o.yon


def kahin_ortanca(akis, s):
    """Her degeri dogrusal tarama ile sirali listeye sokar, ortadakini okur."""
    sirali, yanit = [], []
    for x in akis:
        i = 0
        while i < len(sirali):
            s.say()
            if sirali[i] >= x:
                break
            i += 1
        sirali.insert(i, x)
        n = len(sirali)
        yanit.append(sirali[n // 2] if n % 2
                     else (sirali[n // 2 - 1] + sirali[n // 2]) / 2)
    return yanit


def kalip_iki_yigin(akis, dengele, s):
    """ONKOSUL: iki yigin dengede kalmali. alt en buyuk yigin, ust en kucuk yigin."""
    alt, ust, yanit = [], [], []
    en_buyuk_fark = 0
    for x in akis:
        if not alt or x <= alt[0].d:
            heapq.heappush(alt, Olcu(x, -1, s))
        else:
            heapq.heappush(ust, Olcu(x, 1, s))
        if dengele:
            if len(alt) > len(ust) + 1:
                heapq.heappush(ust, Olcu(heapq.heappop(alt).d, 1, s))
            elif len(ust) > len(alt):
                heapq.heappush(alt, Olcu(heapq.heappop(ust).d, -1, s))
        en_buyuk_fark = max(en_buyuk_fark, abs(len(alt) - len(ust)))
        if not alt:
            yanit.append(ust[0].d)
        elif not ust or len(alt) > len(ust):
            yanit.append(alt[0].d)
        elif len(ust) > len(alt):
            yanit.append(ust[0].d)
        else:
            yanit.append((alt[0].d + ust[0].d) / 2)
    return yanit, en_buyuk_fark


K = akis_dagarcik()
print("dagarcik:", len(K), "akis x", UZUNLUK, "deger =", len(K) * UZUNLUK, "ortanca")
print("kurulum       yanlis ortanca  ayrilan akis/40  kalip  kahin   oran  en buyuk fark")
for ad, dengele in (("dengeli   ", True), ("dengesiz  ", False)):
    yanlis, ayrilan, ak, ah, fark = 0, 0, 0, 0, 0
    for k in K:
        s1, s2 = Sayac(), Sayac()
        a, f = kalip_iki_yigin(k["akis"], dengele, s1)
        b = kahin_ortanca(k["akis"], s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        fark = max(fark, f)
        bozuk = sum(1 for x, y in zip(a, b) if x != y)
        yanlis += bozuk
        ayrilan += bozuk > 0
    print(f"{ad}  {yanlis:14d}  {ayrilan:15d}  {ak:5d}  {ah:5d}  {ah / ak:5.2f}"
          f"  {fark:13d}")
```

```
dagarcik: 40 akis x 12 deger = 480 ortanca
kurulum       yanlis ortanca  ayrilan akis/40  kalip  kahin   oran  en buyuk fark
dengeli                  0                0   1261   1693   1.34              1
dengesiz               295               39    550   1693   3.08             12
```

Dengeli kurulumda **480 ortancanın 480'i** kâhinle aynı; en büyük boy farkı **1**, yani
denge hiç bozulmuyor. Denge adımı kaldırıldığında **295 ortanca** yanlış çıkıyor ve 40
akışın **39'u** bozuluyor.

İkinci satırın adım sütunu dersin ana çelişkisini taşıyor: dengesiz kalıp **550** adım
harcıyor, dengelinin **yarısından az**. Denge adımı, kalıbın toplam işinin büyük bölümünü
oluşturuyor; onu kaldırmak kalıbı kâhine göre 1,34 kattan **3,08 kata** hızlandırıyor. En
hızlı kurulum yine en bozuk kurulumdur. En büyük boy farkı da 1'den **12'ye** çıkıyor:
bütün değerler tek bir yığında birikiyor.

#### Dengesizlik Nasıl Yanlış Ortanca Üretiyor

Denge adımı olmadan yerleştirme kuralı kendi kendini besler. Küçük bir değer geldiğinde
küçük yarıya konur; küçük yarının tepesi daha da küçülmez, ama yarı büyür. Bir sonraki
değer o büyümüş yarının tepesiyle karşılaştırılır ve çoğunlukla yine oraya düşer. Bir yığın
şişerken öbürü boş kalır ve ortanca okuma kuralı sürekli **aynı** tepeye bakar.

```python
import heapq


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def akis_dagarcik(tohum, n=40, uzunluk=12):
    r = uretec(tohum)
    return [[r(30) - 9 for _ in range(uzunluk)] for _ in range(n)]


def kahin_ortanca(akis):
    sirali, yanit = [], []
    for x in akis:
        i = 0
        while i < len(sirali) and sirali[i] < x:
            i += 1
        sirali.insert(i, x)
        n = len(sirali)
        yanit.append(sirali[n // 2] if n % 2
                     else (sirali[n // 2 - 1] + sirali[n // 2]) / 2)
    return yanit


def kalip_iki_yigin(akis, dengele):
    """alt en buyuk yigin (isaret ters cevrilerek), ust en kucuk yigin."""
    alt, ust, yanit = [], [], []
    for x in akis:
        if not alt or x <= -alt[0]:
            heapq.heappush(alt, -x)
        else:
            heapq.heappush(ust, x)
        if dengele:
            if len(alt) > len(ust) + 1:
                heapq.heappush(ust, -heapq.heappop(alt))
            elif len(ust) > len(alt):
                heapq.heappush(alt, -heapq.heappop(ust))
        if not alt:
            yanit.append(ust[0])
        elif not ust or len(alt) > len(ust):
            yanit.append(-alt[0])
        elif len(ust) > len(alt):
            yanit.append(ust[0])
        else:
            yanit.append((-alt[0] + ust[0]) / 2)
    return yanit


ilk = akis_dagarcik(20260218)[0]
print("akis 1     :", ilk)
print("  kahin    :", kahin_ortanca(ilk))
print("  dengeli  :", kalip_iki_yigin(ilk, True))
print("  dengesiz :", kalip_iki_yigin(ilk, False))
print()
print("tohum      kurulum    yanlis ortanca/480  ayrilan akis/40  ilk yanlis konum")
for tohum in (20260218, 20260219):
    K = akis_dagarcik(tohum)
    for ad, dengele in (("dengeli ", True), ("dengesiz", False)):
        yanlis, ayrilan, ilk_konum = 0, 0, []
        for akis in K:
            a, b = kalip_iki_yigin(akis, dengele), kahin_ortanca(akis)
            bozuk = [i + 1 for i, (x, y) in enumerate(zip(a, b)) if x != y]
            yanlis += len(bozuk)
            ayrilan += bool(bozuk)
            if bozuk:
                ilk_konum.append(bozuk[0])
        ortalama = round(sum(ilk_konum) / len(ilk_konum), 2) if ilk_konum else 0
        print(f"{tohum}  {ad}  {yanlis:18d}  {ayrilan:15d}  {ortalama:16}")
```

```
akis 1     : [-8, -5, 2, -1, 2, 5, 4, 1, 16, 17, 6, -1]
  kahin    : [-8, -6.5, -5, -3.0, -1, 0.5, 2, 1.5, 2, 2.0, 2, 2.0]
  dengeli  : [-8, -6.5, -5, -3.0, -1, 0.5, 2, 1.5, 2, 2.0, 2, 2.0]
  dengesiz : [-8, -6.5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5]

tohum      kurulum    yanlis ortanca/480  ayrilan akis/40  ilk yanlis konum
20260218  dengeli                    0                0                 0
20260218  dengesiz                 295               39              3.44
20260219  dengeli                    0                0                 0
20260219  dengesiz                 268               38              3.89
```

Birinci akışın izi, mekanizmayı tek satırda gösteriyor. Dengeli kurulum kâhinin bütün
yanıtlarını üretiyor. Dengesiz kurulum ilk üç ortancayı doğru veriyor, sonra **−5'te
kilitleniyor** ve akışın sonuna kadar aynı sayıyı yazıyor. Değerler 16 ve 17'ye çıksa bile
yanıt değişmiyor, çünkü bütün büyük değerler tek bir yığında birikmiş ve okunan tepe hep
aynı kalmış.

**PK44.** İkinci dağarcık `20260219` tohumundan gelir. Yanlış ortanca **268/480**,
birincide **295/480**; ayrılan akış **38/40** ve **39/40**. Oranlar 0,56 ile 0,61
arasında, aynı büyüklük düzeninde — sonuç dağarcığa bağlı değildir.
**PK45.** İlk yanlış ortanca ortalama **3,44'üncü** değerde çıkıyor. Denge en geç dördüncü
değerde bozuluyor, yani kusur akışın sonunda değil **başında** oluşuyor.

Bu son sayı, kusurun neden gözden kaçmadığından çok neden **kaçtığını** açıklar. İlk üç
yanıt doğrudur; kısa bir sınama akışı bu üç yanıta bakıp geçerse hiçbir şey görmez. Kusur
yalnız akış uzadıkça birikir ve tek bir yanlış ortanca da çıktıya bakarak seçilemez —
dengesiz kalıp yine akla yatkın bir sayı döndürüyor.

#### Kalıbın Kazancı Akış Uzadıkça Büyüyor

On iki değerlik akışta dengeli kalıbın oranı **1,34**. Bu, kalıbı kurmaya değmeyecek kadar
küçük bir kazançtır ve bir soru doğurur: iki yığın niçin var. Yanıt akış uzunluğundadır.
Kâhinin her eklemesi listenin yarısını tarar, yani maliyeti o ana kadarki değer sayısıyla
doğru orantılıdır; yığın eklemesi ise yığının **derinliği** kadar karşılaştırma yapar.

**PK46.** Bu ölçümde akış sayısı 10'a indirilir ve akış uzunluğu 12'den 200'e çıkarılır.
Dağarcık aynı üreteçten, aynı tohumla gelir; değişen tek şey uzunluktur.

```python
import heapq


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def akis_dagarcik(tohum, n, uzunluk):
    r = uretec(tohum)
    return [[r(30) - 9 for _ in range(uzunluk)] for _ in range(n)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


class Olcu:
    """Yiginin her karsilastirmasini sayar. yon=-1 en buyuk yigin verir."""
    __slots__ = ("d", "yon", "s")

    def __init__(self, d, yon, s):
        self.d, self.yon, self.s = d, yon, s

    def __lt__(self, o):
        self.s.say()
        return self.d * self.yon < o.d * o.yon


def kahin_ortanca(akis, s):
    sirali, yanit = [], []
    for x in akis:
        i = 0
        while i < len(sirali):
            s.say()
            if sirali[i] >= x:
                break
            i += 1
        sirali.insert(i, x)
        n = len(sirali)
        yanit.append(sirali[n // 2] if n % 2
                     else (sirali[n // 2 - 1] + sirali[n // 2]) / 2)
    return yanit


def kalip_iki_yigin(akis, s):
    alt, ust, yanit = [], [], []
    for x in akis:
        if not alt or x <= alt[0].d:
            heapq.heappush(alt, Olcu(x, -1, s))
        else:
            heapq.heappush(ust, Olcu(x, 1, s))
        if len(alt) > len(ust) + 1:
            heapq.heappush(ust, Olcu(heapq.heappop(alt).d, 1, s))
        elif len(ust) > len(alt):
            heapq.heappush(alt, Olcu(heapq.heappop(ust).d, -1, s))
        yanit.append(alt[0].d if len(alt) > len(ust)
                     else (alt[0].d + ust[0].d) / 2)
    return yanit


print("akis uzunlugu  ayrilan akis/10  kalip   kahin    oran")
for uzunluk in (12, 25, 50, 100, 200):
    K = akis_dagarcik(20260218, 10, uzunluk)
    ayrilan, ak, ah = 0, 0, 0
    for akis in K:
        s1, s2 = Sayac(), Sayac()
        a = kalip_iki_yigin(akis, s1)
        b = kahin_ortanca(akis, s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        ayrilan += (a != b)
    print(f"{uzunluk:13d}  {ayrilan:15d}  {ak:5d}  {ah:6d}  {ah / ak:6.2f}")
```

```
akis uzunlugu  ayrilan akis/10  kalip   kahin    oran
           12                0    303     431    1.42
           25                0    949    1602    1.69
           50                0   2471    6440    2.61
          100                0   5971   25292    4.24
          200                0  13686   98651    7.21
```

Ayrılan akış beş uzunlukta da **sıfır**: denge korunduğu sürece kalıp uzunluktan bağımsız
olarak doğrudur. Oran ise 1,42'den **7,21'e** çıkıyor. Kâhinin adımı 431'den 98.651'e,
yani **229 kat** artarken kalıbın adımı 303'ten 13.686'ya, **45 kat** artıyor.

Kalıbı kurmanın gerekçesi bu sütundur. On iki değerlik bir akışta iki yığın tutmak
gereksizdir; iki yüz değerlik bir akışta yedi kat, daha uzun akışlarda daha çok kazandırır.
**Bir kalıbın kazancı bir sayı değil, bir eğilimdir** ve tek bir girdi büyüklüğünde
ölçülürse yanlış okunur.

#### Üç Sayı

| Ölçüt | Kâhin | Kalıp | Ayrılan girdi |
|---|---|---|---|
| Dengeli, 12 değerlik akış | 1693 adım | 1261 adım | **0/480 ortanca** |
| Dengesiz, 12 değerlik akış | 1693 adım | 550 adım | **295/480 ortanca** |
| Dengeli, 200 değerlik akış | 98.651 adım | 13.686 adım | **0/10 akış** |

Birinci ve ikinci satır arasındaki tek fark iki satırlık bir denge adımıdır; o iki satır
kalıbın adımının yarısından fazlasını harcar ve **295 yanlış yanıtı** önler. Üçüncü satır,
o bedelin neden ödendiğini gösterir.

Tablonun ayrılan girdi sütununda iki ayrı birim durduğuna dikkat edilmelidir. İlk iki
satırda birim **ortancadır**, çünkü akan veride her değer bir soru üretir ve ölçülmesi
gereken şey kaç sorunun yanlış yanıtlandığıdır. Üçüncü satırda birim **akıştır**, çünkü
orada sorulan şey kalıbın uzunlukla bozulup bozulmadığıdır. İki birimi karıştırmak
oranları karşılaştırılamaz kılar: 295/480 ile 0/10 aynı ölçekte okunamaz. Akan veri
ölçen her kalıpta bu ayrım açıkça yazılmalıdır.

#### Özet

- İki yığın kalıbı akan veride ortancayı, küçük yarıyı en büyük yığında ve büyük yarıyı en
  küçük yığında tutarak iki tepe okumasıyla verir.
- Ön koşul iki yığının dengede kalmasıdır; denge adımı kaldırıldığında 480 ortancanın
  **295'i** yanlış çıkıyor ve 40 akışın 39'u bozuluyor.
- Denge adımı kalıbın adımının yarısından fazlasını harcar: onsuz kalıp 1261 yerine 550
  adım harcıyor ve oran 1,34'ten 3,08'e çıkıyor.
- İlk yanlış ortanca ortalama 3,44'üncü değerde çıkıyor; kusur akışın başında oluşur ama
  ilk birkaç yanıt doğru olduğu için kısa bir sınamada görünmez.
- Kalıbın kazancı akış uzunluğuyla büyür: oran 12 değerde 1,42, 200 değerde 7,21 ve ayrılan
  akış her uzunlukta sıfırdır.

#### Sonraki Adım

İki yığın, ortancayı yani **sıralı düzende ortadaki** değeri verdi. Ortanca özel bir
durumdur; genel soru, sıralı düzende **k'ıncı** değerin ne olduğudur. Sonraki ders bu
soruyu iki ayrı kalıpla yanıtlar — k boyutlu bir yığınla ve bölümlemeyle — ve ikisini de
aynı kâhinle sınar. Orada ön koşul veriye değil **sorunun kendisine** dokunur: tekrarlı
değerlerde "k'ıncı eleman" ile "k'ıncı ayrı değer" aynı şey değildir ve aradaki fark
sayılacaktır.

### K'ıncı Eleman

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/kinci-eleman

Seçim problemlerinde yığın tabanlı ve bölümlemeli çözümler; sorunun tanımı kaydığında 27 girdide ayrılan yanıt ve pivot seçiminin adımı 15 kat büyütmesi.

Önceki kalıp ortancayı, yani sıralı düzende **ortadaki** değeri veriyordu. Ortanca genel bir
sorunun özel bir durumudur: sıralı düzende **k'ıncı** değer nedir. K ortadaysa ortanca, k
bir ise en küçük, k n ise en büyük değer sorulmuş olur.

Bu ders soruyu iki ayrı kalıpla yanıtlar. Birincisi k boyutlu bir **yığın** tutar; ikincisi
diziyi **bölümleyip** yalnız ilgili yarıda arar. İkisi de aynı kâhinle sınanır ve iki farklı
türde ön koşul ortaya çıkar: biri **sorunun tanımına**, öbürü **girdinin düzenine** dokunur.

#### İki Kalıp, Bir Kâhin

Kâhin diziyi karşılaştırarak baştan sona yerleştirir ve k'ıncı konumu okur; her
karşılaştırma bir adım sayılır. Yığın kalıbı yalnız k eleman tutar: her yeni değer en büyük
yığına konur, yığın k'yı aşarsa tepesi atılır. Sonunda tepede duran değer, görülen bütün
değerler arasında k'ıncı en küçük olandır. Yığın koşulu ve süzme işlemleri Veri Yapıları
kursunun Yığınlar dersinde kuruldu; burada tekrarlanmaz.

Aynı yapı bir soruya daha yanıt veriyor gibi görünür: **k'ıncı en küçük ayrı değer.** Tek
fark, daha önce görülmüş bir değerin yığına konmamasıdır. Kod neredeyse aynıdır, çıktı bir
sayıdır, kalıp yine ucuzdur.

**PK47.** Dağarcık önceki derslerin dağarcığıdır: 40 dizi, her biri 12 değer, değerler −9
ile 20 arasında; tohum `20260218`.
**PK48.** Kâhinin yanıtı **k'ıncı elemandır**: sıralı düzende k'ıncı konumdaki değer,
tekrarlar dahil.
**PK49.** Yığın adımları gerçek karşılaştırmalardır; değerler karşılaştırmayı sayan bir
sarmalayıcıya konur.
**PK50.** Dört k değeri süpürülür: 3, 4, 6 ve 8.

```python
import heapq

TOHUM, UZUNLUK, DAGARCIK = 20260218, 12, 40


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum=TOHUM, n=DAGARCIK, uzunluk=UZUNLUK):
    r = uretec(tohum)
    return [{"no": i + 1, "dizi": [r(30) - 9 for _ in range(uzunluk)]}
            for i in range(n)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


class Olcu:
    """Yiginin her karsilastirmasini sayar. yon=-1 en buyuk yigin verir."""
    __slots__ = ("d", "yon", "s")

    def __init__(self, d, yon, s):
        self.d, self.yon, self.s = d, yon, s

    def __lt__(self, o):
        self.s.say()
        return self.d * self.yon < o.d * o.yon


def kahin_kinci(dizi, k, s):
    """Butun diziyi karsilastirarak yerlestirir, k'inci konumu okur."""
    a = list(dizi)
    for i in range(len(a)):
        en = i
        for j in range(i + 1, len(a)):
            s.say()
            if a[j] < a[en]:
                en = j
        a[i], a[en] = a[en], a[i]
    return a[k - 1]


def kalip_yigin(dizi, k, s):
    """k boyutlu en buyuk yigin tutar; tepesi k'inci en kucuk degerdir."""
    y = []
    for x in dizi:
        heapq.heappush(y, Olcu(x, -1, s))
        if len(y) > k:
            heapq.heappop(y)
    return y[0].d


def kalip_ayri_deger(dizi, k, s):
    """AYNI yapi, BASKA soru: k'inci en kucuk AYRI deger."""
    y, gorulen = [], set()
    for x in dizi:
        s.say()
        if x in gorulen:
            continue
        gorulen.add(x)
        heapq.heappush(y, Olcu(x, -1, s))
        if len(y) > k:
            heapq.heappop(y)
    return y[0].d if len(y) == k else None


K40 = dagarcik()
print("dagarcik: 40 dizi x 12 deger | tekrarli deger iceren dizi:",
      sum(1 for k in K40 if len(set(k["dizi"])) < UZUNLUK))
print(" k  kalip              ayrilan/40  kalip  kahin   oran")
for k in (3, 4, 6, 8):
    for ad, kalip in (("k'inci eleman    ", kalip_yigin),
                      ("k'inci ayri deger", kalip_ayri_deger)):
        ayrilan, ak, ah = 0, 0, 0
        for kayit in K40:
            s1, s2 = Sayac(), Sayac()
            a = kalip(kayit["dizi"], k, s1)
            b = kahin_kinci(kayit["dizi"], k, s2)
            ak, ah = ak + s1.adim, ah + s2.adim
            ayrilan += (a != b)
        print(f"{k:2d}  {ad}  {ayrilan:10d}  {ak:5d}  {ah:5d}  {ah / ak:5.2f}")
print()
ilk = K40[0]["dizi"]
print("girdi 1          :", ilk)
print("  sirali duzen   :", sorted(ilk))
print("  kahin k=4      :", kahin_kinci(ilk, 4, Sayac()))
print("  yigin k=4      :", kalip_yigin(ilk, 4, Sayac()))
print("  ayri deger k=4 :", kalip_ayri_deger(ilk, 4, Sayac()))
```

```
dagarcik: 40 dizi x 12 deger | tekrarli deger iceren dizi: 37
 k  kalip              ayrilan/40  kalip  kahin   oran
 3  k'inci eleman               0   1440   2640   1.83
 3  k'inci ayri deger           9   1640   2640   1.61
 4  k'inci eleman               0   1559   2640   1.69
 4  k'inci ayri deger          17   1723   2640   1.53
 6  k'inci eleman               0   1348   2640   1.96
 6  k'inci ayri deger          27   1503   2640   1.76
 8  k'inci eleman               0   1292   2640   2.04
 8  k'inci ayri deger          30   1381   2640   1.91

girdi 1          : [-8, -5, 2, -1, 2, 5, 4, 1, 16, 17, 6, -1]
  sirali duzen   : [-8, -5, -1, -1, 1, 2, 2, 4, 5, 6, 16, 17]
  kahin k=4      : -1
  yigin k=4      : -1
  ayri deger k=4 : 1
```

Yığın kalıbı dört k değerinin dördünde de **40 girdinin 40'ında** kâhinle aynı yanıtı
veriyor; oran 1,69 ile 2,04 arasında. Ayrı değer biçimi ise k büyüdükçe daha çok ayrılıyor:
**9, 17, 27, 30**.

Birinci girdi farkı bir bakışta gösteriyor. Sıralı düzen `[-8, -5, -1, -1, 1, ...]`;
dördüncü eleman **−1**, çünkü −1 iki kez var. Dördüncü **ayrı** değer ise **1**. İki yanıt
da doğrudur — ama iki **ayrı soruya**. Kalıbın kusuru değil, sorunun kaymasıdır.

#### Soru Aynı Görünüyor, Aynı Değil

Ayrılmanın nedeninin gerçekten tekrarlar olduğunu göstermek için bir denetim gerekir: aynı
ölçüm, tekrarları ayıklanmış bir dağarcıkta yinelenir. Tekrar yoksa iki soru **aynı**
soruya dönüşmelidir ve ayrılma sıfır olmalıdır.

**PK51.** Denetim dağarcığı, aynı dizilerin ayrı değerlerinden oluşur; başka bir üreteçten
gelmez. Diziler kısaldığı için yalnız k=3 ve k=6 ölçülür.
**PK52.** İkinci dağarcık `20260219` tohumundan gelir ve aynı dört k değeriyle süpürülür.

```python
import heapq


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum, n=40, uzunluk=12):
    r = uretec(tohum)
    return [[r(30) - 9 for _ in range(uzunluk)] for _ in range(n)]


def kahin_kinci(dizi, k):
    a = list(dizi)
    for i in range(len(a)):
        en = i
        for j in range(i + 1, len(a)):
            if a[j] < a[en]:
                en = j
        a[i], a[en] = a[en], a[i]
    return a[k - 1]


def kalip_yigin(dizi, k):
    y = []
    for x in dizi:
        heapq.heappush(y, -x)
        if len(y) > k:
            heapq.heappop(y)
    return -y[0]


def kalip_ayri_deger(dizi, k):
    y, gorulen = [], set()
    for x in dizi:
        if x in gorulen:
            continue
        gorulen.add(x)
        heapq.heappush(y, -x)
        if len(y) > k:
            heapq.heappop(y)
    return -y[0] if len(y) == k else None


print("tohum      k  k'inci eleman  k'inci ayri deger  ayri deger orani")
for tohum in (20260218, 20260219):
    K = dagarcik(tohum)
    for k in (3, 4, 6, 8):
        e = sum(1 for d in K if kalip_yigin(d, k) != kahin_kinci(d, k))
        f = sum(1 for d in K if kalip_ayri_deger(d, k) != kahin_kinci(d, k))
        print(f"{tohum}  {k:2d}  {e:13d}  {f:17d}  {f / 40:16.4f}")
print()
print("tekrarsiz dagarcikta ayni olcum (her dizinin ayri degerleri):")
for tohum in (20260218, 20260219):
    K = [sorted(set(d)) for d in dagarcik(tohum)]
    for k in (3, 6):
        f = sum(1 for d in K if kalip_ayri_deger(d, k) != kahin_kinci(d, k))
        print(f"  {tohum}  k={k}  ayrilan {f}/40")
```

```
tohum      k  k'inci eleman  k'inci ayri deger  ayri deger orani
20260218   3              0                  9            0.2250
20260218   4              0                 17            0.4250
20260218   6              0                 27            0.6750
20260218   8              0                 30            0.7500
20260219   3              0                 12            0.3000
20260219   4              0                 24            0.6000
20260219   6              0                 28            0.7000
20260219   8              0                 32            0.8000

tekrarsiz dagarcikta ayni olcum (her dizinin ayri degerleri):
  20260218  k=3  ayrilan 0/40
  20260218  k=6  ayrilan 0/40
  20260219  k=3  ayrilan 0/40
  20260219  k=6  ayrilan 0/40
```

Denetim kesin: tekrarları ayıklanmış dağarcıkta dört ölçümün dördünde ayrılan girdi
**sıfır**. Ayrılmanın kaynağı kalıpta değil, dağarcıktaki tekrarlarda ve sorunun
tanımındadır.

İkinci dağarcıkta oranlar 0,30 ile 0,80 arasında, birincide 0,2250 ile 0,7500 arasında —
**aynı büyüklük düzeninde** ve aynı yönde artıyor. Sonuç dağarcığa bağlı değildir. Yığın
kalıbının k'ıncı eleman sütunu sekiz satırın sekizinde **sıfırdır**.

Buradan çıkan uyarı, konunun en pratik uyarısıdır. İki soru arasındaki fark **kodda tek bir
satırdır** ve o satır bir iyileştirme gibi görünür: aynı değeri iki kez yığına koymamak
yığını küçültür, adım sayısını düşürür. Sekiz satırın hiçbirinde bu satırın yanlış olduğunu
söyleyen bir belirti yoktur; belirti yalnız kâhinin sütunundadır.

#### Bölümlemenin Ön koşulu Pivot Seçimidir

İkinci kalıp diziyi bir **pivot** çevresinde bölümler: pivottan küçükler sola, büyükler
sağa geçer ve pivot kendi son konumuna oturur. O konum k'ıncıysa yanıt bulunmuştur;
değilse arama yalnız bir yanda sürer. Bölümleme yordamı Algoritmalar kursunda hızlı
sıralama içinde kuruldu; burada tekrarlanmaz.

Bu kalıbın ön koşulu doğruluğa değil **adıma** dokunur: pivotun bölmeyi gerçekten ikiye
ayırması gerekir. Pivot her seferinde uçtaki değere denk gelirse bölme bir eleman kadar
ilerler ve arama alanı yavaş küçülür.

**PK53.** İki pivot seçimi karşılaştırılır: bölümün **ilk** değeri ve **orta** değeri.
**PK54.** k, dizinin ortasıdır (`uzunluk // 2`), yani en çok bölümleme gerektiren konum.
**PK55.** Ölçüm iki uzunlukta yinelenir: 12 ve 60 değer. Sıralı öbek, aynı dizilerin
sıralanmış biçimidir.

```python
def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum, n=40, uzunluk=12):
    r = uretec(tohum)
    return [[r(30) - 9 for _ in range(uzunluk)] for _ in range(n)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_kinci(dizi, k, s):
    a = list(dizi)
    for i in range(len(a)):
        en = i
        for j in range(i + 1, len(a)):
            s.say()
            if a[j] < a[en]:
                en = j
        a[i], a[en] = a[en], a[i]
    return a[k - 1]


def kalip_bolumleme(dizi, k, pivot_ilk, s):
    """Bolumleme ile secim. ONKOSUL: pivot secimi girdinin duzeniyle ortusmemeli."""
    a, sol, sag = list(dizi), 0, len(dizi) - 1
    while sol < sag:
        p = sol if pivot_ilk else (sol + sag) // 2
        a[p], a[sag] = a[sag], a[p]
        pivot, i = a[sag], sol
        for j in range(sol, sag):
            s.say()
            if a[j] < pivot:
                a[i], a[j] = a[j], a[i]
                i += 1
        a[i], a[sag] = a[sag], a[i]
        if k - 1 == i:
            return a[i]
        if k - 1 < i:
            sag = i - 1
        else:
            sol = i + 1
    return a[sol]


print("uzunluk  girdi     pivot      ayrilan/40  kalip   kahin    oran")
for uzunluk in (12, 60):
    K = dagarcik(20260218, 40, uzunluk)
    for gad, hazirla in (("sirasiz", list), ("sirali ", sorted)):
        for pad, pivot_ilk in (("ilk deger ", True), ("orta deger", False)):
            ayrilan, ak, ah = 0, 0, 0
            for dizi in K:
                d = hazirla(dizi)
                s1, s2 = Sayac(), Sayac()
                a = kalip_bolumleme(d, uzunluk // 2, pivot_ilk, s1)
                b = kahin_kinci(d, uzunluk // 2, s2)
                ak, ah = ak + s1.adim, ah + s2.adim
                ayrilan += (a != b)
            print(f"{uzunluk:7d}  {gad}  {pad}  {ayrilan:10d}  {ak:5d}"
                  f"  {ah:6d}  {ah / ak:6.2f}")
```

```
uzunluk  girdi     pivot      ayrilan/40  kalip   kahin    oran
     12  sirasiz  ilk deger            0    943    2640    2.80
     12  sirasiz  orta deger           0   1073    2640    2.46
     12  sirali   ilk deger            0   2040    2640    1.29
     12  sirali   orta deger           0    473    2640    5.58
     60  sirasiz  ilk deger            0   6726   70800   10.53
     60  sirasiz  orta deger           0   7346   70800    9.64
     60  sirali   ilk deger            0  53400   70800    1.33
     60  sirali   orta deger           0   3407   70800   20.78
```

Sekiz satırın sekizinde ayrılan girdi **sıfır**. Bölümlemeli seçim, pivot ne olursa olsun
doğru yanıt verir; bu kalıbın ön koşulu doğrulukla ilgili değildir.

Adım sütunu ise 60 değerlik sıralı girdide çarpıcıdır: ilk değeri pivot seçmek **53.400**
adım, orta değeri seçmek **3407** adım harcıyor. Tek satırlık bir seçim, adımı **15,7
kat** büyütüyor ve kalıbın oranını 20,78'den **1,33'e** düşürüyor — yani kalıp neredeyse
kâhin kadar pahalı hâle geliyor.

Bu satır kursun ikinci iddiasının bir başka yüzüdür: **ön koşul bozulduğunda her zaman
yanlış yanıt gelmez.** Bazen yalnız adım patlar ve patlaması için verinin bozuk olması
gerekmez — burada girdi **sıralı**, yani en düzenli hâlindedir. Kalıbı bozan şey verinin
düzensizliği değil, pivot kuralının o düzenle **örtüşmesidir**.

#### Üç Sayı

| Ölçüt | Kâhin | Kalıp | Ayrılan girdi |
|---|---|---|---|
| Yığın, k'ıncı eleman (k=6) | 2640 adım | 1348 adım | **0/40** |
| Yığın, k'ıncı ayrı değer (k=6) | 2640 adım | 1503 adım | **27/40** |
| Bölümleme, 60 değer, sırasız, orta pivot | 70.800 adım | 7346 adım | **0/40** |
| Bölümleme, 60 değer, sıralı, ilk pivot | 70.800 adım | 53.400 adım | **0/40** |

İlk iki satır doğruluğun sorunun tanımına bağlı olduğunu, son iki satır başarımın pivot
kuralına bağlı olduğunu gösterir. Dört satırın hiçbirinde adım sütunu ayrılan girdi
sütununu haber vermiyor: en düşük adımlı satır doğru, ikinci en düşük adımlı satır 27
girdide yanlış.

#### Özet

- K'ıncı eleman sorusu ortancanın genelidir; k boyutlu bir yığın tutmak, bütün diziyi
  yerleştirmeden yanıtı verir.
- Yığın kalıbı dört k değerinin dördünde de 40 girdinin 40'ında kâhinle aynı yanıtı
  veriyor; oran 1,69 ile 2,04 arasında.
- Tekrarlı değerlerde "k'ıncı eleman" ile "k'ıncı ayrı değer" ayrı sorulardır; ikincisi
  k=6 için **27 girdide** kâhinden ayrılıyor ve fark kodda tek satırdır.
- Tekrarları ayıklanmış denetim dağarcığında ayrılan girdi dört ölçümün dördünde sıfırdır;
  kaynak kalıp değil, sorunun tanımıdır.
- Bölümlemeli seçim her pivotla doğrudur, ama sıralı 60 değerlik girdide ilk değeri pivot
  seçmek adımı 3407'den 53.400'e çıkarıyor ve oranı 20,78'den 1,33'e düşürüyor.

#### Sonraki Adım

Bu konunun yedi kalıbı da tek boyutlu veriyle çalıştı: bir dizi, bir akış, bir ardıl
zinciri. Son kalıp veriyi **iki boyutta** ele alır. Bir ızgarada birbirine bağlı hücre
kümeleri aranır ve gezinme, Veri Yapıları kursunun enine ve derine aramasıyla yapılır — o
iki yordam tekrarlanmaz, doğrudan çağrılır. Ön koşul yine bir tanım seçimidir: **komşuluğun
ne demek olduğu**. Sonraki ders iki komşuluk tanımının aynı ızgarada kaç farklı yanıt
ürettiğini sayacak.

### Izgara Gezinmesi

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/izgara-gezinmesi

Matriste bağlı bileşen arama; komşuluk tanımının 38 ızgarada değiştirdiği yanıt ve işaretleme ile gezinme seçiminin tutulan hücre sayısına etkisi.

Bu konunun yedi kalıbı da tek boyutlu veriyle çalıştı: bir dizi, bir akış, bir ardıl
zinciri. Son kalıp veriyi **iki boyutta** ele alır. Bir ızgarada dolu hücreler vardır ve
soru şudur: birbirine bağlı kaç hücre kümesi var.

Izgara aslında yeni bir yapı değildir. Her dolu hücre bir düğüm, her komşu hücre çifti bir
kenardır; sorulan şey **bağlı bileşen** sayısıdır ve yordam Veri Yapıları kursunda kuruldu.
Enine ve derine aramanın kendisi, ziyaret işaretlemesinin neden zorunlu olduğu ve
$O(V + E)$ maliyeti orada ölçüldü; burada **tekrarlanmaz**, doğrudan kullanılır.

Kalıbın eklediği şey modellemedir ve ön koşulu da orada durur: **komşuluğun ne demek
olduğu**. Bu ders iki komşuluk tanımının aynı ızgarada kaç farklı yanıt ürettiğini sayar.

#### Problem, Kâhin ve Kalıp

Kâhin hiçbir gezinme yapmaz. Bütün dolu hücre çiftlerini tarar, bitişik olanları aynı gruba
alır ve hiçbir birleşme kalmayana kadar yineler. Kalıp her ziyaret edilmemiş dolu hücreden
bir enine arama başlatır ve her arama bir bileşen sayar.

**PK56.** Dağarcık 40 ızgaradır; her ızgara 8×8'dir ve her hücre 100'de 30 olasılıkla
doludur. Tohum `20260218`.
**PK57.** İki komşuluk tanımı ölçülür: **dört komşuluk** (yalnız kenardaş hücreler) ve
**sekiz komşuluk** (köşedeş hücreler de dahil).
**PK58.** Kâhin ve kalıp **ayrı ayrı** komşuluk tanımıyla çalıştırılır; dört bileşim de
ölçülür. Böylece ayrılmanın kalıptan mı tanım farkından mı geldiği görülür.
**PK59.** Ziyaret işaretlemesi kuyruğa **eklerken** yapılır; bu, enine arama dersinde
kurulan kuraldır ve burada gerekçesi tekrarlanmaz.

```python
from collections import deque

TOHUM, BOY, DAGARCIK = 20260218, 8, 40


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def izgara_dagarcik(tohum=TOHUM, n=DAGARCIK, boy=BOY):
    r = uretec(tohum)
    return [{"no": i + 1,
             "izgara": [[1 if r(101) < 30 else 0 for _ in range(boy)]
                        for _ in range(boy)]}
            for i in range(n)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def bitisik(a, b, komsuluk):
    di, dj = abs(a[0] - b[0]), abs(a[1] - b[1])
    return di + dj == 1 if komsuluk == 4 else max(di, dj) == 1 and (di or dj)


def kahin_bilesen(izgara, komsuluk, s):
    """Butun hucre ciftlerini tarar, bitisik olanlari ayni gruba alir, degisiklik
    bitene kadar yineler. Her zaman dogru, her zaman pahali."""
    hucre = [(i, j) for i, satir in enumerate(izgara)
             for j, v in enumerate(satir) if v]
    grup = {h: i for i, h in enumerate(hucre)}
    degisti = True
    while degisti:
        degisti = False
        for a in range(len(hucre)):
            for b in range(a + 1, len(hucre)):
                s.say()
                if bitisik(hucre[a], hucre[b], komsuluk) and \
                        grup[hucre[a]] != grup[hucre[b]]:
                    eski, yeni = grup[hucre[b]], grup[hucre[a]]
                    for h in hucre:
                        if grup[h] == eski:
                            grup[h] = yeni
                    degisti = True
    return len(set(grup.values()))


YON4 = ((-1, 0), (1, 0), (0, -1), (0, 1))
YON8 = YON4 + ((-1, -1), (-1, 1), (1, -1), (1, 1))


def kalip_enine(izgara, komsuluk, s):
    """Veri Yapilari kursunun enine aramasi dogrudan kullanilir: isaretleme
    kuyruga EKLERKEN yapilir."""
    yon = YON4 if komsuluk == 4 else YON8
    n, m = len(izgara), len(izgara[0])
    ziyaret, bilesen, en_genis = set(), 0, 0
    for i in range(n):
        for j in range(m):
            if not izgara[i][j] or (i, j) in ziyaret:
                continue
            bilesen += 1
            ziyaret.add((i, j))
            kuyruk = deque([(i, j)])
            while kuyruk:
                en_genis = max(en_genis, len(kuyruk))
                x, y = kuyruk.popleft()
                s.say()
                for dx, dy in yon:
                    a, b = x + dx, y + dy
                    if 0 <= a < n and 0 <= b < m and izgara[a][b] \
                            and (a, b) not in ziyaret:
                        ziyaret.add((a, b))
                        kuyruk.append((a, b))
    return bilesen, en_genis


K = izgara_dagarcik()
dolu = sum(sum(sum(s) for s in k["izgara"]) for k in K)
print("dagarcik:", len(K), "izgara x", BOY, "x", BOY, "| dolu hucre:", dolu,
      "| ortalama", round(dolu / len(K), 2))
print("kalip komsulugu  kahin komsulugu  ayrilan/40  kalip   kahin    oran")
for kk in (4, 8):
    for hk in (4, 8):
        ayrilan, ak, ah = 0, 0, 0
        for kayit in K:
            s1, s2 = Sayac(), Sayac()
            a, _ = kalip_enine(kayit["izgara"], kk, s1)
            b = kahin_bilesen(kayit["izgara"], hk, s2)
            ak, ah = ak + s1.adim, ah + s2.adim
            ayrilan += (a != b)
        print(f"{kk:15d}  {hk:15d}  {ayrilan:10d}  {ak:5d}  {ah:6d}"
              f"  {ah / ak:6.1f}")
```

```
dagarcik: 40 izgara x 8 x 8 | dolu hucre: 723 | ortalama 18.07
kalip komsulugu  kahin komsulugu  ayrilan/40  kalip   kahin    oran
              4                4           0    723   12768    17.7
              4                8          38    723   12834    17.8
              8                4          38    723   12768    17.7
              8                8           0    723   12834    17.8
```

Köşegen satırlar sıfır, diğer ikisi **38**. Kalıp, kâhinle **aynı komşuluk tanımını**
kullandığında 40 ızgaranın 40'ında aynı yanıtı veriyor; tanımlar ayrıldığında 40
ızgaranın 38'i ayrılıyor.

Bu tablo bir şeyi kesin kılıyor: **ayrılma kalıptan gelmiyor.** Enine arama her iki tanımla
da doğru çalışır; bozulan şey kalıbın kendisi değil, kalıba verilen **komşuluk tanımıdır**.
Kalıbın adımı iki tanımda da **723**, kâhinin adımı 12.768 ile 12.834; oran **17,7**. Adım
sütunu dört satırda da neredeyse aynı ve ayrılan girdi sütununu hiç haber vermiyor.

#### Komşuluk Bir Veri Özelliği Değil, Bir Karardır

Önceki yedi kalıpta ön koşul girdinin bir özelliğiydi: sıralı mı, negatif var mı, tekrar var
mı. Burada ön koşul girdide değildir; **modelleyenin verdiği bir karardır** ve veriye
bakarak doğrulanamaz. Aşağıdaki küçük ızgara farkı bir bakışta gösterir.

**PK60.** Küçük örnek elle kurulmuştur; üreteçten gelmez ve yalnız iki tanımın ayrımını
göstermek içindir.
**PK61.** İkinci dağarcık `20260219` tohumundan gelir. Ölçülen şey, iki tanımın **kaç
ızgarada** farklı bileşen sayısı verdiğidir.

```python
from collections import deque


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def izgara_dagarcik(tohum, n=40, boy=8):
    r = uretec(tohum)
    return [[[1 if r(101) < 30 else 0 for _ in range(boy)] for _ in range(boy)]
            for _ in range(n)]


YON4 = ((-1, 0), (1, 0), (0, -1), (0, 1))
YON8 = YON4 + ((-1, -1), (-1, 1), (1, -1), (1, 1))


def bilesen(izgara, komsuluk):
    """Enine arama ile bagli bilesen sayimi; isaretleme eklerken yapilir."""
    yon = YON4 if komsuluk == 4 else YON8
    n, m = len(izgara), len(izgara[0])
    ziyaret, say = set(), 0
    for i in range(n):
        for j in range(m):
            if not izgara[i][j] or (i, j) in ziyaret:
                continue
            say += 1
            ziyaret.add((i, j))
            kuyruk = deque([(i, j)])
            while kuyruk:
                x, y = kuyruk.popleft()
                for dx, dy in yon:
                    a, b = x + dx, y + dy
                    if 0 <= a < n and 0 <= b < m and izgara[a][b] \
                            and (a, b) not in ziyaret:
                        ziyaret.add((a, b))
                        kuyruk.append((a, b))
    return say


kucuk = [[1, 0, 0, 0, 1],
         [0, 1, 0, 1, 0],
         [0, 0, 1, 0, 0],
         [0, 1, 0, 1, 0],
         [1, 0, 0, 0, 1]]
for satir in kucuk:
    print("  ", "".join("#" if v else "." for v in satir))
print("  4 komsuluk:", bilesen(kucuk, 4), "bilesen")
print("  8 komsuluk:", bilesen(kucuk, 8), "bilesen")
print()
print("tohum      4 komsuluk toplam  8 komsuluk toplam  farkli izgara/40")
for tohum in (20260218, 20260219):
    K = izgara_dagarcik(tohum)
    d4 = [bilesen(g, 4) for g in K]
    d8 = [bilesen(g, 8) for g in K]
    farkli = sum(1 for a, b in zip(d4, d8) if a != b)
    print(f"{tohum}  {sum(d4):17d}  {sum(d8):17d}  {farkli:16d}")
```

```
   #...#
   .#.#.
   ..#..
   .#.#.
   #...#
  4 komsuluk: 9 bilesen
  8 komsuluk: 1 bilesen

tohum      4 komsuluk toplam  8 komsuluk toplam  farkli izgara/40
20260218                387                233                38
20260219                377                227                38
```

Beş satırlık ızgarada dokuz hücre var ve hiçbiri kenardaş değil; hepsi köşegen üzerinde
duruyor. Dört komşulukla **9 ayrı bileşen**, sekiz komşulukla **tek bir bileşen** çıkıyor.
Aynı veri, aynı yordam, dokuz kat fark.

Kırk ızgaralık dağarcıkta toplam bileşen sayısı **387'den 233'e** iniyor ve **38 ızgarada**
iki tanım farklı yanıt veriyor. İkinci dağarcıkta sayılar 377 ve 227, farklı ızgara yine
**38**; sonuç dağarcığa bağlı değildir.

Bu ölçümün pratik sonucu şudur: **bir ızgara problemi okunurken sorulacak ilk soru
komşuluğun tanımıdır.** Tanım problem metninde yazmıyorsa, kalıbın verdiği yanıtın
doğruluğu sınanamaz — kâhin bile kurulamaz, çünkü kâhinin de aynı tanıma ihtiyacı vardır.
Bu, konudaki tek ön koşuldur ki **veriye bakarak** saptanamaz.

#### İşaretleme ve Gezinme Yanıtı Değil, Tutulanı Değiştirir

Komşuluk tanımı sabitlendiğinde geriye iki uygulama kararı kalır: işaretleme kuyruğa
eklerken mi çıkarırken mi yapılacak ve gezinme enine mi derine mi olacak. İkisi de
bileşen sayısını değiştirmez. Değiştirdikleri şey **aynı anda tutulan hücre sayısıdır**.

**PK62.** Bu ölçüm daha büyük ızgarada yapılır: 10 ızgara, her biri 20×20, hücreler 100'de
60 olasılıkla dolu. Küçük ve seyrek ızgarada bu üç yordamın farkı çözünürlüğün altında
kalır.
**PK63.** Ölçülen şey, enine aramada **kuyruğun en geniş hâli**, derine aramada
**özyineleme derinliğinin en büyük değeridir**.

```python
from collections import deque


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def izgara_dagarcik(tohum=20260218, n=10, boy=20):
    r = uretec(tohum)
    return [[[1 if r(101) < 60 else 0 for _ in range(boy)] for _ in range(boy)]
            for _ in range(n)]


YON4 = ((-1, 0), (1, 0), (0, -1), (0, 1))


def enine(izgara, eklerken):
    """Isaretleme kuyruga EKLERKEN mi, kuyruktan CIKARIRKEN mi yapiliyor."""
    n, m = len(izgara), len(izgara[0])
    ziyaret, bilesen, adim, en_cok = set(), 0, 0, 0
    for i in range(n):
        for j in range(m):
            if not izgara[i][j] or (i, j) in ziyaret:
                continue
            bilesen += 1
            kuyruk = deque([(i, j)])
            if eklerken:
                ziyaret.add((i, j))
            while kuyruk:
                en_cok = max(en_cok, len(kuyruk))
                x, y = kuyruk.popleft()
                adim += 1
                if not eklerken:
                    if (x, y) in ziyaret:
                        continue
                    ziyaret.add((x, y))
                for dx, dy in YON4:
                    a, b = x + dx, y + dy
                    if 0 <= a < n and 0 <= b < m and izgara[a][b] \
                            and (a, b) not in ziyaret:
                        if eklerken:
                            ziyaret.add((a, b))
                        kuyruk.append((a, b))
    return bilesen, adim, en_cok


def derine(izgara):
    """Ozyinelemeli derine arama; en derin yigit boyu olculur."""
    n, m = len(izgara), len(izgara[0])
    ziyaret, bilesen, adim, en_derin = set(), 0, 0, 0

    def gez(x, y, derinlik):
        nonlocal adim, en_derin
        adim += 1
        en_derin = max(en_derin, derinlik)
        ziyaret.add((x, y))
        for dx, dy in YON4:
            a, b = x + dx, y + dy
            if 0 <= a < n and 0 <= b < m and izgara[a][b] \
                    and (a, b) not in ziyaret:
                gez(a, b, derinlik + 1)

    for i in range(n):
        for j in range(m):
            if izgara[i][j] and (i, j) not in ziyaret:
                bilesen += 1
                gez(i, j, 1)
    return bilesen, adim, en_derin


K = izgara_dagarcik()
temel = [enine(g, True) for g in K]
print("dagarcik: 10 izgara x 20 x 20 | dolu hucre:",
      sum(sum(sum(s) for s in g) for g in K))
print("gezinme                     ayni bilesen  adim  en cok tutulan hucre")
for ad, sonuc in (("enine, eklerken isaretle ", temel),
                  ("enine, cikarirken isaretle", [enine(g, False) for g in K]),
                  ("derine (ozyinelemeli)     ", [derine(g) for g in K])):
    ayni = sum(1 for a, b in zip(sonuc, temel) if a[0] == b[0])
    print(f"{ad}  {ayni:12d}  {sum(x[1] for x in sonuc):4d}"
          f"  {max(x[2] for x in sonuc):20d}")
```

```
dagarcik: 10 izgara x 20 x 20 | dolu hucre: 2378
gezinme                     ayni bilesen  adim  en cok tutulan hucre
enine, eklerken isaretle             10  2378                    18
enine, cikarirken isaretle            10  2887                    24
derine (ozyinelemeli)                 10  2378                   103
```

Üç satırın üçünde de bileşen sayısı aynı: on ızgaranın onunda aynı yanıt. Ayrılan girdi
**sıfırdır** ve bu satırlar bir doğruluk ölçümü değildir.

Ölçülen fark iki sütundadır. İşaretlemeyi kuyruktan çıkarırken yapmak adımı 2378'den
**2887'ye**, en geniş kuyruğu 18'den **24'e** çıkarıyor: aynı hücre ziyaret edilmeden önce
birden çok kez kuyruğa giriyor. Derine arama adım sayısında enine aramayla **birebir aynı**
(2378), ama en derin yığıt **103** hücre tutuyor — enine aramanın en geniş kuyruğunun
yaklaşık **beş buçuk katı**.

Son sayı pratik bir sınırdır. Yoğun ve büyük bir ızgarada özyinelemeli derine arama, ızgara
büyüklüğüyle orantılı bir yığıt derinliği ister; enine arama ise en geniş katmanla
orantılı bir kuyruk ister. Hangisinin daha ucuz olduğu ızgaranın **biçimine** bağlıdır ve
bu ayrım Veri Yapıları kursunun karşılaştırma tablosunda kurulmuştu; burada aynı ayrım
sayıyla doğrulanmış oluyor.

#### Üç Sayı

| Ölçüt | Kâhin | Kalıp | Ayrılan girdi |
|---|---|---|---|
| Dört komşuluk, dört komşuluk | 12.768 adım | 723 adım | **0/40** |
| Sekiz komşuluk, dört komşuluk | 12.768 adım | 723 adım | **38/40** |
| Enine arama, 20×20 ızgara | — | 2378 adım / 18 hücre | **0/10** |
| Derine arama, 20×20 ızgara | — | 2378 adım / 103 hücre | **0/10** |

İlk iki satırda kalıbın adımı **birebir aynıdır** ve ayrılan girdi 0 ile 38 arasında
değişir; son iki satırda ayrılan girdi aynıdır ve tutulan hücre sayısı beş buçuk kat
değişir. İki tür karar, iki ayrı sütunda görünür — ve hiçbiri öbürünün sütununda iz
bırakmaz.

#### Özet

- Izgara bir çizgedir: dolu hücreler düğüm, komşu hücre çiftleri kenardır; bağlı bileşen
  sayımı Veri Yapıları kursunun enine ve derine aramasıyla yapılır.
- Kalıp, kâhinle aynı komşuluk tanımını kullandığında 40 ızgaranın 40'ında doğru; tanımlar
  ayrıldığında **38 ızgarada** ayrılıyor ve kalıbın adımı iki durumda da 723'tür.
- Komşuluk tanımı verinin değil modelleyenin bir kararıdır; veriye bakarak saptanamaz ve
  tanım belirlenmeden kâhin bile kurulamaz.
- Dokuz köşegen hücreden oluşan ızgarada dört komşuluk 9, sekiz komşuluk 1 bileşen veriyor;
  40 ızgaralık dağarcıkta toplam 387'den 233'e iniyor.
- İşaretlemeyi çıkarırken yapmak yanıtı değiştirmiyor ama adımı 2378'den 2887'ye, kuyruğu
  18'den 24'e çıkarıyor; derine arama aynı adımda 103 hücrelik yığıt tutuyor.

#### Sonraki Adım

Bu konu sekiz kalıbı aynı çerçeveyle ölçtü: bir kâhin, bir kalıp ve ikisinin ayrıldığı
girdi sayısı. Sekiz dersin sekizinde de ayrılmanın kaynağı kalıbın hatası değil, **kabul
edilip denetlenmeyen bir ön koşuldu** — sıra, işaret, tek ardıl, sıralama anahtarı, değer
aralığı, denge, sorunun tanımı ve komşuluk. Sonraki konu kalıplardan **adı olan
problemlere** geçer: sırt çantası, gezgin satıcı, en uzun yol, vezir yerleşimi. Orada
sorulacak soru değişir — bir kalıbın ön koşulunun bozulup bozulmadığı değil, bir problemin
**neden zor** olduğu sorulur; ve kâhin, o zorluğun ölçüldüğü araç olarak kalır.

## Klasik Problemler

Sırt çantası, gezgin satıcı, en uzun yol, N vezir, at turu ve Hamilton yolu problemlerinin kâhinle karşılaştırmalı çözümleri ve yaklaşık çözümlerin ölçülen sapması.

### Sırt Çantası Problemi

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/sirt-cantasi-problemi

Aynı açgözlü sıranın iki varyantta iki farklı sonuç vermesi: bölünebilir sırt çantasında 40 girdinin 40'ında kâhinle aynı yanıt ve 579,3 kat az adım; 0/1 varyantında aynı sıra 40 girdinin 5'inde kâhinden ayrılıyor ve en büyük kayıp 7 değer birimi. Dinamik programlama 40/40 kâhinle aynı yanıtı 4544 adımda veriyor, kâhin 15.360 adımda. İkinci dağarcıkta ayrılan girdi 6, yani oran aynı büyüklük düzeninde kalıyor.

Problem Çözme Kalıpları konusu sekiz kalıbı ön koşuluyla birlikte ölçtü. Orada problemin adı
kalıbın adıydı: iki işaretçi, kayan pencere, ızgara gezinmesi. Bu konu tersine döner. Adı
kalıptan değil **problemden** gelen altı klasik problem ele alınır ve her birinde soru şudur:
bu problem hangi kalıba oturuyor ve oturduğu sanılan kalıp nerede kırılıyor.

İlk problem sırt çantasıdır ve iki yüzü vardır. Ağırlığı ve değeri olan eşyalar, sınırlı
kapasiteli bir çanta. **Bölünebilir** varyantta bir eşyanın parçası alınabilir; **0/1**
varyantında eşya ya bütün olarak alınır ya da hiç alınmaz. Tek bir kelimelik bu fark, aynı
açgözlü sıranın bir varyantta kâhinle **her girdide** aynı yanıtı vermesine, ötekinde
**yanılmasına** yol açar.

- **KP1.** Ortak tanımın üreteci ve dağarcığı aynen kullanılır: tohum **20260218**, ikinci
  dağarcık **20260219**, 40 dizi ve dizi başına 12 değer.
- **KP2.** Her dizinin **ilk altı değeri ağırlık**, **son altı değeri değerdir**. İkisinde de
  mutlak değer alınıp bir eklenir, böylece ağırlık ve değer 1 ile 21 arasında kalır.
- **KP3.** Kapasite toplam ağırlığın yarısının aşağı yuvarlanmışıdır. Girdiden türetilir, elle
  seçilmez; yoksa kapasiteyi seçerek istenen sonuç üretilebilirdi.
- **KP4.** Kâhin iki varyantta da kaba kuvvettir: bölünebilirde **bütün doldurma sıraları**,
  0/1'de **bütün 64 alt küme**. Kâhin pahalıdır ve öyle kalır.
- **KP5.** Ölçü **adımdır**; süre ölçülmez. Adım, kâhin ile kalıbın karşılaştırılabilir tek
  birimidir.
- **KP6.** Ayrılan girdi sayısı 40 üzerindendir. Ortak tanımın Bölüm 5 çözünürlüğü gereği
  **3'ün altındaki fark ölçülmemiş sayılır**.
- **KP7.** Açgözlü sıra iki varyantta da aynıdır: **değer bölü ağırlık** oranı azalan sıra.
  Değişen tek şey eşyanın bölünüp bölünemediğidir.
- **KP8.** Dinamik programlama yordamı Algoritma Tasarım Yaklaşımları konusunda kuruldu;
  **tekrarlanmaz**, yalnız kâhinle karşılaştırılır.
- **KP9.** Kapasite bir **ayar** olarak ayrıca süpürülür. Tek bir kapasitede ölçülüp
  genellenen bir ayrılma sayısı, seçilen ayarın sonucudur ve öyle yazılır.

#### Bölünebilir Varyant ve Kâhini

Bölünebilir varyantta bir eşyanın istenen kesri alınabilir. Sezgi açıktır: birim ağırlık başına
en çok değer veren eşyadan başlanır, çanta dolana kadar sırayla devam edilir, son eşyadan
kalan boşluk kadar kesir alınır.

Bu sezginin doğru olduğu **iddia edilmez, sınanır**. Kâhin, altı eşyanın bütün doldurma
sıralarını dener ve en iyisini alır. Bir doldurma sırası çantayı sırayla doldurur; hangi sıra
alınırsa alınsın elde edilen değer olabilecek en iyi değeri geçemez ve en iyi değeri veren
sıra bu 720 sıranın içindedir. Yani kâhin gerçekten bir üst sınırdır.

```python
from itertools import permutations

TOHUM = 20260218


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def dagarcik(tohum=TOHUM, n=40, uzunluk=12):
    r = uretec(tohum)
    return [{"no": i + 1, "dizi": [r(30) - 9 for _ in range(uzunluk)]}
            for i in range(n)]


def esyalar(dizi):
    """Ortak tanimin 12 degerinden alti esya: agirlik ilk alti, deger son alti."""
    return [(abs(dizi[i]) + 1, abs(dizi[i + 6]) + 1) for i in range(6)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_bolunebilir(es, kap, s):
    """Butun doldurma siralari denenir, en iyisi alinir."""
    en = 0.0
    for p in permutations(range(len(es))):
        kalan, d = kap, 0.0
        for i in p:
            s.say()
            a, v = es[i]
            if a <= kalan:
                kalan, d = kalan - a, d + v
            else:
                d += v * kalan / a
                break
        en = max(en, d)
    return round(en, 4)


def kalip_bolunebilir(es, kap, s):
    """ONKOSUL: esya bolunebilir olmali. Deger/agirlik oranina gore acgozlu."""
    kalan, d = kap, 0.0
    for a, v in sorted(es, key=lambda t: -t[1] / t[0]):
        s.say()
        if a <= kalan:
            kalan, d = kalan - a, d + v
        else:
            d += v * kalan / a
            break
    return round(d, 4)


ayrilan, ak, ah = 0, 0, 0
for k in dagarcik():
    es = esyalar(k["dizi"])
    kap = sum(a for a, _ in es) // 2
    s1, s2 = Sayac(), Sayac()
    if kalip_bolunebilir(es, kap, s1) != kahin_bolunebilir(es, kap, s2):
        ayrilan += 1
    ak, ah = ak + s1.adim, ah + s2.adim
print("ilk ornek (agirlik, deger):", esyalar(dagarcik()[0]["dizi"]))
print("bolunebilir | girdi 40 | ayrilan", ayrilan, "| kalip adim", ak,
      "| kahin adim", ah, "| oran", round(ah / ak, 1))
```

```
ilk ornek (agirlik, deger): [(9, 5), (6, 2), (3, 17), (2, 18), (3, 7), (6, 2)]
bolunebilir | girdi 40 | ayrilan 0 | kalip adim 174 | kahin adim 100800 | oran 579.3
```

Üç sayı yan yana duruyor: **kâhin 100.800 adım**, **kalıp 174 adım**, **ayrılan girdi 0**.
Kalıp 579,3 kat az adım harcıyor ve 40 girdinin 40'ında kâhinle aynı yanıtı veriyor.

Bölünebilirde açgözlünün yanılmamasının nedeni, kesir alma serbestliğinin bir **değiş tokuş**
imkânı vermesidir. Çantada oranı düşük bir eşyadan bir birim ağırlık varsa, o birimi çıkarıp
yerine oranı yüksek bir eşyadan bir birim koymak değeri düşürmez. Bu değiş tokuş akıl yürütmesi her zaman
mümkün olduğu için, oran sırasından sapan hiçbir çözüm oran sırasını geçemez. Ön koşul tam
olarak budur: **birim ağırlık takas edilebilmeli.**

#### Aynı Sıra, Bölünmeyen Eşya

0/1 varyantında bu takas ortadan kalkar. Bir birim ağırlık çıkarmak için eşyanın **tamamını**
çıkarmak gerekir ve yerine konan eşya da tam gelir. Açgözlü sıra değişmez, kod neredeyse
aynıdır, ama ön koşul artık yoktur.

```python
TOHUM = 20260218


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def esya_dagarcigi(tohum=TOHUM, n=40):
    """Ortak tanimin dagarcigi: 40 dizi x 12 deger, her diziden alti esya."""
    r = uretec(tohum)
    kume = []
    for i in range(n):
        z = [r(30) - 9 for _ in range(12)]
        kume.append((i + 1, [(abs(z[j]) + 1, abs(z[j + 6]) + 1) for j in range(6)]))
    return kume


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_01(es, kap, s):
    """Alti esyanin butun 64 altkumesi denenir."""
    en = 0
    for maske in range(1 << len(es)):
        a = d = 0
        for i, (ai, vi) in enumerate(es):
            s.say()
            if maske >> i & 1:
                a, d = a + ai, d + vi
        if a <= kap and d > en:
            en = d
    return en


def kalip_01(es, kap, s):
    """Ayni acgozlu sira, ama esya bolunemiyor."""
    kalan, d = kap, 0
    for a, v in sorted(es, key=lambda t: -t[1] / t[0]):
        s.say()
        if a <= kalan:
            kalan, d = kalan - a, d + v
    return d


def dp_01(es, kap, s):
    en = [0] * (kap + 1)
    for a, v in es:
        for c in range(kap, a - 1, -1):
            s.say()
            en[c] = max(en[c], en[c - a] + v)
    return en[kap]


def olc(cozucu, tohum):
    ayrilan, ak, ah, kayip = [], 0, 0, 0
    for no, es in esya_dagarcigi(tohum):
        kap = sum(a for a, _ in es) // 2
        s1, s2 = Sayac(), Sayac()
        a, b = cozucu(es, kap, s1), kahin_01(es, kap, s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        if a != b:
            ayrilan.append(no)
            kayip = max(kayip, b - a)
    return ayrilan, ak, ah, kayip


for ad, cozucu, tohum in (("acgozlu 20260218", kalip_01, 20260218),
                          ("acgozlu 20260219", kalip_01, 20260219),
                          ("dinamik 20260218", dp_01, 20260218)):
    ay, ak, ah, f = olc(cozucu, tohum)
    print(ad, "| ayrilan", len(ay), "/40", ay[:6], "| kalip adim", ak,
          "| kahin adim", ah, "| en buyuk kayip", f)
```

```
acgozlu 20260218 | ayrilan 5 /40 [4, 21, 22, 32, 40] | kalip adim 240 | kahin adim 15360 | en buyuk kayip 7
acgozlu 20260219 | ayrilan 6 /40 [8, 11, 20, 28, 36, 39] | kalip adim 240 | kahin adim 15360 | en buyuk kayip 3
dinamik 20260218 | ayrilan 0 /40 [] | kalip adim 4544 | kahin adim 15360 | en buyuk kayip 0
```

#### Ayrılan Girdilerin Okunması

Açgözlü 0/1'de **40 girdinin 5'inde** kâhinden ayrılıyor. Ayrılma oranı 0,1250 ve çözünürlük
sınırının (3 girdi) üzerinde, yani ölçülmüş sayılır. Ayrıldığı girdilerin numaraları belli:
4, 21, 22, 32 ve 40. En büyük kayıp **7 değer birimidir** — çantanın taşıyabileceği en iyi
değerden yedi birim aşağıda kalınıyor.

Bu kayıp çıktıya bakılarak fark edilemez. Açgözlü yine bir sayı döndürüyor, yine kapasiteyi
aşmıyor, yine geçerli bir seçim üretiyor. **Geçerli olmak en iyi olmak demek değildir** ve
aradaki farkı ancak kâhin gösterir. Kalıbın verdiği yanıtı tek başına inceleyerek yanlış
olduğunu söylemenin bir yolu yoktur.

İkinci dağarcık zorunludur ve burada da uygulanıyor: `20260219` tohumuyla ayrılan girdi
**6**, oran 0,1500. Beş ile altı arasındaki fark çözünürlüğün altındadır, ama iki oran da
aynı büyüklük düzenindedir. Sonuç **dağarcığa bağlı değildir**: açgözlü 0/1 varyantında
girdilerin yaklaşık yedide birinde yanılıyor.

#### Bir Ayrılan Girdinin İçi

Ayrılan girdi sayısı tek başına bir sayıdır; neyin bozulduğunu göstermez. Dördüncü girdi
açılıp seçimler yan yana konduğunda mekanizma görünür hale gelir. Aynı blok ayrıca kapasiteyi
bir ayar olarak süpürür.

```python
TOHUM = 20260218


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def esya_dagarcigi(tohum=TOHUM, n=40):
    r = uretec(tohum)
    kume = []
    for i in range(n):
        z = [r(30) - 9 for _ in range(12)]
        kume.append((i + 1, [(abs(z[j]) + 1, abs(z[j + 6]) + 1) for j in range(6)]))
    return kume


def kahin_01(es, kap):
    en, sec = 0, ()
    for maske in range(1 << len(es)):
        a = d = 0
        for i, (ai, vi) in enumerate(es):
            if maske >> i & 1:
                a, d = a + ai, d + vi
        if a <= kap and d > en:
            en, sec = d, tuple(i for i in range(len(es)) if maske >> i & 1)
    return en, sec


def kalip_01(es, kap):
    kalan, d, sec = kap, 0, []
    for i in sorted(range(len(es)), key=lambda j: -es[j][1] / es[j][0]):
        a, v = es[i]
        if a <= kalan:
            kalan, d = kalan - a, d + v
            sec.append(i)
    return d, tuple(sorted(sec))


es = dict(esya_dagarcigi())[4]
kap = sum(a for a, _ in es) // 2
print("girdi 4 |", es, "| kapasite", kap)
print("  acgozlu:", kalip_01(es, kap), " kahin:", kahin_01(es, kap))
print("kapasite payi -> ayrilan girdi (40 uzerinden)")
for pay in (2, 3, 4, 5, 6):
    ayrilan = sum(1 for _, e in esya_dagarcigi()
                  if kalip_01(e, sum(a for a, _ in e) * pay // 8)[0]
                  != kahin_01(e, sum(a for a, _ in e) * pay // 8)[0])
    print(f"  {pay}/8 -> {ayrilan}")
```

```
girdi 4 | [(5, 21), (6, 8), (9, 5), (2, 4), (19, 13), (18, 16)] | kapasite 29
  acgozlu: (38, (0, 1, 2, 3))  kahin: (45, (0, 1, 5))
kapasite payi -> ayrilan girdi (40 uzerinden)
  2/8 -> 3
  3/8 -> 13
  4/8 -> 5
  5/8 -> 12
  6/8 -> 8
```

Açgözlü dört eşya alıyor ve 38 değer topluyor; kullandığı ağırlık 22 ve geriye 7 birim boş
yer kalıyor. Kâhin üç eşya alıyor, kapasiteyi **tam** dolduruyor ve 45 değer topluyor.
Farkın kaynağı, açgözlünün oranı yüksek küçük eşyaları erken alması ve ardından oranı düşük
ama **büyük** olan altıncı eşyaya yer bırakmamasıdır. Altıncı eşyanın oranı 0,889 ile listenin
dördüncüsüdür, ama tek başına 16 değer taşır. Açgözlü onu asla göremez, çünkü sırayı yalnız
orana bakarak kurar ve **geriye dönmez**.

Kapasite süpürmesi ikinci ve daha rahatsız edici sonucu veriyor. Ayrılan girdi sayısı
kapasiteyle birlikte düzenli değişmiyor: toplam ağırlığın sekizde ikisinde 3, sekizde üçünde
13, sekizde dördünde 5, sekizde beşinde 12, sekizde altısında 8. **Kalıbın yanılma sayısı bir
sabit değil, ayarın bir işlevidir.** Ölçüm tek bir kapasitede yapılıp genellenseydi, sekizde
ikilik ayarda çıkan 3 sonucu açgözlünün neredeyse hep doğru olduğu izlenimini verirdi; sekizde
üçlük ayarda çıkan 13 ise tam tersini söylerdi. Ölçünün hangi ayarda alındığı yazılmadıkça
sayı okunamaz.

#### Dinamik Programlamanın Bedeli

Üçüncü satır dinamik programlamayı gösteriyor: **40 girdinin 40'ında** kâhinle aynı yanıt,
**4544 adım**. Kâhin 15.360 adım harcıyor, yani doğru yanıt kaba kuvvetin 3,4'te biri kadar
adımla alınabiliyor. Ama açgözlünün 240 adımıyla karşılaştırıldığında **18,9 kat** pahalı.

Bu üç satır kursun ölçü okumasının tam karşılığıdır. Açgözlü ucuzdur ve yanılır; dinamik
programlama pahalıdır ve yanılmaz; kâhin en pahalısıdır ve yanılmadığı **tanım gereği**
bilinir. Aradaki seçim bir hız tercihi değil, **kaç girdide yanlış yanıta razı olunduğu**
sorusudur.

Dinamik programlamanın adım sayısının kapasiteyle birlikte büyüdüğüne dikkat edilmeli. Burada
kapasiteler küçüktür, çünkü ağırlıklar 1 ile 21 arasındadır. Ağırlıklar bin katına çıkarılsa
eşya sayısı hiç değişmeden dinamik programlamanın adımı da bin katına çıkardı; kaba kuvvetin
adımı ise **hiç değişmezdi**, çünkü o yalnız alt küme sayısına bakar. Hangi yordamın pahalı
olduğu, girdinin hangi boyutunun büyüdüğüne bağlıdır.

#### Özet

- Sırt çantasının iki varyantı aynı açgözlü sırayı paylaşır; ayrıldıkları tek nokta eşyanın
  bölünüp bölünemediğidir ve bu tek nokta yanıtı değiştirir.
- Bölünebilir varyantta açgözlü, 40 girdinin 40'ında kâhinle aynı yanıtı **174 adımda**
  veriyor; kâhin aynı işi **100.800 adımda** yapıyor.
- 0/1 varyantında aynı sıra **40 girdinin 5'inde** kâhinden ayrılıyor ve en büyük kayıp
  **7 değer birimidir**; ikinci dağarcıkta ayrılan girdi 6, oran aynı büyüklük düzeninde.
- Açgözlünün ürettiği yanıt her zaman geçerlidir; geçerli olması en iyi olduğu anlamına
  gelmez ve farkı yalnız kâhin gösterir.
- Kapasite bir ayar olarak süpürüldüğünde ayrılan girdi 3 ile 13 arasında oynuyor; kalıbın
  yanılma sayısı bir sabit değil, ayarın işlevidir.
- Dinamik programlama 40/40 doğru yanıtı 4544 adımda veriyor: kâhinden 3,4 kat ucuz, açgözlüden
  18,9 kat pahalı.

#### Sonraki Adım

Sırt çantasında kaba kuvvet kâhini 64 alt küme taradı ve bu tarama küçük kaldı. Sonraki ders
gezgin satıcı problemini alır; orada aday çözüm sayısı alt küme değil **sıralama** sayısıdır
ve altı şehirden on şehre çıkıldığında tarama alanı sayıyla gösterilebilir biçimde patlar.
Soru şu olacak: kâhin artık koşturulamayacak kadar büyüdüğünde, bir yaklaşık çözümün kâhinden
yüzde kaç saptığı nasıl ölçülür.

### Gezgin Satıcı Problemi

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/gezgin-satici-problemi

Sekiz şehirde tam sayımla alınan en iyi turun kâhin yapılması ve iki yaklaşık çözümün ondan yüzde kaç saptığının ölçülmesi: en yakın komşu 20 örneğin 17'sinde kâhinden ayrılıyor, ortalama sapma yüzde 9,3 ve en kötüsü yüzde 33,33; iki-değişim iyileştirmesi ayrılan örneği 2'ye, ortalama sapmayı yüzde 0,49'a indiriyor ve bunu 819 adımda yapıyor. Kâhin aynı 20 örnek için 352.800 adım harcıyor. Arama alanının büyümesi koşturularak değil sayılarak gösteriliyor; on sekiz şehirde tur sayısı 177.843.714.048.000. Neden zor sorusu Hesaplama Kuramı kursuna havale ediliyor.

Sırt çantasında kâhin altı eşyanın 64 alt kümesini taradı ve bu tarama ucuzdu. Aday çözüm
sayısı eşya sayısının üstel bir işleviydi, ama üs tabanı ikiydi ve altı eşya için 64 küçük bir
sayı kaldı. Gezgin satıcı probleminde aday sayısı alt küme değil **sıralama** sayısıdır ve o
sayı çok daha hızlı büyür.

Problem şudur: şehirler ve aralarındaki uzaklıklar verilir; her şehre tam bir kez uğrayıp
başlangıç şehrine dönen **en kısa kapalı tur** aranır. Bu ders iki soruyu ayrı ayrı yanıtlar.
Birincisi ölçülebilir: yaklaşık bir çözüm kâhinden yüzde kaç sapıyor. İkincisi bu kursta
yanıtlanmaz: bu problem **neden** zor. İkinci soru bir sınıflandırma sorusudur ve M01/K08
Hesaplama Kuramı kursuna aittir; burada yalnız sorunun kendisi kurulur.

- **KP10.** Şehir konumları ortak tanımın üretecinden gelir: 30'a 30'luk bir ızgarada
  rastgele nokta. Tohum **20260218**, ikinci dağarcık **20260219**.
- **KP11.** Her dağarcıkta **20 örnek**, örnek başına **8 şehir**. Şehir sayısı kâhin
  koşturulabilsin diye küçük tutulur; büyük şehir sayısı **sayılarak** anlatılır.
- **KP12.** Uzaklık tam sayıdır: koordinat farklarının karelerinin toplamının tam sayı
  karekökü. Kayan noktalı sayı kullanılmaz, böylece iki tur eşitliği kesin olarak sınanır.
- **KP13.** Kâhin **tam sayımdır**: sıfırıncı şehir sabitlenir, kalan yedi şehrin bütün
  sıraları denenir; her turun tersi aynı tur olduğu için yarısı elenir, geriye 2520 tur kalır.
- **KP14.** Ölçü **adımdır**. Bir adım, bir kenar uzunluğunun hesaplanmasıdır.
- **KP15.** Sapma **yüzde** olarak yazılır: kalıbın turu eksi en iyi tur, bölü en iyi tur.
  Ortalama sapmanın yanına **en kötü sapma** da konur; ortalama tek başına yanıltıcıdır.
- **KP16.** İki-değişim iyileştirmesi, turdaki iki kenarı kaldırıp aradaki parçayı ters
  çevirerek yeniden bağlar. Hiçbir iyileştirme kalmayana kadar sürer.
- **KP17.** Büyük şehir sayıları **koşturulmaz**, sayılır. Tam sayım tablosu çarpanların
  hesaplanmasıyla üretilir, hiçbir tur gerçekten taranmaz.
- **KP18.** Zorluğun **adı** bu derste yazılmaz. Karmaşıklık sınıfları, indirgeme ve
  yaklaşım oranı kavramları M01/K08'e aittir.
- **KP19.** Sapmanın şehir sayısıyla nasıl değiştiği ayrıca süpürülür. Her şehir sayısı için
  ayrı bir dağarcık üretilir; satırlar birbirinin genişletilmişi değildir.

#### Kâhin ve İki Yaklaşık Çözüm

En yakın komşu yordamı sezgiyi doğrudan izler: bulunulan şehirden en yakın uğranmamış şehre
git, bu bitince başa dön. İki-değişim ise elde edilen turu alıp yerel olarak düzeltir; turda
kesişen iki kenar varsa onları kaldırıp aradaki parçayı ters çevirmek turu kısaltır.

İkisinin de doğruluğu **iddia edilmez**. Kâhin her örnekte en iyi turu tam sayımla bulur ve
iki kalıp ona göre ölçülür.

```python
from itertools import permutations
from math import isqrt

TOHUM = 20260218


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def sehirler(tohum=TOHUM, ornek=20, n=8):
    """Ortak tanimin ureteci; her ornek n sehrin (x, y) konumu."""
    r = uretec(tohum)
    return [[(r(30), r(30)) for _ in range(n)] for _ in range(ornek)]


def uzaklik(p, q):
    return isqrt((p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2)


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_tam(k, s):
    """Sifirinci sehir sabit; kalan sehirlerin butun siralari denenir."""
    en = None
    for p in permutations(range(1, len(k))):
        if p[0] > p[-1]:
            continue
        tur, onceki = 0, 0
        for i in p:
            s.say()
            tur += uzaklik(k[onceki], k[i])
            onceki = i
        tur += uzaklik(k[onceki], k[0])
        if en is None or tur < en:
            en = tur
    return en


def en_yakin_komsu(k, s):
    kalan, yol, simdi, tur = set(range(1, len(k))), [0], 0, 0
    while kalan:
        en_i, en_d = None, None
        for i in kalan:
            s.say()
            d = uzaklik(k[simdi], k[i])
            if en_d is None or d < en_d:
                en_i, en_d = i, d
        kalan.remove(en_i)
        yol.append(en_i)
        tur, simdi = tur + en_d, en_i
    return tur + uzaklik(k[simdi], k[0]), yol


def iki_degisim(k, yol, s):
    n = len(yol)
    tur = sum(uzaklik(k[yol[i]], k[yol[(i + 1) % n]]) for i in range(n))
    iyilesti = True
    while iyilesti:
        iyilesti = False
        for i in range(1, n - 1):
            for j in range(i + 1, n):
                s.say()
                a, b, c, d = yol[i - 1], yol[i], yol[j], yol[(j + 1) % n]
                fark = (uzaklik(k[a], k[c]) + uzaklik(k[b], k[d])
                        - uzaklik(k[a], k[b]) - uzaklik(k[c], k[d]))
                if fark < 0:
                    yol[i:j + 1] = reversed(yol[i:j + 1])
                    tur, iyilesti = tur + fark, True
    return tur


for tohum in (20260218, 20260219):
    ha, top = 0, {"en yakin komsu": [0, 0, 0.0, 0.0], "iki degisim   ": [0, 0, 0.0, 0.0]}
    for k in sehirler(tohum):
        sh = Sayac()
        eniyi = kahin_tam(k, sh)
        ha += sh.adim
        s1, s2 = Sayac(), Sayac()
        t1, yol = en_yakin_komsu(k, s1)
        t2 = iki_degisim(k, list(yol), s2)
        for ad, t, s in (("en yakin komsu", t1, s1), ("iki degisim   ", t2, s2)):
            r = top[ad]
            r[0] += 0 if t == eniyi else 1
            r[1] += s.adim
            sap = 100.0 * (t - eniyi) / eniyi
            r[2], r[3] = r[2] + sap, max(r[3], sap)
    print("tohum", tohum, "| kahin adim", ha)
    for ad, r in top.items():
        print(f"  {ad} ayrilan {r[0]:2d}/20 | kalip adim {r[1]:4d}"
              f" | ortalama sapma yuzde {round(r[2] / 20, 2)}"
              f" | en kotu yuzde {round(r[3], 2)}")
```

```
tohum 20260218 | kahin adim 352800
  en yakin komsu ayrilan 17/20 | kalip adim  560 | ortalama sapma yuzde 9.3 | en kotu yuzde 33.33
  iki degisim    ayrilan  2/20 | kalip adim  819 | ortalama sapma yuzde 0.49 | en kotu yuzde 7.59
tohum 20260219 | kahin adim 352800
  en yakin komsu ayrilan 18/20 | kalip adim  560 | ortalama sapma yuzde 10.65 | en kotu yuzde 26.67
  iki degisim    ayrilan  6/20 | kalip adim  924 | ortalama sapma yuzde 1.13 | en kotu yuzde 6.59
```

#### Sapmanın Okunması

Üç sayı yan yana: **kâhin 352.800 adım**, **en yakın komşu 560 adım**, **ayrılan örnek 17**.
Kalıp 630 kat az adım harcıyor ve 20 örneğin 17'sinde en iyi turu bulamıyor. Ama ayrılan örnek
sayısı burada tek başına yeterli değildir, çünkü soru "doğru mu" değil, **"ne kadar uzak"**
sorusudur. Ortalama sapma yüzde 9,3 ve **en kötü sapma yüzde 33,33**. Ortalamanın tek başına
yazılması, üçte bir uzunluğunda bir turun varlığını gizlerdi.

İki-değişim aynı turu alıp yerel düzeltmeler yapıyor. Ayrılan örnek 17'den **2'ye**, ortalama
sapma yüzde 9,3'ten **yüzde 0,49'a**, en kötü sapma yüzde 33,33'ten **yüzde 7,59'a** iniyor.
Bunun bedeli 560 adım yerine 819 adım, yani yüzde 46 daha çok iş. Aynı yordam hâlâ kâhin
değildir: iki örnekte en iyiyi bulamıyor ve bulamadığını **kendisi bilemez**.

İkinci dağarcık zorunludur. `20260219` tohumunda en yakın komşu 18 örnekte ayrılıyor, ortalama
sapma yüzde 10,65; iki-değişim 6 örnekte ayrılıyor, ortalama sapma yüzde 1,13. En yakın
komşunun sonucu iki dağarcıkta da aynı: neredeyse her örnekte en iyiyi kaçırıyor. İki-değişimin
ayrılan örneği 2'den 6'ya çıkıyor ve bu fark çözünürlüğün üstündedir; yani **iki-değişimin
kaç örnekte en iyiyi bulacağı dağarcığa bağlıdır** ve tek bir dağarcıktan genellenemez.
Ortalama sapmanın büyüklük düzeni ise değişmiyor: yüzde bir mertebesinde kalıyor.

#### Kâhinin Nerede Bittiği

Kâhinin sekiz şehirde 352.800 adım harcaması sorun değildir. Sorun, bu sayının şehir sayısıyla
nasıl büyüdüğüdür. Tur sayısı, sıfırıncı şehir sabitlendikten sonra kalan şehirlerin bütün
sıralamalarının yarısıdır. Bu sayı koşturularak değil **sayılarak** gösterilir.

```python
from math import factorial


def bic(x):
    return f"{x:,}".replace(",", ".")


print(" n | tur sayisi | kahin adimi | en yakin komsu adimi | oran")
for n in (8, 10, 12, 14, 16, 18):
    tur = factorial(n - 1) // 2
    kahin = tur * (n - 1)
    kalip = n * (n - 1) // 2
    print(f"{n:2d} | {bic(tur):>22s} | {bic(kahin):>25s} |"
          f" {kalip:4d} | {bic(kahin // kalip)}")
```

```
 n | tur sayisi | kahin adimi | en yakin komsu adimi | oran
 8 |                  2.520 |                    17.640 |   28 | 630
10 |                181.440 |                 1.632.960 |   45 | 36.288
12 |             19.958.400 |               219.542.400 |   66 | 3.326.400
14 |          3.113.510.400 |            40.475.635.200 |   91 | 444.787.200
16 |        653.837.184.000 |         9.807.557.760.000 |  120 | 81.729.648.000
18 |    177.843.714.048.000 |     3.023.343.138.816.000 |  153 | 19.760.412.672.000
```

Sekiz şehirden on sekiz şehre çıkıldığında kâhinin adımı 17.640'tan 3.023.343.138.816.000'e
çıkıyor; en yakın komşunun adımı ise 28'den 153'e. Oran 630'dan on dokuz trilyonun üzerine
tırmanıyor. Bu tablo bir hız iddiası değildir; **kâhinin nerede koşturulamaz hale geldiğinin
sayımıdır**.

Buradan çıkan sonuç, kursun üçüncü iddiasının en sert biçimidir. Kaba kuvvet bir kâhindir,
ama **her ölçekte kâhin olamaz**. Sekiz şehirde ölçülen yüzde 9,3'lük sapma on sekiz şehirde
ölçülemez, çünkü karşılaştırılacak en iyi tur bilinmez. Yaklaşık bir çözümün büyük girdideki
kalitesi hakkında yazılan her cümle, ya **küçük girdide ölçülmüş** bir sapmadan genelleme
yapar ya da bir **alt sınır** kullanır. İkisi de kanıt değildir ve bu ders ikisini de kanıt
diye sunmaz.

#### Sapma Şehir Sayısıyla Ne Yapıyor

Bir önceki bölümün son cümlesi bir soru bırakıyor: küçük girdide ölçülen sapma büyük girdi için
ne söyler. Bu soruya tam olarak yanıt verilemez, ama sapmanın **kâhinin koşturulabildiği
aralıkta** hangi yöne gittiği ölçülebilir. Şehir sayısı beşten dokuza kadar süpürülür.

```python
from itertools import permutations
from math import isqrt

TOHUM = 20260218


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def sehirler(tohum=TOHUM, ornek=20, n=8):
    r = uretec(tohum)
    return [[(r(30), r(30)) for _ in range(n)] for _ in range(ornek)]


def uzaklik(p, q):
    return isqrt((p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2)


def kahin_tam(k):
    en = None
    for p in permutations(range(1, len(k))):
        if p[0] > p[-1]:
            continue
        tur, onceki = 0, 0
        for i in p:
            tur += uzaklik(k[onceki], k[i])
            onceki = i
        tur += uzaklik(k[onceki], k[0])
        if en is None or tur < en:
            en = tur
    return en


def en_yakin_komsu(k):
    kalan, simdi, tur = set(range(1, len(k))), 0, 0
    while kalan:
        en_i = min(kalan, key=lambda i: uzaklik(k[simdi], k[i]))
        tur += uzaklik(k[simdi], k[en_i])
        kalan.remove(en_i)
        simdi = en_i
    return tur + uzaklik(k[simdi], k[0])


print(" n | ayrilan / 20 | ortalama sapma | en kotu sapma")
for n in (5, 6, 7, 8, 9):
    ay, top, kot = 0, 0.0, 0.0
    for k in sehirler(n=n):
        e, t = kahin_tam(k), en_yakin_komsu(k)
        if t != e:
            ay += 1
        sap = 100.0 * (t - e) / e
        top, kot = top + sap, max(kot, sap)
    print(f"{n:2d} | {ay:11d}  | yuzde {round(top / 20, 2):5} "
          f"   | yuzde {round(kot, 2)}")
```

```
 n | ayrilan / 20 | ortalama sapma | en kotu sapma
 5 |          12  | yuzde  5.03    | yuzde 16.67
 6 |          14  | yuzde  6.29    | yuzde 17.57
 7 |          15  | yuzde  7.17    | yuzde 17.86
 8 |          17  | yuzde   9.3    | yuzde 33.33
 9 |          14  | yuzde  9.77    | yuzde 22.99
```

Ortalama sapma beş şehirde yüzde 5,03, dokuz şehirde yüzde 9,77. Beş satırın beşinde de artıyor
ve yaklaşık iki katına çıkıyor. Ayrılan örnek sayısı ise düzenli değil: 12, 14, 15, 17, sonra
14. Bu düzensizlik beklenir, çünkü her satır kendi şehir sayısına göre üretilmiş **ayrı bir
dağarcıktır**; dokuz şehirlik örnekler sekiz şehirlik örneklerin genişletilmişi değildir.

Tablodan çıkarılabilecek tek şey **yönün** kendisidir: sapma şehir sayısıyla artıyor. Bu, sekiz
şehirde ölçülen yüzde 9,3'ün büyük girdiler için bir **alt tahmin** olduğunu düşündürür.
Düşündürür — kanıtlamaz. Beşten dokuza uzanan beş nokta üzerinden on sekiz şehir hakkında bir
şey söylemek, ölçülmüş bir aralığın dışına çıkmaktır ve bu ders o çıkışı yapmaz.

#### Neden Zor Sorusu Nereye Ait

Buraya kadar ölçülen her şey somuttur: adım, sapma, ayrılan örnek. Ölçülmeyen bir şey kaldı.
Tam sayımdan **köklü biçimde** daha ucuz bir yordam var mı, yoksa bu problemin doğasında mı bir
zorluk var.

Bu soru bu kursta yanıtlanmaz ve yanıtlanmaya çalışılmaz. Sorunun yanıtı problemleri zorluk
derecesine göre **sınıflandırmayı**, bir problemi bir başkasına çevirmeyi ve yaklaşık çözümün
en iyiden ne kadar sapabileceğine dair kanıtlanmış sınırlar kurmayı gerektirir. Bu araçların
tamamı M01/K08 Hesaplama Kuramı kursuna aittir. Burada yapılan tek şey soruyu **ölçülebilir
parçasından ayırmaktır**: sapma ölçülür, zorluğun adı ölçülmez.

Bu ayrımın pratik bir karşılığı vardır. Bir problemin zor olduğunu bilmek, yaklaşık çözümün
sapmasını bilmenin yerine geçmez. Sapma ölçülmeden "zaten zor bir problem" demek, kalıbı
kâhinsiz bırakmaktır.

#### Özet

- Gezgin satıcı probleminde aday çözüm sayısı alt küme değil sıralama sayısıdır; sekiz şehirde
  2520 tur, on sekiz şehirde 177.843.714.048.000 tur.
- Kâhin 20 örnek için 352.800 adım harcıyor; en yakın komşu 560 adımda bitiriyor ama
  **20 örneğin 17'sinde** en iyi turu kaçırıyor.
- Sapma ortalama yüzde 9,3, en kötü yüzde 33,33; ortalamanın tek başına yazılması en kötü
  durumu gizler.
- İki-değişim ayrılan örneği 2'ye ve ortalama sapmayı yüzde 0,49'a indiriyor, bedeli 560 yerine
  819 adım; ikinci dağarcıkta ayrılan örnek 6'ya çıktığı için bu sayı dağarcığa bağlıdır.
- Kâhin her ölçekte koşturulamaz; büyük girdide ölçülen bir sapma yoktur, yalnız küçük girdiden
  yapılmış bir genelleme vardır.
- Problemin **neden** zor olduğu sorusu M01/K08 Hesaplama Kuramı kursuna aittir ve burada
  yanıtlanmaz.

#### Sonraki Adım

Gezgin satıcıda kâhin tam sayımdı ve kalıp yaklaşık bir turdu. Sonraki ders daha rahatsız edici
bir durumu ele alır: kalıbın **kanıtlanmış** bir yordam olduğu, ama uygulandığı problemin
değiştiği durum. En kısa yol algoritmaları M01/K04 Algoritmalar kursunda kanıtlanmıştı; aynı
yordam en uzun yola çevrildiğinde kaç çizgede yanlış yanıt veriyor ve bu yanlışın kaynağı
hangi varsayımın düşmesi.

### En Uzun Yol Problemi

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/en-uzun-yol-problemi

En kısa yol gevşetmesinin en uzun yola çevrilmesi ve iki çizge ailesinde ölçülmesi: çevrimsiz çizgelerde 40 girdinin 40'ında kâhinle aynı yanıt, çevrimli çizgelerde 40 girdinin 38'inde ayrılma ve en büyük fazla tahmin 228 ağırlık birimi; ikinci dağarcıkta 39. Ayrılmanın nedeni yordamın yavaşlaması değil, aradığı nesnenin değişmesidir: gevşetme en çok ağırlıklı yürüyüşü bulur, problem ise en çok ağırlıklı basit yolu ister. Kâhin sekiz düğümde kalıptan ucuzdur; on üç düğümde 86,37 kat pahalı hale gelir.

Gezgin satıcıda kalıp bir yaklaşık çözümdü ve yanılması beklenen bir şeydi. Bu ders daha
rahatsız edici bir duruma bakar: kalıp **kanıtlanmış** bir yordamdır, kodu doğrudur, hiçbir
hata içermez ve yine de yanlış yanıt verir. Değişen tek şey uygulandığı problemdir.

En kısa yol problemi M01/K04 Algoritmalar kursunun Çizge Algoritmaları konusunda kuruldu.
Dijkstra Algoritması ve Bellman–Ford Algoritması dersleri gevşetme işlemini, algoritmanın
değişmezini ve negatif ağırlık kısıtını ölçtü; **bu ders o yordamları tekrarlamaz**. Burada
tek bir değişiklik yapılır: gevşetmedeki karşılaştırma **küçüktür**'den **büyüktür**'e çevrilir
ve aynı yordam en uzun yolu aramaya koşulur. Sorulan şey, bu tek karakterlik değişikliğin kaç
çizgede yanlış yanıt ürettiğidir.

- **KP20.** Çizgeler ortak tanımın üretecinden gelir: 8 düğüm, her yönlü kenar yüzde 45 payla
  var, ağırlık 1 ile 9 arasında. Tohum **20260218**, ikinci dağarcık **20260219**.
- **KP21.** İki çizge ailesi kullanılır. **Çevrimsiz** ailede kenar yalnız küçük numaralı
  düğümden büyüğe gider, bu yüzden hiçbir çevrim oluşamaz. **Çevrimli** ailede bu kısıt yoktur.
- **KP22.** Sorulan soru her iki ailede de aynıdır: sıfırıncı düğümden yedinci düğüme giden
  **en çok ağırlıklı basit yol**. Basit yol, hiçbir düğümü tekrarlamayan yoldur.
- **KP23.** Kâhin, bütün basit yolları derine arama ile tarar ve en ağırlıklısını alır. Bu,
  problemin tanımının doğrudan karşılığıdır.
- **KP24.** Kalıp, en kısa yol gevşetmesinin en uzuna çevrilmiş halidir: bütün kenarlar üzerinde
  düğüm sayısı eksi bir kez geçilir ve her geçişte daha büyük değer kabul edilir.
- **KP25.** Ölçü **adımdır**. Kâhinde bir adım bir düğüm ziyareti, kalıpta bir kenar
  gevşetmesidir. İki sayı aynı birimde değildir ve öyle okunur.
- **KP26.** Ayrılan girdi 40 üzerindendir; 3'ün altındaki fark ölçülmemiş sayılır.
- **KP27.** Hedef düğüme hiç yol yoksa her iki yordam da yanıtsız döner; bu bir ayrılma
  sayılmaz, çünkü ikisi de aynı yanıtı verir.
- **KP28.** Düğüm sayısı ayrıca süpürülür. Kâhinin pahalı olduğu bir varsayım değil, ölçülen
  bir eşiktir.
- **KP29.** Optimal alt yapının düşüşünü gösteren altı düğümlük çizge **elle kurulmuştur**,
  üreteçten gelmez. Tek bir mekanizmayı en küçük halinde göstermek için seçilmiştir.

#### Aynı Yordam, İki Çizge Ailesi

Aşağıdaki blok iki aileyi de üretir, kâhini ve kalıbı her çizgede yan yana koşturur ve ayrılan
girdi sayısını iki dağarcık için ayrı ayrı yazar.

```python
TOHUM = 20260218
N = 8


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def cizgeler(tohum=TOHUM, ornek=40, cevrimli=False, n=N):
    """Yonlu cizge: cevrimsiz surumde kenar yalnizca kucukten buyuge gider."""
    r = uretec(tohum)
    kume = []
    for _ in range(ornek):
        kenar = []
        for i in range(n):
            for j in range(n):
                if i == j or (not cevrimli and j < i):
                    continue
                if r(100) < 45:
                    kenar.append((i, j, r(9) + 1))
        kume.append(kenar)
    return kume


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_en_uzun(kenar, s, n=N):
    """Butun basit yollar taranir: 0'dan n-1'e, hicbir dugum tekrarlanmadan."""
    komsu = {}
    for i, j, w in kenar:
        komsu.setdefault(i, []).append((j, w))
    en = None

    def gez(v, gorulen, agirlik):
        nonlocal en
        s.say()
        if v == n - 1:
            if en is None or agirlik > en:
                en = agirlik
            return
        for j, w in komsu.get(v, []):
            if j not in gorulen:
                gez(j, gorulen | {j}, agirlik + w)
    gez(0, {0}, 0)
    return en


def kalip_gevsetme(kenar, s, n=N):
    """En kisa yol gevsetmesinin en uzuna cevrilmisi: kucuk yerine buyuk."""
    en = [None] * n
    en[0] = 0
    for _ in range(n - 1):
        for i, j, w in kenar:
            s.say()
            if en[i] is not None and (en[j] is None or en[i] + w > en[j]):
                en[j] = en[i] + w
    return en[n - 1]


for ad, cevrimli in (("cevrimsiz", False), ("cevrimli ", True)):
    for tohum in (20260218, 20260219):
        ayrilan, ak, ah, fazla = [], 0, 0, 0
        for no, kenar in enumerate(cizgeler(tohum, cevrimli=cevrimli), 1):
            s1, s2 = Sayac(), Sayac()
            a = kalip_gevsetme(kenar, s1)
            b = kahin_en_uzun(kenar, s2)
            ak, ah = ak + s1.adim, ah + s2.adim
            if a != b:
                ayrilan.append(no)
                if a is not None and b is not None:
                    fazla = max(fazla, a - b)
        print(ad, tohum, "| ayrilan", len(ayrilan), "/40", ayrilan[:6],
              "| kalip adim", ak, "| kahin adim", ah, "| en buyuk fazla", fazla)
```

```
cevrimsiz 20260218 | ayrilan 0 /40 [] | kalip adim 3626 | kahin adim 562 | en buyuk fazla 0
cevrimsiz 20260219 | ayrilan 0 /40 [] | kalip adim 3696 | kahin adim 536 | en buyuk fazla 0
cevrimli  20260218 | ayrilan 38 /40 [1, 2, 3, 4, 5, 6] | kalip adim 7210 | kahin adim 3235 | en buyuk fazla 228
cevrimli  20260219 | ayrilan 39 /40 [1, 2, 3, 4, 5, 6] | kalip adim 7203 | kahin adim 3272 | en buyuk fazla 226
```

#### Çevrimsizde Doğru, Çevrimlide Değil

Çevrimsiz ailede ayrılan girdi **0**. Aynı yordam, aynı kod, 40 çizgenin 40'ında kâhinle aynı
yanıtı veriyor ve ikinci dağarcıkta da 0'da kalıyor. Çevrimli ailede ayrılan girdi **38**,
ikinci dağarcıkta **39**. Yordam bozulmadı, çizge değişti.

Ayrılmanın **yönü** de ölçülüyor: kalıbın verdiği değer kâhinin değerinden hiç küçük olmuyor,
en büyük fark **228 ağırlık birimi**. Kalıp fazla tahmin ediyor, yani var olmayan bir yolun
ağırlığını bildiriyor. Bu, iki yordamın **farklı nesneleri** aradığının doğrudan kanıtıdır.

Gevşetme yordamının hesapladığı şey, en çok ağırlıklı **yürüyüştür**: bir düğümden ötekine
kenarları izleyerek gitmek, düğüm tekrarına izin vererek. Sorulan şey ise en çok ağırlıklı
**basit yoldur**: düğüm tekrarı yasak. Çevrimsiz çizgede bu iki nesne çakışır, çünkü çevrim
olmadığında hiçbir yürüyüş bir düğümü iki kez ziyaret edemez. Çevrim varsa çakışma biter ve
gevşetme, çevrimin etrafında dönerek ağırlık biriktirmeye başlar.

Biriktirmenin sonsuza gitmemesinin tek nedeni geçiş sayısının sınırlı olmasıdır. Ama bu sınır
sanıldığı kadar dar değildir: tek bir geçiş içinde kenarlar sabit bir sırayla işlendiği için
bir kenarın ürettiği değer aynı geçişte sonraki kenarlara zincirlenebilir. Elde edilen yürüyüş
bu yüzden geçiş sayısından çok daha uzun olabilir ve fazla tahminin 228'e kadar çıkmasının
nedeni budur. Kenarların işlenme sırası değişirse dönen yanlış değer de değişir — **yanlış
yanıt kararlı bile değildir**.

#### Optimal Alt Yapının Düşmesi

En kısa yolun kanıtı tek bir özelliğe dayanır: bir en kısa yolun her parçası da bir en kısa
yoldur. Bu özellik sayesinde bir düğümün en iyi değeri, komşularının en iyi değerlerinden
kurulabilir ve gevşetme çalışır.

En uzun basit yolda bu özellik düşer. Sıfırıncı düğümden beşinci düğüme en uzun basit yol ve
beşinci düğümden yedinci düğüme en uzun basit yol ayrı ayrı bulunabilir; ama ikisini uç uca
eklemek bir **basit yol vermeyebilir**, çünkü iki parça ortak bir düğümden geçiyor olabilir.
Birleştirme yasaksa alt problemlerin çözümü ana problemin çözümüne taşınmaz ve gevşetmenin
dayandığı zemin kalkar.

Bu düşüş altı düğümlük tek bir çizgede açıkça gösterilebilir. Aşağıdaki blok üç sayıyı yan yana
basar: iki alt problemin ayrı ayrı en iyi çözümlerinin toplamı, gerçek en iyi çözüm ve
gevşetmenin verdiği değer.

```python
KENAR = [(0, 1, 5), (1, 2, 5), (2, 3, 5), (0, 3, 1),
         (3, 2, 5), (2, 4, 5), (4, 5, 5), (3, 5, 1)]
N = 6


def kahin_yol(kenar, bas, son):
    """Butun basit yollar taranir; en agirliklisi yoluyla birlikte doner."""
    komsu = {}
    for i, j, w in kenar:
        komsu.setdefault(i, []).append((j, w))
    en = (None, None)

    def gez(v, yol, agirlik):
        nonlocal en
        if v == son:
            if en[0] is None or agirlik > en[0]:
                en = (agirlik, tuple(yol))
            return
        for j, w in komsu.get(v, []):
            if j not in yol:
                gez(j, yol + [j], agirlik + w)
    gez(bas, [bas], 0)
    return en


def kalip_gevsetme(kenar, son, n=N):
    en = [None] * n
    en[0] = 0
    for _ in range(n - 1):
        for i, j, w in kenar:
            if en[i] is not None and (en[j] is None or en[i] + w > en[j]):
                en[j] = en[i] + w
    return en[son]


a, b, c = kahin_yol(KENAR, 0, 3), kahin_yol(KENAR, 3, 5), kahin_yol(KENAR, 0, 5)
print("0'dan 3'e en uzun basit yol:", a)
print("3'ten 5'e en uzun basit yol:", b)
print("iki parcanin toplami       :", a[0] + b[0],
      "| ortak dugum:", sorted(set(a[1]) & set(b[1])))
print("0'dan 5'e en uzun basit yol:", c)
print("gevsetmenin verdigi        :", kalip_gevsetme(KENAR, 5))
```

```
0'dan 3'e en uzun basit yol: (15, (0, 1, 2, 3))
3'ten 5'e en uzun basit yol: (15, (3, 2, 4, 5))
iki parcanin toplami       : 30 | ortak dugum: [2, 3]
0'dan 5'e en uzun basit yol: (20, (0, 1, 2, 4, 5))
gevsetmenin verdigi        : 70
```

Üç sayı ayrı ayrı okunmalı. Alt problemlerin en iyi çözümleri 15 ve 15; toplamları **30**. Bu
30 hiçbir yolun ağırlığı değildir, çünkü iki parça 2 ve 3 numaralı düğümleri paylaşıyor.
Gerçek yanıt **20** ve karşılık geldiği yol iki alt çözümün hiçbirini içermiyor. Gevşetmenin
verdiği değer ise **70**: çevrimin etrafında birden çok kez dönerek biriktirilmiş bir yürüyüşün
ağırlığı.

Otuz ile yetmiş arasındaki fark önemlidir. Optimal alt yapı düşünce yalnız "alt çözümler
birleştirilemez" olmuyor; gevşetme, birleştirilemeyen parçaları da aşıp tamamen farklı bir
nesneyi ölçmeye başlıyor. Çıktıya bakan biri bunu anlayamaz: dönen değer bir tam sayıdır,
işaretlenmemiştir ve hiçbir uyarı taşımaz. Kursun kuralı burada da geçerlidir — **yanlışlık
çıktısına bakarak seçilemez**, ancak kâhinle görülür.

Aynı gözlem tersinden okunduğunda kullanışlı bir kural verir: bir yordamı yeni bir probleme
taşırken sorulacak soru "kod çalışıyor mu" değil, **"kanıtın dayandığı özellik hâlâ duruyor
mu"** sorusudur. Çevrimsizlik kısıtı bu derste tam olarak o özelliği ayakta tutan koşuldur.

#### Kâhin Ne Zaman Pahalı

Yukarıdaki çıktıda dikkat çeken bir aykırılık var: sekiz düğümde kâhin **562 adım**, kalıp
**3626 adım** harcıyor. Kâhin kalıptan ucuz. Ortak tanımın "kâhin her zaman pahalıdır"
okuması burada ölçülen sayıyla uyuşmuyor ve sayı gizlenmez. Pahalılık bir tanım değil, bir
**ölçek işlevidir**. Düğüm sayısı süpürülünce eşik görünür.

```python
TOHUM = 20260218


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def cizgeler(n, tohum=TOHUM, ornek=10):
    r = uretec(tohum)
    return [[(i, j, r(9) + 1) for i in range(n) for j in range(n)
             if i != j and r(100) < 45] for _ in range(ornek)]


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def kahin_en_uzun(kenar, n, s):
    komsu = {}
    for i, j, w in kenar:
        komsu.setdefault(i, []).append((j, w))
    en = None

    def gez(v, gorulen, agirlik):
        nonlocal en
        s.say()
        if v == n - 1:
            en = agirlik if en is None or agirlik > en else en
            return
        for j, w in komsu.get(v, []):
            if j not in gorulen:
                gez(j, gorulen | {j}, agirlik + w)
    gez(0, {0}, 0)
    return en


def kalip_gevsetme(kenar, n, s):
    en = [None] * n
    en[0] = 0
    for _ in range(n - 1):
        for i, j, w in kenar:
            s.say()
            if en[i] is not None and (en[j] is None or en[i] + w > en[j]):
                en[j] = en[i] + w
    return en[n - 1]


print(" n | kahin adim | kalip adim | oran")
for n in (6, 8, 10, 12, 13):
    ah = ak = 0
    for kenar in cizgeler(n):
        s1, s2 = Sayac(), Sayac()
        kalip_gevsetme(kenar, n, s1)
        kahin_en_uzun(kenar, n, s2)
        ak, ah = ak + s1.adim, ah + s2.adim
    print(f"{n:2d} | {ah:10d} | {ak:10d} | {round(ah / ak, 2)}")
```

```
 n | kahin adim | kalip adim | oran
 6 |        102 |        655 | 0.16
 8 |        746 |       1785 | 0.42
10 |      10210 |       3762 | 2.71
12 |     196705 |       6677 | 29.46
13 |     747243 |       8652 | 86.37
```

Eşik on düğümdedir. Altı düğümde kâhin kalıbın altıda biri kadar adım harcıyor; on üç düğümde
**86,37 kat** fazla harcıyor. Kalıbın adımı 655'ten 8652'ye, yani on üç katına çıkıyor; kâhinin
adımı 102'den 747.243'e, yani yedi bin katına. Aradaki fark, kalıbın çevrimli çizgede
**hâlâ yanlış** yanıt vermesini değiştirmiyor: ucuzlaşan şey doğru yanıt değil, yanlış yanıttır.

Bu tablo bir önceki dersle birlikte okunmalıdır. Gezgin satıcıda kâhin küçük girdide
koşturulabiliyordu ve büyük girdide koşturulamıyordu; burada kâhin küçük girdide **kalıptan
ucuz**. İki durumda da kâhinin bedeli ölçülmüştür, varsayılmamıştır.

#### Özet

- En uzun basit yol, en kısa yolun ters çevrilmiş hali değildir; gevşetme yordamı en çok
  ağırlıklı **yürüyüşü** bulur, problem ise en çok ağırlıklı **basit yolu** ister.
- Çevrimsiz çizgede iki nesne çakışır ve kalıp 40 girdinin 40'ında kâhinle aynı yanıtı verir;
  ikinci dağarcıkta da ayrılan girdi 0.
- Çevrimli çizgede kalıp 40 girdinin **38'inde** ayrılıyor, ikinci dağarcıkta 39 ve fazla
  tahminin en büyüğü **228 ağırlık birimi**.
- Ayrılmanın kaynağı optimal alt yapının düşmesidir: iki alt yolun birleşimi ortak düğüm
  içerebileceği için basit yol olmayabilir.
- Kâhinin pahalılığı ölçekle gelir; sekiz düğümde kâhin kalıptan ucuz, on üç düğümde 86,37 kat
  pahalıdır. Bir yordamın pahalı sayılması ölçülmeden yazılamaz.

#### Sonraki Adım

Bu derste kalıp yanlış yanıt verdi, kâhin doğru yanıtı verdi ve kâhinin bedeli ölçüldü. Sonraki
ders kâhinin **kendisini** hızlandırmayı ele alır. N vezir probleminde geri izleme bütün
yerleşimleri taramak yerine çatışan dalları keser; ortak tanım bunun sayılarını çoktan verdi.
Sorulacak yeni soru şudur: tahtanın simetrilerini eleyerek arama alanı ne kadar küçülür ve bu
küçülme üstel büyümeyi değiştirir mi.

### N Vezir Problemi

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/n-vezir-problemi

Ortak tanımın geri izleme sayılarının girdi alınması ve üstüne simetri elemesinin ölçülmesi: ilk veziri tahtanın yarısıyla sınırlamak sekiz vezirde düğümü 2057'den 1029'a indiriyor, oran 2,00; beş vezirde 1,69, dokuz vezirde 1,78. Yansıtılan çözümler kâhinin çözüm kümesini beş tahta boyunun beşinde de tam olarak geri veriyor. Temel çözüm sayısı sekiz vezirde 92 yerine 12, ama en küçük denklik sınıfı 8 değil 4. Budamanın kazancı n ile büyür, simetri elemesinin kazancı 2'de sabit kalır ve ilk çözümü bulmada hiçbir şey kazandırmaz.

Bir önceki derste kalıp yanlış yanıt verdi ve kâhin doğru yanıtı verdi. Bu ders kâhinin
kendisine bakar. N vezir probleminde kâhin, tahtaya bütün vezir yerleşimlerini deneyen kaba
kuvvettir; geri izleme ise aynı aramayı çatışan dalları keserek yapar. Ortak tanım bu ikisinin
sayılarını çoktan verdi ve **bu ders onları tekrar ölçmez, girdi alır**.

Problem şudur: n satır ve n sütunluk bir tahtaya n vezir yerleştirilecek, hiçbir ikisi aynı
satırı, aynı sütunu ya da aynı çaprazı paylaşmayacak. Yeni soru şudur: tahtanın kendi
simetrileri kullanılarak arama alanı küçültülebilir mi ve küçültülürse bu, budamanın
kattığıyla **aynı türden** bir kazanç mıdır.

- **KP30.** Ortak tanımın geri izleme sayıları **girdidir**: n=5'te budamalı 54 düğüm ve
  budamasız 3906 düğüm, n=6'da 153 ve 55.987, n=7'de 552 ve 960.800, n=8'de budamalı 2057
  düğüm ve budamasız 16.777.216 yerleşim. Bu sayılar tekrar ölçülmez.
- **KP31.** Budamalı gezinti ortak tanımdaki yordamın aynısıdır: her satıra bir vezir konur,
  çatışma varsa dal orada kesilir. Simetri elemesi bunun **üstüne** eklenir.
- **KP32.** Simetri elemesi tek bir kısıtla kurulur: **ilk satırdaki vezir yalnız tahtanın ilk
  yarısına** konabilir. Tek sayıda sütunda orta sütun bu yarıya dahildir.
- **KP33.** Kâhin, kısıtsız budamalı gezintinin ürettiği **çözüm kümesidir**. Yarım tahtadan
  çıkan çözümler yansıtılıp bu kümeyle karşılaştırılır; küme eşitliği tam olarak sınanır.
- **KP34.** Tahtanın simetri grubu sekiz elemanlıdır: dört çeyrek dönüş ve her birinin
  yansıması. Bir çözümün denklik sınıfı bu sekiz dönüşümün ürettiği ayrı çözümlerden oluşur.
- **KP35.** Ölçü **adımdır** ve bir adım, arama ağacında ziyaret edilen bir düğümdür.
- **KP36.** Tahta boyu 5 ile 9 arasında tutulur; büyük n **sayılarak** anlatılır, koşturularak
  değil. **Bu dersin girdisi üreteçten gelmez:** tek girdi tahta boyudur ve bütün sayılar tam
  sayımdır. İkinci dağarcık kuralının yerini bu derste **tahta boyu süpürmesi** alır; her satır
  bağımsız bir koşumdur ve tohuma bağlı hiçbir değer yoktur.
- **KP37.** Bütün çözümleri bulmak ile **tek** çözüm bulmak ayrı ölçülür. İkisi aynı problemin
  iki farklı sorusudur ve maliyetleri karşılaştırılabilir değildir.
- **KP38.** Bir adayın doğrulanması ayrı sayılır: bütün vezir çiftleri karşılaştırılır.
- **KP39.** İkinci kısıt olan ileri bakış, simetri elemesinden bağımsızdır. İkisinin
  birlikte kattığı, ayrı ayrı kattıklarının çarpımıyla karşılaştırılır; eşitlik
  varsayılmaz, ölçülür.

#### Simetri Elemesinin Ölçülmesi

Bir çözüm bulunduğunda onun aynadaki görüntüsü de bir çözümdür. Aynı şey çeyrek dönüşler için
de geçerlidir. Bu, aramanın bir bölümünün **gereksiz** olduğu anlamına gelir: ilk vezir sağ
yarıya konarak bulunan her çözüm, sol yarıya konarak bulunmuş bir çözümün yansımasıdır.

Aşağıdaki blok kısıtı ekler, iki gezintinin düğüm sayısını yan yana koyar, yansıtmayla geri
kurulan kümeyi kâhinin kümesiyle karşılaştırır ve denklik sınıflarını sayar.

```python
class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def vezir(n, ilk_yari=False):
    """Ortak tanimin budamali geri izlemesi; ilk_yari simetri elemesini ekler."""
    s = Sayac()
    cozum = []

    def gez(satir, yer):
        s.say()
        if satir == n:
            cozum.append(tuple(yer))
            return
        ust = (n + 1) // 2 if (ilk_yari and satir == 0) else n
        for sutun in range(ust):
            if any(sutun == y or abs(sutun - y) == satir - i
                   for i, y in enumerate(yer)):
                continue
            yer.append(sutun)
            gez(satir + 1, yer)
            yer.pop()
    gez(0, [])
    return s.adim, cozum


def yansit(c, n):
    return tuple(n - 1 - x for x in c)


def dondur(c, n):
    yeni = [0] * n
    for i, x in enumerate(c):
        yeni[x] = n - 1 - i
    return tuple(yeni)


def sinif(c, n):
    """Bir cozumun sekiz donusum altindaki butun goruntuleri."""
    kume, d = set(), c
    for _ in range(4):
        kume.add(d)
        kume.add(yansit(d, n))
        d = dondur(d, n)
    return kume


print(" n | tam dugum | tam cozum | yarim dugum | yarim cozum | dugum orani"
      " | yansitilinca | temel cozum | en kucuk sinif")
for n in (5, 6, 7, 8, 9):
    d1, c1 = vezir(n)
    d2, c2 = vezir(n, ilk_yari=True)
    geri = set(c2) | {yansit(c, n) for c in c2}
    tam = set(c1)
    temel, gorulen, en_kucuk = 0, set(), 99
    for c in sorted(tam):
        if c in gorulen:
            continue
        k = sinif(c, n)
        gorulen |= k
        temel += 1
        en_kucuk = min(en_kucuk, len(k))
    print(f"{n:2d} | {d1:9d} | {len(c1):9d} | {d2:11d} | {len(c2):11d} |"
          f" {round(d1 / d2, 2):11} | {'ayni' if geri == tam else 'FARKLI':12s}"
          f" | {temel:11d} | {en_kucuk:14d}")
```

```
 n | tam dugum | tam cozum | yarim dugum | yarim cozum | dugum orani | yansitilinca | temel cozum | en kucuk sinif
 5 |        54 |        10 |          32 |           6 |        1.69 | ayni         |           2 |              2
 6 |       153 |         4 |          77 |           2 |        1.99 | ayni         |           1 |              4
 7 |       552 |        40 |         316 |          23 |        1.75 | ayni         |           6 |              4
 8 |      2057 |        92 |        1029 |          46 |         2.0 | ayni         |          12 |              4
 9 |      8394 |       352 |        4704 |         203 |        1.78 | ayni         |          46 |              4
```

#### Kazancın Büyüklüğü ve Türü

İlk sütun ortak tanımın sayılarını doğruluyor: 54, 153, 552, 2057. Simetri kısıtı eklendiğinde
düğüm sayısı sekiz vezirde 2057'den **1029'a** iniyor, oran **2,00**. Beş vezirde 1,69, altı
vezirde 1,99, yedi vezirde 1,75, dokuz vezirde 1,78. Beş satırın hiçbirinde oran 2'yi geçmiyor.

Yansıtma sütunu kâhin karşılaştırmasıdır. Yarım tahtadan çıkan çözümlere yansımaları eklendiğinde
elde edilen küme, kısıtsız aramanın çözüm kümesiyle **beş tahta boyunun beşinde de aynı**.
Simetri elemesi bir yaklaşık çözüm değildir: hiçbir çözüm kaybolmuyor, yalnız bulunma yolu
değişiyor. Bu, kursta ölçülen ender bir durumdur — kalıp kâhinden hiçbir girdide ayrılmıyor.

Şimdi bu kazanç ortak tanımın budama kazancının yanına konmalıdır.

| n | budamasız (ortak tanım) | budamalı | budama oranı | yarım tahta | simetri oranı |
|---|---|---|---|---|---|
| 5 | 3.906 | 54 | 72,3 | 32 | 1,69 |
| 6 | 55.987 | 153 | 365,9 | 77 | 1,99 |
| 7 | 960.800 | 552 | 1740,6 | 316 | 1,75 |
| 8 | 16.777.216 yerleşim | 2057 | 8156,2 | 1029 | 2,00 |

İki sütun aynı birimde ama **aynı türden değil**. Budama oranı 72,3'ten 8156,2'ye çıkıyor, yani
n büyüdükçe budamanın kattığı da büyüyor. Simetri oranı 1,69 ile 2,00 arasında kalıyor ve
büyümüyor; büyüyemez de, çünkü elenen şey **tahtanın sabit sayıda simetrisidir** ve o sayı n
ile değişmez.

Bu ayrım kursun ölçü okumasının bir parçasıdır. Bir iyileştirmenin sayısı tek başına yeterli
değildir; **sayının n ile ne yaptığı** sorulmalıdır. Sekiz vezirde bin düğüm kazandıran bir
kısıt, on altı vezirde de yalnız yarısını kazandırır ve o yarı hâlâ üsteldir. Ortak tanımın
dördüncü okuması bunu budama için söylüyordu: budamanın kazancı ölçekle artar ama **sonuç hâlâ
üsteldir**. Simetri elemesinde ise kazanç ölçekle artmıyor bile.

Temel çözüm sütunu üçüncü bir sayı veriyor. Sekiz vezirde 92 çözüm, ama denklik sınıflarının
sayısı **12**. Bu 92'nin 8'e bölümü değildir: 92 bölü 8, 11,5 eder. Fark, en küçük sınıf
sütununda görünür. Bazı çözümler bir dönüşüm altında **kendilerine** eşlenir, bu yüzden
sınıfları sekiz değil dört ya da iki elemanlıdır. Beş vezirde en küçük sınıf 2 elemanlıdır.
Simetriyle eleme yaparken "sekize böl" demek bu yüzden yanlış olur.

#### İki İyileştirme Çarpılıyor mu

Simetri elemesi aramanın **başlangıcını** kısıtlar. Bundan bağımsız ikinci bir kısıt daha
eklenebilir: bir vezir yerleştirildikten sonra kalan satırlardan birinde hiç boş sütun kalmadıysa
o dal hemen kesilir. Buna ileri bakış denir ve çatışmanın **görünmesini beklemez**, geleceğe
bakıp keser. İki iyileştirme birbirinden bağımsızdır; sorulacak şey, birlikte kullanıldıklarında
kazançların çarpılıp çarpılmadığıdır.

```python
class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def catisiyor(sutun, satir, yer):
    return any(sutun == y or abs(sutun - y) == satir - i
               for i, y in enumerate(yer))


def vezir(n, ilk_yari=False, ileri=False):
    """Iki bagimsiz iyilestirme: simetri elemesi ve ileri bakis."""
    s = Sayac()
    sayi = 0

    def gez(satir, yer):
        nonlocal sayi
        s.say()
        if satir == n:
            sayi += 1
            return
        ust = (n + 1) // 2 if (ilk_yari and satir == 0) else n
        for sutun in range(ust):
            if catisiyor(sutun, satir, yer):
                continue
            yer.append(sutun)
            if not ileri or all(any(not catisiyor(c, r, yer) for c in range(n))
                                for r in range(satir + 1, n)):
                gez(satir + 1, yer)
            yer.pop()
    gez(0, [])
    return s.adim, sayi


print(" n | yalin | simetri | ileri bakis | ikisi birden"
      " | ayri ayri carpim | olculen oran")
for n in (6, 7, 8, 9, 10):
    d0, _ = vezir(n)
    d1, _ = vezir(n, ilk_yari=True)
    d2, _ = vezir(n, ileri=True)
    d3, _ = vezir(n, ilk_yari=True, ileri=True)
    carpim = (d0 / d1) * (d0 / d2)
    print(f"{n:2d} | {d0:5d} | {d1:7d} | {d2:11d} | {d3:12d} |"
          f" {round(carpim, 2):16} | {round(d0 / d3, 2)}")
```

```
 n | yalin | simetri | ileri bakis | ikisi birden | ayri ayri carpim | olculen oran
 6 |   153 |      77 |          87 |           44 |             3.49 | 3.48
 7 |   552 |     316 |         334 |          187 |             2.89 | 2.95
 8 |  2057 |    1029 |        1165 |          583 |             3.53 | 3.53
 9 |  8394 |    4704 |        4720 |         2658 |             3.17 | 3.16
10 | 35539 |   17770 |       17489 |         8745 |             4.06 | 4.06
```

Son iki sütun beş satırın beşinde de örtüşüyor: 3,49 ile 3,48, 2,89 ile 2,95, 3,53 ile 3,53,
3,17 ile 3,16, 4,06 ile 4,06. Ayrı ayrı ölçülen kazançların çarpımı, birlikte ölçülen kazanca
eşit çıkıyor. İki kısıt aramanın **farklı yerlerine** dokunduğu için birbirlerinin işini
tekrarlamıyorlar.

Bu örtüşme bir kural değildir, ölçülmüş bir sonuçtur; iki kısıt aynı dalları kesiyor olsaydı
çarpım ölçülen orandan büyük çıkardı ve fark **fazla sayılan kazanç** olurdu. Ölçüm tam da bunu
sınamak için yapılır.

Yine de birleşik oran 4,06'da kalıyor. Üç iyileştirme üst üste kondu ve arama alanı hâlâ n ile
üstel büyüyor; on vezirde 8745 düğüm, on iki vezirde bu sayı yine kat kat artacak. **Sabit
çarpanlar toplandığında bile büyüme türü değişmez.**

#### Simetrinin Hiçbir Şey Kazandırmadığı Soru

Buraya kadar sorulan soru "bütün çözümler" sorusuydu. Aynı problem başka bir soruyla da
sorulabilir: **bir tane** çözüm bulmak. İki soru aynı tahtaya bakar ama maliyetleri
karşılaştırılamaz.

```python
class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def gezinti(n, ilk_yari=False, dur=False):
    """Budamali geri izleme. dur=True ise ilk cozumde durur."""
    s = Sayac()
    bulunan = []

    def gez(satir, yer):
        s.say()
        if dur and bulunan:
            return
        if satir == n:
            bulunan.append(tuple(yer))
            return
        ust = (n + 1) // 2 if (ilk_yari and satir == 0) else n
        for sutun in range(ust):
            if dur and bulunan:
                return
            if any(sutun == y or abs(sutun - y) == satir - i
                   for i, y in enumerate(yer)):
                continue
            yer.append(sutun)
            gez(satir + 1, yer)
            yer.pop()
    gez(0, [])
    return s.adim, bulunan


def dogrula(c, n):
    """Aday cozum butun vezir ciftleri karsilastirilarak sinanir."""
    adim = 0
    for i in range(n):
        for j in range(i + 1, n):
            adim += 1
            if c[i] == c[j] or abs(c[i] - c[j]) == j - i:
                return False, adim
    return True, adim


print(" n | ilk cozum | ilk cozum yarim | butun cozumler | oran | dogrulama")
for n in (5, 6, 7, 8, 9, 10):
    d1, b1 = gezinti(n, dur=True)
    d2, _ = gezinti(n, ilk_yari=True, dur=True)
    d3, hepsi = gezinti(n)
    ok, adim = dogrula(b1[0], n)
    print(f"{n:2d} | {d1:9d} | {d2:15d} | {d3:14d} | {d3 // d1:4d} |"
          f" {adim:3d} adim, dogru mu {ok}")
```

```
 n | ilk cozum | ilk cozum yarim | butun cozumler | oran | dogrulama
 5 |         6 |               6 |             54 |    9 |  10 adim, dogru mu True
 6 |        32 |              32 |            153 |    4 |  15 adim, dogru mu True
 7 |        10 |              10 |            552 |   55 |  21 adim, dogru mu True
 8 |       114 |             114 |           2057 |   18 |  28 adim, dogru mu True
 9 |        42 |              42 |           8394 |  199 |  36 adim, dogru mu True
10 |       103 |             103 |          35539 |  345 |  45 adim, dogru mu True
```

İkinci ve üçüncü sütun **altı satırın altısında da aynı**. Simetri elemesi ilk çözümü bulmakta
hiçbir şey kazandırmıyor, çünkü aramanın bulduğu ilk çözümde birinci vezir zaten sıfırıncı
sütundadır ve o sütun kısıtın içindedir. Kısıt, hiç girilmeyecek bir bölgeyi kapatıyor.

Bu, iyileştirmelerin **soruya bağlı** olduğunun ölçülmüş halidir. Aynı kısıt bütün çözümler
sorulduğunda düğümü tam yarıya indiriyor, tek çözüm sorulduğunda sıfır kazandırıyor. Bir
iyileştirmenin değeri yordamdan değil, **sorulan sorudan** gelir.

Son iki sütun daha keskin bir farkı gösteriyor. On vezirde bütün çözümleri bulmak 35.539 düğüm,
tek çözüm bulmak **103 düğüm**, oran 345. Bulunmuş bir adayın doğru olduğunu **sınamak** ise
yalnız 45 adım: bütün vezir çiftlerinin karşılaştırılması. Aramak, bulmak ve doğrulamak üç ayrı
maliyettir ve aralarındaki fark tahta boyuyla açılır.

#### Özet

- Simetri elemesi düğüm sayısını sekiz vezirde 2057'den 1029'a indiriyor; oran beş tahta boyunda
  1,69 ile 2,00 arasında kalıyor.
- Yansıtmayla geri kurulan çözüm kümesi kâhinin kümesiyle beş boyun beşinde de aynı; hiçbir
  çözüm kaybolmuyor.
- Budamanın oranı 72,3'ten 8156,2'ye çıkıyor, simetrinin oranı 2'de duruyor: biri n ile büyüyen,
  öteki **sabit çarpanlı** bir kazançtır.
- Temel çözüm sayısı sekiz vezirde 12; 92'yi sekize bölmek yanlış olur, çünkü bazı çözümler
  kendi görüntülerine eşlenir ve en küçük denklik sınıfı 4 elemanlıdır.
- Simetri elemesi tek çözüm arandığında hiçbir şey kazandırmıyor; aynı kısıt bir soruda yarı
  yarıya kazandırıp ötekinde sıfır kazandırıyor.

#### Sonraki Adım

N vezirde arama alanı tahtanın kendisinden okunuyordu: her satıra bir vezir, her vezire n
seçenek. Sonraki ders arama alanının **verili olmadığı** problemlere geçer. At turu ve labirent
aynı ızgarada tanımlanır, ama nasıl modellendiklerine göre taranacak nesne sayısı binlerce kat
değişir. Sorulacak soru şudur: arama alanı problemin bir özelliği midir, yoksa modelin mi.

### At Turu ve Labirent Problemleri

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/at-turu-ve-labirent-problemleri

Aynı ızgaranın iki ayrı modelle temsil edilmesi ve arama alanının modelle birlikte değişmesi: beşe beşlik ızgarada çözüm adayı yol sayılınca 8512 basit yol ve 90.111 adım, hücre sayılınca 25 adım; oran 3604 ve iki model aynı en kısa uzunluğu veriyor. At turunda en az seçenek bırakan kareyi önce deneme sırası beşe beşlik tahtada dört başlangıcın dördünde de turu 25 düğümde buluyor, doğal sıra 182 ile 101.718 arasında düğüm geziyor. Dörde dörtlük tahtada hiç tur yok ve iki sıralama da tam olarak 29.976 düğüm geziyor: sıralama sezgiseli hayır yanıtında hiçbir şey kazandırmıyor.

N vezirde arama alanı tahtadan okunuyordu: her satıra bir vezir, her vezire n seçenek. Uzayın
büyüklüğü problemin tanımından çıkıyordu ve tartışılacak bir yanı yoktu. Bu ders o rahatlığı
kaldırır. At turu ve labirent aynı ızgara üzerinde tanımlanır, ama taranacak nesnenin ne
olduğuna karar veren şey problem değil **modeldir** ve iki model arasındaki fark binlerce
kata çıkar.

Labirent problemi şudur: bir ızgaranın sol üst hücresinden sağ alt hücresine, komşu hücreler
arasında ilerleyerek gidilecek. At turu problemi şudur: beşe beşlik bir tahtada bir attan
başlanıp her kareye tam bir kez uğranacak. İkisi de bir arama problemidir ve ikisinde de
sorulacak ilk soru "hangi algoritma" değil, **"neyin arandığı"** sorusudur.

- **KP40.** Labirentte iki model karşılaştırılır. **Birinci modelde** çözüm adayı bir
  **yoldur**; taranan nesne, sol üstten sağ alta giden bütün basit yollardır. **İkinci modelde**
  çözüm adayı bir **hücredir**; her hücre en çok bir kez açılır.
- **KP41.** İkinci model, M01/K03 Veri Yapıları kursunun Çizgeler konusunda kurulan **enine
  aramadır**. Yordam **tekrarlanmaz**, doğrudan kullanılır.
- **KP42.** Kâhin birinci modeldir: bütün basit yolları tarar ve en kısasını bulur. İkinci
  modelin verdiği uzunluk bununla karşılaştırılır.
- **KP43.** Izgara engelsizdir. Engel eklemek yol sayısını değiştirir ama iki modelin **oranını
  değiştirmez**; ölçülen şey oranın büyüklük düzenidir. **Bu dersin girdisi de üreteçten
  gelmez:** ızgara boyu ve tahta boyu tek girdidir, bütün sayılar tam sayımdır. İkinci dağarcık
  kuralının yerini **boy süpürmesi** alır ve tohuma bağlı hiçbir değer yoktur.
- **KP44.** Ölçü **adımdır**. Birinci modelde bir adım bir kısmi yol düğümü, ikinci modelde bir
  hücre açılışıdır.
- **KP45.** At turunda iki sıralama karşılaştırılır: kareleri **doğal sırada** denemek ve
  **en az seçenek bırakan** kareyi önce denemek. İkinci sıralama bir budama değildir; hiçbir dal
  kesilmez, yalnız denenme sırası değişir.
- **KP46.** İkinci sıralama bir sezgiseldir ve doğruluğu **varsayılmaz**. Bulduğu her tur
  kâhinle doğrulanır: bütün kareler bir kez mi ziyaret edilmiş, her adım geçerli bir at hamlesi
  mi.
- **KP47.** Beşe beşlik tahtada dört başlangıç karesi ölçülür. Bütün kareleri ölçmek, tur
  bulunmayan karelerde arama alanının tamamının taranmasını gerektirir ve koşum bütçesini aşar.
- **KP48.** Tur bulunmayan durum **dörde dörtlük tahtada** ölçülür; orada hiçbir kareden tur
  yoktur ve arama alanının tamamı taranabilir.
- **KP49.** Ucuz modelin sınırı, soruyu değiştirerek sınanır. Aynı sayımdan hem en kısa yol
  hem de bütün hücrelerden geçen yol sayılır; bir modelin yeterliliği sorudan bağımsız
  yazılmaz.

#### Aynı Izgara, İki Model

Birinci model soruyu şöyle kurar: sol üstten sağ alta giden bütün yolları üret, en kısasını
seç. İkinci model şöyle kurar: sol üstten başlayarak hücreleri uzaklık sırasına göre aç, sağ
alta ilk varışta dur. İkisi de doğru yanıtı verir; ölçülen şey neye mal olduğudur.

```python
from collections import deque


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def komsular(h, r, c):
    for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
        i, j = h[0] + dr, h[1] + dc
        if 0 <= i < r and 0 <= j < c:
            yield (i, j)


def kahin_butun_yollar(r, c, s):
    """Model 1: cozum adayi bir YOLDUR. Butun basit yollar taranir."""
    hedef, sayi, en_kisa = (r - 1, c - 1), 0, None

    def gez(h, gorulen):
        nonlocal sayi, en_kisa
        s.say()
        if h == hedef:
            sayi += 1
            if en_kisa is None or len(gorulen) < en_kisa:
                en_kisa = len(gorulen)
            return
        for k in komsular(h, r, c):
            if k not in gorulen:
                gez(k, gorulen | {k})
    gez((0, 0), {(0, 0)})
    return sayi, en_kisa


def kalip_enine(r, c, s):
    """Model 2: cozum adayi bir HUCREDIR. Her hucre en cok bir kez acilir."""
    hedef = (r - 1, c - 1)
    kuyruk, gorulen = deque([((0, 0), 1)]), {(0, 0)}
    while kuyruk:
        h, u = kuyruk.popleft()
        s.say()
        if h == hedef:
            return u
        for k in komsular(h, r, c):
            if k not in gorulen:
                gorulen.add(k)
                kuyruk.append((k, u + 1))
    return None


print(" izgara | basit yol sayisi | kahin adim | enine adim | oran | en kisa")
for n in (3, 4, 5):
    s1, s2 = Sayac(), Sayac()
    sayi, en_kisa = kahin_butun_yollar(n, n, s1)
    u = kalip_enine(n, n, s2)
    print(f" {n}x{n:2d} | {sayi:16d} | {s1.adim:10d} | {s2.adim:10d} |"
          f" {s1.adim // s2.adim:5d} | {en_kisa} ve {u}")
```

```
 izgara | basit yol sayisi | kahin adim | enine adim | oran | en kisa
 3x 3 |               12 |         51 |          9 |     5 | 5 ve 5
 4x 4 |              184 |       1271 |         16 |    79 | 7 ve 7
 5x 5 |             8512 |      90111 |         25 |  3604 | 9 ve 9
```

#### Uzayı Belirleyen Şey Model

Son sütun iki modelin **aynı yanıtı** verdiğini gösteriyor: üç ızgaranın üçünde de en kısa
uzunluk aynı, ayrılan girdi 0. İkinci model bir yaklaşık çözüm değil, kâhinle tam olarak
uyuşan bir çözümdür.

Ama maliyetleri aynı değil. Beşe beşlik ızgarada birinci model **8512 basit yol** üretmek için
**90.111 adım** harcıyor; ikinci model **25 adım** harcıyor, yani hücre başına bir adım.
Oran **3604**. Üçe üçlük ızgarada oran 5, dörde dörtlükte 79, beşe beşlikte 3604. Oran
ızgara büyüdükçe hızla açılıyor.

Farkın kaynağı algoritma değil, **sayılan nesnedir**. Birinci modelde aday bir yoldur ve yol
sayısı hücre sayısıyla üstel büyür. İkinci modelde aday bir hücredir ve hücre sayısı ızgaranın
alanı kadardır. İkinci model, birinci modelin ürettiği 8512 yolu **üretmeden** aralarındaki en
kısayı bulur, çünkü aynı hücreye farklı yollardan varmanın sonrasını değiştirmediğini kullanır.

Buradan kursun ölçü okumasına bağlanan bir cümle çıkar: **arama alanının büyüklüğü problemin
değil, modelin özelliğidir.** Bir problem için "arama alanı çok büyük" demek, ancak hangi
modelin kastedildiği söylendiğinde bir anlam taşır. Aynı labirent bir modelde 8512 adaya, başka
bir modelde 25 adaya sahiptir.

#### Ucuz Modelin Yanıtlayamadığı Soru

Ucuz modelin bir bedeli var ve şimdiye kadar görünmedi. Enine arama, aynı hücreye farklı
yollardan varmanın sonrasını değiştirmediğini kullanıyordu. Bu varsayım en kısa yol sorusunda
doğrudur, ama her soruda doğru değildir. Aşağıdaki blok aynı sayımı yapıp yolları
**uzunluklarına göre** ayırır.

```python
from collections import Counter


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def yollar(n, s):
    """Butun basit yollar; uzunluklarina gore sayilir."""
    hedef, sayim = (n - 1, n - 1), Counter()

    def gez(h, gorulen):
        s.say()
        if h == hedef:
            sayim[len(gorulen)] += 1
            return
        for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
            k = (h[0] + dr, h[1] + dc)
            if 0 <= k[0] < n and 0 <= k[1] < n and k not in gorulen:
                gez(k, gorulen | {k})
    gez((0, 0), {(0, 0)})
    return sayim


for n in (4, 5):
    s = Sayac()
    sayim = yollar(n, s)
    tam = sayim[n * n]
    print(f"{n}x{n} | yol {sum(sayim.values()):5d} | adim {s.adim:6d}"
          f" | en kisa {min(sayim)} uzunlugunda {sayim[min(sayim)]:3d} yol"
          f" | butun hucreleri gezen {tam:3d} yol")
    print("      uzunluga gore:", dict(sorted(sayim.items())))
```

```
4x4 | yol   184 | adim   1271 | en kisa 7 uzunlugunda  20 yol | butun hucreleri gezen   0 yol
      uzunluga gore: {7: 20, 9: 36, 11: 48, 13: 48, 15: 32}
5x5 | yol  8512 | adim  90111 | en kisa 9 uzunlugunda  70 yol | butun hucreleri gezen 104 yol
      uzunluga gore: {9: 70, 11: 224, 13: 510, 15: 956, 17: 1586, 19: 2224, 21: 2106, 23: 732, 25: 104}
```

Beşe beşlik ızgarada 8512 yolun **70'i** en kısa uzunluktadır ve **104'ü bütün 25 hücreden**
geçer. Enine arama birinci sayıyı 25 adımda verir. İkinci sayı için hiçbir şey söyleyemez:
hücre başına tek bir değer tuttuğu için, bir hücreye hangi hücreler kullanılarak gelindiğini
unutur ve "bütün hücrelerden geçen bir yol var mı" sorusu tam olarak bu bilgiyi ister.
Soru değişince 25 adımlık model düşer, geriye 90.111 adımlık sayım kalır.

Dörde dörtlük satır aynı sorunun **hayır** yanıtını gösteriyor: 184 yolun hiçbiri 16 hücrenin
tamamından geçmiyor, en uzunu 15 hücrelik. Bu hayır yanıtı da 1271 adımın tamamı harcanmadan
alınamaz. Aramanın bir sorusu ucuzlarken ötekinin ucuzlamadığını görmek, modelin **soruyla
birlikte** seçildiğinin kanıtıdır.

#### At Turunda Sıralamanın Payı

At turunda ikinci model doğrudan kurulamaz. Bir kareye hangi yoldan gelindiği **önemlidir**,
çünkü hangi karelerin kullanıldığı sonrasını belirler. Aday, hücre değil **kısmi turdur** ve
arama alanı üsteldir. Elde kalan tek araç, dalların **denenme sırasıdır**.

Sezgi şudur: seçeneği en az olan kareye erken gidilmezse o kare sonradan erişilemez hale gelir
ve arama boşa dolaşır. Aşağıdaki blok bu sırayı doğal sırayla karşılaştırır ve bulunan her turu
kâhinle doğrular.

```python
HAMLE = ((1, 2), (2, 1), (-1, 2), (-2, 1), (1, -2), (2, -1), (-1, -2), (-2, -1))


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def secenekler(h, gidilen, n):
    r, c = h
    return [(r + a, c + b) for a, b in HAMLE
            if 0 <= r + a < n and 0 <= c + b < n and (r + a, c + b) not in gidilen]


def tur(bas, n, siralama, s):
    """Ilk tam turda durur. siralama=True: en az secenek birakan kare once."""
    def gez(h, gidilen):
        s.say()
        if len(gidilen) == n * n:
            return [h]
        aday = secenekler(h, gidilen, n)
        if siralama:
            aday.sort(key=lambda k: len(secenekler(k, gidilen | {k}, n)))
        for k in aday:
            y = gez(k, gidilen | {k})
            if y:
                return [h] + y
        return None
    return gez(bas, {bas})


def dogrula(yol, n):
    """Hakem: her kare bir kez mi, her adim gecerli bir at hamlesi mi."""
    if yol is None or len(set(yol)) != n * n:
        return False
    return all((abs(a[0] - b[0]), abs(a[1] - b[1])) in ((1, 2), (2, 1))
               for a, b in zip(yol, yol[1:]))


print("5x5 | baslangic | dogal sira dugum | en az secenek dugum | dogrulanan tur")
for bas in ((0, 0), (0, 2), (2, 2), (4, 0)):
    s1, s2 = Sayac(), Sayac()
    y1, y2 = tur(bas, 5, False, s1), tur(bas, 5, True, s2)
    print(f"    | {str(bas):9s} | {s1.adim:16d} | {s2.adim:19d} |"
          f" {dogrula(y1, 5)} ve {dogrula(y2, 5)}")
t1 = t2 = bulan = 0
for i in range(4):
    for j in range(4):
        s1, s2 = Sayac(), Sayac()
        y1, y2 = tur((i, j), 4, False, s1), tur((i, j), 4, True, s2)
        t1, t2 = t1 + s1.adim, t2 + s2.adim
        bulan += 1 if dogrula(y1, 4) else 0
print(f"4x4 | 16 kare   | {t1:16d} | {t2:19d} | bulunan tur {bulan}")
```

```
5x5 | baslangic | dogal sira dugum | en az secenek dugum | dogrulanan tur
    | (0, 0)    |            70716 |                  25 | True ve True
    | (0, 2)    |           101718 |                  25 | True ve True
    | (2, 2)    |            25542 |                  25 | True ve True
    | (4, 0)    |              182 |                  25 | True ve True
4x4 | 16 kare   |            29976 |               29976 | bulunan tur 0
```

#### Evet Ucuz, Hayır Değil

Beşe beşlik tahtada en az seçenek sırası dört başlangıcın dördünde de turu **25 düğümde**
buluyor. Yirmi beş, tahtadaki kare sayısıdır: arama hiç geri dönmüyor, her karede doğru hamleyi
ilk denemede seçiyor. Doğal sıra aynı turları 182 ile **101.718** düğüm arasında buluyor.
Kareler farklı olduğu için satırlar birbiriyle değil, kendi içinde karşılaştırılmalıdır; her
satırda oran en az 7, en çoğu 4068.

Doğrulama sütunu bu satırların hepsinde `True`. Sezgiselin bulduğu turlar gerçekten turdur:
yirmi beş karenin hepsi bir kez ziyaret edilmiş ve her adım geçerli bir at hamlesidir. Sezgisel
hızlandırmıyor demek yetmez, **yanlış da yapmıyor** ve bu ayrıca ölçülmüştür.

Son satır dersin en sert sayısıdır. Dörde dörtlük tahtada hiçbir kareden tur yoktur. İki
sıralama da bunu bulmak için **tam olarak 29.976 düğüm** geziyor. Sayılar eşit, fark sıfır.
Sıralama sezgiseli burada hiçbir şey kazandırmıyor, çünkü kazandırdığı şey **doğru dalı erken
bulmaktı**; doğru dal yoksa bütün dallar denenir ve denenme sırası toplamı değiştirmez.

Bu, sıralama sezgiselleriyle budamanın arasındaki farkın ölçülmüş halidir. Budama arama
uzayından **parça siler**, bu yüzden yanıt evet de olsa hayır da olsa kazandırır. Sıralama
uzaydan hiçbir şey silmez, yalnız gezinme yönünü değiştirir; bu yüzden yalnız **evet**
yanıtında kazandırır. Bir sezgiselin sayısı, hangi yanıt için ölçüldüğü söylenmeden okunamaz.

#### Özet

- Aynı labirent iki modelde iki ayrı arama alanı üretir: çözüm adayı yol sayılınca beşe beşlik
  ızgarada 8512 aday ve 90.111 adım, hücre sayılınca 25 adım; oran 3604.
- İki model üç ızgaranın üçünde de aynı en kısa uzunluğu veriyor; ayrılan girdi 0, yani ucuz
  model bir yaklaşım değil tam çözümdür.
- Arama alanının büyüklüğü problemin değil modelin özelliğidir; "uzay çok büyük" cümlesi hangi
  modelin kastedildiği söylenmeden anlam taşımaz.
- Ucuz modelin sınırı soruyla belirlenir: 8512 yolun 104'ü bütün hücrelerden geçiyor, ama enine
  arama bu sayıyı veremez ve soru değişince 90.111 adımlık sayıma dönülür.
- At turunda en az seçenek bırakan kareyi önce deneme sırası, beşe beşlik tahtada dört
  başlangıcın dördünde de turu 25 düğümde buluyor; doğal sıra 182 ile 101.718 düğüm arasında.
- Dörde dörtlük tahtada tur yoktur ve iki sıralama da 29.976 düğüm geziyor: sıralama sezgiseli
  hayır yanıtında hiçbir şey kazandırmaz, çünkü uzaydan parça silmez.

#### Sonraki Adım

Bu dersin son satırı bir ayrım açtı: bir aramanın evet yanıtı ile hayır yanıtı aynı şeye mal
olmuyor. Sonraki ders bu ayrımı konunun ana sorusu yapar. Hamilton yolu problemi bir **varlık**
sorusudur ve karar problemi olarak kurulur; ölçülecek şey karar vermek, yolu bulmak ve verilen
bir yolu doğrulamak arasındaki adım farkıdır. Sınıf adları yine M01/K08'e bırakılacak, ama fark
burada sayılacak.

### Hamilton Yolları

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/hamilton-yollari

Bir varlık sorusunun karar problemi olarak kurulması ve karar, arama, sayma ile doğrulamanın ayrı ayrı sayılması: 40 çizgenin 25'inde yol var ve karar 1526 adım harcarken sayma 29.270 adım harcıyor, oran 19,18. Yol olmayan 15 çizgenin 15'inde karar adımı sayma adımına tam olarak eşit, ikisi de 2538. İkinci dağarcıkta 23 ve 17 çizgeyle aynı yapı çıkıyor. Bulunan bir yolun doğrulanması çizge başına 7 adım, aranması ortalama 61 adım. Karmaşıklık sınıflarının adları Hesaplama Kuramı kursuna bırakılıyor.

At turunda son satır bir ayrım açtı: dörde dörtlük tahtada tur yoktu ve iki sıralama da tam
olarak aynı sayıda düğüm gezdi. Evet yanıtı ucuzlayabiliyordu, hayır yanıtı ucuzlamıyordu. Bu
ders o ayrımı konunun ana sorusu yapar.

Hamilton yolu, bir çizgenin **bütün düğümlerinden tam bir kez geçen** yoldur. At turu bunun
özel bir halidir: kareler düğüm, at hamleleri kenardır. Buradaki soru artık "en iyi hangisi"
değil, **"var mı"** sorusudur. Bu, bir **karar problemidir** ve yanıtı tek bir sözcüktür.
Yanında iki soru daha durur: **arama** problemi bir yol ister, **sayma** problemi kaç tane
olduğunu ister. Üç soru aynı girdiye bakar ve üçünün maliyeti ayrı ayrı ölçülür.

- **KP50.** Çizgeler ortak tanımın üretecinden gelir: 8 düğüm, her düğüm ikilisi yüzde 38 payla
  bağlı, yönsüz. Tohum **20260218**, ikinci dağarcık **20260219**, dağarcık başına 40 çizge.
- **KP51.** **Karar problemi:** bütün düğümlerden geçen bir yol var mı. Yordam ilk yolu bulunca
  durur; yol yoksa arama alanının tamamını tarar.
- **KP52.** **Arama problemi:** böyle bir yol varsa **hangisi**. Bu derste karar yordamı yolu
  zaten üretiyor; arama ile kararın adımı bu problemde **aynıdır** ve bu ölçülmüş bir sonuçtur,
  genel bir kural değildir.
- **KP53.** **Sayma problemi:** kaç tane var. Yordam hiç durmaz, uzayın tamamını tarar. Her yol
  iki uçtan da gezildiği için sayım her yolu iki kez içerir; ölçülen şey **adım**, sayının
  kendisi değildir.
- **KP54.** **Doğrulama:** verilen bir aday yolun gerçekten Hamilton yolu olup olmadığı
  sınanır. Bir adım, ardışık iki düğümün komşuluk denetimidir.
- **KP55.** Kâhin sayma yordamıdır: uzayı tamamen tarar, bu yüzden yanıtı tanım gereği doğrudur.
  Kararın yanıtı her çizgede kâhinin sayısıyla karşılaştırılır.
- **KP56.** Ölçü **adımdır**; bir adım, arama ağacında bir düğüm ziyaretidir.
- **KP57.** Evet çizgeleri ile hayır çizgeleri **ayrı toplanır**. İkisini tek ortalamada
  birleştirmek dersin ölçtüğü farkı yok ederdi.
- **KP58.** **Karmaşıklık sınıflarının adları bu derste yazılmaz.** Doğrulama ile aramanın
  farkının nasıl adlandırıldığı M01/K08 Hesaplama Kuramı kursuna aittir.
- **KP59.** Ucuz sınama yalnız **gerek koşulları** denetler. Yanlış sayısı kâhinle ölçülür;
  sıfır çıkması varsayılmaz, sayılır.

#### Üç Soru, Bir Girdi

Aşağıdaki blok her çizgede kararı ve saymayı ayrı ayrı koşturur, bulunan yolu doğrular ve
sonuçları evet ile hayır çizgeleri için ayrı toplar.

```python
TOHUM = 20260218
N = 8


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def cizgeler(tohum=TOHUM, ornek=40, pay=38, n=N):
    """Yonsuz cizge: her ikili yuzde pay olasilikla bagli."""
    r = uretec(tohum)
    kume = []
    for _ in range(ornek):
        komsu = {i: set() for i in range(n)}
        for i in range(n):
            for j in range(i + 1, n):
                if r(100) < pay:
                    komsu[i].add(j)
                    komsu[j].add(i)
        kume.append(komsu)
    return kume


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def ara(komsu, s, dur, n=N):
    """dur=True: ilk Hamilton yolunda durur. dur=False: hepsini sayar."""
    bulunan, sayi = [], 0

    def gez(v, yol):
        nonlocal sayi
        s.say()
        if len(yol) == n:
            sayi += 1
            if not bulunan:
                bulunan.append(tuple(yol))
            return True
        for k in sorted(komsu[v]):
            if k not in yol:
                yol.append(k)
                bitti = gez(k, yol)
                yol.pop()
                if bitti and dur:
                    return True
        return False

    for bas in range(n):
        if gez(bas, [bas]) and dur:
            break
    return (bulunan[0] if bulunan else None), sayi


def dogrula(komsu, yol, n=N):
    """Hakem: aday yol n dugum mu, ardisik ikililer komsu mu."""
    adim = 0
    if yol is None or len(set(yol)) != n:
        return False, adim
    for a, b in zip(yol, yol[1:]):
        adim += 1
        if b not in komsu[a]:
            return False, adim
    return True, adim


for tohum in (20260218, 20260219):
    evet = {"n": 0, "karar": 0, "sayma": 0, "dogrulama": 0}
    hayir = {"n": 0, "karar": 0, "sayma": 0}
    esit = 0
    for komsu in cizgeler(tohum):
        s1, s2 = Sayac(), Sayac()
        yol, _ = ara(komsu, s1, True)
        _, kac = ara(komsu, s2, False)
        if yol is not None:
            ok, d = dogrula(komsu, yol)
            evet["n"] += 1
            evet["karar"] += s1.adim
            evet["sayma"] += s2.adim
            evet["dogrulama"] += d
        else:
            hayir["n"] += 1
            hayir["karar"] += s1.adim
            hayir["sayma"] += s2.adim
            if s1.adim == s2.adim:
                esit += 1
    print("tohum", tohum)
    print("  evet ", evet, "oran", round(evet["sayma"] / evet["karar"], 2))
    print("  hayir", hayir, "karar=sayma olan cizge:", esit, "/", hayir["n"])
```

```
tohum 20260218
  evet  {'n': 25, 'karar': 1526, 'sayma': 29270, 'dogrulama': 175} oran 19.18
  hayir {'n': 15, 'karar': 2538, 'sayma': 2538} karar=sayma olan cizge: 15 / 15
tohum 20260219
  evet  {'n': 23, 'karar': 915, 'sayma': 26792, 'dogrulama': 161} oran 29.28
  hayir {'n': 17, 'karar': 4390, 'sayma': 4390} karar=sayma olan cizge: 17 / 17
```

#### Hayır Yanıtı Neden İndirim Kabul Etmiyor

İlk dağarcıkta 40 çizgenin **25'inde** yol var, 15'inde yok. Evet çizgelerinde karar toplam
**1526 adım**, sayma **29.270 adım** harcıyor; oran **19,18**. Karar yordamı ilk yolu bulur
bulmaz duruyor ve uzayın geri kalanına hiç girmiyor.

Hayır çizgelerinde iki sayı **birebir aynı**: 2538 ve 2538. Üstelik bu eşitlik toplamda değil,
**15 çizgenin 15'inde tek tek** sağlanıyor. Bunun nedeni yordamsal değil mantıksaldır. Karar
yordamı "yok" diyebilmek için tek bir dalın da yol vermediğini görmek zorundadır; görmediği bir
dal kalırsa yanıtı bir tahmindir. Yol yokken erken durma noktası hiç oluşmaz, bu yüzden karar
yordamı tam olarak sayma yordamının gezdiği ağacı gezer.

İkinci dağarcık aynı yapıyı veriyor: 23 evet, 17 hayır, oran 29,28 ve hayır çizgelerinin
**17'sinde 17'si** için karar adımı sayma adımına eşit. Evet çizgelerindeki oran 19,18'den
29,28'e çıkıyor, yani **oranın büyüklüğü dağarcığa bağlıdır**; ama hayır çizgelerindeki tam
eşitlik iki dağarcıkta da **istisnasız** duruyor. Biri ölçülen bir değer, öteki yordamın
yapısından gelen bir sonuçtur ve ikisi aynı cümlede yazılmaz.

Buradan bir önceki dersin at turu satırı yeniden okunabilir. Dörde dörtlük tahtada iki
sıralamanın da 29.976 düğüm gezmesi bir rastlantı değildi: yanıt hayır olduğu için erken durma
noktası yoktu ve sıralama ancak erken durmayı öne çekerek kazandırabilirdi.

#### Tek Yöne Çalışan Ucuz Sınama

Hayır yanıtının pahalı olması, **her** hayır yanıtının pahalı olduğu anlamına gelmiyor. Bazı
çizgelerde yolun olmadığı aramaya hiç girmeden görülebilir: çizge parçalıysa hiçbir yol bütün
düğümlere ulaşamaz ve ikiden çok düğümün yalnız tek komşusu varsa bunların hepsi yolun ucunda
olamaz. Bunlar **gerek koşullardır**: sağlanmazsa yol yoktur, sağlanırsa hiçbir şey söylemezler.

```python
from collections import deque

TOHUM, N = 20260218, 8


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def cizgeler(tohum=TOHUM, ornek=40, pay=38, n=N):
    r = uretec(tohum)
    kume = []
    for _ in range(ornek):
        komsu = {i: set() for i in range(n)}
        for i in range(n):
            for j in range(i + 1, n):
                if r(100) < pay:
                    komsu[i].add(j)
                    komsu[j].add(i)
        kume.append(komsu)
    return kume


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def ucuz_sinama(komsu, s, n=N):
    """Gerek kosullari sinar. 'yok' diyebilir, 'var' diyemez."""
    kuyruk, gorulen = deque([0]), {0}
    while kuyruk:
        v = kuyruk.popleft()
        for k in komsu[v]:
            s.say()
            if k not in gorulen:
                gorulen.add(k)
                kuyruk.append(k)
    if len(gorulen) < n:
        return "yok"
    tek = 0
    for v in range(n):
        s.say()
        if len(komsu[v]) <= 1:
            tek += 1
    return "yok" if tek > 2 else "bilinmiyor"


def karar(komsu, s, n=N):
    def gez(v, yol):
        s.say()
        if len(yol) == n:
            return True
        for k in sorted(komsu[v]):
            if k not in yol:
                yol.append(k)
                if gez(k, yol):
                    return True
                yol.pop()
        return False
    return any(gez(bas, [bas]) for bas in range(n))


for tohum in (20260218, 20260219):
    ucuz_yok, ucuz_adim, yanlis = 0, 0, 0
    kalan_hayir, kalan_adim = 0, 0
    for komsu in cizgeler(tohum):
        s1, s2 = Sayac(), Sayac()
        y = ucuz_sinama(komsu, s1)
        var = karar(komsu, s2)
        ucuz_adim += s1.adim
        if y == "yok":
            ucuz_yok += 1
            if var:
                yanlis += 1
        elif not var:
            kalan_hayir += 1
            kalan_adim += s2.adim
    print("tohum", tohum, "| ucuz sinama 'yok' dedigi:", ucuz_yok,
          "| yanlis:", yanlis, "| ucuz toplam adim:", ucuz_adim)
    print("   ucuzun cozemedigi hayir cizgesi:", kalan_hayir,
          "| tam tarama adimi:", kalan_adim)
```

```
tohum 20260218 | ucuz sinama 'yok' dedigi: 11 | yanlis: 0 | ucuz toplam adim: 1092
   ucuzun cozemedigi hayir cizgesi: 4 | tam tarama adimi: 676
tohum 20260219 | ucuz sinama 'yok' dedigi: 13 | yanlis: 0 | ucuz toplam adim: 1102
   ucuzun cozemedigi hayir cizgesi: 4 | tam tarama adimi: 1638
```

İlk dağarcıkta 15 hayır çizgesinin **11'i** ucuz sınamayla çözülüyor ve **yanlış sayısı 0**:
sınama, yolu olan hiçbir çizgeye "yok" demiyor. Geriye 4 çizge kalıyor ve onlar için tam tarama
kaçınılmaz, 676 adım. İkinci dağarcıkta 17 hayır çizgesinin 13'ü çözülüyor, yine 0 yanlış,
yine 4 çizge kalıyor, bu kez 1638 adıma.

Sınamanın kırk çizgenin tamamı için harcadığı 1092 adım, hayır çizgelerinde tam taramanın
harcadığı 2538 adımın altındadır. Ama tasarrufun tamamı **tek yönlüdür**. Sınama hiçbir çizgede
"var" diyemez; "bilinmiyor" dediği 29 çizgenin 25'inde yol vardır, 4'ünde yoktur ve bu ikisini
ayırmak için yine tam tarama gerekir. Ucuz kanıt her zaman bir yöne çalışır ve hangi yöne
çalıştığı yazılmadan sayısı okunamaz.

#### Aramak ile Doğrulamak

Dördüncü sayı doğrulamadır. Evet çizgelerinde toplam **175 adım**, yani çizge başına 7 adım:
sekiz düğümlü bir yolda yedi ardışık ikili. Aynı çizgelerde yolu **bulmak** ortalama 61 adım
harcıyor, yani doğrulamanın 8,7 katı.

Bu iki sayı arasındaki fark, bu ders için tek bir çizgede küçüktür, ama iki sayının **ölçekle
ne yaptığı** aynı değildir. Doğrulama, düğüm sayısı eksi bir adımdır ve düğüm sayısıyla düz
büyür. Aramanın adımı ise gezilen ağacın büyüklüğüne bağlıdır. Aşağıdaki blok düğüm sayısını
süpürüp iki büyümeyi yan yana koyar.

```python
TOHUM = 20260218


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def cizgeler(n, pay, tohum=TOHUM, ornek=20):
    r = uretec(tohum)
    kume = []
    for _ in range(ornek):
        komsu = {i: set() for i in range(n)}
        for i in range(n):
            for j in range(i + 1, n):
                if r(100) < pay:
                    komsu[i].add(j)
                    komsu[j].add(i)
        kume.append(komsu)
    return kume


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self):
        self.adim += 1


def karar(komsu, n, s):
    """Ilk Hamilton yolunda durur; yol yoksa uzayin tamami taranir."""
    bulunan = []

    def gez(v, yol):
        s.say()
        if len(yol) == n:
            bulunan.append(tuple(yol))
            return True
        for k in sorted(komsu[v]):
            if k not in yol:
                yol.append(k)
                bitti = gez(k, yol)
                yol.pop()
                if bitti:
                    return True
        return False

    for bas in range(n):
        if gez(bas, [bas]):
            return bulunan[0]
    return None


print(" n | evet | evet ortalama adim | hayir ortalama adim | dogrulama adimi")
for n, pay in ((6, 30), (8, 30), (10, 30), (12, 30)):
    e = h = ea = ha = 0
    for komsu in cizgeler(n, pay):
        s = Sayac()
        y = karar(komsu, n, s)
        if y is None:
            h, ha = h + 1, ha + s.adim
        else:
            e, ea = e + 1, ea + s.adim
    print(f"{n:2d} | {e:4d} | {(ea // e if e else 0):18d} |"
          f" {(ha // h if h else 0):19d} | {n - 1:15d}")
```

```
 n | evet | evet ortalama adim | hayir ortalama adim | dogrulama adimi
 6 |    2 |                  8 |                  23 |               5
 8 |    6 |                121 |                  92 |               7
10 |    9 |                164 |                 757 |               9
12 |   10 |               1054 |               11601 |              11
```

Üç sütun üç ayrı büyüme gösteriyor. Doğrulama 5, 7, 9, 11: düğüm sayısıyla birlikte birer birer
artıyor. Evet çizgelerinde karar 8, 121, 164, 1054. Hayır çizgelerinde karar 23, 92, 757,
**11.601**; altı düğümden on iki düğüme çıkarken beş yüz kattan fazla büyüyor. Sekiz düğümlük
satırda hayır ortalaması evet ortalamasının altında kalıyor; bu tek satırlık ters yön, o
dağarcıkta hayır çizgelerinin seyrek olmasından gelir ve genellenmez.

Ölçülen şey şu: **bir yanıtın doğrulanması ile bulunması aynı büyüklükte değildir** ve aradaki
açıklık düğüm sayısıyla açılır. On iki düğümde bir aday yolu sınamak 11 adımdır; böyle bir
yolun olmadığını göstermek ortalama 11.601 adımdır. Aynı çizge, aynı soru, bin kat fark.

#### Adlandırma Nereye Ait

Buraya kadar dört sayı ölçüldü: karar, arama, sayma, doğrulama. Ölçülmeyen bir şey kaldı ve
bilerek bırakıldı. Doğrulaması ucuz olup bulunması pahalı görünen problemlerin **ortak bir adı**
vardır; bu problemlerin birbirine çevrilmesinin bir yordamı ve "pahalı görünüyor" ile
"pahalıdır" arasındaki farkın bir kuramı vardır.

Bunların hiçbiri bu kursta kurulmaz. Gezgin satıcı dersinde "neden zor" sorusu M01/K08 Hesaplama
Kuramı kursuna havale edilmişti; buradaki adlandırma sorusu da aynı yere gider. Bu kursun
yaptığı, o kursun üzerine konuşacağı **farkı ölçüp masaya koymaktır**: 11 adıma karşı 11.601
adım ve bunun bir gözlem olduğu, bir kanıt olmadığı.

Bu ayrım işe yarar bir alışkanlık bırakır. Bir problemi zor diye adlandırmadan önce, o problemin
**hangi sorusunun** pahalı olduğu yazılmalıdır. Aynı çizge için karar 1526 adım, sayma 29.270
adım, doğrulama 175 adım harcadı. "Hamilton yolu pahalıdır" cümlesi bu üç sayıdan hangisini
kastettiğini söylemeden bir şey anlatmaz.

#### Özet

- Bir varlık sorusu karar problemi olarak kurulur; yanında arama ve sayma problemleri durur ve
  üçünün maliyeti aynı girdide ayrı ayrı ölçülür.
- Yol bulunan 25 çizgede karar 1526, sayma 29.270 adım harcıyor; oran 19,18 ve ikinci dağarcıkta
  29,28, yani oranın büyüklüğü dağarcığa bağlıdır.
- Yol bulunmayan çizgelerin **hepsinde** karar adımı sayma adımına tam olarak eşit: ilk
  dağarcıkta 15'te 15, ikincisinde 17'de 17. Hayır yanıtı erken durma noktası bırakmaz.
- Doğrulama çizge başına 7 adım, arama ortalama 61 adım; düğüm sayısı süpürüldüğünde doğrulama
  birer birer artarken hayır yanıtının bedeli 23'ten 11.601'e çıkıyor.
- Ölçülen bu fark bir gözlemdir; adlandırılması ve kanıtlanması M01/K08 Hesaplama Kuramı
  kursuna aittir ve bu derste yapılmaz.

#### Sonraki Adım

Klasik Problemler konusu altı problemde aynı yordamı izledi: kâhini kur, kalıbı ölç, ayrılan
girdiyi say. Sonraki konu bu yordamı problemden bağımsız hale getirir. Alıştırma Disiplini,
bir problemin kısıtlarından hangi kalıbın adım bütçesine sığdığını çıkarmayı ve bir çözümü
kâhinle karşılaştırmayı bir alışkanlığa dönüştürür. İlk soru şudur: girdi büyüklüğü verildiğinde
hangi kalıbın bütçeye sığdığı, tahmin edilmeden **sayılarak** nasıl bulunur.

## Alıştırma Disiplini

Girdi büyüklüğünden hedef karmaşıklığı çıkarma, çözümü kâhinle karşılaştırarak doğrulama ve tekrarlı çalışmanın ilerleme ölçütünün kalıp çeşitliliğiyle sayılması.

### Problem Okuma ve Kısıt Çözümlemesi

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/problem-okuma-ve-kisit-cozumlemesi

Girdi büyüklüğünden adım bütçesi çıkarma ve bu bütçenin kalıbı tek başına seçemediğini ölçme: n=12'de sıralayan kalıp kâhinden 1,61 kat pahalı, yanlış kısıt okuması 40 girdinin 18'inde yanlış yanıt veriyor.

Önceki konu, adı konmuş problemlerle çalıştı: sırt çantası, gezgin satıcı, n vezir. Bu
problemlerin biçimi verilmişti; sorulan şey hangi yaklaşımın onları çözdüğüydü. Alıştırmada
durum tersinedir. Elde bir metin vardır, metnin içinde birkaç sayı geçer ve hangi kalıbın
uygun olduğu söylenmez. Kalıbı seçen şey problemin adı değil, **kısıtlarıdır**.

Bu ders, kısıt okumasını bir yordama çevirir ve o yordamın nerede işe yaradığını, nerede
tahmine dönüştüğünü ölçer. Sonuç iki yönlüdür: girdi büyüklüğü büyük çözüm ailelerini gerçekten
eler, ama geriye kalanlar arasından seçimi **yapmaz** — ve kısıt yanlış okunduğunda ortaya
çıkan çözüm hepsinden ucuz olabilir, yine de yanlıştır.

#### Girdi Büyüklüğünden Adım Bütçesine

Kısıt okuma tek bir hesaptır: problem metnindeki en büyük girdi büyüklüğü alınır, o büyüklükte
harcanabilecek adım sayısına bir üst sınır konur ve her aday çözümün o sınıra sığıp sığmadığı
hesaplanır. Sınıra **adım bütçesi** denir. Asimptotik gösterimin kendisi Algoritmalar kursunun
Çözümleme konusunda kuruldu ve burada tekrarlanmıyor; burada yapılan şey gösterimi bir **karar
ölçütüne** çevirmektir.

- **AD1** — Adım bütçesi, bir çözümün kabul edilebilir sayılması için harcayabileceği en çok
  adımdır. Bu derste bütçe **10<sup>8</sup> adımdır** ve bir sabit olarak alınır.
- **AD2** — Bütçe **en kötü durum** üzerinden hesaplanır. Ortalama girdi bütçe hesabına
  girmez; hesabın amacı bir çözümü elemektir, övmek değil.
- **AD3** — Karma tablosundaki bir arama bir adım, bir ekleme bir adımdır. İkisi de sabittir.
- **AD4** — Sıralamanın adımı, gerçekten yapılan **kıyaslama sayısıdır** ve sayılarak ölçülür.
  Sıralama algoritmalarının kendisi Algoritmalar kursunda kuruldu, burada kullanılıyor.
- **AD5** — En kötü girdi şöyle üretilir: bütün değerler çift, hedef tek. O zaman hiçbir ikili
  hedefi veremez ve her çözüm sonuna kadar çalışmak zorunda kalır.

Problem şudur: bir dizide **toplamı hedefe eşit iki ayrı konum** var mı. Üç aday vardır. Kaba
kuvvet bütün ikilileri dener; bu kursun kâhini odur. İkinci aday diziyi sıralar ve iki işaretçi
ile tarar. Üçüncüsü tek geçişte karma tablosu tutar.

```python
"""Uc cozumun ayni problemdeki adimi , en kotu girdide sayiliyor."""
from functools import cmp_to_key

TOHUM = 20260218


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self, n=1):
        self.adim += n


def en_kotu_girdi(n, tohum=TOHUM):
    """Butun degerler cift , hedef tek: hicbir ikili hedefi vermez."""
    dizi = [2 * i for i in range(n)]
    r = uretec(tohum)
    for i in range(n - 1, 0, -1):
        j = r(i + 1)
        dizi[i], dizi[j] = dizi[j], dizi[i]
    return dizi


def kahin_ciftler(dizi, hedef, s):
    for i in range(len(dizi)):
        for j in range(i + 1, len(dizi)):
            s.say()
            if dizi[i] + dizi[j] == hedef:
                return True
    return False


def kalip_sirala_iki(dizi, hedef, s):
    def kiyas(a, b):
        s.say()
        return -1 if a < b else (1 if a > b else 0)
    d = sorted(dizi, key=cmp_to_key(kiyas))
    sol, sag = 0, len(d) - 1
    while sol < sag:
        s.say()
        t = d[sol] + d[sag]
        if t == hedef:
            return True
        sol, sag = (sol + 1, sag) if t < hedef else (sol, sag - 1)
    return False


def kalip_karma(dizi, hedef, s):
    gorulen = set()
    for x in dizi:
        s.say()
        if hedef - x in gorulen:
            return True
        s.say()
        gorulen.add(x)
    return False


print("n      kaba kuvvet   sirala+iki isaretci   karma tablo")
for n in (4, 8, 32, 128, 512, 2048):
    d = en_kotu_girdi(n)
    s1, s2, s3 = Sayac(), Sayac(), Sayac()
    kahin_ciftler(d, 7, s1)
    kalip_sirala_iki(d, 7, s2)
    kalip_karma(d, 7, s3)
    print(f"{n:5d}  {s1.adim:11d}  {s2.adim:19d}  {s3.adim:11d}")
print()
for ad, kalip in (("sirala+iki isaretci", kalip_sirala_iki),
                  ("karma tablo    ", kalip_karma)):
    for n in range(2, 40):
        d = en_kotu_girdi(n)
        s1, s2 = Sayac(), Sayac()
        kahin_ciftler(d, 7, s1)
        kalip(d, 7, s2)
        if s2.adim < s1.adim:
            print(f"{ad} kaba kuvvetten ucuzlastigi ilk n: {n}"
                  f"  (kalip {s2.adim} , kahin {s1.adim})")
            break
```

```
n      kaba kuvvet   sirala+iki isaretci   karma tablo
    4            6                   10            8
    8           28                   22           16
   32          496                  152           64
  128         8128                  861          256
  512       130816                 4483         1024
 2048      2096128                22015         4096

sirala+iki isaretci kaba kuvvetten ucuzlastigi ilk n: 8  (kalip 22 , kahin 28)
karma tablo     kaba kuvvetten ucuzlastigi ilk n: 6  (kalip 12 , kahin 15)
```

Tablonun ilk satırı kısıt okumasının en çok gözden kaçan sonucudur: n=4'te **kaba kuvvet üç
çözümün en ucuzudur** — 6 adım, karma tablosunun 8 adımına ve sıralayan kalıbın 10 adımına
karşı. Sıralayan kalıp kâhinden ancak **n=8'de**, karma tablosu **n=6'da** ucuzlar. Kaba
kuvvet küçük girdide kötü bir seçenek değildir; kötü olan, hangi girdide kötüleştiğini
bilmeden onu seçmektir.

#### Dört Girdi Büyüklüğünde Bütçe Hesabı

Ölçüm 2048'e kadar gidiyor, ama kısıt okuması 10<sup>8</sup>'e kadar karar vermek zorundadır ve
o büyüklükte kaba kuvveti gerçekten koşturmak bir seçenek değildir. Bu yüzden bütçe hesabı
**ölçülmüş sayıdan doğrulanmış bir formülle** yapılır: önce formülün küçük n'de ölçümü tutup
tutmadığına bakılır, sonra formül büyük n'ye taşınır.

- **AD6** — Kaba kuvvetin en kötü adımı $n(n-1)/2$, karma tablosununki $2n$, sıralayan kalıbınki
  $n\log_2 n + n$ olarak tahmin edilir.
- **AD7** — Bir formül, ölçülen adımı **aşmadığı sürece** bütçe hesabında kullanılabilir. Az
  gösteren formül kabul edilmez.

```python
"""Olculen adim formulu tutuyor mu , ve dort buyuklukte butce hesabi."""
from math import log2

OLCUM = {8: (28, 22, 16), 32: (496, 152, 64), 128: (8128, 861, 256),
         512: (130816, 4483, 1024), 2048: (2096128, 22015, 4096)}
BUTCE = 10 ** 8


def tahmin(n):
    return (n * (n - 1) // 2, int(n * log2(n)) + n, 2 * n)


print("n      kaba kuvvet         sirala+iki isaretci     karma tablo")
print("       olculen  tahmin  o/t   olculen  tahmin  o/t   olculen  tahmin")
for n, (a, b, c) in OLCUM.items():
    ta, tb, tc = tahmin(n)
    print(f"{n:5d}  {a:7d}  {ta:6d}  {a / ta:.2f}  {b:8d}  {tb:6d}  {b / tb:.2f}"
          f"  {c:8d}  {tc:6d}")
print()
print(f"adim butcesi: {BUTCE}")
print("n            kaba kuvvet      sirala+iki      karma tablo")
for n in (10 ** 3, 10 ** 5, 10 ** 6, 10 ** 8):
    ta, tb, tc = tahmin(n)
    d = [("sigar" if t <= BUTCE else "sigmaz") for t in (ta, tb, tc)]
    print(f"{n:<11d}  {ta:.2e} {d[0]:6s}  {tb:.2e} {d[1]:6s}  {tc:.2e} {d[2]:6s}")
```

```
n      kaba kuvvet         sirala+iki isaretci     karma tablo
       olculen  tahmin  o/t   olculen  tahmin  o/t   olculen  tahmin
    8       28      28  1.00        22      32  0.69        16      16
   32      496     496  1.00       152     192  0.79        64      64
  128     8128    8128  1.00       861    1024  0.84       256     256
  512   130816  130816  1.00      4483    5120  0.88      1024    1024
 2048  2096128  2096128  1.00     22015   24576  0.90      4096    4096

adim butcesi: 100000000
n            kaba kuvvet      sirala+iki      karma tablo
1000         5.00e+05 sigar   1.10e+04 sigar   2.00e+03 sigar 
100000       5.00e+09 sigmaz  1.76e+06 sigar   2.00e+05 sigar 
1000000      5.00e+11 sigmaz  2.09e+07 sigar   2.00e+06 sigar 
100000000    5.00e+15 sigmaz  2.76e+09 sigmaz  2.00e+08 sigmaz
```

Üst tablo formülü doğruluyor. Kaba kuvvette ölçülen ile tahmin **birebir aynı** (oran 1,00);
karma tablosunda da aynı. Sıralayan kalıpta oran 0,69'dan 0,90'a **yükseliyor**: formül gerçek
kıyaslama sayısını her n'de fazla gösteriyor, yani güvenli yönde yanılıyor ve AD7'yi
karşılıyor. Fazla göstermenin miktarı sabit değil — küçük n'de tahmin ölçümün 1,45 katı,
n=2048'de 1,12 katı. **Kısıt okumasının tahmine dönüştüğü nokta burasıdır:** kullanılan formül
bir katsayı içerir ve o katsayı ölçülmeden bütçe hesabı bir tahmindir.

Alt tablo dersin istediği dört büyüklüktür. n=1.000'de **üç çözüm de** bütçeye sığıyor; kısıt
okuma burada hiçbir şey elemiyor ve seçim başka bir ölçüte kalıyor. n=100.000'de kaba kuvvet
5,00·10<sup>9</sup> adımla eleniyor, geriye iki aday kalıyor. n=1.000.000'da tablo aynı kalıyor.
n=10<sup>8</sup>'de **üçü de sığmıyor** — en ucuzu olan karma tablosu bile 2,00·10<sup>8</sup>
adımla bütçenin iki katını istiyor. Bu son satır kısıt okumasının en yararlı çıktısıdır: o
büyüklükte tek geçişli bir çözüm bile yetmiyorsa, problem metni **başka bir kısıt** veriyor
olmalıdır ve okunmamıştır.

#### Kısıt Yanlış Okunduğunda

Yukarıdaki hesabın tamamı bir şeyi varsaydı: adayların üçü de doğru yanıt veriyor. Bu varsayım
sınanmadan bütçe hesabı bir çözüm seçemez. Kursun kuralı burada da geçerlidir — **ön koşulu
kâhinle sınanmayan kalıp ölçülmemiş sayılır.**

Dördüncü bir aday eklenir. Problem metninde "değerler 20'yi aşmaz" yazdığını düşünen biri,
0..20 arasını işaretleyen bir dizi tutup tek geçişte yanıt verebilir. Metinde alt sınırın
yazmadığı ve değerlerin negatif olabildiği gözden kaçmıştır.

- **AD8** — Dağarcık ortak tanımın dağarcığıdır: 40 girdi, her biri 12 değer, değerler
  −9 ile 20 arasında. Hedef 11.
- **AD9** — Yanlış kısıt okuması, aralık dışındaki değeri **sessizce atlar**; çökmez, bir yanıt
  döndürür.
- **AD10** — İkinci dağarcık `20260219` tohumuyla üretilir ve ayrılan girdi oranının aynı
  büyüklük düzeninde kalıp kalmadığı yazılır.

```python
"""Butce hesabi kalibi secmez , kahin secer. Iki dagarcik."""
from functools import cmp_to_key

TOHUM, IKINCI, UZUNLUK, DAGARCIK = 20260218, 20260219, 12, 40


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self, n=1):
        self.adim += n


def dagarcik(tohum=TOHUM, n=DAGARCIK, uzunluk=UZUNLUK):
    r = uretec(tohum)
    return [{"no": i + 1, "dizi": [r(30) - 9 for _ in range(uzunluk)]}
            for i in range(n)]


def kahin_ciftler(dizi, hedef, s):
    for i in range(len(dizi)):
        for j in range(i + 1, len(dizi)):
            s.say()
            if dizi[i] + dizi[j] == hedef:
                return True
    return False


def kalip_sirala_iki(dizi, hedef, s):
    def kiyas(a, b):
        s.say()
        return -1 if a < b else (1 if a > b else 0)
    d = sorted(dizi, key=cmp_to_key(kiyas))
    sol, sag = 0, len(d) - 1
    while sol < sag:
        s.say()
        t = d[sol] + d[sag]
        if t == hedef:
            return True
        sol, sag = (sol + 1, sag) if t < hedef else (sol, sag - 1)
    return False


def kalip_karma(dizi, hedef, s):
    gorulen = set()
    for x in dizi:
        s.say()
        if hedef - x in gorulen:
            return True
        s.say()
        gorulen.add(x)
    return False


def kalip_sayma(dizi, hedef, s, ust=20):
    """YANLIS KISIT OKUMASI: degerler 0..ust araliginda saniliyor."""
    var = [False] * (ust + 1)
    for x in dizi:
        s.say()
        if 0 <= x <= ust:
            e = hedef - x
            if 0 <= e <= ust and var[e]:
                return True
            var[x] = True
    return False


def olc(kalip, kume, hedef):
    ayrilan, ak, ah = [], 0, 0
    for k in kume:
        s1, s2 = Sayac(), Sayac()
        a = kalip(k["dizi"], hedef, s1)
        b = kahin_ciftler(k["dizi"], hedef, s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        if a != b:
            ayrilan.append(k["no"])
    return {"ayrilan": len(ayrilan), "no": ayrilan[:5], "kalip_adim": ak,
            "kahin_adim": ah}


for tohum in (TOHUM, IKINCI):
    print(f"dagarcik tohum {tohum} , 40 girdi x 12 deger , hedef 11")
    K = dagarcik(tohum)
    for ad, kalip in (("sirala+iki isaretci", kalip_sirala_iki),
                      ("karma tablo        ", kalip_karma),
                      ("sayma (kisit yanlis)", kalip_sayma)):
        print(f"  {ad}", olc(kalip, K, 11))
    print()
```

```
dagarcik tohum 20260218 , 40 girdi x 12 deger , hedef 11
  sirala+iki isaretci {'ayrilan': 0, 'no': [], 'kalip_adim': 1398, 'kahin_adim': 866}
  karma tablo         {'ayrilan': 0, 'no': [], 'kalip_adim': 486, 'kahin_adim': 866}
  sayma (kisit yanlis) {'ayrilan': 18, 'no': [4, 10, 13, 14, 15], 'kalip_adim': 396, 'kahin_adim': 866}

dagarcik tohum 20260219 , 40 girdi x 12 deger , hedef 11
  sirala+iki isaretci {'ayrilan': 0, 'no': [], 'kalip_adim': 1441, 'kahin_adim': 970}
  karma tablo         {'ayrilan': 0, 'no': [], 'kalip_adim': 566, 'kahin_adim': 970}
  sayma (kisit yanlis) {'ayrilan': 17, 'no': [3, 4, 6, 7, 8], 'kalip_adim': 391, 'kahin_adim': 970}
```

Üç sayı yan yana. **Kâhin:** 866 adım, 40 girdinin 40'ında doğru. **Kalıp:** karma tablosu 486
adım, sıralayan kalıp 1398 adım, yanlış kısıt okuması 396 adım. **Ayrılan girdi:** ilk ikisinde
**0**, yanlış okumada **18** — yani 40 girdinin 18'inde kâhinden farklı yanıt, oran **0,4500**.

Sonuç iki şey söylüyor. Birincisi, **adım sayısı sıralaması doğruluk sıralaması değildir**:
en az adımı harcayan aday (396) tam da yanlış yanıt veren adaydır. İkincisi, n=12'de sıralayan
kalıp kâhinden **1,61 kat pahalıdır** (1398'e karşı 866). Bütçe hesabı bu kalıbı büyük n için
seçmişti; gerçek girdi büyüklüğü 12 olduğunda aynı seçim kâhinden kötüdür. Bütçe hesabı bir
**eleme** aracıdır, bir seçme aracı değil.

İkinci dağarcık sonucu doğruluyor: ayrılan girdi 18'den 17'ye (oran 0,4250) iniyor, aynı
büyüklük düzeninde kalıyor; doğru iki kalıp orada da 0 ayrılan girdi veriyor. Ortak tanımın
birinci okuması, sırasız girdide iki işaretçinin 40 girdinin 25'inde yanıldığını zaten
göstermişti; buradaki 18, aynı kuralın **kısıt okuma** tarafındaki karşılığıdır — kalıp
doğru, okuma yanlış.

#### Özet

- Kısıt okuma, girdi büyüklüğünden bir **adım bütçesi** çıkarıp adayları eleme işlemidir; bu
  derste bütçe 10<sup>8</sup> adım ve hesap her zaman en kötü durum üzerinden yapılır.
- Ölçüm küçük girdide kaba kuvveti haklı çıkarıyor: n=4'te üç çözümün en ucuzu 6 adımla kaba
  kuvvet; karma tablosu ancak n=6'da, sıralayan kalıp n=8'de ondan ucuzluyor.
- Bütçe dört büyüklükte farklı sayıda aday bırakıyor: n=1.000'de üçü de sığıyor, n=100.000 ve
  n=1.000.000'da kaba kuvvet eleniyor, n=10<sup>8</sup>'de **hiçbiri** sığmıyor — karma tablosu
  bile 2,00·10<sup>8</sup> adım istiyor.
- Formül ölçümle doğrulanmadan bütçe hesabı bir tahmindir: sıralayan kalıpta tahmin ölçümün
  n=8'de 1,45, n=2048'de 1,12 katı; kaba kuvvet ve karma tablosunda oran tam olarak 1,00.
- Kâhin karşılaştırması bütçe hesabının söyleyemediğini söylüyor: en az adımı harcayan aday
  (396 adım) 40 girdinin **18'inde** yanlış yanıt veriyor, ikinci dağarcıkta 17'sinde; doğru
  iki kalıp ise iki dağarcıkta da 0 ayrılan girdi veriyor.
- Gerçek girdi büyüklüğünde bütçe sıralaması tersine dönebilir: n=12'de sıralayan kalıp
  kâhinden 1,61 kat pahalı. Bütçe hesabı eler, seçmez.

#### Sonraki Adım

Bu derste kâhin bir kez kullanıldı ve yanlış bir kısıt okumasını 18 girdide yakaladı. Ama o
18 girdi hazır bir dağarcıktan geldi; dağarcık başka olsaydı yakalanan sayı da başka olurdu.
Sonraki ders bu bağımlılığı konu edinir: bir çözümün kâhinle karşılaştırılması **hangi
girdilerle** yapılmalıdır, kenar durumları rastgele üretilen bir dağarcıkta kaç kez çıkar ve
ayrılan bir girdi bulunduğunda o girdi nasıl en küçük hâline indirilir. Ölçü yine sayıdır:
bilerek bozulmuş sürümlerin kaçı hangi dağarcıkla yakalanıyor.

### Çözüm Doğrulama

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/cozum-dogrulama

Bir çözümün kâhinle karşılaştırılmasının yordama çevrilmesi: 40 rastgele girdi beş kusurun dördünü yakalıyor, 12 kenar durumu beşini de yakalıyor ve ayrılan bir girdi 12 değerden 1 değere 23 denemede iniyor.

Önceki ders bir çözümün adım bütçesine sığmasının onu doğru yapmadığını gösterdi: en az adımı
harcayan aday, 40 girdinin 18'inde kâhinden ayrılıyordu. O 18 sayısı hazır bir dağarcıktan
geldi ve sorgulanmadı. Bu ders o sorgulamayı yapar — **hangi girdilerle karşılaştırıldığı,
karşılaştırmanın kendisi kadar belirleyicidir.**

Test yazma disiplini, kenar durumu düşünme ve gözden geçirme alışkanlıkları başka kurslarda
kuruldu ve burada tekrarlanmıyor. Bu dersin eklediği tek şey **kâhindir**: doğruluğun bir
görüşe değil, ikinci bir çözümün yanıtına karşı sayılmasıdır.

#### Doğrulama Yordamı

Yordam üç adımdır ve sırası değişmez. Birincisi, girdi kümesi üretilir. İkincisi, hem sınanan
çözüm hem kâhin aynı girdilerde koşturulur. Üçüncüsü, iki yanıtın farklı çıktığı girdiler
**sayılır**. Çıktı bir sayıdır: **ayrılan girdi**. Sıfırsa o kümede kanıt bulunamamıştır;
sıfırdan büyükse çözüm yanlıştır ve elde somut bir örnek vardır.

- **AD11** — Sınanan çözüm sıralı dizide iki işaretçidir. Ön koşul her girdide sağlanır; bu
  ders ön koşulu değil **gerçeklemeyi** sınar.
- **AD12** — Beş kusurlu sürüm elle üretilir ve her kusur **tek satırlık** bir değişikliktir:
  bitiş koşulu, başlangıç konumu, bitiş konumu, ilerleme yönü, boş girdi.
- **AD13** — Bir sürüm çöktüğünde bu da ayrılma sayılır; çökme sessiz yanlıştan daha az
  tehlikelidir ama yine bir ayrılmadır.
- **AD14** — Rastgele dağarcık 40 girdidir, her biri **tam 12 değer** taşır; hedef de aynı
  üreteçten gelir ve $-18$ ile $22$ arasındadır.
- **AD15** — Kenar durumu kümesi 12 girdidir ve elle kurulur: boş dizi, tek eleman, iki eleman,
  yalnız tekrar eden değerler, uçlardaki değerler, ulaşılamayan hedef.

```python
"""Bes kusurlu surum , iki ayri girdi kumesi , kahin karsisinda."""
TOHUM, IKINCI = 20260218, 20260219


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self, n=1):
        self.adim += n


def rastgele_kume(tohum=TOHUM, n=40, uzunluk=12):
    r = uretec(tohum)
    return [(sorted(r(30) - 9 for _ in range(uzunluk)), r(41) - 18)
            for _ in range(n)]


KENAR = [([], 11), ([5], 10), ([5], 5), ([5, 6], 11), ([5, 6], 10),
         ([7, 7], 14), ([-9, -9], -18), ([0, 0, 0], 0), ([-9, 20], 11),
         ([1, 2, 3, 4], 3), ([1, 2, 3, 4], 7), ([2, 2, 2, 2], 4)]


def kahin(d, hedef, s):
    for i in range(len(d)):
        for j in range(i + 1, len(d)):
            s.say()
            if d[i] + d[j] == hedef:
                return True
    return False


def iki_isaretci(d, hedef, s, kusur=None):
    """kusur=None dogru surum. Her kusur tek satirdir."""
    sol = 1 if kusur == "K2" else 0
    sag = (len(d) - 2) if kusur == "K3" else (len(d) - 1)
    if kusur == "K5":
        d[0]                                   # bos diziyi hesaba katmiyor
    while (sol <= sag) if kusur == "K1" else (sol < sag):
        s.say()
        t = d[sol] + d[sag]
        if t == hedef:
            return True
        ileri = (t > hedef) if kusur == "K4" else (t < hedef)
        sol, sag = (sol + 1, sag) if ileri else (sol, sag - 1)
    return False


KUSUR = [(None, "dogru surum"), ("K1", "sol <= sag"), ("K2", "ilk eleman atlaniyor"),
         ("K3", "son eleman atlaniyor"), ("K4", "yonler ters"),
         ("K5", "bos dizi hesapta yok")]


def olc(kusur, kume):
    ayrilan, ak, ah = 0, 0, 0
    for d, hedef in kume:
        s1, s2 = Sayac(), Sayac()
        try:
            a = iki_isaretci(list(d), hedef, s1, kusur)
        except IndexError:
            a = "coktu"
        b = kahin(list(d), hedef, s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        if a != b:
            ayrilan += 1
    return ayrilan, ak, ah


R = rastgele_kume()
print(f"rastgele dagarcik {len(R)} girdi x 12 deger | "
      f"kenar durumu kumesi {len(KENAR)} girdi")
print("kusur                    rastgele  kenar  kalip adim  kahin adim")
for k, ad in KUSUR:
    ar, akr, ahr = olc(k, R)
    ak, akk, ahk = olc(k, KENAR)
    print(f"{ad:22s}  {ar:8d}  {ak:5d}  {akr + akk:10d}  {ahr + ahk:10d}")
print()
Y = rastgele_kume(IKINCI)
print("ikinci dagarcik 20260219 , rastgele kume:",
      {ad: olc(k, Y)[0] for k, ad in KUSUR})
```

```
rastgele dagarcik 40 girdi x 12 deger | kenar durumu kumesi 12 girdi
kusur                    rastgele  kenar  kalip adim  kahin adim
dogru surum                    0      0         329        1596
sol <= sag                     3      2         344        1596
ilk eleman atlaniyor           4      5         297        1596
son eleman atlaniyor           3      5         317        1596
yonler ters                   25      2         423        1596
bos dizi hesapta yok           0      1         329        1596

ikinci dagarcik 20260219 , rastgele kume: {'dogru surum': 0, 'sol <= sag': 2, 'ilk eleman atlaniyor': 2, 'son eleman atlaniyor': 2, 'yonler ters': 27, 'bos dizi hesapta yok': 0}
```

Üç sayı yan yana. **Kâhin:** 52 girdinin tamamında **1596 adım** ve her girdide doğru yanıt.
**Kalıp:** doğru sürüm **329 adım**, yani kâhinin **4,85 katı azı**. **Ayrılan girdi:** doğru
sürümde iki kümede de **0**; beş kusurlu sürümde 0 ile 25 arasında.

Tablonun asıl bulgusu son satırdadır. Rastgele dağarcık **40 girdiyle beş kusurun dördünü**
yakalıyor ve beşincisini — boş girdiyi hesaba katmayan sürümü — **hiç yakalamıyor**. Sebep
yordamsaldır, şanssızlık değil: dağarcığın her girdisi tam 12 değer taşır, yani boş ve tek
elemanlı girdi o kümede **hiç yoktur**. Bir kusur sınıfı, girdi üretecinin biçimi yüzünden
yapısal olarak erişilemez durumdadır ve dağarcığı 40'tan 400'e çıkarmak bunu değiştirmez.

Kenar durumu kümesi bunun tersini yapıyor: **12 girdiyle beş kusurun beşini** de yakalıyor.
Girdi başına verim 40'ta 4'e karşı 12'de 5'tir — **0,1000'e karşı 0,4167**. Buna karşılık
kenar kümesi tek başına da yetmiyor: yönleri ters çevrilmiş sürümü yalnız 2 girdide
yakalarken rastgele dağarcık onu 25 girdide yakalıyor. İki küme farklı kusur sınıfları
görüyor ve biri diğerinin yerini almıyor.

İkinci dağarcık sonucu doğruluyor: `20260219` tohumuyla sayılar 2, 2, 2, 27, 0 çıkıyor — dört
kusur yine yakalanıyor, boş girdi kusuru yine kaçıyor ve yönleri ters sürümün baskınlığı
sürüyor. Ayrılan girdi oranı aynı büyüklük düzeninde; sonuç dağarcığa bağlı değildir.

#### Ayrılan Girdiyi Küçültmek

Bir ayrılma bulunduğunda elde 12 değerlik bir dizi vardır ve o dizinin hangi özelliği kusuru
tetikliyor belli değildir. Küçültme bunu çözer: girdiden bir değer çıkarılır, ayrılma sürüyorsa
küçük hâli tutulur, sürmüyorsa değer geri konur. İşlem hiçbir çıkarma ayrılmayı korumayana
kadar tekrarlanır.

- **AD16** — Küçültme yalnız **değer çıkarır**; değer eklemez ve değiştirmez. Sonuç, başlangıç
  girdisinin bir alt dizisidir.
- **AD17** — Bir deneme, aday girdide hem kusurlu sürümü hem kâhini bir kez koşturmaktır.
- **AD18** — Küçültmenin sonucu **yerel olarak en küçüktür**: tek değer çıkarmayla daha da
  küçültülemez. Bütün alt diziler arasında en küçük olduğu iddia edilmez.

```python
"""Ayrilan girdiyi en kucuk haline indirme."""
from math import comb

TOHUM = 20260218


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def rastgele_kume(tohum=TOHUM, n=40, uzunluk=12):
    r = uretec(tohum)
    return [(sorted(r(30) - 9 for _ in range(uzunluk)), r(41) - 18)
            for _ in range(n)]


def kahin(d, hedef):
    return any(d[i] + d[j] == hedef
               for i in range(len(d)) for j in range(i + 1, len(d)))


def iki_isaretci(d, hedef, kusur=None):
    sol = 1 if kusur == "K2" else 0
    sag = (len(d) - 2) if kusur == "K3" else (len(d) - 1)
    if kusur == "K5":
        d[0]
    while (sol <= sag) if kusur == "K1" else (sol < sag):
        t = d[sol] + d[sag]
        if t == hedef:
            return True
        ileri = (t > hedef) if kusur == "K4" else (t < hedef)
        sol, sag = (sol + 1, sag) if ileri else (sol, sag - 1)
    return False


def ayriliyor(d, hedef, kusur):
    try:
        return iki_isaretci(list(d), hedef, kusur) != kahin(d, hedef)
    except IndexError:
        return True


def kucult(d, hedef, kusur):
    """Bir deger cikarilir; ayrilma suruyorsa cikarilmis hali tutulur."""
    deneme = 0
    ilerledi = True
    while ilerledi:
        ilerledi = False
        for i in range(len(d)):
            aday = d[:i] + d[i + 1:]
            deneme += 1
            if ayriliyor(aday, hedef, kusur):
                d, ilerledi = aday, True
                break
    return d, deneme


R = rastgele_kume()
print("kusur  ilk ayrilan girdi no  uzunluk  kuculmus  deneme  en kucuk girdi")
for kusur in ("K1", "K2", "K3", "K4"):
    for no, (d, hedef) in enumerate(R, 1):
        if ayriliyor(d, hedef, kusur):
            kd, deneme = kucult(d, hedef, kusur)
            print(f"{kusur:5s}  {no:20d}  {len(d):7d}  {len(kd):8d}"
                  f"  {deneme:6d}  {kd} hedef {hedef}")
            break
print()
print("12 uzunlukta sirali girdi sayisi (degerler -9..20):", comb(41, 12))
print("52 girdilik kume bu uzayin", f"{52 / comb(41, 12):.1e}", "kadarini gorur")
```

```
kusur  ilk ayrilan girdi no  uzunluk  kuculmus  deneme  en kucuk girdi
K1                        1       12         1      23  [-8] hedef -16
K2                       17       12         2      31  [-8, -5] hedef -13
K3                        7       12         2      21  [-5, 16] hedef 11
K4                        4       12         3      20  [-3, 18, 20] hedef 15

12 uzunlukta sirali girdi sayisi (degerler -9..20): 7898654920
52 girdilik kume bu uzayin 6.6e-09 kadarini gorur
```

Dört ayrılan girdinin dördü de **12 değerden 1, 2 ve 3 değere** iniyor ve bu 20 ile 31 arası
denemeyle oluyor. Küçültmenin kazandırdığı şey adım değil, **okunabilirliktir**: `[-3, 18, 20]`
ve hedef 15 girdisine bakan biri, yönleri ters çevrilmiş sürümün neden yanıldığını doğrudan
görebilir; aynı bilgi 12 değerlik bir dizide gizlidir.

Birinci satır iki bölümü birbirine bağlıyor. Bitiş koşulu bozuk sürümün en küçük ayrılan
girdisi **tek elemanlı** bir dizidir: `[-8]`, hedef $-16$. O tek elemanlı girdi, önceki
bölümdeki kenar durumu kümesinde **elle** yazılmıştı. Yani küçültme, elle kurulan kenar
durumlarının bir kısmını **kendiliğinden üretiyor** — rastgele bir 12 değerlik girdiden yola
çıkıp aynı yere varıyor. Kenar durumu listesi bir sezgi ürünü olmak zorunda değildir; ayrılan
bir girdiden türetilebilir.

#### Kâhinin Bağımsız Olması

Yordamın tamamı kâhinin doğruluğuna dayanıyor. Kâhin yanlışsa ayrılan girdi sayısı yanlış
çıkar ve bu yanlışlık en tehlikeli yönde çıkar: **sıfır.** Kâhin, sınanan çözümle aynı gözden
kaçırmayı paylaşıyorsa ikisi de aynı yanlış yanıtı verir ve karşılaştırma hiçbir şey bulmaz.

- **AD19** — Kâhin, sınanan çözümle **aynı düşünceden türetilmemiş** olmalıdır. Kaba kuvvet bu
  şartı taşır, çünkü hiçbir kalıp varsayımı kullanmaz.
- **AD20** — Aynı gözden kaçırmayı paylaşan bir kâhin, sınanan çözümle **0 ayrılan girdi**
  üretir; bu sıfır bir doğruluk kanıtı değil, bir bağımlılık belirtisidir.

```python
"""Kahinin bagimsizligi: ayni yanlisi paylasan iki cozum 0 ayrilan girdi verir."""
TOHUM = 20260218


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def rastgele_kume(tohum=TOHUM, n=40, uzunluk=12):
    r = uretec(tohum)
    return [(sorted(r(30) - 9 for _ in range(uzunluk)), r(41) - 18)
            for _ in range(n)]


def tara(d, hedef):
    return any(d[i] + d[j] == hedef
               for i in range(len(d)) for j in range(i + 1, len(d)))


def kahin_dogru(d, hedef):
    return tara(d, hedef)


def kahin_kusurlu(d, hedef):
    """Ayni gozden kacirma: son eleman hic bakilmiyor."""
    return tara(d[:-1], hedef)


def kalip_kusurlu(d, hedef):
    """Iki isaretci son elemani hic gormuyor."""
    sol, sag = 0, len(d) - 2
    while sol < sag:
        t = d[sol] + d[sag]
        if t == hedef:
            return True
        sol, sag = (sol + 1, sag) if t < hedef else (sol, sag - 1)
    return False


def ayrilan(a, b, kume):
    return sum(1 for d, hedef in kume if a(d, hedef) != b(d, hedef))


R = rastgele_kume()
print("karsilastirilan iki cozum              ayrilan girdi / 40")
print("kusurlu kalip  -  dogru kahin         ", ayrilan(kalip_kusurlu, kahin_dogru, R))
print("kusurlu kalip  -  kusurlu kahin       ", ayrilan(kalip_kusurlu, kahin_kusurlu, R))
print("kusurlu kahin  -  dogru kahin         ", ayrilan(kahin_kusurlu, kahin_dogru, R))
```

```
karsilastirilan iki cozum              ayrilan girdi / 40
kusurlu kalip  -  dogru kahin          3
kusurlu kalip  -  kusurlu kahin        0
kusurlu kahin  -  dogru kahin          3
```

Aynı kusurlu kalıp, aynı 40 girdide, iki farklı kâhine karşı **3** ve **0** veriyor. Ortadaki
satırın sıfırı bir doğruluk haberi gibi okunur ve değildir: kalıp da kâhin de son elemanı
görmüyor, ikisi de aynı girdilerde aynı yanlış yanıtı veriyor ve fark ölçen bir yordam farkı
olmayan yerde kör kalıyor. Üçüncü satır kusurlu kâhinin kendisinin **3 girdide** yanlış
olduğunu gösteriyor — yani hata oradaydı, ölçüm onu göremedi.

Bu, kaba kuvvetin neden bu kursta kâhin olarak kaldığının ölçülmüş gerekçesidir. Kaba kuvvet
hiçbir kalıp varsayımı taşımaz: sıralılık, işaret, aralık, tekillik varsaymaz. Bir kalıbın
hızlandırılmış bir sürümünü kâhin yapmak, hızlandırmanın dayandığı varsayımı ölçümün içine
taşımak demektir.

#### Sıfır Ayrılan Girdi Ne Kanıtlar

Doğru sürüm iki kümede de 0 ayrılan girdi verdi. Bu, sürümün doğru olduğunu **kanıtlamaz** ve
öyle yazılamaz. Son iki satır neden olmadığını söylüyor: değerleri $-9$ ile $20$ arasında
olan 12 uzunluklu sıralı girdilerin sayısı **7.898.654.920** ve 52 girdilik küme bu uzayın
**6,6·10<sup>-9</sup>** kadarını görüyor. Hedefin de 41 ayrı değer aldığı düşünülürse oran
daha da küçülür.

Bu yüzden doğrulamanın çıktısı iki farklı cümledir ve karıştırılmamalıdır. **Ayrılan girdi
sıfırdan büyükse:** çözüm yanlıştır, elde kanıt vardır ve kanıt küçültülerek okunabilir hâle
getirilebilir. **Ayrılan girdi sıfırsa:** bu kümede kusur bulunamamıştır — kusur yok değildir,
bulunamamıştır. İkinci cümlenin gücü kümenin genişliğiyle değil, **kapsadığı kusur
sınıflarıyla** artar; 40 rastgele girdi boş girdi kusurunu 400'e çıkarılsa da yakalayamazdı,
12 elle kurulmuş girdi ise tek satırda yakaladı.

#### Özet

- Doğrulama yordamı üç adımdır: dağarcık üret, kâhini aynı girdilerde koştur, iki yanıtın
  ayrıldığı girdiyi say. Çıktı bir görüş değil, bir sayıdır.
- 40 girdilik rastgele dağarcık beş kusurun **dördünü** yakalıyor; boş girdiyi hesaba katmayan
  kusuru yakalayamıyor, çünkü dağarcığın her girdisi tam 12 değer taşıyor ve o kusur sınıfı
  yapısal olarak erişilemez.
- 12 girdilik kenar durumu kümesi beş kusurun **beşini** de yakalıyor; girdi başına verim
  0,1000'e karşı 0,4167. Ama yönleri ters sürümü yalnız 2 girdide yakalıyor, rastgele dağarcık
  onu 25 girdide yakalıyor — iki küme birbirinin yerine geçmiyor.
- İkinci dağarcık aynı sonucu veriyor (2, 2, 2, 27, 0). Ayrılan bir girdi, tek değer çıkarma
  denemeleriyle 12 değerden 1–3 değere iniyor ve bu 20–31 deneme tutuyor; bitiş koşulu bozuk
  sürümün en küçük ayrılan girdisi `[-8]`, yani elle yazılmış kenar durumlarından biri.
- Kâhin sınanan çözümle aynı gözden kaçırmayı paylaşırsa ayrılan girdi 3'ten **0'a** düşüyor;
  o sıfır doğruluk değil, bağımlılık belirtisidir. Kaba kuvvet kâhin olarak kalır çünkü hiçbir
  kalıp varsayımı taşımaz.
- Sıfır ayrılan girdi doğruluk kanıtı değildir: 52 girdilik küme, 7.898.654.920 girdilik
  uzayın 6,6·10<sup>-9</sup> kadarını görüyor.

#### Sonraki Adım

İki ders bir çözümün nasıl seçileceğini ve nasıl sınanacağını sayıya bağladı. Geriye tek bir
soru kalıyor ve o soru tek bir çözümle ilgili değil: bu işi tekrar tekrar yapan biri
ilerlediğini nereden bilecek. Sonraki ders alıştırma ortamlarının türlerini ayırır ve tekrarlı
çalışmanın ilerleme ölçütünü ortak tanımın kalıp kümesi üzerinde sayar. Ölçülecek şey
şaşırtıcıdır: çözülen problem sayısı bir ilerleme ölçütü değildir ve bunun neden olmadığı
üç ortam türünde ayrı ayrı sayılacaktır.

### Alıştırma Ortamlarını Kullanma

Source: https://academia.sh/tr/kurslar/ileri-algoritmalar/alistirma-ortamlarini-kullanma

Tekrarlı çalışmanın ilerleme ölçütünün ortak tanımın beş kalıbı üzerinde sayılması: 144 problem çözen ortam beş kalıbı da görüyor ama denge oranı 0,2431, 30 problem çözen ortamda 0,6667.

Önceki iki ders tek bir çözümle ilgiliydi: hangi kalıp seçilir, seçilen kalıp nasıl sınanır.
Bu ders soruyu tek çözümün dışına taşır. Alıştırma tekrarlı bir iştir ve tekrarlı işin bir
ilerleme ölçütü olmak zorundadır, yoksa çalışma sürer ama nereye gittiği bilinmez.

Ortamlar **türleriyle** anılır, çünkü ölçülen şey ortamın kendisi değil, ürettiği **problem
akışının biçimidir**. Sonuç iki adımda çıkar: çözülen problem sayısı bir ilerleme ölçütü
değildir, kaç ayrı kalıp görüldüğü de değildir.

#### Üç Ortam Türü

**Havuz tabanlı alıştırma ortamında** çok sayıda problem durur ve seçimi çalışan yapar. Seçim
serbest olduğu için tanıdık olana kayar; kalıp dağılımı çalışanın alışkanlığını yansıtır.
**Zamanlı yarışma ortamında** kümeyi ortam belirler ve çalışan hepsini görmek zorundadır;
küme küçüktür. **Şirket içi değerlendirmede** problem sayısı en azdır, dağılımı yine çalışan
seçmez, girdiler hazırlanmamıştır.

- **AD21** — Kalıp kümesi ortak tanımın beş kalıbıdır: iki işaretçi, kayan pencere, açgözlü,
  geri izleme, not alma. Kurs boyunca ölçülen kalıplar bunlardır.
- **AD22** — Bir tur, ortamda geçirilen bir çalışma oturumudur. Tur başına problem sayısı
  ortam türüne göre sabittir: havuz 24, yarışma 5, değerlendirme 2. Altı tur ölçülür.
- **AD23** — Havuz tabanlı ortamda seçimin ağırlığı 50, 30, 10, 6, 4'tür; diğer iki ortamda
  dağılım düzgündür. Ağırlık, çalışanın tanıdık kalıba dönme eğilimini modeller.
- **AD24** — Seçim ortak tanımın belirlenimci üreteciyle yapılır; `random` kullanılmaz.
- **AD25** — **Denge oranı**, en az görülen kalıbın sayısının eşit dağılımdaki paya
  bölümüdür. Beş kalıp eşit görülseydi oran 1,0000 olurdu.

```python
"""Uc ortam turunde kalip cesitliligi , ortak tanimin bes kalibi uzerinde."""
TOHUM, IKINCI = 20260218, 20260219
KALIP = ["iki isaretci", "kayan pencere", "acgozlu", "geri izleme", "not alma"]
ORTAM = [("havuz tabanli alistirma ", 24, [50, 30, 10, 6, 4]),
         ("zamanli yarisma         ", 5, [20, 20, 20, 20, 20]),
         ("sirket ici degerlendirme", 2, [20, 20, 20, 20, 20])]


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


def sec(r, agirlik):
    x = r(sum(agirlik))
    for i, a in enumerate(agirlik):
        if x < a:
            return i
        x -= a
    return len(agirlik) - 1


def olcut(sayim):
    toplam = sum(sayim)
    esit = toplam / len(sayim)
    return {"problem": toplam, "kapsanan": sum(1 for c in sayim if c),
            "denge": round(min(sayim) / esit, 4),
            "bas_iki": round(sum(sorted(sayim)[-2:]) / toplam, 4)}


def kosum(tohum, tur=6):
    print(f"tohum {tohum} , {tur} tur")
    print("ortam                     problem  kapsanan   denge  bas iki  dagilim")
    for ad, basina, agirlik in ORTAM:
        r = uretec(tohum)
        sayim = [0] * len(KALIP)
        for _ in range(tur * basina):
            sayim[sec(r, agirlik)] += 1
        o = olcut(sayim)
        print(f"{ad}  {o['problem']:7d}  {o['kapsanan']:8d}  {o['denge']:6.4f}"
              f"  {o['bas_iki']:7.4f}  {sayim}")


kosum(TOHUM)
print()
kosum(IKINCI)
print()
print("havuz tabanli ortam , tur tur birikimli (tohum 20260218)")
print("tur  problem  kapsanan   denge  bas iki")
r = uretec(TOHUM)
sayim = [0] * len(KALIP)
for t in range(1, 7):
    for _ in range(24):
        sayim[sec(r, ORTAM[0][2])] += 1
    o = olcut(sayim)
    print(f"{t:3d}  {o['problem']:7d}  {o['kapsanan']:8d}  {o['denge']:6.4f}"
          f"  {o['bas_iki']:7.4f}")
```

```
tohum 20260218 , 6 tur
ortam                     problem  kapsanan   denge  bas iki  dagilim
havuz tabanli alistirma       144         5  0.2431   0.7986  [64, 51, 11, 11, 7]
zamanli yarisma                30         5  0.6667   0.5000  [4, 5, 9, 6, 6]
sirket ici degerlendirme       12         5  0.4167   0.5833  [1, 4, 3, 2, 2]

tohum 20260219 , 6 tur
ortam                     problem  kapsanan   denge  bas iki  dagilim
havuz tabanli alistirma       144         5  0.1389   0.8194  [76, 42, 16, 6, 4]
zamanli yarisma                30         5  0.6667   0.5667  [8, 4, 9, 4, 5]
sirket ici degerlendirme       12         5  0.4167   0.6667  [1, 1, 5, 2, 3]

havuz tabanli ortam , tur tur birikimli (tohum 20260218)
tur  problem  kapsanan   denge  bas iki
  1       24         4  0.0000   0.8333
  2       48         5  0.2083   0.7917
  3       72         5  0.2778   0.7639
  4       96         5  0.2604   0.7500
  5      120         5  0.2500   0.7750
  6      144         5  0.2431   0.7986
```

Birinci tablo iki ölçütü aynı anda çürütüyor. Havuz tabanlı ortam **144**, zamanlı yarışma
**30**, şirket içi değerlendirme **12** problem çözüyor — arada 12 kat fark var. Buna karşılık
**kapsanan kalıp sayısı üçünde de 5**. Yani iki soru da ortamları ayırt etmiyor: "kaç problem
çözdüm" 12 kat sapıyor, "kaç ayrı kalıp gördüm" hiç oynamıyor.

Ayırt eden ölçüt dağılımdır. Havuz tabanlı ortamda denge oranı **0,2431**: en az görülen kalıp,
eşit dağılımdaki payının dörtte birinden azını alıyor ve en çok görülen iki kalıp problemlerin
**0,7986'sını** kaplıyor. Zamanlı yarışma 30 problemle **0,6667** denge veriyor — beş kat az
problemle **2,74 kat** dengeli. Şirket içi değerlendirmedeki 0,4167 ise dağılımdan değil,
örneklemin küçüklüğünden gelir.

Üçüncü tablo dersin en aykırı sonucudur. Havuz tabanlı ortamda kapsama **ikinci turda** 5'e
çıkıyor ve bir daha hiç değişmiyor: üçüncü turdan altıncı tura kadar çözülen 72 problem
kapsama ölçütüne **hiçbir şey eklemiyor**. Denge oranı ise üçüncü turda 0,2778 ile en yüksek
noktasına çıkıp sonra **düşüyor** — dördüncü turda 0,2604, altıncı turda 0,2431. Problem sayısı
artarken ilerleme ölçütü geriliyor, çünkü eklenen her problem baskın iki kalıba gidiyor.

İkinci dağarcık sıralamayı doğruluyor: `20260219` tohumunda havuz 0,1389, yarışma 0,6667,
değerlendirme 0,4167. Havuzun değeri 0,2431'den 0,1389'a düşüyor, yani **mutlak değer
dağarcığa bağlıdır**; ama üç ortamın sırası ve havuz ile yarışma arasındaki büyüklük farkı iki
dağarcıkta da aynı yönde.

#### Ortam Hatayı Gösteriyor mu

Dağılım tek başına da yetmez. Bir kalıbı elli kez uygulamak, o kalıbın **ön koşulu bozulduğunda
ne yaptığını** göstermez — ortak tanımın birinci okuması bunu zaten kanıtladı: ön koşul sağlanan
40 girdinin 40'ında iki işaretçi kâhinle aynı yanıtı veriyor; ön koşulu sağlayan girdilerle
dolu bir ortamda çalışan biri kalıbın yanlışını **hiç görmez**.

Burada ölçülen şey ortamın karışımıdır: girdilerin yüzde kaçı ön koşulu sağlıyor ve bu oran
görülen yanlış yanıt sayısını nasıl değiştiriyor. İki seçim politikası karşılaştırılır.

- **AD26** — Havuz tabanlı ortamda girdilerin **yüzde 90'ı**, zamanlı yarışmada **yüzde 75'i**,
  şirket içi değerlendirmede **yüzde 50'si** ön koşulu sağlar. Havuzda problemler kalıba göre
  hazırlanmıştır, değerlendirmede girdi olduğu gibi gelir.
- **AD27** — **Alışkanlıkla seçim**, en çok pratik edilen kalıbı her probleme uygular ve
  ön koşulu sınamaz.
- **AD28** — **Ön koşul kontrolüyle seçim**, önce girdinin sıralı olup olmadığına bakar; sıralı
  değilse kalıbı uygulamaz, kâhine düşer. Sınama en fazla $n-1$ adımdır ve ilk aykırı çiftte
  durur.
- **AD29** — Her ortamda 40 problem çözülür ve hedef 11'dir.

```python
"""Iki secim politikasi , kahin karsisinda , uc ortam karisimi."""
TOHUM, IKINCI = 20260218, 20260219


def uretec(tohum):
    d = tohum

    def sonraki(n):
        nonlocal d
        d = (d * 1103515245 + 12345) % 2147483648
        return d % n
    return sonraki


class Sayac:
    def __init__(self):
        self.adim = 0

    def say(self, n=1):
        self.adim += n


def karisim(sirali_oran, tohum=TOHUM, n=40, uzunluk=12):
    """sirali_oran: girdilerin yuzde kaci on_kosulu sagliyor."""
    r = uretec(tohum)
    kume = []
    for _ in range(n):
        dizi = [r(30) - 9 for _ in range(uzunluk)]
        kume.append(sorted(dizi) if r(100) < sirali_oran else dizi)
    return kume


def kahin_ciftler(dizi, hedef, s):
    for i in range(len(dizi)):
        for j in range(i + 1, len(dizi)):
            s.say()
            if dizi[i] + dizi[j] == hedef:
                return True
    return False


def iki_isaretci(dizi, hedef, s):
    sol, sag = 0, len(dizi) - 1
    while sol < sag:
        s.say()
        t = dizi[sol] + dizi[sag]
        if t == hedef:
            return True
        sol, sag = (sol + 1, sag) if t < hedef else (sol, sag - 1)
    return False


def aliskanlik(dizi, hedef, s):
    """En cok pratik edilen kalibi her probleme uygular."""
    return iki_isaretci(dizi, hedef, s)


def on_kosul_kontrolu(dizi, hedef, s):
    """Once on_kosulu sinar; saglanmiyorsa kahine duser."""
    for i in range(len(dizi) - 1):
        s.say()
        if dizi[i] > dizi[i + 1]:
            return kahin_ciftler(dizi, hedef, s)
    return iki_isaretci(dizi, hedef, s)


def olc(politika, kume, hedef=11):
    ayrilan, ak, ah = 0, 0, 0
    for dizi in kume:
        s1, s2 = Sayac(), Sayac()
        a = politika(list(dizi), hedef, s1)
        b = kahin_ciftler(list(dizi), hedef, s2)
        ak, ah = ak + s1.adim, ah + s2.adim
        if a != b:
            ayrilan += 1
    return ayrilan, ak, ah


ORTAM = [("havuz tabanli alistirma ", 90), ("zamanli yarisma         ", 75),
         ("sirket ici degerlendirme", 50)]

for tohum in (TOHUM, IKINCI):
    print(f"tohum {tohum} , her ortamda 40 problem , hedef 11")
    print("ortam                     sirali  aliskanlik      on_kosul kontrolu   kahin")
    print("                          girdi   ayrilan  adim   ayrilan  adim      adim")
    for ad, oran in ORTAM:
        K = karisim(oran, tohum)
        s = sum(1 for d in K if d == sorted(d))
        a1, k1, h1 = olc(aliskanlik, K)
        a2, k2, _ = olc(on_kosul_kontrolu, K)
        print(f"{ad}  {s:6d}  {a1:7d}  {k1:4d}   {a2:7d}  {k2:4d}      {h1:4d}")
    print()
```

```
tohum 20260218 , her ortamda 40 problem , hedef 11
ortam                     sirali  aliskanlik      on_kosul kontrolu   kahin
                          girdi   ayrilan  adim   ayrilan  adim      adim
havuz tabanli alistirma       34        5   165         0   600       834
zamanli yarisma               29        8   199         0   681       914
sirket ici degerlendirme      17       15   255         0   754       856

tohum 20260219 , her ortamda 40 problem , hedef 11
ortam                     sirali  aliskanlik      on_kosul kontrolu   kahin
                          girdi   ayrilan  adim   ayrilan  adim      adim
havuz tabanli alistirma       36        2   213         0   609      1055
zamanli yarisma               31        4   238         0   691      1079
sirket ici degerlendirme      22       10   296         0   823      1161
```

Üç sayı yan yana. **Kâhin:** havuz karışımında 834 adım, 40 girdinin 40'ında doğru. **Kalıp:**
alışkanlıkla seçim 165 adım, ön koşul kontrolüyle seçim 600 adım. **Ayrılan girdi:** alışkanlıkla
seçim havuzda **5**, yarışmada **8**, değerlendirmede **15**; ön koşul kontrolü üç ortamda da
**0**.

Birinci okuma ortamla ilgilidir. Aynı politika, aynı kalıp, aynı problem sayısı — ve gördüğü
yanlış yanıt sayısı 5'ten 15'e, **üç katına** çıkıyor. Farkı yaratan tek şey ortamın girdi
karışımıdır: havuzda 40 girdinin 34'ü ön koşulu sağlıyor, değerlendirmede 17'si. Hazırlanmış
girdilerle çalışan biri kalıbının yanlışını daha az görür ve az görmek düzelmek değildir.

İkinci okuma politikayla ilgilidir. Ön koşul kontrolü üç ortamda da **0 ayrılan girdi** veriyor,
yani hiç yanılmıyor; bedeli havuz karışımında 165 adımdan **600 adıma** çıkmak, yani **3,64
kat**. Buna karşılık kâhinin 834 adımının hâlâ altında: kontrol, doğruluğu kaba kuvvet
pahasına değil, **kaba kuvvetin yüzde 72'si** pahasına satın alıyor. İkinci dağarcık aynı
sıralamayı veriyor (2, 4, 10) ve ön koşul kontrolü orada da üç ortamda 0 ayrılan girdi
üretiyor; mutlak sayılar dağarcığa bağlı, sıralama değil.

Buradan tekrarlı çalışmanın ilerleme ölçütü çıkar ve üç sayıdan oluşur: **kaç kalıp görüldü**,
**dağılımın denge oranı** ve **kaç kalıp ön koşulu bozulmuş hâlde görüldü**. Çözülen problem
sayısı bu üçünün hiçbirini vermez.

#### Özet

- Ortamlar türleriyle ayrılır: havuz tabanlı alıştırmada dağılımı çalışan seçer, zamanlı
  yarışmada ve şirket içi değerlendirmede ortam seçer ve küme küçüktür.
- Çözülen problem sayısı ortamları 12 kat ayırıyor (144, 30, 12) ama kapsanan kalıp sayısı
  üçünde de 5; iki ölçüt de ilerlemeyi göstermiyor.
- Ayırt eden ölçüt dağılımdır: havuz tabanlı ortamda denge oranı 0,2431 ve baskın iki kalıp
  problemlerin 0,7986'sını kaplıyor; zamanlı yarışma beş kat az problemle 0,6667 veriyor.
- Kapsama ikinci turda 5'e çıkıp donuyor, denge oranı üçüncü turda 0,2778 ile tepe yapıp
  0,2431'e **geriliyor** — problem eklemek ölçütü kötüleştirebiliyor.
- Ortamın girdi karışımı görülen yanlışı belirliyor: aynı politika havuzda 5, yarışmada 8,
  şirket içi değerlendirmede 15 ayrılan girdi üretiyor.
- Ön koşul kontrolüyle seçim üç ortamda da 0 ayrılan girdi veriyor; bedeli 165 adımdan 600
  adıma çıkmak, yani kâhinin 834 adımının yüzde 72'si.

#### Kurs Kapanışı

Yirmi üç ders tek bir soruyu sordu: bir kalıp seçmek neyi kabul etmektir. Yanıt her derste
üç sayıyla verildi. **Kâhin** her zaman kaba kuvvetti ve her zaman doğruydu — ama **her zaman
pahalı değildi**: `En Uzun Yol Problemi` sekiz düğümde kâhini 562, kalıbı 3626 adımda ölçtü ve
eşiğin on düğümde olduğunu gösterdi. **Kalıp** genellikle daha az adım harcadı. **Ayrılan
girdi**, kalıbın kâhinden farklı yanıt verdiği girdi sayısıydı. Kursun kuralı buydu: bir kalıbın
sayısı kazandırdığı adım değil, **ön koşulu bozulduğunda verdiği yanlış yanıt sayısıdır**.

| Ders | Kâhin (adım) | Kalıp (adım) | Ayrılan girdi / bedel |
|---|---|---|---|
| Kaba Kuvvet | tam sayım **2640** | eleme 866 | eleme **0**; örnekleme 406'da **14** |
| Böl ve Yönet | **3120** | doğrusal birleştirme **2680** | **0**; eksik birleştirme 920'de **40/40** |
| Açgözlü Algoritmalar | alttan yukarı | açgözlü | **969 sistemin 827'si** (0,8535); en büyük fazlalık **16 para** |
| Dinamik Programlama | **163.840** | tam anahtarlı not alma **2440** (67,15 kat az) | **0**; eksik anahtar **39/40**; örtüşme yokken defter 19 giriş boşa |
| Geri İzleme | budamasız **960.800** düğüm, n=7 | budamalı **552** düğüm | **1740,6 kat**, oran n ile büyür (72,3 → 8156,2); aşırı budama 0 çözüm |
| Rastgeleleştirme | **780** | doğrulamalı örnekleme **944–1056** | **0**; doğrulamasız 200'de, ön koşul bozukken **40/40** |
| İki İşaretçi | **972** | **154** (6,31 kat az) | sırasızda **25/40**, hızlanma **2,33 kata** |
| Kayan Pencere | **2396** | **867** (2,76 kat az) | negatifte **10/40**, **daha az adımda** |
| Hızlı ve Yavaş İşaretçi | **480** | **299** | ikinci kenarda **14/40**; adım **hiç değişmez** |
| Aralık Birleştirme | başlangıca göre sıralama: **40/40** doğru | **2154** | bitişe göre **29/40**, uzunluğa göre ve sıralamasız **40/40** |
| Döngüsel Yerleştirme | 1..n, tekrarsız | karşılaştırmasız | tekrarlıda yalın **40/40**, korumalı **39/40**, eksikte **0/40** |
| İki Yığın | **480** ortanca | denge adımıyla **1261** | dengesiz **295/480** yanlış; denge kalıbın **yarısından fazlası** (1261/550) |
| K'ıncı Eleman | dört k x 40 girdi | k boyutlu yığın | **0/40**; "k'ıncı ayrı değer" ayrı sorudur: **27/40** |
| Izgara Gezinmesi | aynı komşulukla **40/40** | bağlı bileşen | tanımlar ayrılınca **38/40** |
| Sırt Çantası | bölünebilirde **100.800** | açgözlü **174** | bölünebilirde **0/40**; aynı sıra 0/1'de **5/40**, kayıp 7 |
| Gezgin Satıcı | **352.800** (20 örnek) | en yakın komşu **560** | **17/20**; sapma ort. 9,3, **en kötü 33,33**; iki değişim 2/20 |
| En Uzun Yol | çevrimsizde **40/40** | gevşetme yordamı | çevrimlide **38/40**; en büyük fazla tahmin **228** |
| N Vezir | budamalı **2057** düğüm, n=8 | simetri elemesiyle **1029** | oran **2,00**, beş boyda sabit; budamanınki büyür, simetrininki **büyümez** |
| At Turu ve Labirent | yol modeli **90.111** | hücre modeli **25** | oran **3604**, ayrılan **0** |
| Hamilton Yolları | sayma **29.270** | karar **1526** | oran **19,18**; yol bulunmayanların **hepsinde** karar = sayma |
| Problem Okuma ve Kısıt | **866** | karma tablo **486**, sıralayan kalıp **1398**, sayma **396** | yanlış kısıt okuması 40 girdinin **18'inde** ayrılıyor (ikinci dağarcıkta 17); n=10<sup>8</sup>'de üç çözüm de bütçeye sığmıyor |
| Çözüm Doğrulama | kaba kuvvet, 52 girdide **1596** | doğru iki işaretçi **329** | 40 rastgele girdi 5 kusurun **4'ünü**, 12 kenar durumu **5'ini** yakalıyor; aynı kusuru paylaşan kâhinde ayrılan **3'ten 0'a** düşüyor |
| Alıştırma Ortamları | **834** | alışkanlıkla seçim **165**, ön koşul kontrolü **600** | havuzda **5**, yarışmada **8**, değerlendirmede **15** ayrılan girdi; ön koşul kontrolü 0 ayrılan ama 3,64 kat adım |

Tablonun ikinci okuması birincisinden değerlidir: **hızlandırma yanlışa yaklaştırır ve bazen
hızlandırmaz bile.** Sırasız girdide iki işaretçinin hızlanması 2,33 kata düşerken 25 girdide
yanılıyor; açgözlü 969 sistemin 827'sinde fazla veriyor; not alma örtüşme yokken 19 girişi boşa
tutuyor. Hiçbiri saklanmadı: bir kalıbın ne zaman yanıldığını bilmeyen, onu ne zaman seçeceğini
de bilemez.

Üçüncü ve kalıcı sonuç kâhinin kendisiyle ilgilidir: **kaba kuvvet bir taban değil, bir
kâhindir.** Kursun bütün sayıları onun sayesinde yazılabildi. Son derste bu bir alışkanlığa
döndü: doğruluğu bir görüşe değil, ikinci bir çözümün yanıtına karşı saymak.

Bir sonraki kurs, **Hesaplama Kuramı**, kâhinin gölgesinde bırakılan soruyu alır. Gezgin satıcı
ve Hamilton yolları derslerinde "bu problem neden zor" sorusu açıkça ertelendi. Orada sorulacak
olan şudur: bazı problemler için kaba kuvvetten daha iyisinin bilinmemesi, **bilgimiz hakkında
mı yoksa problemler hakkında mı** bir gerçektir. Yanıt karar verilebilirlik, indirgeme ve
karmaşıklık sınıflarıyla verilecek.

