霍夫曼编码是一种无损数据压缩算法,其中数据中的每个字符都分配有可变长度的前缀代码。出现频率最低的字符获得最大代码,出现频率最高的字符获得最小代码。使用这种技术对数据进行编码非常简单且高效。但是,解码使用此技术生成的比特流效率低下。解码器(或解压缩器)需要了解所使用的编码机制,以便将编码数据解码回原始字符。
因此,需要将编码过程的信息与编码数据一起作为字符表及其对应代码传递给解码器。在对大量数据进行常规霍夫曼编码时,此表会占用大量内存空间,而且如果数据中存在大量唯一字符,则由于存在代码本,压缩(或编码)数据大小会增加。因此,为了使解码过程在计算上高效,同时仍保持良好的压缩率,引入了规范霍夫曼码。
在标准霍夫曼编码中,使用为每个符号生成的标准霍夫曼代码的位长。首先根据符号的位长按非递减顺序对符号进行排序,然后根据每个位长按字典顺序对符号进行排序。第一个符号获得一个全为零且长度与原始位长相同的代码。对于后续符号,如果符号的位长等于前一个符号的位长,则将前一个符号的代码加一并分配给当前符号。
否则,如果符号的位长大于前一个符号的位长,则在增加前一个符号的代码后,将零附加到该代码上,直到长度等于当前符号的位长,然后将代码分配给当前符号。
此过程继续处理其余符号。
以下示例说明了该过程:
考虑以下数据:
特点 | 频率 |
---|---|
A | 10 |
b | 1 |
C | 15 |
d | 7 |
生成的标准霍夫曼码的位长度为:
特点 | 霍夫曼编码 | 位长度 |
---|---|---|
A | 11 | 2 |
b | 100 | 3 |
C | 0 | 1 |
d | 101 | 3 |
- 步骤 1:根据位长对数据进行排序,然后根据每个位长度按字典顺序对符号进行排序。
特点 | 位长度 |
---|---|
C | 1 |
A | 2 |
b | 3 |
d | 3 |
- 步骤 2:为第一个符号的代码分配与位长相同数量的“0”。
‘c’的代码:0
下一个符号‘a’的位长为 2 > 前一个符号‘c’的位长为 1。将前一个符号的代码增加 1 并附加 (2-1)=1 个零并将代码分配给‘a’。
‘a’的代码:10
下一个符号‘b’的位长为 3 > 前一个符号‘a’的位长为 2。将前一个符号的代码增加 1 并附加 (3-2)=1 个零并将代码分配给‘b’。
‘b’的代码:110
下一个符号‘d’的位长为 3 = 前一个符号‘b’的位长为 3。将前一个符号的代码增加 1 并将其分配给‘d’。
‘d’的代码:111 - 步骤3:最终结果。
特点 | 规范霍夫曼编码 |
---|---|
C | 0 |
A | 10 |
b | 110 |
d | 111 |
该方法的基本优点是,传递给解码器的编码信息可以更紧凑、更节省内存。例如,可以简单地将字符或符号的位长度传递给解码器。由于长度是连续的,因此可以轻松根据长度生成规范代码。
有关使用 Huffman 树生成 Huffman 代码的信息,请参阅之前的文章如下:
c语言:c语言 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)_霍夫曼的贪婪c语言-优快云博客
c++:c++ 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)_霍夫曼的贪婪算法设计核心代码-优快云博客
c#:C# 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)-优快云博客
c++ STL:c++ STL 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)-优快云博客
java:java 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)-优快云博客
python:python 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)-优快云博客
javascript:JavaScript 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)-优快云博客
方法:一种简单有效的方法是为数据生成一棵哈夫曼树,并使用类似于 Java 中的 TreeMap 的数据结构来存储符号和位长,以使信息始终保持排序。然后可以使用增量和按位左移运算来获取规范代码。
执行:
import java.util.*;
// Node class to store data and its frequency
class Node implements Comparable<Node> {
char data;
int freq;
Node left, right;
// Constructor
Node(char c, int f) {
data = c;
freq = f;
left = right = null;
}
// Comparator: less_than
public int compareTo(Node other) {
return this.freq - other.freq;
}
}
public class CanonicalHuffman {
// Function to generate Huffman codes
static void codeGen(Node root, StringBuilder codeLength, Map<Integer, List<Character>> codeMap) {
if (root == null) return;
if (root.left == null && root.right == null) {
codeMap.computeIfAbsent(codeLength.length(), k -> new ArrayList<>()).add(root.data);
return;
}
codeGen(root.left, codeLength.append('0'), codeMap);
codeLength.deleteCharAt(codeLength.length() - 1);
codeGen(root.right, codeLength.append('1'), codeMap);
codeLength.deleteCharAt(codeLength.length() - 1);
}
// Main function implementing Huffman coding
static void testCanonicalHC(char[] charArr, int[] freq) {
// Priority queue to store heap tree
PriorityQueue<Node> q = new PriorityQueue<>();
for (int i = 0; i < charArr.length; i++) {
q.add(new Node(charArr[i], freq[i]));
}
while (q.size() > 1) {
Node left = q.poll();
Node right = q.poll();
Node merged = new Node('-', left.freq + right.freq);
merged.left = left;
merged.right = right;
q.add(merged);
}
Node root = q.poll();
Map<Integer, List<Character>> codeMap = new HashMap<>();
codeGen(root, new StringBuilder(), codeMap);
// Generate Canonical Huffman codes
Map<Character, String> canonicalMap = new TreeMap<>();
int cCode = 0;
for (int length : new TreeSet<>(codeMap.keySet())) {
List<Character> chars = codeMap.get(length);
for (char ch : chars) {
canonicalMap.put(ch, String.format("%" + length + "s", Integer.toBinaryString(cCode++)).replace(' ', '0'));
}
cCode <<= 1;
}
// Print Canonical Huffman codes
for (char ch : canonicalMap.keySet()) {
System.out.println(ch + ": " + canonicalMap.get(ch));
}
}
// Driver code
public static void main(String[] args) {
char[] charArr = {'a', 'b', 'c', 'd'};
int[] freq = {10, 1, 15, 7};
testCanonicalHC(charArr, freq);
}
}
输出:
c:0
a:10
b:110
d:111