Livro de SW: sec.5.5, p.826-838. Website do livro: resumo sec.5.5, slides. Veja também a página http://algs4.cs.princeton.edu/code/, que tem o código fonte, a API, e dados de teste de todos os programas do livro.
Resumo:
Pré-requisitos:
010000010100001001010010010000010100001100000010100010001000001001000010010100100100000100100001
A B R A C A D A B R A !
! 001 ! 1010 A 010 A 0 B 011 B 111 C 100 C 1011 D 101 D 100 R 110 R 110 ... ...
public static void compress() { String s = BinaryStdIn.readString(); char[] input = s.toCharArray(); // cálculo da tabela de códigos st[] // discutido mais adiante for (int i = 0; i < input.length; i++) { String code = st[input[i]]; for (int j = 0; j < code.length(); j++) if (code.charAt(j) == '1') BinaryStdOut.write(true); else BinaryStdOut.write(false); } BinaryStdOut.close(); }
! 11 A 0 B 1 C 01 D 10 R 00
! 101 A 0 B 1111 C 110 D 100 R 1110
A 0 0 1 1 B 100 1 01 01 C 10 00 001 001 D 11 11 0001 000
101 ! 0 A 1111 B 110 C 100 D 1110 R
private static class Node implements Comparable<Node> { private char ch; // usado só nas folhas private int freq; // usado só para construção da trie private final Node left, right; Node(char ch, int freq, Node left, Node right) { this.ch = ch; this.freq = freq; this.left = left; this.right = right; } public boolean isLeaf() { return left == null && right == null; } public int compareTo(Node that) { return this.freq - that.freq; } }
public static void expand() { Node root = readTrie(); // discutido abaixo int N = BinaryStdIn.readInt(); // comprimento da string original for (int i = 0; i < N; i++) { // decodifica próximo caractere Node x = root; while (!x.isLeaf()) if (BinaryStdIn.readBoolean()) x = x.right; else x = x.left; BinaryStdOut.write(x.ch); } BinaryStdOut.close(); }
a b e f h i m o r s t w LF SP 2 1 5 2 2 4 2 3 1 6 8 3 1 11
(Aqui, SP é o caractere 32, ou 20 em hexadecimal, que representa um espaço.) Segue a construção da trie de Huffman:
private static Node buildTrie(int[] freq) { MinPQ<Node> pq = new MinPQ<Node>(); for (char c = 0; c < R; c++) if (freq[c] > 0) pq.insert(new Node(c, freq[c], null, null)); while (pq.size() > 1) { Node x = pq.delMin(); Node y = pq.delMin(); Node parent = new Node('\0', x.freq + y.freq, x, y); pq.insert(parent); } return pq.delMin(); }
private static String[] buildCode(Node root) {
String[] st = new String[R];
buildCode(st, root, "");
return st;
}
private static void buildCode(String[] st, Node x, String s) {
if (x.isLeaf()) {
st[x.ch] = s;
return;
}
buildCode(st, x.left, s + '0');
buildCode(st, x.right, s + '1');
}
public static void compress() { String s = BinaryStdIn.readString(); char[] input = s.toCharArray(); int[] freq = new int[R]; for (int i = 0; i < input.length; i++) freq[input[i]]++; Node root = buildTrie(freq); String[] st = new String[R]; buildCode(st, root, ""); writeTrie(root); // discutido abaixo BinaryStdOut.write(input.length); for (int i = 0; i < input.length; i++) { String code = st[input[i]]; for (int j = 0; j < code.length(); j++) if (code.charAt(j) == '1') BinaryStdOut.write(true); else BinaryStdOut.write(false); } BinaryStdOut.close(); }
int[] freq = new int[R];
for (int c = 0; c < R; c++)
for (int i = 0; i < input.length; i++)
if (input[i] == c) freq[c]++;
Proposição U.
A cadeia de bits produzida pelo algoritmo de Huffman
é mínima no seguinte sentido:
nenhuma outra cadeia produzida por um código livre de prefixos
é mais curta que a cadeia produzida pelo algoritmo de Huffman.
Como descrever a trie de Huffman por meio de uma cadeia de bits?
Resposta: percorra a trie em pré-ordem
(visite a raiz, depois a subtrie esquerda, depois a subtrie direita).
Sempre que visitar um nó interno, escreva um bit 0.
Sempre que visitar uma folha, escreva um bit 1 seguido
pelos 8 bits do caractere associado.
Esse representação da trie é fácil produzir
e fácil decodificar.
Cálculo da cadeia de bits que representa a trie:
private static void writeTrie(Node x) {
if (x.isLeaf()) {
BinaryStdOut.write(true);
BinaryStdOut.write(x.ch);
return;
}
BinaryStdOut.write(false);
writeTrie(x.left);
writeTrie(x.right);
}
Reconstrução da trie a partir da cadeia de bits:
private static Node readTrie() {
if (BinaryStdIn.readBoolean()) {
char c = BinaryStdIn.readChar();
return new Node(c, 0, null, null);
}
return new Node('\0', 0, readTrie(), readTrie());
}
00010101011010011001110100111100101010000101000001101010010000101110100...
Desenhe a trie do código de compressão.
public class Huffman { private static int R = 256; // alfabeto ASCII private static class Node implements Comparable<Node> { /* veja acima */ } public static void expand() { /* veja acima */ } private static Node buildTrie(int[] freq) { /* veja acima */ } private static String[] buildCode(Node root) { /* veja acima */ } public static void compress() { /* veja acima */ } private static void writeTrie(Node x) { /* veja acima */ } private static Node readTrie() { /* veja acima */ } }
% more abra.txt ABRACADABRA! % java Huffman - < abra.tx | java BinaryDump 60 010100000100101000100010000101010100001101010100101010000100 000000000000000000000000000110001111100101101000111110010100 120 bits
Eis a repetição dos 120 bits produzidos por BinaryDump com espaços e quebras de linha inseridos para facilitar a leitura:
0 1 01000001 0 0 1 01000100 0 1 00001010 1 01000011 0 1 01010010 1 01000010 00000000000000000000000000001100 0 111 110 0 1011 0 100 0 111 110 0 1010 0
Os 59 bits da primeira linha descrevem a trie, os 32 bits da segunda linha dão o número de caracteres da string original (12), os 28 bits da terceira linha são a string codificada propriamente dita, o bit da última é enchimento para que o número total de bits seja um múltiplo de 8.
% more tinytinyTale.txt it was the best of times it was the worst of times % java Huffman - < tinytinyTale.txt | java BinaryDump 64 0001011001010101110111101101111100100000001011100110010111001001 0000101010110001010110100100010110011010110100001011011011011000 0110111010000000000000000000000000000110011101111101001011011100 0111111001000011010110001001110100111100001111101111010000100011 0111110100101101110001111110010000100100011101001001110100111100 00111110111101000010010101000000 352 bits
Os primeiros 139 bits representam a trie. Os 32 bits seguintes representam o número de caracteres, 51, da string original. Os 176 bits seguintes são a cadeia de bits codificada. Os últimos 5 bits são enchimento para completar um múltiplo de 8. Taxa de compressão: (139+32+176+5)/(51×8) = 352/408 = 0.86. Segue a cadeia de bits codificada com espaços e quebras de linha inseridas para facilitar a leitura:
0 0 0 1 01100101 0 1 01110111 1 01101111 1 00100000 0 0 1 01110011 0 0 1 01110010 0 1 00001010 1 01100010 1 01101001 0 0 0 1 01100110 1 01101000 0 1 01101101 1 01100001 1 01110100 00000000000000000000000000110011 1011 111 01 0010 11011 100 01 111 11001 000 01 101011 000 100 111 01 0011 11000 01 111 1011 11010 000 100 01 1011 111 01 0010 11011 100 01 111 11001 000 01 0010 0011 10100 100 111 01 00111 1000 01 111 1011 11010 000 100 101010 00000
A decodificação reproduz a string original:
% java Huffman - < tinytinyTale.txt | java Huffman + it was the best of times it was the worst of times
% java PictureDump 512 88 < medTale.txt45056 bits % java Huffman - < medTale.txt | java PictureDump 512 47
23912 bits
% java BinaryDump 0 < tale.txt 5812552 bits % java Huffman - < tale.txt > tale.txt.huf % java BinaryDump 0 < tale.txt.huf 3043928 bits
% java Genome - < genomeVirus.txt | java PictureDump 512 2512536 bits % java Huffman - < genomeVirus.txt | java PictureDump 512 25
12576 bits
% java RunLength - < q32x48.bin | java BinaryDump 0 1144 bits % java Huffman - < q32x48.bin | java BinaryDump 0 816 bits % java RunLength - < q64x96.bin | java BinaryDump 0 2296 bits % java Huffman - < q64x96.bin | java BinaryDump 0 2032 bits