Bölüm 12: 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.
Borůvka ve Kruskal algoritmalarını Union-Find ile uygulamak, adım adım izlemek ve O(E log V) karmaşıklığını gerekçelendirmek.
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 dönüş (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 · 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ü, Dinamik Programlama, Böl ve Fethet
| Açgözlü | Dinamik programlama | Böl ve fethet | |
|---|---|---|---|
| Karar | her adımda tek, yerel en iyi seçim; geri alınmaz | tüm seçenekler denenir, alt sonuçlar tabloda | problem bağımsız parçalara bölünür |
| Alt problemler | seçimden sonra tek alt problem kalır | örtüşen alt problemler | örtüşmeyen alt problemler |
| Doğruluk | açgözlü seçim özelliği gerekli | optimal alt yapı yeterli | birleştirme adımına bağlı |
| Maliyet | genelde O(n log n) | genelde polinom, daha yavaş, ek bellek | yineleme bağıntısı ile |
| Örnek | Kruskal, Huffman, etkinlik seçimi | 0/1 sırt çantası, genel para üstü | birleştirmeli sıralama |
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).
Nesneler bölünemiyorsa aynı ölçüt 1 ve 2'yi alır: 160 (30 kg; 3 sığmaz).
En iyi çözüm 2 + 3 = 220. 0/1 sırt çantası açgözlü seçim özelliğini sağlamaz; dinamik programlama ile O(n · W) zamanda çözülür.
En Küçük Kapsayan Ağaç
En Küçük Kapsayan Ağaç ve Kesme Özelliği
- En küçük 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.
- Kesme özelliği (cut property): düğümler S ve V − S diye ikiye ayrılırsa, bu kesmeyi geçen en hafif kenar bir MST'de yer alır.
- Borůvka, Kruskal ve Prim bu özelliği farklı biçimlerde kullanan açgözlü algoritmalardır.
- Ağırlıklar farklıysa MST tektir.
Kesmeyi geçen kenarlar: 9, 4, 8, 11. En hafifi r–s (4) MST'dedir.
Borůvka
Borůvka'nın Algoritması
- Çizge teorisinde kullanılan bir en küçük kapsayan ağaç algoritmasıdır (Otakar Borůvka, 1926).
- Başlangıçta her düğüm bir alt ağaç (bileşen) oluşturur.
- Her turda, her alt ağacın dışarı çıkan en az maliyetli kenarı bulunur ve bu kenarlar eklenerek alt ağaçlar birleştirilir.
- Açgözlü: her bileşen için yerel olarak en iyi kenarı seçer (kesme özelliği: bileşen ile geri kalan arasındaki en hafif kenar).
- Her turda en az bir kenar eklenir ve ağaç büyür.
- Adım 1: Her düğüm bir alt ağaç olarak başlar.
- Adım 2: Her alt ağaçtan, dışarı çıkan en az maliyetli kenar seçilir.
- Adım 3: Seçilen kenarlarla alt ağaçlar birleştirilir.
- Adım 4: Alt ağaçlar tek bir ağaçta toplanana kadar tekrarlanır.
Borůvka · Adım adım
Borůvka Adım Adım
Başlangıç: her düğüm tek başına bir alt ağaç → 12 bileşen, 0 kenar.
Tur 1 · düğüm A: dışarı çıkan en ucuz kenar A–C (6).
Tur 1 · düğüm B: dışarı çıkan en ucuz kenar B–D (1).
Tur 1 · düğüm C: dışarı çıkan en ucuz kenar A–C (6) — bu kenar zaten seçilmişti.
Tur 1 · düğüm D: dışarı çıkan en ucuz kenar B–D (1) — bu kenar zaten seçilmişti.
Tur 1 · düğüm E: dışarı çıkan en ucuz kenar E–F (2).
Tur 1 · düğüm F: dışarı çıkan en ucuz kenar E–F (2) — bu kenar zaten seçilmişti.
Tur 1 · düğüm G: dışarı çıkan en ucuz kenar G–I (5).
Tur 1 · düğüm H: dışarı çıkan en ucuz kenar G–H (15).
Tur 1 · düğüm I: dışarı çıkan en ucuz kenar G–I (5) — bu kenar zaten seçilmişti.
Tur 1 · düğüm J: dışarı çıkan en ucuz kenar J–L (4).
Tur 1 · düğüm K: dışarı çıkan en ucuz kenar G–K (10).
Tur 1 · düğüm L: dışarı çıkan en ucuz kenar J–L (4) — bu kenar zaten seçilmişti.
Tur 1 sonu: seçilen 7 farklı kenar eklendi → 5 bileşen: {A,C}, {B,D}, {E,F}, {G,H,I,K}, {J,L}. Ağırlık: 43.
Tur 2 · bileşen {A, C}: dışarı çıkan en ucuz kenar B–C (7).
Tur 2 · bileşen {B, D}: dışarı çıkan en ucuz kenar D–F (3).
Tur 2 · bileşen {E, F}: dışarı çıkan en ucuz kenar D–F (3) — bu kenar zaten seçilmişti.
Tur 2 · bileşen {G, H, I, K}: dışarı çıkan en ucuz kenar K–L (12).
Tur 2 · bileşen {J, L}: dışarı çıkan en ucuz kenar K–L (12) — bu kenar zaten seçilmişti.
Tur 2 sonu: seçilen 3 farklı kenar eklendi → 2 bileşen: {A,B,C,D,E,F}, {G,H,I,J,K,L}. Ağırlık: 65.
Tur 3 · bileşen {A, B, C, D, E, F}: dışarı çıkan en ucuz kenar E–J (18).
Tur 3 · bileşen {G, H, I, J, K, L}: dışarı çıkan en ucuz kenar E–J (18) — bu kenar zaten seçilmişti.
Tur 3 sonu: tek bileşen kaldı. MST: 11 kenar (V − 1), toplam ağırlık 83. Yalnızca 3 tur yetti.
Borůvka
Borůvka: Java Kodu
static int boruvka(int n, int[][] kenar) { AyrikKume kume = new AyrikKume(n); int bilesen = n, toplam = 0; while (bilesen > 1) { int[] enUcuz = new int[n]; // kök -> kenar no Arrays.fill(enUcuz, -1); for (int i = 0; i < kenar.length; i++) { int a = kume.bul(kenar[i][0]), b = kume.bul(kenar[i][1]); if (a == b) continue; // bileşen içi kenar int w = kenar[i][2]; if (enUcuz[a] < 0 || w < kenar[enUcuz[a]][2]) enUcuz[a] = i; if (enUcuz[b] < 0 || w < kenar[enUcuz[b]][2]) enUcuz[b] = i; } for (int c = 0; c < n; c++) { // seçilenleri ekle if (enUcuz[c] < 0) continue; int[] k = kenar[enUcuz[c]]; if (kume.bul(k[0]) != kume.bul(k[1])) { // iki kez eklenmesin kume.birlestir(k[0], k[1]); toplam += k[2]; bilesen--; } } } return toplam; }
Borůvka
Borůvka: Analiz
Her bileşen en az bir başka bileşenle birleşir; bu yüzden her turda bileşen sayısı en az yarıya iner. Örnekte 12 → 5 → 2 → 1: turlarda 7, 3 ve 1 kenar eklendi.
Her kenar bir kez taranır (Union-Find işlemleri neredeyse sabit). Toplam: O(E log V).
İki bileşen eşit ağırlıklı farklı kenarlar seçerse döngü oluşabilir. Eşitlik tutarlı bozulmalı: örn. (ağırlık, kenar no) çifti karşılaştırılır.
Bir turdaki bileşenler birbirinden bağımsız en ucuz kenarını arar; bu yüzden paralel ve dağıtık MST algoritmalarının temelidir.
Union-Find
Union-Find (Ayrık Kümeler) Veri Yapısı
class AyrikKume { int[] ebeveyn, rutbe; AyrikKume(int n) { ebeveyn = new int[n]; rutbe = new int[n]; for (int i = 0; i < n; i++) ebeveyn[i] = i; } int bul(int x) { // yol sıkıştırma if (ebeveyn[x] != x) ebeveyn[x] = bul(ebeveyn[x]); return ebeveyn[x]; } void birlestir(int x, int y) { int kx = bul(x), ky = bul(y); if (kx == ky) return; if (rutbe[kx] < rutbe[ky]) { int t = kx; kx = ky; ky = t; } ebeveyn[ky] = kx; // rütbeye göre if (rutbe[kx] == rutbe[ky]) rutbe[kx]++; } }
- Amaç: küme birleştirme ve küme bulma işlemlerini hızlı yapmak.
- Union(x, y) →
birlestir: x ve y'nin kümelerini birleştirir (bir kök diğerinin çocuğu olur). - Find(x) →
bul: x'in bulunduğu kümenin kök elemanını bulur. - Her küme bir ağaçtır;
ebeveyn[kök] = kök. - Yol sıkıştırma + rütbeye (rank) göre birleştirme ile işlem başına amortize O(α(n)); α pratikte ≤ 4.
Kruskal
Kruskal Algoritması
- Amaç: ağırlıklı, bağlı bir çizgenin en küçük kapsayan ağacını bulmak.
- Açgözlü yaklaşım: her adımda kalan kenarlar arasından en küçük ağırlıklı olanı seçerek ilerler.
- Döngü denetimi Union-Find ile yapılır: uçların kökleri aynıysa kenar döngü oluşturur.
- Tüm kenarları ağırlıklarına göre sırala.
- En küçük ağırlıklı kenarı seç.
- Kenar mevcut ormanda döngü oluşturmuyorsa ekle (iki kümeyi birleştir), oluşturuyorsa at.
- V − 1 kenar eklenene (ya da kenarlar bitene) kadar tekrarla.
Kruskal · Kodu adım adım çalıştır
kruskal(5, kenar): Kodu Adım Adım İzle
Kenarlar ağırlığa göre sıralandı: 1, 2, 3, 4, 5, 6, 7. Her düğüm kendi kümesinin kökü (ebeveyn[x] = x).
Kenar a–c (1): bul(a) = a, bul(c) = c → kökler farklı, döngü oluşmaz.
birlestir(a, c): ebeveyn[c] = a. Kenar ağaca eklendi; toplam = 1, sayac = 1.
Kenar d–e (2): bul(d) = d, bul(e) = e → kökler farklı, döngü oluşmaz.
birlestir(d, e): ebeveyn[e] = d. Kenar ağaca eklendi; toplam = 3, sayac = 2.
Kenar a–b (3): bul(a) = a, bul(b) = b → kökler farklı, döngü oluşmaz.
birlestir(a, b): ebeveyn[b] = a. Kenar ağaca eklendi; toplam = 6, sayac = 3.
Kenar b–c (4): bul(b) = bul(c) = a → aynı küme; eklenirse döngü oluşur, atlanır.
Kenar b–d (5): bul(b) = a, bul(d) = d → kökler farklı, döngü oluşmaz.
birlestir(b, d): ebeveyn[d] = a. Kenar ağaca eklendi; toplam = 11, sayac = 4. sayac = n − 1 → döngüden çıkılır.
Sonuç: a–c, d–e, a–b, b–d → toplam 11. c–d (6) ve c–e (7) hiç incelenmedi.
static int kruskal(int n, int[][] kenar) { Arrays.sort(kenar, (x, y) -> x[2] - y[2]); AyrikKume kume = new AyrikKume(n); int toplam = 0, sayac = 0; for (int[] k : kenar) { int u = k[0], v = k[1], w = k[2]; if (kume.bul(u) != kume.bul(v)) { kume.birlestir(u, v); toplam += w; sayac++; if (sayac == n - 1) break; } } return toplam; }
Kruskal
Örnek: Altı Düğümlü Çizge
Kenarlar: (A,B) 2, (A,D) 4, (B,C) 1, (B,D) 3, (C,E) 2, (D,E) 1, (D,F) 4, (E,F) 3. Sıralı işleme:
| kenar | w | karar |
|---|---|---|
| B–C | 1 | eklendi (toplam 1) |
| D–E | 1 | eklendi (toplam 2) |
| A–B | 2 | eklendi (toplam 4) |
| C–E | 2 | eklendi (toplam 6) |
| B–D | 3 | reddedildi: döngü |
| E–F | 3 | eklendi (toplam 9) |
| A–D | 4 | incelenmez (5 kenar tamam) |
| D–F | 4 | incelenmez (5 kenar tamam) |
Ağaç: B–C → D–E → A–B → C–E → E–F, toplam 9 (5 kenar = V − 1).
Kruskal
Kruskal, Prim ve Borůvka
| Kruskal | Prim | Borůvka | |
|---|---|---|---|
| Açgözlü seçim | tüm çizgedeki en hafif güvenli 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 |
| Veri yapısı | sıralama + Union-Find | öncelik kuyruğu | Union-Find |
| Zaman | O(E log V) | O(E log V) (ikili yığın) | 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; öncelik kuyruğu 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 öncelik kuyruğuna (min-yığın) 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ğı yığına 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ğı yığına 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
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) |
| Borůvka | her bileşenin en ucuz dış kenarı | O(E log V) | evet |
| Kruskal | döngü kurmayan en hafif kenar | O(E log V) | evet |
| Huffman | en düşük frekanslı iki ağaç | O(n log n) | evet |
| Union-Find | bul / birlestir, yol sıkıştırma + rütbeye göre birleştirme → amortize O(α(n)) | ||
Özet
Etkileşimli Simülatörler
Özet
Sık Yapılan Hatalar
ebeveyn[u] != ebeveyn[v] yetmez; kökler karşılaştırılmalı: bul(u) != bul(v).Ö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.
- Borůvka örnek çizgesine Kruskal uygulayın: kenarlar hangi sırayla eklenir?
- V = 1000 düğümlü bir çizgede Borůvka en fazla kaç tur sürer?
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).
- B–D 1, E–F 2, D–F 3, J–L 4, G–I 5, A–C 6, B–C 7, G–K 10, K–L 12, G–H 15, E–J 18 → toplam 83 (Borůvka ile aynı).
- Bileşen sayısı her turda en az yarıya iner: ⌈log₂ 1000⌉ = 10 tur.
Ö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 19: Disjoint Sets; 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.
- O. Borůvka, "O jistém problému minimálním", Práce Moravské přírodovědecké společnosti 3, 1926.
- R. E. Tarjan, "Efficiency of a Good But Not Linear Set Union Algorithm", Journal of the ACM 22(2), 1975.