Se desejarmos armazenar a palavra BANANA em nosso computador, é necessário representá-la através da linguagem da máquina, números binários. Existem várias formas de se fazer isso, uma delas é através de uma variação da codificação ASCII, que atribui um número de 0 a 255 para cada letra:
| Letra | Decimal | Binário |
|---|---|---|
| B | 66 | 01000001 |
| A | 65 | 01000010 |
| N | 78 | 01001110 |
Assim, BANANA poderia ser escrito como 01000010 01000001 01001110 01000001 01001110 01000001, o que ocuparia 48 bits de espaço de armazenamento.
Essa codificação é capaz de lidar com um alfabeto de 256 símbolos, abrangendo letras, acentos, sinais de pontuação, etc.. Mas o nosso alfabeto só possui 3, B, A e N. Então ao invés de usar 8 bit para representar cada símbolo, podemos usar somente 2:
| Letra | Decimal | Binário |
|---|---|---|
| B | 0 | 00 |
| A | 1 | 01 |
| N | 2 | 10 |
Assim, BANANA poderia ser escrito como 00 01 10 01 10 01, o que ocuparia 12 bits de espaço de armazenamento. Já foi uma grande melhora, mas ainda podemos fazer melhor.
Nas codificações que vimos até agora, sempre usamos a mesma quantidade de bits para cada símbolo, por isso, bastou percorrer o texto olhando o dicionário a cada n bits (nos exemplos, n foi 8 e depois 2) e fazer a devida substituição para ter nossa palavra escrita novamente na forma original.
Mas, para otimizar espaço, podemos usar quantidades diferentes de bits por símbolo, e mais, podemos escolher os menores números para os símbolos mais frequentes, o que diminui ainda mais o resultado:
| Letra | Decimal | Binário |
|---|---|---|
| A | 0 | 0 |
| N | 1 | 1 |
| B | 2 | 10 |
Assim, BANANA poderia ser escrito como 10 0 1 0 1 0, o que ocuparia 7 bits de espaço de armazenamento.
Como não temos mais um número fixo de bits por símbolo, para ter a palavra de volta na forma original, precisamos nos perguntar a cada bit que lemos se encontramos o símbolo correspondente. O que nos mostra um problema no nosso processo, ambiguidade: Como saberemos se o primeiro bit 1 é um N ou o início de um B?
Para resolver esse problema, devemos escolher nossos números de modo que os bits que representam um símbolo nunca seja prefixo da representação de outro símbolo. A solução proposta por David A. Huffman, foi gerar uma árvore binária que informasse quando um símbolo foi alcançado durante a leitura dos bits. Ao ler o bit 0, seguimos para o nó à esquerda, ao ler o bit 1, seguimos para o nó à direita.
A árvore é gerada da seguinte maneira:
-
Para cada símbolo, cria-se um nó folha e o insere numa fila priorizada pela menor frequência. Fila: (1, B), (2, N), (3, A)
-
Enquanto há mais de um nó na fila:
- Uni-se os dois nós de maior prioridade (os de símbolo de menor frequência) criando um terceiro cuja frequência seja a soma das frequências dos dois primeiros.
Fila: (3, (1, B), (2, N)), (3, A)
(3) (3, A) / \ / \ (1, B) (2, N)Fila: (6, (3, (1, B), (2, N)), (3, A))
(6) / \ / \ (3) (3, A) / \ / \ (1, B) (2, N) -
O nó que resta é o nó raiz e a árvore está completa.