Ders 05 / 10
P Sınıfı
Polinom zamanda çözülebilir karar problemlerinin, sonlu bir adım bütçesi altında ölçülmesi: aynı 20 örnekte eşik problemi 12 adımda, ikili problemi 66 adımda karara bağlanıyor, alt küme toplamı ise 10.000 adım bütçesi gerektiriyor. Girdi boyu 8'den 20'ye çıkarken ilk iki yordamın en kötü adımı 8'den 20'ye ve 28'den 190'a giderken üçüncüsü 256'dan 1.048.576'ya çıkıyor. Karar problemi sınıfının, Algoritmalar kursundaki büyüme sınıfından farkı ayrıca yazılıyor.
İçindekiler
Hesaplama Modelleri konusu, bir sorunun yanıtlanıp yanıtlanamayacağını sordu ve sonlu bir bütçenin o soruyu her zaman kapatamadığını ölçtü. Karmaşıklık Sınıfları konusu daha dar bir alanda çalışır: yanıtı bulunabilen problemler arasında, bulmanın bedelinin girdiyle nasıl büyüdüğünü sorar. İlk soru şudur: bir problemi ucuz kılan nedir, ve ucuzluk problemin özelliği midir yoksa seçilen yordamın mı.
Bu ders üç karar problemini aynı girdi üzerinde aynı bütçeyle koşturur. İki tanesi bütçeyi neredeyse hiç zorlamıyor, üçüncüsü bütçeyi büyütmeyi gerektiriyor. Ölçülen şey hangisinin “hızlı” olduğu değil, bütçenin hangisini karara bağladığıdır.
- KS1. Bu konudaki bütün problemler karar problemidir: yanıt tek bir sözcüktür, “evet” ya da “hayır”. Karar problemi kavramı M01/K07 İleri Algoritmalar kursunda kuruldu ve burada tekrarlanmaz.
- KS2. Girdi boyu n, örnekteki sayı adedidir. Sayılar 3 ile 99 arasında tutulur, yani bir sayının basamak sayısı sabittir ve girdi büyüklüğü yalnız n ile değişir.
- KS3. Ölçü adımdır, süre değil. Bir adımın ne olduğu her yordamda ayrıca tanımlanır ve o tanım ders boyunca değişmez.
- KS4. Bütçe bir adım bütçesidir. Yordam bütçeyi tüketirse yanıt vermez; “karara bağlanmadı” döner. Bu, yanıtın olmadığı anlamına gelmez, bu bütçede kurulamadığı anlamına gelir.
- KS5. Örnekler ortak tanımın üretecinden gelir: tohum 20260218, 20 örnek, örnek başına 12 sayı. Bu kursta ikinci tohum yoktur; yerine bütçe süpürmesi vardır.
- KS6. Üç problem şunlardır. Eşik: sayıların toplamı hedefi aşıyor mu. İkili: iki sayının toplamı hedefe tam eşit mi. Alt küme toplamı: herhangi bir alt kümenin toplamı hedefe tam eşit mi.
- KS7. Bütçe süpürmesi zorunludur. Her sonuç en az üç bütçe değerinde gösterilir ve bütçeyle değişip değişmediği yazılır.
- KS8. Bir yordamın gözlenen adımı ile en kötü durumdaki adımı ayrı yazılır. İkisini karıştırmak bu dersin ölçtüğü farkı yok eder.
- KS9. Bir problemin P sınıfına ait olup olmadığı ölçülmez. Ölçülen tek şey, belirli bir yordamın adım sayısının girdi büyüklüğüyle nasıl büyüdüğüdür.
- KS10. Sabit çarpanlar elenmez. Ölçüm asimptotik değil, sayılan adımdır.
Karar Problemi Sınıfı ile Büyüme Sınıfı
M01/K04 Algoritmalar kursundaki “Karmaşıklık Sınıflarını Okumak” dersi de “karmaşıklık sınıfı” terimini kullanıyordu, ama ayrı bir şeyi adlandırıyordu. Fark tek bir cümleyle yazılabilir: orada bir sınıf tek bir yordamın maliyet ifadesini gruplar — , iç içe iki döngünün büyüme biçimidir — burada bir sınıf, hakkında yordam aranan problemleri gruplar.
Ayrımın pratik sonucu şudur. Bir yordam her zaman bir büyüme sınıfına aittir; bu, koda bakılarak belirlenir. Bir problem ise ancak “böyle bir yordamın var olduğu” gösterilerek bir karar problemi sınıfına sokulabilir, ve bu bir varlık iddiasıdır. Kod ölçülür, varlık iddiası ölçülmez.
P, bir karar problemi sınıfıdır: girdi büyüklüğünün bir polinomuyla sınırlı adımda kesin yanıt veren belirlenimci bir yordamın bulunduğu problemler. Tanımdaki üç sözcük de yüklüdür. “Polinom” bir büyüme sınırıdır; “kesin” yaklaşık çözümü dışarıda bırakır; “bulunduğu” bir varlık iddiasıdır ve tek bir koşumla kurulamaz.
Üç Karar Problemi, Bir Adım Bütçesi
Aşağıdaki blok üç problemi aynı 20 örnek üzerinde koşturur. Her yordam bir adım bütçesi alır ve
bütçe dolduğunda None döndürerek karara varamadığını bildirir.
TOHUM = 20260218 def ornekler(tohum=TOHUM, n=12, sayi=20): """Ortak tanimin ornek ureteci: her ornek n sayi ve bir hedef.""" d = tohum kume = [] for _ in range(sayi): sayilar = [] for _ in range(n): d = (d * 1103515245 + 12345) % 2147483648 sayilar.append(d % 97 + 3) d = (d * 1103515245 + 12345) % 2147483648 kume.append({"sayilar": sayilar, "hedef": sum(sayilar) // 3 + d % 7}) return kume def esik(sayilar, hedef, butce): """Toplam hedefi asiyor mu. Bir adim = bir toplama.""" toplam, adim = 0, 0 for s in sayilar: if adim >= butce: return None, adim toplam += s adim += 1 return toplam > hedef, adim def ikili(sayilar, hedef, butce): """Iki sayinin toplami hedefe esit mi. Bir adim = bir ikili.""" adim, n = 0, len(sayilar) for i in range(n): for j in range(i + 1, n): if adim >= butce: return None, adim adim += 1 if sayilar[i] + sayilar[j] == hedef: return True, adim return False, adim def alt_kume(sayilar, hedef, butce): """Bir alt kumenin toplami hedefe esit mi. Bir adim = bir alt kume.""" adim, n = 0, len(sayilar) for maske in range(1 << n): if adim >= butce: return None, adim adim += 1 if sum(sayilar[i] for i in range(n) if maske >> i & 1) == hedef: return True, adim return False, adim ORN = ornekler() print("butce yordam karara baglanan baglanmayan en cok adim") for b in (12, 100, 1000, 10000): for ad, f in (("esik ", esik), ("ikili ", ikili), ("alt kume", alt_kume)): cevap = ust = 0 for o in ORN: y, a = f(o["sayilar"], o["hedef"], b) if y is not None: cevap += 1 ust = max(ust, a) print(f"{b:5d} {ad} {cevap:15d} {20 - cevap:11d} {ust:11d}")
butce yordam karara baglanan baglanmayan en cok adim 12 esik 20 0 12 12 ikili 0 20 0 12 alt kume 0 20 0 100 esik 20 0 12 100 ikili 20 0 66 100 alt kume 7 13 98 1000 esik 20 0 12 1000 ikili 20 0 66 1000 alt kume 19 1 354 10000 esik 20 0 12 10000 ikili 20 0 66 10000 alt kume 20 0 1047
Bütçenin Yanıtladığı ve Yanıtlayamadığı
Üç sayı yan yana konur. Bütçe 12: eşik problemi 20 örneğin 20’sini karara bağlıyor, öteki iki problem hiçbirini. Bütçe 100: ikili problemi de 20’de 20’ye çıkıyor ve en çok 66 adım harcıyor; alt küme toplamı 7 örnekte kalıyor, 13 örnek karara bağlanmıyor. Bütçe 10.000: üçü de 20’de 20.
Bütçe süpürmesinin okunması burada iki ayrı sonuç veriyor. Eşik ve ikili problemleri için bütçeyi 100’den 10.000’e çıkarmak hiçbir şeyi değiştirmiyor: en çok adım 12 ve 66’da sabit kalıyor, çünkü bu iki yordam girdiyi zaten tüketmiş durumda. Alt küme toplamı için ise bütçe büyütmek her şeyi değiştiriyor: 7, sonra 19, sonra 20. Bu kursun ikinci iddiası tek bir tabloda iki kez görünüyor.
Sabit kalan sayıların anlamı, kalmayanlardan daha önemlidir. Eşik yordamının 12 adımı, girdide 12 sayı olduğu içindir; ikili yordamının 66 adımı, 12 sayının 66 ikilisi olduğu içindir. İkisi de girdinin kendisinden okunabilen sayılardır ve örneğe bakmadan önceden yazılabilir. Alt küme toplamının 98, 354, 1047 adımları ise önceden yazılamaz; hangi maskede duracağı örneğe bağlıdır.
Girdi Boyu Büyüyünce
Bir bütçe değerinde alınan sonuç, girdi büyüklüğü değiştiğinde ayakta kalmayabilir. İkinci süpürme girdi büyüklüğünu 8’den 20’ye çıkarır ve her yordam için en kötü durumdaki adım sayısını gözlenen en yüksek adımın yanına koyar.
TOHUM = 20260218 def ornekler(tohum=TOHUM, n=12, sayi=20): d = tohum kume = [] for _ in range(sayi): sayilar = [] for _ in range(n): d = (d * 1103515245 + 12345) % 2147483648 sayilar.append(d % 97 + 3) d = (d * 1103515245 + 12345) % 2147483648 kume.append({"sayilar": sayilar, "hedef": sum(sayilar) // 3 + d % 7}) return kume def alt_kume(sayilar, hedef): """Butun alt kumeler; ilk uyanda durur. Bir adim = bir alt kume.""" adim, n = 0, len(sayilar) for maske in range(1 << n): adim += 1 if sum(sayilar[i] for i in range(n) if maske >> i & 1) == hedef: return True, adim return False, adim print(" n esik ikili alt kume (en kotu) alt kume (gozlenen en cok)") for n in (8, 12, 16, 20): ust = max(alt_kume(o["sayilar"], o["hedef"])[1] for o in ornekler(n=n)) print(f"{n:2d} {n:4d} {n * (n - 1) // 2:5d} {1 << n:18d} {ust:26d}")
n esik ikili alt kume (en kotu) alt kume (gozlenen en cok) 8 8 28 256 256 12 12 66 4096 1047 16 16 120 65536 945 20 20 190 1048576 2684
Eşik sütunu 8, 12, 16, 20; girdi büyüklüğüyle birebir. İkili sütunu 28, 66, 120, 190; girdi büyüklüğü iki buçuk katına çıkarken adım yaklaşık yedi katına çıkıyor, yani karesel bir artış. Alt küme sütununun en kötü durumu 256’dan 1.048.576’ya gidiyor: her yeni sayı, taranacak alt küme sayısını ikiye katlıyor.
Son sütun bir tuzağı açığa çıkarıyor. Gözlenen en yüksek adım 12 sayıda 1047, 16 sayıda 945, 20 sayıda 2684. Yani gözlenen değer girdi büyüklüğüyle düzenli artmıyor, hatta 12’den 16’ya inerken azalıyor. Bunun nedeni yordamın uyan ilk alt kümede durmasıdır; bu örneklerde uyan bir alt küme erken bulunuyor. Sekiz sayılık satırda gözlenen değer en kötü durumla çakışıyor — 256’ya 256 — çünkü o dağarcıkta hiçbir alt kümenin uymadığı bir örnek var ve orada tarama sonuna kadar gidiyor.
Buradan çıkan kural şudur: gözlenen adım, bir yordamın bedeli değildir. Yirmi örnekte gözlenen en yüksek değerin 2684 olması, yirmi birinci örneğin 2684 adımda biteceğini söylemez. Bir yordamın bedelini yazmak, gözlenen bir sayıyı değil, her girdi için geçerli bir sınırı yazmaktır.
Girdi Boyu Neyle Ölçülür
P tanımındaki “polinom”, girdi büyüklüğünün polinomudur. O halde girdi büyüklüğünün ne olduğu tanımın parçasıdır, ve bu noktada dikkatsizlik sessiz bir hataya dönüşür. Alt küme toplamı için tam taramadan başka bir yordam daha vardır: erişilebilen toplamların tablosu tutulur ve her sayı tabloyu bir kez günceller. Bu yordam n çarpı hedef adım harcar, yani 12 sayı ve 180 hedef için yaklaşık 2160 adım — 4096’nın altında. Görünüşte polinom bir yordamdır.
Aşağıdaki blok bu görünüşü sınar. Bütün sayılar ve hedef aynı çarpanla büyütülür; sayı adedi değişmez, yalnız her sayının basamak sayısı artar.
TOHUM = 20260218 def ornekler(tohum=TOHUM, n=12, sayi=20): d = tohum kume = [] for _ in range(sayi): sayilar = [] for _ in range(n): d = (d * 1103515245 + 12345) % 2147483648 sayilar.append(d % 97 + 3) d = (d * 1103515245 + 12345) % 2147483648 kume.append({"sayilar": sayilar, "hedef": sum(sayilar) // 3 + d % 7}) return kume def tam_tarama(sayilar, hedef): """Bir adim = bir alt kume.""" adim, n = 0, len(sayilar) for maske in range(1 << n): adim += 1 if sum(sayilar[i] for i in range(n) if maske >> i & 1) == hedef: return True, adim return False, adim def dinamik(sayilar, hedef): """Erisilen toplamlar tablosu. Bir adim = bir tablo hucresi.""" ulasilan = [False] * (hedef + 1) ulasilan[0] = True adim = 0 for s in sayilar: for t in range(hedef, s - 1, -1): adim += 1 if ulasilan[t - s]: ulasilan[t] = True return ulasilan[hedef], adim print("carpan basamak uyusan tam tarama adimi dinamik adimi") for c in (1, 10, 100): uyan, ta, da = 0, 0, 0 basamak = 0 for o in ornekler(): s = [x * c for x in o["sayilar"]] h = o["hedef"] * c basamak = max(basamak, len(str(h))) y1, a1 = tam_tarama(s, h) y2, a2 = dinamik(s, h) uyan += y1 == y2 ta, da = ta + a1, da + a2 print(f"{c:6d} {basamak:7d} {uyan:6d} {ta:16d} {da:13d}")
carpan basamak uyusan tam tarama adimi dinamik adimi
1 3 20 4321 37994
10 4 20 4321 377780
100 5 20 4321 3775640
İki yordam 20 örneğin 20’sinde aynı yanıtı veriyor, yani tablo yordamı doğru çalışıyor. Ama adım sütunları ters yönlere gidiyor. Sayılar on katına çıkarıldığında tam taramanın adımı 4321’de sabit kalıyor; tablo yordamının adımı 37.994’ten 377.780’e, sonra 3.775.640’a çıkıyor. Girdiye eklenen şey tek bir basamaktır ve sayı adedi hiç değişmemiştir.
Sonuç şudur: tablo yordamı, hedefin değerinin polinomudur, hedefi yazmak için gereken basamak sayısının değil. Bir sayıyı yazmak için gereken yer basamak sayısı kadardır, değeri kadar değil; bu yüzden girdi büyüklüğü bu yordam için üstel olarak büyür. Girdi boyu “sayı adedi” diye okunursa yordam polinom görünür, “yazılan basamak adedi” diye okunursa görünmez. Bu ders girdi büyüklüğünu KS2’de sabit basamakla tanımladığı için ilk satır dürüsttür; çarpan büyütüldüğünde o tanım bozulur ve tablo hemen bunu gösterir.
P Sınıfı Ne Söyler, Ne Söylemez
Yukarıdaki iki tablo eşik ve ikili problemlerini P sınıfına sokmaz. Sokan şey ölçüm değil, yordamın yapısıdır: eşik yordamı her girdide tam olarak n adım, ikili yordamı en çok n(n-1)/2 adım harcar ve bu iki sınır girdiye bakılmadan gösterilebilir. Ölçüm bu gösterimi sınar, yerine geçmez.
Ters yön daha da katıdır. Alt küme toplamı probleminin 20 sayıda 1.048.576 adım istemesi, bu problemin P sınıfında olmadığını göstermez. Gösterdiği tek şey, burada kurulan yordamın bu kadar adım harcadığıdır. Aynı problem için başka bir yordam bulunmadığı ölçülmedi; yalnız bu derste kurulmadı. Bu kursun kuralı bunu bir cümleye bağlar: bütçesi yazılmayan “çözülemez” iddiası ölçülmemiş sayılır.
Mühendislik karşılığı doğrudandır. Bir raporda “bu problem polinom zamanda çözülüyor” cümlesi, yanına o polinomu ve bir sınır gerekçesi ister. “Bizim yordamımız 20 örnekte 66 adımda bitiyor” cümlesi ise ölçülmüş, doğru ve çok daha zayıf bir iddiadır. İkisi aynı cümlede yazılmaz.
Özet
- Karar problemi sınıfı ile büyüme sınıfı ayrı şeylerdir: biri problemleri, öteki tek bir yordamın maliyet ifadesini gruplar.
- Aynı 20 örnekte eşik problemi 12, ikili problemi 66 adımda karara bağlanıyor; alt küme toplamı 100 adım bütçesinde 20 örneğin yalnız 7’sini bağlıyor ve 10.000’de tamamlanıyor.
- Bütçeyi 100’den 10.000’e çıkarmak ilk iki problemde hiçbir şeyi değiştirmiyor, üçüncüsünde yanıtlanan örnek sayısını 7’den 20’ye taşıyor.
- Girdi boyu 8’den 20’ye çıkarken en kötü adım sayısı 8’den 20’ye, 28’den 190’a ve 256’dan 1.048.576’ya gidiyor.
- Gözlenen adım bir yordamın bedeli değildir: 12 sayıda 1047 gözlenirken 16 sayıda 945 gözleniyor, çünkü yordam uyan ilk alt kümede duruyor.
- Bir problemin P sınıfına ait olduğu ölçülerek gösterilmez; ölçüm yalnız gösterilmiş bir sınırı sınar.
Sonraki Adım
Alt küme toplamı probleminde tarama pahalıydı, ama tarama bitince elde bir şey daha kalıyordu: hedefe toplanan alt kümenin kendisi. O alt küme bir kez elde edildiğinde, doğru olduğunu görmek için taramayı yeniden yapmak gerekmiyor. Sonraki ders bu gözlemi ölçüye çevirir: bir yanıtı bulmak ile verilmiş bir yanıtı doğrulamak arasındaki adım farkı ne kadar, ve bu farkın bir sınıf adı olması ne anlama geliyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.