Esta página é um resumo da discussão que está no capítulo 17 (algoritmos gulosos), seção 3, de CLR.
O algoritmo constroi uma árvore ótima. Vamos supor que cada nó da árvore tem 4 campos: ch, fr, esq e dir. Se z é o endereço de um nó então
Vamos supor que o algoritmo recebe um conjunto C de nós; cada nó z corresponde a um dos caracteres do meu alfabeto e tem esq[z]=dir[z]=NIL. Esses nós serão as folhas da árvore. O algoritmo contrói o resto da árvore e devolve o endereço da raiz da árvore.
HUFFMAN (C)
Q := C
para i := 1 até |C|-1 faça comentário: cada iteração constrói um nó interno da árvore
z := ALOCA ()
x := EXTRACT-MIN (Q)
y := EXTRACT-MIN (Q)
esq[z] := x
dir[z] := y
fr[z] := fr[x]+fr[y]
Q := Q+{z}
devolve EXTRACT-MIN (Q)
O comando "Q := C" cria uma "fila" com prioridades com o conjunto de todos os nós. Os detalhes da implementação da fila ficam por conta do leitor, mas tudo se passa como se Q fosse uma seqüência de nós em ordem decrescrescente de fr. O subalgoritmo EXTRACT-MIN retira da fila com prioridades um nó que tem fr mínimo. O sub-algoritmo ALOCA gera um novo nó e devolve o endereço desse nó.
Observe que no início de cada iteração temos uma floresta composta de diversas árvores. Em cada iteração, duas árvores são reunidas. No fim da última iteração, a floresta tem uma única árvore.
É óbvio que o algoritmo produz uma árvore que minimiza o tamanho do arquivo codificado? Não. Isso não é nada óbvio. Veja capítulo 17 do CLR.
f(A)=1, f(B)=1, f(C)=2, f(D)=3, f(E)=5, f(F)=8, f(G)=13, f(H)=21.
O arquivo original tem 1+1+ . .+21 =
54 caracteres.
Quantos bits tem o arquivo comprimido?
1. Se for necessário representar cada caracter por um número fixo de bits, quantos bits você usaria por caracter? Qual será o tamanho total do arquivo?
2. Qual o tamanho (em bits) do arquivo se usarmos um código de Huffman?
[Solução]
A descrição acima não explica como implementar os detalhes de maneira eficiente. Eis uma implementação que usa uma fila com prioridades implementada num heap. A fila com prioridades mora em um vetor H[1..N] cujos elementos são endereços de nós. Mais precisamente, para cada i, H[i] será o endereço da raiz de uma das árvores da floresta. O vetor H será um heap no seguinte sentido:
fr[H[i/2]] <= fr[H[i]]
para cada i maior que 1. Aqui, como no resto desta página, a expressão "i/2" deve ser entendida como "chão(i/2)".
Para simplificar, suporemos que nosso alfabeto é 1, 2, . . , n. Suporemos que a freqüência de um caracter i é f[i] . O algoritmo HUFFMAN-DETALHADO constroi uma árvore ótima relativa ao vetor de freqüências f[1..n]. O algoritmo devolve o endereço da raiz da árvore que construiu.
HUFFMAN-DETALHADO (n, f)
para i := 1 até n faça
z := ALOCA ()
esq[z] := dir[z] := NIL
fr[z] := f[i]
H[i] := z
N := n
para i := N/2 decrescendo até 1 faça
PENEIRA (H, N, i)
comentário: agora H[1..N] é um heap
para i := 1 até n-1 faça
comentário: cada iteração constrói um nó interno da árvore
z := ALOCA ()
x := EXTRAIMIN (H, N)
N := N-1
y := EXTRAIMIN (H, N)
N := N-1
esq[z] := x
dir[z] := y
fr[z] := fr[x]+fr[y]
INSERE (z, H, N)
N := N+1
devolve H[1]
Procedimentos auxiliares:
EXTRAIMIN (H, N)
m := H[1] comentário: fr[m] é mínimo
H[1] := H[N]
PENEIRA (H, N, 1)
devolve m
INSERE (z, H, N)
i := N+1
enquanto i > 1 e fr[H[i/2]] > fr[z] faça
H[i] := H[i/2]
i := i/2
H[i] := z
PENEIRA (H, N, i)
se 2i <= N e fr[H[2i]] < fr[H[i]]
então m := 2i
senão m := i
se 2i+1 <= N e fr[H[2i+1]] < fr[H[m]]
então m := 2i+1
se m <> i
então troque H[m] :=: H[i]
PENEIRA (H, N, m)
Mostre que HUFFMAN-DETALHADO consome O(n lg(n)) unidades de tempo.