Bölüm 7: Açgözlü Algoritmalar

Algoritmalar
Açgözlü yaklaşımPara üstüEtkinlik seçimiİş sıralama Kesirli sırt çantasıMST ve kesim özelliğiHuffman kodlama
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Yaklaşım

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.

Klasik problemler

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.

Kapsayan ağaçlar

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.

Veri sıkıştırma

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.
Kazanç ve risk

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;
}
  1. Uygun çözümler kümesini belirle (hangi seçimler kısıtları bozmaz?).
  2. Bir açgözlü ölçüt seç: en ucuz, en erken biten, en kârlı, en sık…
  3. Ölçüte göre yerel olarak en iyi adayı seç; uygunsa çözüme ekle.
  4. Çözüm tamamlanana kadar tekrarla.
sıralı adaylara1✓a2✓a3✗a4✓a5✗a6✓tek geçiş: her aday bir kez incelenir, karar geri alınmaz →çözüm = {a1, a2, a4, a6}
Çoğu açgözlü algoritmanın maliyeti sıralama ya da öncelikli kuyruk işlemlerinden gelir: tipik olarak O(n log n).

Açgözlü Yaklaşım

Ne Zaman Doğru Sonuç Verir?

Açgözlü seçim özelliği

(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 alt yapı

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

Kanıt tekniği: değiştirme argümanı

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

İki özellik de sağlanmıyorsa açgözlü algoritma yalnızca bir sezgisel (heuristic) yöntemdir: hızlıdır ama sonucu en iyiden kötü olabilir.

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;
}
Her zaman doğru mu?

{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;
}
para431tutarkalan 10adet0

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.

para431tutarkalan 10adet0

p = 4: 10 ≥ 4 → bu paradan alınabilir.

para431tutar4kalan 6adet1

tutar −= 4 → 6, adet = 1. Döngü sürer.

para431tutar44kalan 2adet2

tutar −= 4 → 2, adet = 2. 2 < 4: while biter.

para431tutar44kalan 2adet2

p = 3: 2 < 3 → 3'lük para hiç kullanılmaz, sıradaki paraya geçilir.

para431tutar44kalan 2adet2

p = 1: 2 ≥ 1 → bu paradan alınabilir.

para431tutar441kalan 1adet3

tutar −= 1 → 1, adet = 3. Döngü sürer.

para431tutar4411kalan 0adet4

tutar −= 1 → 0, adet = 4. 0 < 1: while biter.

para431tutar4411kalan 0adet4en iyi4333 para

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

madeni paralar50251051seçilenlerkalan tutar68para sayısı: 0

Örnek 1: paralar {50, 25, 10, 5, 1}, tutar 68. En büyük paradan başla.

madeni paralar50251051seçilenler50kalan tutar18para sayısı: 1

Kalan tutara sığan en büyük para 50 → kalan 68 − 50 = 18.

madeni paralar50251051seçilenler5010kalan tutar8para sayısı: 2

Kalan tutara sığan en büyük para 10 → kalan 18 − 10 = 8.

madeni paralar50251051seçilenler50105kalan tutar3para sayısı: 3

Kalan tutara sığan en büyük para 5 → kalan 8 − 5 = 3.

madeni paralar50251051seçilenler501051kalan tutar2para sayısı: 4

Kalan tutara sığan en büyük para 1 → kalan 3 − 1 = 2.

madeni paralar50251051seçilenler5010511kalan tutar1para sayısı: 5

Kalan tutara sığan en büyük para 1 → kalan 2 − 1 = 1.

madeni paralar50251051seçilenler50105111kalan tutar0para sayısı: 6

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

madeni paralar431seçilenlerkalan tutar6para sayısı: 0

Örnek 2: paralar {4, 3, 1}, tutar 6. En büyük paradan başla.

madeni paralar431seçilenler4kalan tutar2para sayısı: 1

Kalan tutara sığan en büyük para 4 → kalan 6 − 4 = 2.

madeni paralar431seçilenler41kalan tutar1para sayısı: 2

Kalan tutara sığan en büyük para 1 → kalan 2 − 1 = 1.

madeni paralar431seçilenler411kalan tutar0para sayısı: 3

Kalan tutara sığan en büyük para 1 → kalan 1 − 1 = 0.

