Ders 09 / 23
Hızlı ve Yavaş İşaretçi
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.
İçindekiler
Ö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.
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.
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.
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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.