1. Algoritmo de compressão Huffman
Danilo Dias1
1
Sistemas de Informação – Universidade Federal do Oeste do Pará (UFOPA)
Santarém – PA – Brasil
danilohdds@gmail.com
Abstract. This article expounding on data compression, in which case the
techniques apply a lossless compression of data, the technique in question is to
present David A. Huffman. This article in question, we present a brief
introduction to compression of data showing an example of compression
occurs in a practical example using the technique of Huffman compression
algorithm.
Resumo. Este artigo disserta acerca de compressão de dados, sendo que neste
caso aplicaremos uma das técnicas de compressão sem perdas de dados, a
técnica em questão que apresentaremos é a de David A. Huffman. Neste artigo
em questão procuramos apresentar uma breve introdução a compressão de
dados mostrando um exemplo de como ocorre a compressão em um exemplo
prático usando a técnica de compressão do algoritmo de Huffman.
1. Introdução
A compressão de dados é o ato de diminuir o espaço ocupado por dados num
determinado dispositivo. Essa intervenção é realizada através de diversos algoritmos de
compressão, reduzindo a quantidade de Bytes para representar um dado, sendo esse
dado uma imagem, um texto, ou um arquivo (ficheiro).
Comprimir dados destina-se também a eliminar redundâncias, baseando-se na premissa
que muitos dados contêm informações redundantes que podem ou precisam ser
eliminadas de alguma forma. Essa forma é através de uma regra, chamada de código ou
protocolo, que, quando seguida, elimina os bits redundantes de informações, de modo a
diminuir seu tamanho nos ficheiros. Um exemplo é a sequência "AAAAA" que ocupa 5
bytes, poderia ser representada pela sequência "5A", que ocupa 2 bytes, economizando
67% de espaço.
Além de eliminar redundâncias, dados são comprimidos pelos mais diversos motivos.
Entre os mais conhecidos estão administrar espaço em dispositivos de armazenamento,
como discos rígidos, ou ganhar desempenho (diminuir tempo) em transmissões.
Ainda que possam parecer sinônimos, compressão e compactação de dados são
processos diferentes. Compressão reduz a quantidade de bits para representar algum
dado, enquanto a compactação tem a função de unir dados que não estejam juntados.
Um exemplo clássico de compactação de dados é a desfragmentação de discos rígidos.
Existem várias formas de classificar compressão de dados, os mais comuns são: com
perda de dados e sem perda de dados, dizemos que um método de compressão é sem
perda (lossless) se os dados obtidos após a aplicação da técnica são idênticos aos dados
2. originais. Estes métodos são uteis para dados que são obtidos por meios digitais, um
exemplo desses tipos de dados são: planilhas eletrônicas, programas, textos entre outros,
onde a perda de uma das partes desses dados torna o funcionamento final destes
problemáticos ou totalmente inútil.
No entanto há situações em que a perda de dados é admissível, caso de sons e imagens
por exemplo poderíamos comprimi-las com perdas de alguns detalhes que num seriam
percebidos pelos olhos e ouvidos humanos, nestes casos a perda de dados é aceitável,
portanto nestes casos os dados obtidos após a compressão não são idênticos aos
originais, pois “perderam” as informações irrelevantes, então dizemos que este método
de compressão é com perdas (lossy). Neste artigo abordaremos uma das técnicas de
compressão sem perdas de dados, neste caso usaremos um algoritmo desenvolvido por
David A. Huffman, apresentaremos uma breve explicação da teoria por trás do
algoritmo, mostraremos o algoritmo em si, apresentaremos a que situações o algoritmo
se aplica, e por fim implementaremos um exemplo prático usando a técnica de Huffman.
2. Teoria
A compilação de Huffman é um procedimento de compressão que usa as probabilismos
de evento dos símbolos no conjunto de dados a ser comprimido para determinar códigos
de tamanho variável para cada símbolo. Foi desenvolvido em 1952 por David A.
Huffman que na época era estudante de doutorado no MIT, foi publicado no artigo "A
Method for the Construction of Minimum-Redundancy Codes".
Uma árvore binária completa, chamada de árvore de Huffman é arquitetada
recursivamente a partir da junção dos dois símbolos de menor possibilidade, que são
então somados em símbolos secundárias e estes símbolos secundários recolocados no
conjunto de símbolos. O processo termina quando todos os símbolos foram unidos em
símbolos auxiliares, formando uma árvore binária. A árvore é então percorrida,
atribuindo-se valores binários de 1 ou 0 para cada aresta, e os códigos são gerados a
partir desse percurso.
Neste método de compressão, é atribuído menos bits a símbolos que aparecem mais
frequentemente e mais bits para símbolos que aparecem menos. Assim, o tamanho em
bits dos caracteres codificados será diferente. Codificação de Huffman é um exemplo de
técnica de codificação estatística, que diz respeito ao uso de um código curto para
representar símbolos comuns, e códigos longos para representar símbolos pouco
frequentes. Esse é o princípio do --_-, e assim por
diante. Usaremos um exemplo para mostrar como a codificação de Huffman funciona.
Suponha que temos um arquivo contendo 1000 caracteres, que são e, t, x e z. A
probabilidade de ocorrência de e, t, x, e z são 0.8, 0.16, 0.02, e 0.02 respectivamente.
Em um método de codificação normal, necessitamos 2 bits para representar cada um dos
quatro caracteres. Assim, necessitamos de 2000 bits para representar o arquivo. Usando
a codificação de Huffman, podemos usar quantidades de bits diferentes para representar
estes caracteres.
Usamos bit 1 para representar e, 01 para representar t, 001 para representar x e 000 para
representar z. Neste caso, o número total de bits necessários para representar o arquivo é
1000*(1*0.8+2*0.16+3*0.02+3*0.02)=1240. Assim, embora tenhamos utilizado mais
3. bits para representar x e z, desde que seus aparecimentos são mais raros, o número total
de bits necessários para o arquivo é menor que o esquema de codificação uniforme. As
regras para atribuir bits (códigos) aos símbolos é chamado um codebook. Codebooks
são normalmente expressos em tabelas: w(e)=1, w(t)=01, w(x)=001, w(z)=000.
3. Algoritmo
Para conferir modos mais frequentes aos códigos binários de menor comprimento,
constrói-se uma árvore binária fundamentada nas probabilidades de ocorrência de cada
símbolo. Nessa árvore as folhas representam os símbolos presentes nos dados,
integrados com suas referentes probabilidades de ocorrência. Os nós mediadores
representam a soma das probabilidades de ocorrência de todos os símbolos presentes em
suas ramificações e a raiz concebe a soma da probabilidade de todos os símbolos no
conjunto de dados. O método se inicia pela junção dos dois símbolos de menor
probabilidade, que são então unidos em um nó ao qual é atribuída a soma de suas
probabilidades. Este novo nó é então tratado como se fosse uma folha da árvore, isto é,
um dos símbolos do alfabeto, e comparado com os demais de acordo com sua
probabilidade. O procedimento se repete até que todos os símbolos estejam unidos sob o
nó raiz.
A cada aresta da árvore é anexo um dos dígitos binários (0 ou 1). O código
correspondente a cada símbolo é então apurado percorrendo-se a árvore e anotando-se
os dígitos das arestas percorridas desde a raiz até a folha que corresponde ao símbolo
almejado.
Tabela de frequência de caracteres:
Carac Freq Cód
espaço 7 111
a 4 010
e 4 000
f 3 1101
h 2 1010
i 2 1000
m 2 0111
n 2 0010
s 2 1011
t 2 0110
l 1 11001
4. o 1 00110
p 1 10011
r 1 11000
u 1 00111
x 1 10010
Pseudocódigo para a construção da árvore:
enquanto tamanho(alfabeto) > 1:
S0 := retira_menor_probabilidade(alfabeto)
S1 := retira_menor_probabilidade(alfabeto)
X := novo_nó
X.filho0 := S0
X.filho1 := S1
X.probabilidade := S0.probabilidade + S1.probabilidade
insere(alfabeto, X)
fim enquanto
X = retira_menor_símbolo(alfabeto) # nesse ponto só existe um símbolo.
para cada folha em folhas(X):
código[folha] := percorre_da_raiz_até_a_folha(folha)
fim para
4. Aplicação
A aplicação do algoritmo de Huffman serve para casos em que não se deseja a perda de
dados, portanto é bastante útil para dados que são obtidos diretamente por meios
digitais, como textos, programas de computador, planilhas eletrônicas, etc., onde a
mínima perda de dados acarreta no não funcionamento ou torna os dados
incompreensíveis. Um texto com letras trocadas por exemplo, uma planilha com valores
faltantes ou inexatos, ou um programa de computador com comandos inválidos são
coisas que não desejamos e que podem causar contratempos. Algumas imagens e sons
necessitam ser reproduzidos de forma exata, como imagens e gravações para perícias,
impressões digitais, etc.
5. Implementação
Nesta implementação procuramos apresentar o funcionamento do algoritmo de
Huffman, sendo que em nosso experimento carregamos um arquivo de texto e jogamos
no algoritmo, obtemos um resultado prático mostrando, o símbolo, a frequência dele e o
código Huffman gerado para o símbolo em questão.
5. A primeira parte do experimento geramos o arquivo “.txt” e armazenamos no disco
“c:/”, o arquivo tem gravado a seguinte frase: testando o algoritmo de compressao
huffman
Aqui temos o algoritmo de Huffman em java:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package Huffman.code;
/**
*
* @author Danilo
*/
abstract class HuffmanTree implements Comparable<HuffmanTree> {
public int frequency; // the frequency of this tree
public HuffmanTree(int freq) { frequency = freq; }
public int compareTo(HuffmanTree tree) {
return frequency - tree.frequency;
}
}
class HuffmanLeaf extends HuffmanTree {
public char value; // the character this leaf represents
public HuffmanLeaf(int freq, char val) {
super(freq);
value = val;
}
}
class HuffmanNode extends HuffmanTree {
6. public HuffmanTree left, right;
public HuffmanNode(HuffmanTree l, HuffmanTree r) {
super(l.frequency + r.frequency);
left = l;
right = r;
}
}
package Huffman.code;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.PriorityQueue;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author Danilo
*/
public class HuffmanCode{
// input is an array of frequencies, indexed by character code
public static HuffmanTree buildTree(int[] charFreqs){
PriorityQueue<HuffmanTree> trees = new PriorityQueue<HuffmanTree>();
// initially, we have a forest of leaves
// one for each non-empty character
for (int i = 0; i < charFreqs.length; i++)
7. if (charFreqs[i] > 0)
trees.offer(new HuffmanLeaf(charFreqs[i], (char)i));
assert trees.size() > 0;
// loop until there is only one tree left
while (trees.size() > 1) {
// two trees with least frequency
HuffmanTree a = trees.poll();
HuffmanTree b = trees.poll();
// put into new node and re-insert into queue
trees.offer(new HuffmanNode(a, b));
}
return trees.poll();
}
public static void printCodes(HuffmanTree tree, Stack<Character> prefix) {
assert tree != null;
if (tree instanceof HuffmanLeaf) {
HuffmanLeaf leaf = (HuffmanLeaf)tree;
// print out character and frequency
System.out.print(leaf.value + "t" + leaf.frequency + "t");
// print out code for this leaf, which is just the prefix
for (char bit : prefix)
System.out.print(bit);
System.out.println();
} else if (tree instanceof HuffmanNode) {
HuffmanNode node = (HuffmanNode)tree;
// traverse left
prefix.push('0');
printCodes(node.left, prefix);
prefix.pop();
8. // traverse right
prefix.push('1');
printCodes(node.right, prefix);
prefix.pop();
}
}
public static void main(String[] args) throws IOException {
String arquivo = "C:/teste.txt";
FileInputStream fis = null;
try {
fis = new FileInputStream(arquivo);
} catch (FileNotFoundException ex) {
Logger.getLogger(HuffmanCode.class.getName()).log(Level.SEVERE, null,
ex);
}
BufferedInputStream buffReader = new BufferedInputStream(fis);
DataInputStream data = new DataInputStream(buffReader);
byte[] b = new byte[fis.available()];
data.read(b);
int[] charFreqs = new int[256];
for (char c : new String(b).toCharArray())
{
charFreqs[c]++;
}
// build tree
HuffmanTree tree = buildTree(charFreqs);
// print out results
System.out.println("SYMBOLtWEIGHTtHUFFMAN CODE");
printCodes(tree, new Stack<Character>());
9. }
}
Como já vimos passamos o arquivo “.txt” com a seguinte frase: testando o algoritmo de
compressao huffman
Como veremos o algoritmo dará valores maiores para os itens menos frequentes e
valores menores para os itens mais frequentes, podemos observar com a tabela gerada
na saída da execução de nosso algoritmo:
1) Os símbolos usados em nosso arquivo, contando com os espaços.
2) A frequência desses símbolos, por exemplo, temos um peso 3 para o caractere
“m”, pois ele repete três vezes, procurando assim representar os 3 elementos
com a menor quantidade de bits possíveis.
3) Por fim podemos observar o código Huffman para cada elemento de acordo com
a sua frequência, ou seja, valores maiores para itens mais frequentes e menores
para itens menos frequentes.
6. Conclusão
Neste artigo abordamos a compressão de dados, mostrando uma breve introdução acerca
do assunto, apresentamos também uma das técnicas usadas para compressão sem perda
de dados, mostramos a teoria por trás desta técnica, apresentamos alguns dos casos em
que esta técnica pode ser usada e por fim apresentamos um exemplo prático do
algoritmo de compressão em questão usando um arquivo texto e submetendo ao
algoritmo de Huffman e realizando uma breve analise em cima do resultado obtido.
Portanto, vimos a aplicação de uma das técnicas de compressão, neste caso a de David
A. Huffman, mais conhecida como Huffman, foi importante fazer uma implementação
da mesma, pois vimos na prático como o algoritmo realmente funciona, observamos o
pensamento empírico por trás do algoritmo que nos dá um embasamento maior em
nossos estudos acerca de estruturas de dados e como funciona o pensamento por trás de
uma implementação.
10. 7. Referencias
Camara, Marco. Criptografia e compressão de dados. UCSAL – Universidade Católica de Salvador.
https://pt.wikipedia.org/wiki/Compressao_de_dados, acesso em 29 de setembro de 2011
https://pt.wikipedia.org/wiki/Codificacao_de_Huffman, acesso em 29 de setembro de 2011
http://rosettacode.org/wiki/Huffman_coding#Java, acesso em 29 de setembro de 2011
http://javafree.uol.com.br/, acesso em 30 de setembro de 2011
http://algs4.cs.princeton.edu/55compression/Huffman.java.html, acesso em 01 de outubro de 2011