madeni paralar431seçilenler33kalan tutar0para sayısı: 2en iyi: 2 para

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
Kararher adımda tek, yerel en iyi seçim; geri alınmazsabit bir kuralla bölünür; seçim yapılmaz
Alt problemlerseçimden sonra tek alt problem kalırbirbirinden bağımsız birkaç alt problem
Doğrulukaçgözlü seçim özelliği + optimal alt yapı kanıtlanmalıbirleştirme adımının doğruluğuna bağlı
Maliyetgenelde O(n log n): sıralama + tek geçişözyineleme bağıntısı ve ana teoremle
Örneketkinlik seçimi, Kruskal, Huffmanbirleştirme sıralaması, Karatsuba
İkisi de problemi küçültür: böl ve fethet bütün parçaları çözüp birleştirir; açgözlü yaklaşım tek bir seçim yapar ve geriye kalan tek alt problemle devam eder.

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.
Etkinlik12345678910
başlangıç12465803710
bitiş35687924912
Zaman: sıralama O(n log n) + tek geçiş O(n). Etkinlikler zaten bitişe göre sıralıysa O(n).

Etkinlik Seçimi · Kodu adım adım çalıştır

sec(e): Kodu Adım Adım İzle

024681012#7#1#8#2#3#5#4#6#9#10

Etkinlikler bitiş zamanına göre sıralandı (yukarıdan aşağı). Kesikli çizgi: son seçilenin bitişi.

024681012#7#1#8#2#3#5#4#6#9#10

En erken biten etkinlik (#7, [0, 2)) her zaman seçilir.

024681012#7#1#8#2#3#5#4#6#9#10

#1 [1, 3): başlangıç 1 < 2 → çakışıyor, atlandı.

024681012#7#1#8#2#3#5#4#6#9#10

#8 [3, 4): başlangıç 3 ≥ 2 → seçildi, i = 2.

024681012#7#1#8#2#3#5#4#6#9#10

#2 [2, 5): başlangıç 2 < 4 → çakışıyor, atlandı.

024681012#7#1#8#2#3#5#4#6#9#10

#3 [4, 6): başlangıç 4 ≥ 4 → seçildi, i = 4.

024681012#7#1#8#2#3#5#4#6#9#10

#5 [5, 7): başlangıç 5 < 6 → çakışıyor, atlandı.

024681012#7#1#8#2#3#5#4#6#9#10

#4 [6, 8): başlangıç 6 ≥ 6 → seçildi, i = 6.

024681012#7#1#8#2#3#5#4#6#9#10

#6 [8, 9): başlangıç 8 ≥ 8 → seçildi, i = 7.

024681012#7#1#8#2#3#5#4#6#9#10

#9 [7, 9): başlangıç 7 < 9 → çakışıyor, atlandı.

024681012#7#1#8#2#3#5#4#6#9#10

#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 erken başlayanaçgözlü 1 · en iyi 3En kısa sürenaçgözlü 1 · en iyi 20246810

"En az çakışan" ölçütünün de karşı örneği vardır.

Değiştirme argümanı

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 tarihkâr
1420
2110
3140
4130
5350
6230

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.

İşleri son tarihe göre sıralamak yanlıştır: (kâr 10, son tarih 1) ve (kâr 40, son tarih 1) için son tarih sırası 10, kâr sırası 40 kazanır. Kâra göre azalan sıra optimaldir: olurlu iş kümeleri bir matroid oluşturur.

İş Sıralama · Kodu adım adım çalıştır

isSirala(is, 6): Kodu Adım Adım İzle

kâra göre sıralı işlerİş 5t ≤ 3kâr 50İş 3t ≤ 1kâr 40İş 4t ≤ 1kâr 30İş 6t ≤ 2kâr 30İş 1t ≤ 4kâr 20İş 2t ≤ 1kâr 10slot (zaman dilimi)1—2—3—4—5—6—

İş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).

kâra göre sıralı işlerİş 5t ≤ 3kâr 50İş 3t ≤ 1kâr 40İş 4t ≤ 1kâr 30İş 6t ≤ 2kâr 30İş 1t ≤ 4kâr 20İş 2t ≤ 1kâr 10slot (zaman dilimi)1—2—3İş 54—5—6—

İş 5 (son tarih 3, kâr 50): en geç uygun slot 3 boş → yerleştirildi. toplam = 50.

kâra göre sıralı işlerİş 5t ≤ 3kâr 50İş 3t ≤ 1kâr 40İş 4t ≤ 1kâr 30İş 6t ≤ 2kâr 30İş 1t ≤ 4kâr 20İş 2t ≤ 1kâr 10slot (zaman dilimi)1İş 32—3İş 54—5—6—

İş 3 (son tarih 1, kâr 40): en geç uygun slot 1 boş → yerleştirildi. toplam = 90.

