İçeriğe geç
academia.sh

Ders 11 / 23

Döngüsel Yerleştirme

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.

İçindekiler

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

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.

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.

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 sorudur. “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.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat