İçeriğe geç
academia.sh

Ders 13 / 23

K'ıncı 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.

İçindekiler

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

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.

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.

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.

İ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