kâra göre sıralı işlerİş 5t ≤ 3kâr 50İş 3t ≤ 1kâr 40İş 4t ≤ 1kâr 30İş 6t ≤ 2kâr 30İş 1t ≤ 4kâr 20İş 2t ≤ 1kâr 10slot (zaman dilimi)1İş 32—3İş 54—5—6—

İş 4 (son tarih 1, kâr 30): slot 1 dolu (İş 3) ve daha erken slot yok → iş atlanır.

kâra göre sıralı işlerİş 5t ≤ 3kâr 50İş 3t ≤ 1kâr 40İş 4t ≤ 1kâr 30İş 6t ≤ 2kâr 30İş 1t ≤ 4kâr 20İş 2t ≤ 1kâr 10slot (zaman dilimi)1İş 32İş 63İş 54—5—6—

İş 6 (son tarih 2, kâr 30): en geç uygun slot 2 boş → yerleştirildi. toplam = 120.

kâra göre sıralı işlerİş 5t ≤ 3kâr 50İş 3t ≤ 1kâr 40İş 4t ≤ 1kâr 30İş 6t ≤ 2kâr 30İş 1t ≤ 4kâr 20İş 2t ≤ 1kâr 10slot (zaman dilimi)1İş 32İş 63İş 54İş 15—6—

İş 1 (son tarih 4, kâr 20): en geç uygun slot 4 boş → yerleştirildi. toplam = 140.

kâra göre sıralı işlerİş 5t ≤ 3kâr 50İş 3t ≤ 1kâr 40İş 4t ≤ 1kâr 30İş 6t ≤ 2kâr 30İş 1t ≤ 4kâr 20İş 2t ≤ 1kâr 10slot (zaman dilimi)1İş 32İş 63İş 54İş 15—6—

İş 2 (son tarih 1, kâr 10): slot 1 dolu (İş 3) ve daha erken slot yok → iş atlanır.

kâra göre sıralı işlerİş 5t ≤ 3kâr 50İş 3t ≤ 1kâr 40İş 4t ≤ 1kâr 30İş 6t ≤ 2kâr 30İş 1t ≤ 4kâr 20İş 2t ≤ 1kâr 10slot (zaman dilimi)1İş 32İş 63İş 54İş 15—6—

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.

Nesnekârağırlıkoran
160106
2100205
3120304

Kesirli: 1 + 2 tümüyle, 3'ün 20/30'u → 60 + 100 + 80 = 240 (en iyi). Zaman O(n log n).

Neden en iyi?

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.

kesirli123 · 20/30kâr 2400W = 50

Sırt Çantası · Kodu adım adım çalıştır

kesirli(s, 50): Kod İzleme

nesnekârağırlıkoran16010621002053120304çanta W = 50kalan = 50kar = 0

Nesneler kâr/ağırlık oranına göre azalan sıralandı: 1 (6), 2 (5), 3 (4). Çanta boş, kalan kapasite 50.

nesnekârağırlıkoran16010621002053120304çanta W = 50kalan = 50kar = 0

Nesne 1: ağırlık 10 ≤ kalan 50 → tamamı sığar.

nesnekârağırlıkoran16010621002053120304çanta W = 501kalan = 40kar = 60

kalan −= 10 → 40, kar += 60 → 60.

nesnekârağırlıkoran16010621002053120304çanta W = 501kalan = 40kar = 60

Nesne 2: ağırlık 20 ≤ kalan 40 → tamamı sığar.

nesnekârağırlıkoran16010621002053120304çanta W = 5012kalan = 20kar = 160

kalan −= 20 → 20, kar += 100 → 160.

nesnekârağırlıkoran16010621002053120304çanta W = 5012kalan = 20kar = 160

Nesne 3: ağırlık 30 > kalan 20 → tamamı sığmaz, else dalı.

nesnekârağırlıkoran16010621002053120304çanta W = 5012320/30kalan = 0kar = 240

Kalan 20 birim için nesne 3'ün 20/30'u: 120 · 20/30 = 80 → kar = 240. Çanta doldu, break.

nesnekârağırlıkoran16010621002053120304çanta W = 5012320/30kalan = 0kar = 240

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.
43562794811pqrstuSV − S

Kesimi geçen kenarlar: 9, 4, 8, 11. En hafifi r–s (4) MST'dedir.

Minimum Kapsayan Ağaç

Kruskal, Prim ve Borůvka

