霍夫曼编码是一种无损数据压缩算法,其中数据中的每个字符都分配有可变长度的前缀代码。出现频率最低的字符获得最大代码,出现频率最高的字符获得最小代码。使用这种技术对数据进行编码非常简单且高效。但是,解码使用此技术生成的比特流效率低下。解码器(或解压缩器)需要了解所使用的编码机制,以便将编码数据解码回原始字符。
因此,需要将编码过程的信息与编码数据一起作为字符表及其对应代码传递给解码器。在对大量数据进行常规霍夫曼编码时,此表会占用大量内存空间,而且如果数据中存在大量唯一字符,则由于存在代码本,压缩(或编码)数据大小会增加。因此,为了使解码过程在计算上高效,同时仍保持良好的压缩率,引入了规范霍夫曼码。
在标准霍夫曼编码中,使用为每个符号生成的标准霍夫曼代码的位长。首先根据符号的位长按非递减顺序对符号进行排序,然后根据每个位长按字典顺序对符号进行排序。第一个符号获得一个全为零且长度与原始位长相同的代码。对于后续符号,如果符号的位长等于前一个符号的位长,则将前一个符号的代码加一并分配给当前符号。
否则,如果符号的位长大于前一个符号的位长,则在增加前一个符号的代码后,将零附加到该代码上,直到长度等于当前符号的位长,然后将代码分配给当前符号。
此过程继续处理其余符号。
以下示例说明了该过程:
考虑以下数据:
特点 | 频率 |
---|---|
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语言-CSDN博客
c++:c++ 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)_霍夫曼的贪婪算法设计核心代码-CSDN博客
c#:C# 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)-CSDN博客
c++ STL:c++ STL 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)-CSDN博客
java:java 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)-CSDN博客
python:python 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)-CSDN博客
javascript:JavaScript 霍夫曼编码 | 贪婪算法(Huffman Coding | Greedy Algo)-CSDN博客
方法:一种简单有效的方法是为数据生成一棵哈夫曼树,并使用类似于 Java 中的 TreeMap 的数据结构来存储符号和位长,以使信息始终保持排序。然后可以使用增量和按位左移运算来获取规范代码。
执行:
#include <bits/stdc++.h>
using namespace std;
// Nodes of Huffman tree
class Node {
public:
int data;
char c;
Node* left;
Node* right;
};
// Comparator class helps to compare the node
// on the basis of one of its attributes.
// Here we will be compared
// on the basis of data values of the nodes.
class Pq_compare {
public:
int operator() (Node* a, Node* b) {
return a->data - b->data;
}
};
class Canonical_Huffman {
// Treemap to store the
// code lengths(sorted) as keys
// and corresponding(sorted)
// set of characters as values
public:
static map<int, set<char>> data;
Canonical_Huffman() {
data = map<int, set<char>>();
}
// Recursive function
// to generate code lengths
// from regular Huffman codes
static void code_gen(Node* root, int code_length) {
if (root == nullptr)
return;
// base case; if the left and right are null
// then its a leaf node.
if (root->left == nullptr && root->right == nullptr) {
// check if key is present or not.
// If not present add a new treeset
// as value along with the key
data[code_length].insert(root->c);
return;
}
// Add 1 when going left or right.
code_gen(root->left, code_length + 1);
code_gen(root->right, code_length + 1);
}
static void testCanonicalHC(int n, char chararr[], int freq[]) {
// min-priority queue(min-heap).
priority_queue<Node*, vector<Node*>, Pq_compare> q;
for (int i = 0; i < n; i++) {
// creating a node object
// and adding it to the priority-queue.
Node* node = new Node();
node->c = chararr[i];
node->data = freq[i];
node->left = nullptr;
node->right = nullptr;
// add functions adds
// the node to the queue.
q.push(node);
}
// Create a root node
Node* root = nullptr;
// extract the two minimum values
// from the heap each time until
// its size reduces to 1, extract until
// all the nodes are extracted.
while (q.size() > 1) {
// first min extract.
Node* x = q.top();
q.pop();
// Second min extract
Node* y = q.top();
q.pop();
// new node f which is equal
Node* nodeobj = new Node();
// to the sum of the frequency of the two nodes
// assigning values to the f node
nodeobj->data = x->data + y->data;
nodeobj->c = '-';
// first extracted node as left child.
nodeobj->left = x;
// second extracted node as the right child.
nodeobj->right = y;
// marking the f node as the root node
root = nodeobj;
// add this node to the priority-queue.
q.push(nodeobj);
}
// creating a canonical Huffman object
Canonical_Huffman obj = Canonical_Huffman();
// generate code lengths by traversing the tree
code_gen(root, 0);
// Object array to the store the keys
auto arr = data;
// Set initial canonical code = 0
int c_code = 0, curr_len = 0, next_len = 0;
for (auto it = arr.begin(); it != arr.end(); it++) {
set<char> s = it->second;
// code length of current character
curr_len = it->first;
for (auto i = s.begin(); i != s.end(); i++) {
// Display the canonical codes
cout << *i << ":" << bitset<32>(c_code).to_string().substr(32 - curr_len, 32) << endl;
// if values set is not
// completed or if it is
// the last set set code length
// of next character as current
// code length
if (next(i) != s.end() || next(it) == arr.end())
next_len = curr_len;
else
next_len = next(it)->first;
// Generate canonical code
// for next character using
// regular code length of next
// character
c_code = (c_code + 1) << (next_len - curr_len);
}
}
}
};
map<int, set<char>> Canonical_Huffman::data;
// Driver code
int main() {
int n = 4;
char chararr[] = {'a', 'b', 'c', 'd'};
int freq[] = {10, 1, 15, 7};
Canonical_Huffman::testCanonicalHC(n, chararr, freq);
return 0;
}
输出:
c:0
a:10
b:110
d:111