Huffman kodlaması: ağaç kurma, kodlar, toplam bit
En küçük iki frekansı birleştirerek Huffman ağacı kurma, kodları ağaçtan okuma, toplam bit ve ortalama hesabı, çözme (decode). Sınavda neredeyse her dönem çıkan iz sorusu.
Huffman kodlaması bir metni sıkıştırmak için her karaktere değişken uzunlukta bir bit dizisi verir: sık geçen karakter kısa kod, nadir geçen uzun kod alır. Algoritma greedy’dir: her adımda en küçük iki frekansı birleştirir. Sınavda hemen her zaman aynı soru gelir: “Bu frekanslarla Huffman ağacını kur, kodları yaz, toplam biti hesapla.” Bazen arkasından “şu bit dizisini çöz” ya da “sabit uzunluklu koda göre tasarruf ne?” eklenir.
Altın kurallar:
- Her adımda kuyruktaki en küçük iki düğümü birleştir. Yeni düğümün ağırlığı ikisinin toplamı.
- Yeni düğüm kuyruğa geri girer ve sonraki adımlarda bir yaprak gibi yarışır.
- Sol 0, sağ 1. Hangisinin sola gideceği sorudaki kurala bağlı; yaz ve ona uy.
- Toplam bit = Σ (frekans × kod uzunluğu).
- Eşitlik kuralı kodları değiştirebilir, toplam biti asla.
Sabit uzunluk ve değişken uzunluk
6 farklı karakterli 50 karakterlik bir metin düşün. Sabit uzunluklu kodda her karakter için bit gerekir: 50 × 3 = 150 bit. Ama karakterlerin hepsi aynı sıklıkta değil:
| Karakter | E | B | D | A | F | C |
|---|---|---|---|---|---|---|
| Frekans | 2 | 4 | 7 | 10 | 12 | 15 |
C’ye kısa kod, E’ye uzun kod verirsek toplam düşer. Tek koşul: çözülebilirlik. Bunun için kod önek kodu (prefix code) olmalı. Hiçbir kod başka bir kodun başlangıcı olamaz. Kodları bir ikili ağacın yapraklarına koyarsan bu otomatik sağlanır: bir yaprağa giden yol başka bir yaprağa giden yolun başı olamaz, çünkü yaprağın altında başka düğüm yok.
Algoritma
1. Her karaktere bir yaprak, hepsi min-PQ'ya
2. PQ'da birden fazla düğüm varken:
x = deleteMin (sol çocuk)
y = deleteMin (sağ çocuk)
z = yeni düğüm: ağırlık x+y, çocuklar x, y
z'yi PQ'ya ekle
3. Kalan tek düğüm kök
n karakter için tam n−1 birleştirme yapılır. Heap ile her işlem : toplam .
Çözümlü örnek
Frekanslar E=2, B=4, D=7, A=10, F=12, C=15. Her satırda kuyruğun sıralı hâlini yaz; sınavda hata yapmamanın en güvenli yolu bu.
kuyruk (sıralı) birleştir
E2 B4 D7 A10 F12 C15 E2 + B4 = 6
6 D7 A10 F12 C15 6 + D7 = 13
A10 F12 13 C15 A10 + F12 = 22
13 C15 22 13 + C15 = 28
22 28 22 + 28 = 50
50 kök
merge 1: E(2) + B(4) = 6
merge 2: (EB)(6) + D(7) = 13
merge 3: A(10) + F(12) = 22
merge 4: ((EB)D)(13) + C(15) = 28
merge 5: (AF)(22) + (((EB)D)C)(28) = 50
- adıma dikkat: 13 yeni oluştu ama A10 ve F12 ondan küçük, önce onlar birleşti. Yeni düğüm otomatik olarak sıradaki birleştirmeye girmez; sadece ağırlığı en küçük ikiden biriyse girer.
Ağaç (sol kenar 0, sağ kenar 1):
(50)
0/ \1
(22) (28)
0/ \1 0/ \1
A10 F12 (13) C15
0/ \1
(6) D7
0/ \1
E2 B4
Kodları kökten yaprağa giden yoldan oku:
| Karakter | Frekans | Kod | Uzunluk | Frekans × uzunluk |
|---|---|---|---|---|
| A | 10 | 00 | 2 | 20 |
| F | 12 | 01 | 2 | 24 |
| C | 15 | 11 | 2 | 30 |
| D | 7 | 101 | 3 | 21 |
| E | 2 | 1000 | 4 | 8 |
| B | 4 | 1001 | 4 | 16 |
| 50 | 119 |
codes {A=00, B=1001, C=11, D=101, E=1000, F=01} total bits 119 of 50 symbols, avg 2.38
Ortalama kod uzunluğu bit. Sabit kod 150 bit tutuyordu; tasarruf 31 bit, yaklaşık %20.7.
İPUCU · toplam biti iki yoldan kontrol et. İç düğümlerin ağırlıklarını topla: 6 + 13 + 22 + 28 + 50 = 119. Her karakterin frekansı, kökten yaprağa giden yoldaki her iç düğümde bir kez sayılır, yani tam olarak kod uzunluğu kadar. İki yol aynı sayıyı vermiyorsa ağaçta hata var.
Kodlama ve çözme (encode / decode)
Kodlama: her karakterin kodunu art arda yaz. FACE → 01 00 11 1000 = 0100111000 (10 bit).
Çözme: bitleri soldan oku, ağaçta kökten aşağı in (0 sol, 1 sağ). Bir yaprağa varınca o karakteri yaz ve köke dön.
bit dizisi: 1001001110100
1 0 0 1 → yaprak B kök'e dön
0 0 → yaprak A
1 1 → yaprak C
1 0 1 → yaprak D
0 0 → yaprak A
sonuç: BACDA
decode 1001001110100 -> BACDA
encode FACE -> 0100111000 (10 bits)
Eşitlik (tie) durumları
Aynı ağırlıkta iki düğüm olursa hangisinin önce çıkacağı tanımlı değildir. Sınavda kural verilir; verilmezse kendi kuralını yaz ve tutarlı uygula. Yaygın kural: “kuyruğa önce giren önce çıkar”, yani yapraklar yeni oluşan iç düğümlerden önce gelir.
Örnek: a=1, b=1, c=2, d=3, e=5.
a1 b1 c2 d3 e5 a1 + b1 = 2
c2 2 d3 e5 c2 + 2 = 4 (c önce)
d3 4 e5 d3 + 4 = 7
e5 7 e5 + 7 = 12
(12)
0/ \1
e (7)
0/ \1
d (4)
0/ \1
c (2)
0/ \1
a b
codes {a=1110, b=1111, c=110, d=10, e=0} total bits 25 of 12 symbols, avg 2.0833333333333335
Frekanslar Fibonacci gibi büyüyünce ağaç bir yana yatar ve en nadir karakter n−1 bitlik kod alır. Tersine, tüm frekanslar eşitse (w=x=y=z=4) Huffman sabit uzunluklu koda döner: 00, 01, 10, 11.
TUZAK · c ile (ab) yer değiştirirse? Kodlar değişir (c=111, a=1100 gibi) ama her iki ağaçta toplam 25 bit. Sınavda kodlar kurala göre farklı çıkabilir; hoca toplam biti ve ağacın geçerliliğini kontrol eder. Kuralını cevabının başına yaz.
Neden greedy burada doğru?
İki gözlem yeter:
- Optimum bir önek kodunda en düşük frekanslı iki karakter en derin seviyede kardeştir. Değilse, daha derindeki bir karakterle yer değiştirmek toplamı artırmaz.
- Bu iki karakteri tek bir “birleşik karakter” gibi düşünürsen geriye bir eksik karakterli aynı problem kalır (optimal substructure).
Yani “en küçük iki frekansı birleştir” seçimi her zaman bir optimum ağaca götürür.
Java ile kurma
static class Node {
char ch; int freq; Node left, right;
Node(char ch, int freq, Node left, Node right) {
this.ch = ch; this.freq = freq; this.left = left; this.right = right;
}
}
static Node build(char[] ch, int[] freq) {
PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> a.freq - b.freq);
for (int i = 0; i < ch.length; i++) pq.add(new Node(ch[i], freq[i], null, null));
while (pq.size() > 1) {
Node l = pq.poll(), r = pq.poll();
pq.add(new Node('\0', l.freq + r.freq, l, r));
}
return pq.poll();
}
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
| Yeni oluşan düğüm hep bir sonraki birleştirmeye girer | Sadece en küçük iki ağırlıktan biriyse |
| Kuyruğu her adımda yeniden sıralamamak | Her satırda sıralı listeyi yaz, en küçük ikiyi al |
| Toplam bit = kod uzunluklarının toplamı | Frekansla çarparak topla: Σ f × uzunluk |
| Sık karakter uzun kod alır | Tersi: sık karakter köke yakın, kısa kod |
| {0, 10, 110, 11} geçerli önek kodu | 11, 110’un başı; geçersiz |
| Eşitlikte farklı ağaç = yanlış cevap | Kural tutarlıysa geçerli; toplam bit aynı kalır |
| n sembol için n birleştirme | n−1 birleştirme |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
How many merge steps does Huffman's algorithm perform for n distinct symbols, and how many nodes does the final tree have?
Cevabı göster
Her birleştirme kuyruktaki eleman sayısını 1 azaltır: n'den 1'e inmek için n-1 birleştirme. Her birleştirme bir iç düğüm üretir: n yaprak + (n-1) iç düğüm = 2n-1. Huffman ağacı tam (full) ikili ağaçtır: her iç düğümün tam iki çocuğu var.
Frequencies: E=2, B=4, D=7, A=10, F=12, C=15. Build the Huffman tree (the first node removed becomes the left child, left edge = 0). Give the code of every symbol.
Cevabı göster
1) E2 + B4 = 6
2) 6 + D7 = 13
3) A10 + F12 = 22
4) 13 + C15 = 28
5) 22 + 28 = 50 (kök)Kökten sol 22 (A, F), sağ 28. 28'in solu 13, sağı C. 13'ün solu 6, sağı D. 6'nın solu E, sağı B. Yolları oku: A 00, F 01, C 11, D 101, E 1000, B 1001. Bu örnekte hiç eşitlik çıkmıyor, ağaç tek türlü.For the tree in the previous question, what is the total number of bits needed to encode the whole text?
Cevabı göster
Σ frekans × kod uzunluğu: A 10·2 = 20, F 12·2 = 24, C 15·2 = 30, D 7·3 = 21, E 2·4 = 8, B 4·4 = 16. Toplam 119. Kontrol: iç düğüm ağırlıklarının toplamı da 6+13+22+28+50 = 119 eder.
With a fixed-length code, how many bits per symbol are needed for these 6 symbols, and how many bits in total for the 50-character text? What percentage does Huffman save?
Cevabı göster
6 sembol için bit. 50 × 3 = 150. Huffman 119: fark 31 bit, 31/150 ≈ %20.7 tasarruf.
What is the average code length (bits per symbol) of that Huffman code?
Cevabı göster
Toplam bit / toplam frekans = 119 / 50 = 2.38. Sabit uzunlukta 3 bit idi.
Using the codes A=00, F=01, C=11, D=101, E=1000, B=1001, decode the bit string 1001001110100.
Cevabı göster
Soldan oku, bir kod tamamlanınca kes: 1001 = B, 00 = A, 11 = C, 101 = D, 00 = A. Önek kodu olduğu için ayırıcıya gerek yok; hiçbir kod başka bir kodun başı değil.
Encode the word FACE with the same codes. How many bits does it take?
Cevabı göster
F 01, A 00, C 11, E 1000 → 01 00 11 1000 = 0100111000. 2+2+2+4 = 10 bit. Sabit kodla 4 × 3 = 12 bit olurdu.
Is the code {0, 10, 110, 11} a prefix code? What about {0, 10, 110, 111}?
Cevabı göster
Önek kodu (prefix-free): hiçbir kod başka bir kodun başlangıcı olamaz. İlkinde 11, 110'un başı: 110 okununca 11 mi 110 mu belli olmaz. İkincisi bir ikili ağacın yapraklarından okunabilir: geçerli.
Frequencies a=1, b=1, c=2, d=3, e=5. Tie rule: among equal weights, the node that entered the queue first is removed first (leaves enter in the order a, b, c, d, e). The first removed node is the left child (0). Give all codes and the total bits.
Cevabı göster
1) a1 + b1 = 2 (yeni düğüm en sona)
2) c2 + (ab)2 = 4 (c önce girdi, sol)
3) d3 + 4 = 7
4) e5 + 7 = 12Ağaç tamamen tek yana yatık: her birleştirmede yaprak solda. Kodlar e 0, d 10, c 110, a 1110, b 1111. Bit: 5·1 + 3·2 + 2·3 + 1·4 + 1·4 = 25. Eşitlik kuralı değişirse kodlar değişir ama toplam bit (25) değişmez.What is the running time of Huffman's algorithm for n symbols using a binary heap?
Cevabı göster
n ekleme (ya da buildHeap ile ), sonra n-1 kez iki deleteMin + bir insert, her biri . Toplam .
Write a method Node build(char[] ch, int[] freq) that builds the Huffman tree with PriorityQueue. Node has fields char ch; int freq; Node left, right;.
Cevabı göster
static Node build(char[] ch, int[] freq) {
PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> a.freq - b.freq);
for (int i = 0; i < ch.length; i++)
pq.add(new Node(ch[i], freq[i], null, null));
while (pq.size() > 1) {
Node left = pq.poll(); // en küçük
Node right = pq.poll(); // ikinci en küçük
pq.add(new Node('\0', left.freq + right.freq, left, right));
}
return pq.poll();
}Java'nın PriorityQueue'su eşitlikte bir sıra garanti etmez; sınavdaki eşitlik kuralını tam uygulamak istersen karşılaştırıcıya ikinci anahtar (ekleme sırası) ekle.Write a recursive method void printCodes(Node n, String code) that prints every symbol with its Huffman code (left = 0, right = 1).
Cevabı göster
static void printCodes(Node n, String code) {
if (n == null) return;
if (n.left == null && n.right == null) {
System.out.println(n.ch + " = " + code);
return;
}
printCodes(n.left, code + "0");
printCodes(n.right, code + "1");
}Çağrı: printCodes(root, ""). Tek sembollü metinde kök yapraktır ve kod boş çıkar; bu özel durumda "0" vermek gerekir.Doğrulama: Tüm birleştirme adımları, kodlar, toplam bit, ortalama, encode/decode sonuçları ve önek kontrolü verify/SEN2212/W12.java ile java PriorityQueue kullanılarak hesaplandı (java W12.java). Eşitlikte kural: kuyruğa önce giren önce çıkar (yapraklar verilen sırayla girer). buildTree ve printCodes metotları verify/SEN2212/WMethods11to14.java içinde test edildi.