KruskalPrimBorůvka
Açgözlü seçimtüm çizgedeki, döngü kurmayan en hafif kenarağaçtan çıkan en hafif kenarher bileşenden çıkan en hafif kenar
Büyümeorman → ağaçtek ağaç, düğüm düğümorman, turlarla birleşme
Kullandığı kesimkenarın uçlarını ayıran iki bileşenağaç ile geri kalan düğümlerher bileşen ile geri kalanı
Veri yapısısıralama + Union-Findöncelikli kuyrukUnion-Find
ZamanO(E log V)O(E log V) (ikili heap)O(E log V)
Uygun olduğuseyrek çizge, kenar listesiyoğun çizge, komşulukparalel / dağıtık
Üçü de aynı çizgede aynı MST'yi bulur (ağırlıklar farklıysa MST tektir); farkları kenarları ele alma sırasında ve gereken veri yapısındadır.

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.
Önek-özgür neden önemli?

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.

011acb✗ a, b'nin yolunda0101cab✓ hepsi yaprak: 00, 01, 1
Kullanım: ZIP/DEFLATE, gzip, PNG, JPEG ve MP3'ün entropi kodlama aşaması.

Huffman Kodlama

Huffman Kodlama: Adımlar

  1. Adım 1: Her karakterin frekansı hesaplanır.
  2. 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.
  3. Adım 3: Ağaçta her karaktere karşılık gelen kodlar atanır: sol kenar 0, sağ kenar 1.
  4. Adım 4: Veri, oluşturulan Huffman kodlarıyla kodlanır.

Örnek frekanslar (toplam 25 karakter):

karakterFORGET
frekans234457

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

F2O3E5T7G4R4

Her karakter için frekansıyla bir yaprak oluşturulup öncelikli kuyruğa (min-heap) eklenir.

F2O3E5T7G4R4

poll() iki kez: en küçük frekanslar F2 (sol) ve O3 (sağ).

F2O3E5T7G4R45

Yeni iç düğüm 5 = 2 + 3; çocukları F2 ve O3, kuyruğa geri eklenir.

F2O3E5T7G4R45

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.

F2O3E5T7G4R458

Yeni iç düğüm 8 = 4 + 4; çocukları G4 ve R4, kuyruğa geri eklenir.

F2O3E5T7G4R458

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.

F2O3E5T7G4R45810

Yeni iç düğüm 10 = 5 + 5; çocukları (5) ve E5, kuyruğa geri eklenir.

F2O3E5T7G4R45810

poll() iki kez: en küçük frekanslar T7 (sol) ve (8) (sağ).

F2O3E5T7G4R4581015

Yeni iç düğüm 15 = 7 + 8; çocukları T7 ve (8), kuyruğa geri eklenir.

F2O3E5T7G4R4581015

poll() iki kez: en küçük frekanslar (10) (sol) ve (15) (sağ).

F2O3E5T7G4R458101525

Yeni iç düğüm 25 = 10 + 15; çocukları (10) ve (15), kuyruğa geri eklenir. Kuyrukta tek düğüm kaldı: kök.

0101010101F2O3E5T7G4R458101525

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.frekanskoduzunlukbit
F200036
O300139
E501210
T710214
G4110312
R4111312
toplam63
  • 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
}
01010101012510155ET8FOGR

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.

01010101012510155ET8FOGR

İç düğüm (10), kod = "0" (sol kenardan gelindi) → yaprak değil, sol çocuğa "00" ile inilir.

01010101012510155ET8FOGR

İç düğüm (5), kod = "00" (sol kenardan gelindi) → yaprak değil, sol çocuğa "000" ile inilir.

01010101012510155ET8F000OGR

Yaprak F (frekans 2): yoldaki bitler yazılır → F = 000.

01010101012510155ET8F000O001GR

Yaprak O (frekans 3): yoldaki bitler yazılır → O = 001.

01010101012510155E01T8F000O001GR

Yaprak E (frekans 5): yoldaki bitler yazılır → E = 01.

01010101012510155E01T8F000O001GR

İç düğüm (15), kod = "1" (sağ kenardan gelindi) → yaprak değil, sol çocuğa "10" ile inilir.

01010101012510155E01T108F000O001GR

Yaprak T (frekans 7): yoldaki bitler yazılır → T = 10.

01010101012510155E01T108F000O001GR

İç düğüm (8), kod = "11" (sağ kenardan gelindi) → yaprak değil, sol çocuğa "110" ile inilir.

01010101012510155E01T108F000O001G110R

Yaprak G (frekans 4): yoldaki bitler yazılır → G = 110.

01010101012510155E01T108F000O001G110R111

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?

Açgözlü seçim

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ı).

Optimal alt yapı

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.

Maliyet

Toplam bit = Σ frekans(c) · derinlik(c). Huffman, önek-özgür kodlar arasında bunu en küçük yapar.

Karmaşıklık

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.

xbpq⇄pbxqx en az frekanslı ama sığx en derine: toplam bit artmaz

