Bölüm 7: Açgözlü Algoritmalar
Giriş
Öğrenme Çıktıları
Açgözlü (greedy) stratejiyi tanımlamak; açgözlü seçim özelliği ve optimal alt yapının ne zaman doğru sonuç garantilediğini açıklamak.
Para üstü, etkinlik seçimi, iş sıralama ve kesirli sırt çantası problemlerini açgözlü olarak çözmek; başarısız olduğu durumlara karşı örnek vermek.
Kesim özelliğinin Kruskal, Prim ve Borůvka'nın açgözlü seçimini neden doğru kıldığını açıklamak; üç algoritmayı seçim kuralı, veri yapısı ve süre bakımından karşılaştırmak.
Huffman ağacını kurmak, önek-özgür kodları türetmek, bir metni kodlayıp çözmek ve kazancı bit olarak hesaplamak.
Açgözlü Yaklaşım
Adım Adım Kazananlar
- Açgözlü algoritmalar (greedy algorithms), her adımda o an en iyi görünen seçeneği seçerek ilerler.
- Tıpkı bir define arayan gibi: her aşamada en fazla kazancı sağlayacak seçeneği tercih eder.
- Basamaklı bir yaklaşımdır: problemi bütün olarak ele almaz, mevcut en iyi seçeneğe odaklanır.
- Verilen bir karar geri alınmaz; geri izleme (backtracking) yoktur.
- Yolculuk planlarken her kavşakta en kısa görünen yolu seçmek gibi.
Hızlı ve basit bir yöntem sunar; kod yapısı genellikle kısa ve anlaşılırdır.
Ancak en iyi (optimal) çözümü bulmayı garanti etmez; çoğu durumda iyi bir yaklaşık çözüm üretir.
Açgözlü Yaklaşım
Açgözlü Paradigma: Genel Şablon
Cozum acgozlu(Aday[] adaylar) {
Cozum s = new Cozum(); // boş çözüm
sirala(adaylar); // ölçüte göre
for (Aday a : adaylar) {
if (s.uygunMu(a)) // kısıt bozulmaz
s.ekle(a); // geri alınmaz
}
return s;
}- Uygun çözümler kümesini belirle (hangi seçimler kısıtları bozmaz?).
- Bir açgözlü ölçüt seç: en ucuz, en erken biten, en kârlı, en sık…
- Ölçüte göre yerel olarak en iyi adayı seç; uygunsa çözüme ekle.
- Çözüm tamamlanana kadar tekrarla.
Açgözlü Yaklaşım
Ne Zaman Doğru Sonuç Verir?
(Greedy-choice property) Yerel olarak en iyi seçimi içeren en az bir en iyi çözüm vardır. İlk açgözlü seçim hiçbir zaman "yanlış yola" sokmaz.
(Optimal substructure) Seçim yapıldıktan sonra kalan alt problemin en iyi çözümü, ilk seçimle birlikte tüm problemin en iyi çözümünü verir.
(Exchange argument) Herhangi bir en iyi çözüm O alınır. O, açgözlü seçimi içermiyorsa, O'daki bir öğe açgözlü seçimle değiştirilir; çözüm uygun kalır ve daha kötü olmaz. Böylece açgözlü seçimi içeren bir en iyi çözüm bulunur; tümevarımla tüm adımlar için geçerlidir.
Açgözlü Yaklaşım
Avantajlar ve Dezavantajlar
Avantajlar
- Anlaması ve uygulaması kolay; kod kısa.
- Hızlı: çoğunlukla sıralama + tek geçiş, O(n log n).
- Az bellek: yalnızca mevcut çözüm tutulur.
- Doğru problemde (MST, Huffman, etkinlik seçimi) en iyi sonucu verir.
- Zor problemlerde iyi bir yaklaşık çözüm ya da başlangıç noktası sağlar.
Dezavantajlar
- En iyi çözümü garanti etmez (para üstü {1, 3, 4}, 0/1 sırt çantası).
- Doğruluğu her problem için ayrıca kanıtlanmalıdır.
- Ölçüt seçimi kritik: yanlış ölçüt yanlış sonuç verir.
- Verilen karar geri alınmaz; hatayı düzeltme şansı yoktur.
Açgözlü Yaklaşım
Para Üstü Problemi (Coin Change)
Belirli bir tutarı en az sayıda madeni parayla ödemek.
Açgözlü ölçüt: kalan tutardan büyük olmayan en büyük parayı seç; tutar sıfır olana kadar tekrarla.
// para[] büyükten küçüğe sıralı static int paraUstu(int[] para, int tutar) { int adet = 0; for (int p : para) { while (tutar >= p) { tutar -= p; adet++; } } return adet; }
{50, 25, 10, 5, 1} gibi kanonik para sistemlerinde evet: 68 = 50 + 10 + 5 + 1 + 1 + 1.
{4, 3, 1} sisteminde 6 için açgözlü 4 + 1 + 1 (3 para) verir; en iyisi 3 + 3 (2 para). Genel durumda dinamik programlama gerekir.
Zaman: O(k + adet), k = para türü sayısı.
Açgözlü Yaklaşım · Kodu adım adım çalıştır
paraUstu({4, 3, 1}, 10): Kod İzleme
// para[] büyükten küçüğe sıralı static int paraUstu(int[] para, int tutar) { int adet = 0; for (int p : para) { while (tutar >= p) { tutar -= p; adet++; } } return adet; }
Paralar {4, 3, 1} büyükten küçüğe sıralı, tutar 10. Bu sistem kanonik değil: açgözlü seçimin sonucu en iyi olmayabilir.
p = 4: 10 ≥ 4 → bu paradan alınabilir.
tutar −= 4 → 6, adet = 1. Döngü sürer.
tutar −= 4 → 2, adet = 2. 2 < 4: while biter.
p = 3: 2 < 3 → 3'lük para hiç kullanılmaz, sıradaki paraya geçilir.
p = 1: 2 ≥ 1 → bu paradan alınabilir.
tutar −= 1 → 1, adet = 3. Döngü sürer.
tutar −= 1 → 0, adet = 4. 0 < 1: while biter.
Açgözlü 4 + 4 + 1 + 1 = 4 para döndürür; oysa 4 + 3 + 3 = 3 para yeterli (DP ile bulunur). İlk iki 4'lük seçim, 3'lüklerin kullanılmasını engelledi.
Açgözlü Yaklaşım · Adım adım
Para Üstü: Adım Adım
Örnek 1: paralar {50, 25, 10, 5, 1}, tutar 68. En büyük paradan başla.
Kalan tutara sığan en büyük para 50 → kalan 68 − 50 = 18.
Kalan tutara sığan en büyük para 10 → kalan 18 − 10 = 8.
Kalan tutara sığan en büyük para 5 → kalan 8 − 5 = 3.
Kalan tutara sığan en büyük para 1 → kalan 3 − 1 = 2.
Kalan tutara sığan en büyük para 1 → kalan 2 − 1 = 1.
Kalan tutara sığan en büyük para 1 → kalan 1 − 1 = 0. Toplam 6 para — bu para sistemi için en iyi sonuç.
Örnek 2: paralar {4, 3, 1}, tutar 6. En büyük paradan başla.
Kalan tutara sığan en büyük para 4 → kalan 6 − 4 = 2.
Kalan tutara sığan en büyük para 1 → kalan 2 − 1 = 1.
Kalan tutara sığan en büyük para 1 → kalan 1 − 1 = 0.
Açgözlü 4 + 1 + 1 = 3 para verdi; oysa 3 + 3 = 2 para yeterli. Yerel en iyi seçim (4) küresel en iyiyi kaçırdı.
Açgözlü Yaklaşım
Deneyin: Açgözlü ve En İyi Para Üstü
En iyi sonuç dinamik programlama ile bulunur: enAz[t] = 1 + min enAz[t − p]. Paralar 1–999 arası tam sayı, en fazla 8 tür; tutar en fazla 999. Enter = hesapla.
Açgözlü Yaklaşım
Açgözlü Yaklaşım ve Böl ve Fethet
| Açgözlü | Böl ve fethet | |
|---|---|---|
| Karar | her adımda tek, yerel en iyi seçim; geri alınmaz | sabit bir kuralla bölünür; seçim yapılmaz |
| Alt problemler | seçimden sonra tek alt problem kalır | birbirinden bağımsız birkaç alt problem |
| Doğruluk | açgözlü seçim özelliği + optimal alt yapı kanıtlanmalı | birleştirme adımının doğruluğuna bağlı |
| Maliyet | genelde O(n log n): sıralama + tek geçiş | özyineleme bağıntısı ve ana teoremle |
| Örnek | etkinlik seçimi, Kruskal, Huffman | birleştirme sıralaması, Karatsuba |
Etkinlik Seçimi
Etkinlik Seçimi (Activity Selection)
- Her etkinliğin bir başlangıç si ve bitiş fi zamanı var; aynı anda tek etkinlik yapılabilir (tek salon).
- Amaç: birbiriyle çakışmayan en fazla sayıda etkinliği seçmek. [si, fi) ile [sj, fj) çakışmaz ⇔ sj ≥ fi ya da si ≥ fj.
- Açgözlü ölçüt: en erken biten etkinliği seç, onunla çakışanları at, kalanlarla tekrarla.
| Etkinlik | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| başlangıç | 1 | 2 | 4 | 6 | 5 | 8 | 0 | 3 | 7 | 10 |
| bitiş | 3 | 5 | 6 | 8 | 7 | 9 | 2 | 4 | 9 | 12 |
Etkinlik Seçimi · Kodu adım adım çalıştır
sec(e): Kodu Adım Adım İzle
Etkinlikler bitiş zamanına göre sıralandı (yukarıdan aşağı). Kesikli çizgi: son seçilenin bitişi.
En erken biten etkinlik (#7, [0, 2)) her zaman seçilir.
#1 [1, 3): başlangıç 1 < 2 → çakışıyor, atlandı.
#8 [3, 4): başlangıç 3 ≥ 2 → seçildi, i = 2.
#2 [2, 5): başlangıç 2 < 4 → çakışıyor, atlandı.
#3 [4, 6): başlangıç 4 ≥ 4 → seçildi, i = 4.
#5 [5, 7): başlangıç 5 < 6 → çakışıyor, atlandı.
#4 [6, 8): başlangıç 6 ≥ 6 → seçildi, i = 6.
#6 [8, 9): başlangıç 8 ≥ 8 → seçildi, i = 7.
#9 [7, 9): başlangıç 7 < 9 → çakışıyor, atlandı.
#10 [10, 12): başlangıç 10 ≥ 9 → seçildi, i = 9. Sonuç: 6 etkinlik (7, 8, 3, 4, 6, 10).
static void sec(Etkinlik[] e) { Arrays.sort(e, (x, y) -> x.bitis - y.bitis); int i = 0; // son seçilen System.out.print(e[i].id + " "); for (int j = 1; j < e.length; j++) { if (e[j].bas >= e[i].bitis) { System.out.print(e[j].id + " "); i = j; } } }
Etkinlik Seçimi
Neden En Erken Biten?
Akla gelen diğer ölçütler en iyi sonucu garanti etmez (turuncu: açgözlünün seçtiği, yeşil: en iyi çözüm):
"En az çakışan" ölçütünün de karşı örneği vardır.
a1 en erken biten etkinlik, O ise bir en iyi çözüm olsun. O'nun ilk etkinliği b ≠ a1 ise b yerine a1 konur: f(a1) ≤ f(b) olduğundan O'nun diğer etkinlikleriyle çakışma oluşmaz ve etkinlik sayısı aynı kalır.
Demek ki a1'i içeren bir en iyi çözüm vardır. a1 ile çakışanlar atılınca kalan problem aynı yapıdadır (optimal alt yapı).
İş Sıralama
Son Tarihli İş Sıralama (Job Sequencing)
- Her iş 1 birim sürer, bir son tarihi (deadline) ve bir kârı vardır.
- Kâr, iş yalnızca son tarihine kadar bitirilirse kazanılır; aynı anda tek iş yapılır.
- Amaç: toplam kârı en büyük yapmak.
- Açgözlü ölçüt: işleri kâra göre azalan sırayla ele al; her işi son tarihinden önceki en geç boş zaman dilimine koy; boş dilim yoksa işi atla.
- En geç dilimi seçmek, erken dilimleri son tarihi daha yakın işlere bırakır.
| İş | son tarih | kâr |
|---|---|---|
| 1 | 4 | 20 |
| 2 | 1 | 10 |
| 3 | 1 | 40 |
| 4 | 1 | 30 |
| 5 | 3 | 50 |
| 6 | 2 | 30 |
Zaman: sıralama O(n log n) + slot arama O(n · dmax) → en kötü O(n²). Union-Find ile slot arama neredeyse O(n)'e iner.
İş Sıralama · Kodu adım adım çalıştır
isSirala(is, 6): Kodu Adım Adım İzle
İşler kâra göre azalan sıralandı (eşit kârlı İş 4 ve İş 6 özgün sırasını korur). Tüm slotlar boş (−1).
İş 5 (son tarih 3, kâr 50): en geç uygun slot 3 boş → yerleştirildi. toplam = 50.
İş 3 (son tarih 1, kâr 40): en geç uygun slot 1 boş → yerleştirildi. toplam = 90.
İş 4 (son tarih 1, kâr 30): slot 1 dolu (İş 3) ve daha erken slot yok → iş atlanır.
İş 6 (son tarih 2, kâr 30): en geç uygun slot 2 boş → yerleştirildi. toplam = 120.
İş 1 (son tarih 4, kâr 20): en geç uygun slot 4 boş → yerleştirildi. toplam = 140.
İş 2 (son tarih 1, kâr 10): slot 1 dolu (İş 3) ve daha erken slot yok → iş atlanır.
Sonuç: slotlar 1–4 = İş 3, İş 6, İş 5, İş 1 → toplam kâr 140. İş 4 ve İş 2 yapılamaz.
static int isSirala(Is[] is, int n) { Arrays.sort(is, (a, b) -> b.kar - a.kar); int[] slot = new int[n]; Arrays.fill(slot, -1); int toplam = 0; for (int i = 0; i < n; i++) { int j = Math.min(n, is[i].sonTarih) - 1; for (; j >= 0; j--) { if (slot[j] == -1) { slot[j] = is[i].id; toplam += is[i].kar; break; } } } return toplam; }
Sırt Çantası
Kesirli Sırt Çantası (Fractional Knapsack)
Kapasite W = 50. Nesneler bölünebiliyorsa açgözlü ölçüt: kâr / ağırlık oranı en büyük olanı al.
| Nesne | kâr | ağırlık | oran |
|---|---|---|---|
| 1 | 60 | 10 | 6 |
| 2 | 100 | 20 | 5 |
| 3 | 120 | 30 | 4 |
Kesirli: 1 + 2 tümüyle, 3'ün 20/30'u → 60 + 100 + 80 = 240 (en iyi). Zaman O(n log n).
En yüksek oranlı nesnenin her birimi, aynı ağırlıktaki başka her seçenekten en az onun kadar kâr getirir. En iyi çözüm bu nesneden alabileceği kadar almıyorsa, başka bir nesnenin aynı ağırlıktaki kısmı onunla değiştirilir ve kâr azalmaz (değiştirme argümanı).
Nesneler bölünemiyorsa (0/1 sırt çantası) oran ölçütü en iyiyi garanti etmez; o problem dinamik programlama ile çözülür.
Sırt Çantası · Kodu adım adım çalıştır
kesirli(s, 50): Kod İzleme
Nesneler kâr/ağırlık oranına göre azalan sıralandı: 1 (6), 2 (5), 3 (4). Çanta boş, kalan kapasite 50.
Nesne 1: ağırlık 10 ≤ kalan 50 → tamamı sığar.
kalan −= 10 → 40, kar += 60 → 60.
Nesne 2: ağırlık 20 ≤ kalan 40 → tamamı sığar.
kalan −= 20 → 20, kar += 100 → 160.
Nesne 3: ağırlık 30 > kalan 20 → tamamı sığmaz, else dalı.
Kalan 20 birim için nesne 3'ün 20/30'u: 120 · 20/30 = 80 → kar = 240. Çanta doldu, break.
Sonuç 240: kesirli problemde en iyi. Maliyet sıralamadan gelir: O(n log n); tarama O(n).
static double kesirli(Nesne[] s, int W) { Arrays.sort(s, (a, b) -> Double.compare(b.oran(), a.oran())); double kar = 0; int kalan = W; for (Nesne x : s) { if (x.agirlik <= kalan) { // tamamı sığar kalan -= x.agirlik; kar += x.kar; } else { // kesirli parça kar += x.kar * (double) kalan / x.agirlik; break; } } return kar; }
Minimum Kapsayan Ağaç
Minimum Kapsayan Ağaç ve Kesim Özelliği
- Minimum kapsayan ağaç (minimum spanning tree, MST): bağlı, ağırlıklı, yönsüz bir çizgenin tüm düğümlerini en az toplam ağırlıkla bağlayan ağaç; V − 1 kenar.
- Kesim özelliği (cut property): düğümler S ve V − S diye ikiye ayrılırsa, bu kesimi geçen en hafif kenar bir MST'de yer alır.
- Kruskal, Prim ve Borůvka bu özelliği farklı kesimlerle kullanan açgözlü algoritmalardır: her eklenen kenar, bir kesimi geçen en hafif kenardır; bu yüzden hiçbir seçim geri alınmaz.
- Ağırlıklar farklıysa MST tektir.
Kesimi geçen kenarlar: 9, 4, 8, 11. En hafifi r–s (4) MST'dedir.
Minimum Kapsayan Ağaç
Kruskal, Prim ve Borůvka
| Kruskal | Prim | Borůvka | |
|---|---|---|---|
| Açgözlü seçim | tüm çizgedeki, döngü kurmayan en hafif kenar | ağaçtan çıkan en hafif kenar | her bileşenden çıkan en hafif kenar |
| Büyüme | orman → ağaç | tek ağaç, düğüm düğüm | orman, turlarla birleşme |
| Kullandığı kesim | kenarın uçlarını ayıran iki bileşen | ağaç ile geri kalan düğümler | her bileşen ile geri kalanı |
| Veri yapısı | sıralama + Union-Find | öncelikli kuyruk | Union-Find |
| Zaman | O(E log V) | O(E log V) (ikili heap) | O(E log V) |
| Uygun olduğu | seyrek çizge, kenar listesi | yoğun çizge, komşuluk | paralel / dağıtık |
Huffman Kodlama
Huffman Kodlama
- Kayıpsız veri sıkıştırma için kullanılır (David Huffman, 1952).
- Değişken uzunluklu kodlar kullanır: sık kullanılan karakterlere kısa, seyrek olanlara uzun kodlar atanır.
- Önek-özgür (prefix-free) kodlar üretir: hiçbir kod, başka bir kodun öneki olamaz → ayırıcı gerekmeden tek anlamlı çözülür.
- Açgözlü adım: en düşük frekanslı iki ağacı birleştir.
Kodlar a = 0, b = 01, c = 1 olsun. "01" → "ab" mi, "b" mi? Belirsiz: 0, 01'in önekidir.
Huffman kodları ikili ağacın yapraklarında durduğu için hiçbir kod diğerinin öneki olamaz.
Huffman Kodlama
Huffman Kodlama: Adımlar
- Adım 1: Her karakterin frekansı hesaplanır.
- Adım 2: Frekanslara göre bir Huffman ağacı oluşturulur: her karakter bir yaprak; en küçük iki frekanslı düğüm birleştirilerek yeni bir iç düğüm yapılır.
- Adım 3: Ağaçta her karaktere karşılık gelen kodlar atanır: sol kenar 0, sağ kenar 1.
- Adım 4: Veri, oluşturulan Huffman kodlarıyla kodlanır.
Örnek frekanslar (toplam 25 karakter):
| karakter | F | O | R | G | E | T |
|---|---|---|---|---|---|---|
| frekans | 2 | 3 | 4 | 4 | 5 | 7 |
n karakter için n − 1 birleştirme; öncelikli kuyruk ile O(n log n).
Huffman Kodlama · Kodu adım adım çalıştır
Huffman Ağacını Kur: Kodu Adım Adım İzle
Her karakter için frekansıyla bir yaprak oluşturulup öncelikli kuyruğa (min-heap) eklenir.
poll() iki kez: en küçük frekanslar F2 (sol) ve O3 (sağ).
Yeni iç düğüm 5 = 2 + 3; çocukları F2 ve O3, kuyruğa geri eklenir.
poll() iki kez: en küçük frekanslar G4 (sol) ve R4 (sağ). G4 ile R4 eşit; hangisinin önce çıkacağı heap'e bağlıdır.
Yeni iç düğüm 8 = 4 + 4; çocukları G4 ve R4, kuyruğa geri eklenir.
poll() iki kez: en küçük frekanslar (5) (sol) ve E5 (sağ). (5) ile E5 eşit; hangisinin önce çıkacağı heap'e bağlıdır, kod uzunlukları değişmez.
Yeni iç düğüm 10 = 5 + 5; çocukları (5) ve E5, kuyruğa geri eklenir.
poll() iki kez: en küçük frekanslar T7 (sol) ve (8) (sağ).
Yeni iç düğüm 15 = 7 + 8; çocukları T7 ve (8), kuyruğa geri eklenir.
poll() iki kez: en küçük frekanslar (10) (sol) ve (15) (sağ).
Yeni iç düğüm 25 = 10 + 15; çocukları (10) ve (15), kuyruğa geri eklenir. Kuyrukta tek düğüm kaldı: kök.
Döngü biter, kök (25) döndürülür. Sol kenar 0, sağ kenar 1: F=000, O=001, E=01, T=10, G=110, R=111.
static Dugum huffman(char[] h, int[] f) { PriorityQueue<Dugum> kuyruk = new PriorityQueue<>((a, b) -> a.f - b.f); for (int k = 0; k < h.length; k++) kuyruk.add(new Dugum(h[k], f[k])); while (kuyruk.size() > 1) { Dugum sol = kuyruk.poll(); Dugum sag = kuyruk.poll(); Dugum ata = new Dugum('\0', sol.f + sag.f); ata.sol = sol; ata.sag = sag; kuyruk.add(ata); } return kuyruk.poll(); }
Huffman Kodlama
Kodlar, Kodlama ve Çözme
| kar. | frekans | kod | uzunluk | bit |
|---|---|---|---|---|
| F | 2 | 000 | 3 | 6 |
| O | 3 | 001 | 3 | 9 |
| E | 5 | 01 | 2 | 10 |
| T | 7 | 10 | 2 | 14 |
| G | 4 | 110 | 3 | 12 |
| R | 4 | 111 | 3 | 12 |
| toplam | 63 | |||
- Sabit uzunluk: 6 karakter → 3 bit, 25 × 3 = 75 bit. Huffman: 63 bit (%16 kazanç, ortalama 2,52 bit).
- Kodlama: "FORTE" → 000 001 111 10 01 = 13 bit (sabit kodla 15).
- Çözme: kökten başla, 0 → sol, 1 → sağ; yaprağa gelince karakteri yaz ve köke dön:
1100110→ 110 | 01 | 10 → "GET". - Sol ve sağ çocuğun yeri keyfidir (ör. T sağ çocuk yapılabilir): kodlar değişir ama kod uzunlukları ve 63 bit değişmez.
Huffman Kodlama · Kodu adım adım çalıştır
Ağaçtan Kodları Üret: Kod İzleme
static void kodUret(Dugum d, String kod) { if (d.sol == null) { // yaprak System.out.println(d.h + " = " + kod); return; } kodUret(d.sol, kod + "0"); // sol: 0 kodUret(d.sag, kod + "1"); // sağ: 1 }
Huffman ağacının kökü (25) ile kodUret(kok, "") çağrıldı: kök yaprak değil, önce sol çocuğa "0" eklenerek inilir.
İç düğüm (10), kod = "0" (sol kenardan gelindi) → yaprak değil, sol çocuğa "00" ile inilir.
İç düğüm (5), kod = "00" (sol kenardan gelindi) → yaprak değil, sol çocuğa "000" ile inilir.
Yaprak F (frekans 2): yoldaki bitler yazılır → F = 000.
Yaprak O (frekans 3): yoldaki bitler yazılır → O = 001.
Yaprak E (frekans 5): yoldaki bitler yazılır → E = 01.
İç düğüm (15), kod = "1" (sağ kenardan gelindi) → yaprak değil, sol çocuğa "10" ile inilir.
Yaprak T (frekans 7): yoldaki bitler yazılır → T = 10.
İç düğüm (8), kod = "11" (sağ kenardan gelindi) → yaprak değil, sol çocuğa "110" ile inilir.
Yaprak G (frekans 4): yoldaki bitler yazılır → G = 110.
Yaprak R (frekans 4): yoldaki bitler yazılır → R = 111. Tüm yapraklar yazıldı: sık karakterler (E, T) 2 bit, seyrekler 3 bit. Ağaç bir kez gezilir: O(n).
Huffman Kodlama
Huffman Neden En İyi?
En düşük frekanslı iki karakter x ve y'nin, en derin seviyede kardeş olduğu bir en iyi kod ağacı vardır: daha derindeki bir karakterle yer değiştirmek toplam biti artırmaz (değiştirme argümanı).
x ve y, frekansı f(x) + f(y) olan tek bir "z" karakteri yapılırsa, küçülen problemin en iyi ağacından z açılarak asıl problemin en iyi ağacı elde edilir.
Toplam bit = Σ frekans(c) · derinlik(c). Huffman, önek-özgür kodlar arasında bunu en küçük yapar.
Frekans sayımı O(m) (m = metin uzunluğu); ağaç O(n log n); kodlama O(m). Ağaç (ya da kod tablosu) da çözücüye gönderilmelidir.
Uygulamalar
Diğer Açgözlü Algoritmalar
Özet
Özet
| Problem | Açgözlü ölçüt | Zaman | En iyi mi? |
|---|---|---|---|
| Para üstü | sığan en büyük para | O(k + adet) | kanonikse |
| Etkinlik seçimi | en erken biten | O(n log n) | evet |
| İş sıralama | en kârlı iş, en geç boş slot | O(n²) | evet |
| Kesirli sırt çantası | en büyük kâr/ağırlık | O(n log n) | evet (0/1: hayır) |
| MST | kesimi geçen en hafif kenar (Kruskal, Prim, Borůvka) | O(E log V) | evet |
| Huffman | en düşük frekanslı iki ağaç | O(n log n) | evet |
Özet
Etkileşimli Simülatörler
Özet
Sık Yapılan Hatalar
Özet
Örnek Problemler
- {1, 5, 10, 25} paralarıyla 63 açgözlü olarak kaç parayla ödenir? {1, 7, 10} ile 14 için açgözlü ve en iyi sonuç nedir?
- Etkinlikler (s, f): (1,4), (3,5), (0,6), (5,7), (3,9), (5,9), (6,10), (8,11), (8,12), (2,14), (12,16). En fazla kaç etkinlik seçilir?
- Frekanslar a:5, b:9, c:12, d:13, e:16, f:45. Huffman kod uzunluklarını ve toplam biti bulun.
- Düğümler {A, B} ve {C, D} olarak ayrılıyor; kesimi geçen kenarlar A–C 5, B–C 3, B–D 4. Hangi kenar kesinlikle her MST'dedir?
- Kruskal, Prim ve Borůvka aynı çizgede farklı kapsayan ağaçlar bulabilir mi?
Cevapları göster
- 63 = 25 + 25 + 10 + 1 + 1 + 1 → 6 para. 14: açgözlü 10 + 1 + 1 + 1 + 1 = 5 para; en iyi 7 + 7 = 2 para.
- Bitişe göre sırala, en erken biteni al: (1,4), (5,7), (8,11), (12,16) → 4 etkinlik.
- f = 1, c = d = e = 3, a = b = 4 bit (ör. f=0, c=100, d=101, a=1100, b=1101, e=111) → 45 + 36 + 39 + 48 + 20 + 36 = 224 bit (sabit 3 bitle 300).
- Kesimi geçen en hafif kenar B–C (3); ağırlıklar farklı olduğundan kesim özelliğine göre her MST'de bulunur.
- Ağırlıklar farklıysa hayır: MST tektir. Eşit ağırlıklar varsa farklı ağaçlar bulabilirler, ama toplam ağırlıkları aynıdır.
Özet
Alıştırmalar
- İşler (id, son tarih, kâr): (a,2,100), (b,1,19), (c,2,27), (d,1,25), (e,3,15). Açgözlü iş sıralamasını uygulayın.
- W = 10, nesneler (kâr, ağırlık): (10,5), (40,4), (30,6), (50,3). Kesirli sırt çantasının değeri nedir?
- "ABRACADABRA" için Huffman kodlaması kaç bit tutar? Sabit uzunluklu kodla karşılaştırın.
- Kruskal'ın tüm kenarlar aynı ağırlıkta olduğunda ne ürettiğini ve O(E log V) maliyetinin hangi adımdan geldiğini açıklayın.
- Açgözlü para üstünün en iyi sonuç vermediği, en küçük tutarlı bir para sistemi bulun ve gerekçelendirin.
Cevaplar / ipuçları
- Kâr sırası a, c, d, b, e: a → slot 2, c → slot 1, d ve b sığmaz, e → slot 3 → c, a, e; kâr 142.
- Oranlar 2, 10, 5, 16,7: 50 (3 kg) + 40 (4 kg) + 30'un 3/6'sı (15) = 105.
- A5 B2 R2 C1 D1 → A 1 bit, diğerleri 3 bit (eşitlik seçimine göre 2–4 bit) → 23 bit; sabit 3 bitle 33 bit.
- Herhangi bir kapsayan ağaç (her ağaç en küçüktür); maliyetin baskın kısmı kenarların sıralanması, Union-Find neredeyse doğrusal.
- {1, 3, 4} ve 6: açgözlü 4+1+1, en iyi 3+3. Kanonik sistemlerde (ör. 1, 5, 10, 25) her büyük para küçükleri en iyi biçimde "kapsar".
Özet
Kaynaklar
- Cormen, Leiserson, Rivest ve Stein, Introduction to Algorithms, 4. baskı, 2022 (Bölüm 15: Greedy Algorithms; Bölüm 21: MST).
- Kleinberg ve Tardos, Algorithm Design, 2006 (Bölüm 4: Greedy Algorithms).
- Sedgewick ve Wayne, Algorithms, 4. baskı, 2011 (4.3 MST, 5.5 Data Compression).
- D. A. Huffman, "A Method for the Construction of Minimum-Redundancy Codes", Proceedings of the IRE 40(9), 1952.
- J. B. Kruskal, "On the Shortest Spanning Subtree of a Graph and the Traveling Salesman Problem", Proc. AMS 7(1), 1956.