Uygulamalar

Diğer Açgözlü Algoritmalar

Dijkstra ve PrimHer adımda uzaklığı / bağlantı maliyeti en küçük düğümü kalıcı olarak seçer. Negatif kenarsız çizgede en iyisidir.
Çizge boyamaDüğümleri sırayla, komşularının kullanmadığı en küçük renkle boya. Hızlı ama renk sayısı en az olmayabilir (NP-zor).
Kutulara yerleştirme(Bin packing) İlk sığan / en iyi sığan: nesneyi sığdığı ilk kutuya koy. Azalan sırayla ilk sığan en fazla ≈ 11/9 · OPT + 1 kutu kullanır.
En büyük kapsama(Maximum coverage) Her adımda en çok yeni öğe kapsayan kümeyi seç: en iyinin en az (1 − 1/e) ≈ %63'ünü garanti eder.
NP-zor problemlerde açgözlü algoritmalar, kanıtlanmış oranlı yaklaşım algoritmaları olarak değerlidir.

Özet

Özet

ProblemAçgözlü ölçütZamanEn iyi mi?
Para üstüsığan en büyük paraO(k + adet)kanonikse
Etkinlik seçimien erken bitenO(n log n)evet
İş sıralamaen kârlı iş, en geç boş slotO(n²)evet
Kesirli sırt çantasıen büyük kâr/ağırlıkO(n log n)evet (0/1: hayır)
MSTkesimi geçen en hafif kenar (Kruskal, Prim, Borůvka)O(E log V)evet
Huffmanen düşük frekanslı iki ağaçO(n log n)evet

Özet

Etkileşimli Simülatörler

Minimum Kapsayan AğaçRastgele bağlı ağırlıklı çizgelerde Prim ve Kruskal adım adım, yan yana: seçilen kenarlar ve toplam ağırlık.
Dizgi SıkıştırmaRLE (Run Length Encoding) ve LZW sıkıştırması adım adım, sıkıştırma oranıyla. Huffman bu sayfada yok; sözlük tabanlı LZW ile frekans tabanlı Huffman'ı karşılaştırmaya elverişlidir.

Özet

Sık Yapılan Hatalar

Açgözlüyü kanıtsız doğru saymak{1, 3, 4} paralarıyla 6: açgözlü 3 para, en iyi 2. Her problemde açgözlü seçim özelliği gösterilmelidir.
Yanlış ölçütEtkinlik seçiminde en erken başlayan ya da en kısa etkinlik ölçütü yanlıştır; doğru ölçüt en erken biten.
0/1 sırt çantasında oranKesirli problemde en iyi olan oran ölçütü, bölünemeyen nesnelerde en iyi sonucu garanti etmez.
En erken boş slotİş sıralamada işi son tarihinden önceki en erken boş slota koymak, son tarihi yakın işlere yer bırakmaz; en geç boş slot seçilir.
Kesim özelliğini yanlış okumakGüvenli olan, bir kesimi geçen en hafif kenardır. Tüm çizgedeki en hafif kenarlar sırayla alınırsa döngü oluşabilir; Kruskal bu yüzden döngü kuranı atar.
Huffman'da ağaçsız çözmeKod tablosu ya da ağaç gönderilmezse veri çözülemez. Tek karakterli metinde kök yapraktır: koda en az 1 bit verilmelidir.

Özet

Örnek Problemler

  1. {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?
  2. 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?
  3. Frekanslar a:5, b:9, c:12, d:13, e:16, f:45. Huffman kod uzunluklarını ve toplam biti bulun.
  4. 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?
  5. Kruskal, Prim ve Borůvka aynı çizgede farklı kapsayan ağaçlar bulabilir mi?
Cevapları göster
  1. 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.
  2. Bitişe göre sırala, en erken biteni al: (1,4), (5,7), (8,11), (12,16) → 4 etkinlik.
  3. 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).
  4. Kesimi geçen en hafif kenar B–C (3); ağırlıklar farklı olduğundan kesim özelliğine göre her MST'de bulunur.
  5. 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

  1. İş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.
  2. 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?
  3. "ABRACADABRA" için Huffman kodlaması kaç bit tutar? Sabit uzunluklu kodla karşılaştırın.
  4. 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.
  5. 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ı
  1. 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.
  2. Oranlar 2, 10, 5, 16,7: 50 (3 kg) + 40 (4 kg) + 30'un 3/6'sı (15) = 105.
  3. 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.
  4. 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.
  5. {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.

Son

Bölüm 7: Açgözlü Algoritmalar
Sonraki bölüm: Bölüm 8: Dinamik Programlama · Terim sözlüğü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.