Huffman

huffman是非常基础的压缩算法。

实现霍夫曼树的方式有很多种,可以使用优先队列(Priority Queue)简单达成这个过程,给与权重较低的符号较高的优先级(Priority),算法如下:

⒈把n个终端节点加入优先队列,则n个节点都有一个优先权Pi,1 ≤ i ≤ n
⒉如果队列内的节点数>1,则:

⑴从队列中移除两个最大的Pi节点,即连续做两次remove(max(Pi), Priority_Queue)
⑵产生一个新节点,此节点为(1)之移除节点之父节点,而此节点的权重值为(1)两节点之权重和
⑶把(2)产生之节点加入优先队列中
⒊最后在优先队列里的点为树的根节点(root)

而此算法的时间复杂度( Time Complexity)为O(n log n);因为有n个终端节点,所以树总共有2n-1个节点,使用优先队列每个循环须O(log n)。
此外,有一个更快的方式使时间复杂度降至线性时间(Linear Time)O(n),就是使用两个队列(Queue)创件霍夫曼树。第一个队列用来存储n个符号(即n个终端节点)的权重,第二个队列用来存储两两权重的合(即非终端节点)。此法可保证第二个队列的前端(Front)权重永远都是最小值,且方法如下:

⒈把n个终端节点加入第一个队列(依照权重大小排列,最小在前端)
⒉如果队列内的节点数>1,则:

⑴从队列前端移除两个最低权重的节点
⑵将(1)中移除的两个节点权重相加合成一个新节点
⑶加入第二个队列
⒊最后在第一个队列的节点为根节点

虽然使用此方法比使用优先队列的时间复杂度还低,但是注意此法的第1项,节点必须依照权重大小加入队列中,如果节点加入顺序不按大小,则需要经过排序,则至少花了O(n log n)的时间复杂度计算。
但是在不同的状况考量下,时间复杂度并非是最重要的,如果我们今天考虑英文字母的出现频率,变量n就是英文字母的26个字母,则使用哪一种算法时间复杂度都不会影响很大,因为n不是一笔庞大的数字。

  1 #include <iostream>
  2 #include <algorithm>
  3 #include <unordered_map>
  4 #include <vector>
  5 #include <queue>
  6 #include <fstream>
  7 #include <sstream>
  8 #include <string>
  9 
 10 using namespace std;
 11 
 12 class Huffman {
 13     public:
 14         Huffman() {}
 15         ~Huffman() {
 16             freeTree(root);
 17         }
 18 
 19         void init(string filename) {
 20             ifstream in(filename.c_str());
 21             string line; 
 22             while(getline(in, line)) {
 23                 stringstream ss(line);
 24                 char symbol; 
 25                 float p;
 26                 ss >> symbol >> p;
 27                 symbolInfo.push_back(new Node(symbol, p));
 28             }
 29             root = buildTree2();
 30             generateCodes(root, "");
 31         }
 32 
 33         void print() const {
 34             for (auto it = codes.begin(); it != codes.end(); ++it) {
 35                 cout << it->first << ": " << it->second << endl;
 36             }
 37         }
 38 
 39         string encode(string input) {
 40             stringstream ans;
 41             for (int i = 0; i < input.length(); ++i) {
 42                 ans << codes[input[i]];
 43             }
 44             return ans.str();
 45         }
 46 
 47         string decode(string input) {
 48             if (root == NULL) return "";
 49             stringstream ans;
 50             for (int i = 0; i < input.length(); ) {
 51                 Node* p = root;
 52                 for ( ; p != NULL; ++i) {
 53                     if (p->left == NULL && p->right == NULL) {
 54                         ans << p->symbol;
 55                         break;
 56                     }
 57                     if (input[i] == '0') {
 58                         p = p->left;
 59                     } else if (input[i] == '1') {
 60                         p = p->right;
 61                     } else {
 62                         return "";
 63                     }
 64                 }
 65             }
 66             return ans.str();
 67         }
 68     private:
 69         struct Node {
 70             char symbol;
 71             float p;
 72             Node* left;
 73             Node* right;
 74             Node(char s, float p, Node* l = NULL, Node* r = NULL):symbol(s), p(p), left(l), right(r) {}
 75         };
 76         
 77         static bool nodeCompare(Node* n1, Node* n2) {
 78             return n1->p > n2->p;
 79         }
 80     
 81         // O(nlgn)
 82         Node* buildTree() {
 83             if (symbolInfo.empty()) return NULL;
 84             make_heap(symbolInfo.begin(), symbolInfo.end(), nodeCompare);
 85             while (symbolInfo.size() > 1) {
 86                 // get the smallest
 87                 Node* n1 = symbolInfo.front();
 88                 pop_heap(symbolInfo.begin(), symbolInfo.end(), nodeCompare);
 89                 symbolInfo.pop_back();
 90                 // get the second smallest
 91                 Node* n2 = symbolInfo.front();
 92                 pop_heap(symbolInfo.begin(), symbolInfo.end(), nodeCompare);
 93                 symbolInfo.pop_back();
 94                 
 95                 Node* n3 = new Node('@', n1->p + n2->p, n1, n2);
 96                 symbolInfo.push_back(n3);
 97                 push_heap(symbolInfo.begin(), symbolInfo.end(), nodeCompare);
 98             }
 99             return symbolInfo[0];
100         }
101 
102         class Comparator {
103             public:
104             bool operator() (const Node* n1, const Node* n2) const {
105                 return n1->p > n2->p;
106             }
107         };
108 
109         Node* buildTree2() {
110             if (symbolInfo.empty()) return NULL;
111             priority_queue<Node*, vector<Node*>, Comparator> queue(symbolInfo.begin(), symbolInfo.end());
112             while (queue.size() > 1) {
113                 Node* n1 = queue.top(); 
114                 queue.pop();
115                 Node* n2 = queue.top();
116                 queue.pop();
117                 Node* n3 = new Node('@', n1->p + n2->p, n1, n2);
118                 queue.push(n3);    
119             }
120             return queue.top();
121         }
122     
123         void freeTree(Node* p) {
124             if (p == NULL) return;
125             freeTree(p->left);
126             freeTree(p->right);
127             delete p;
128             p = NULL;
129         }
130 
131         void generateCodes(Node* p, string str) {
132             if (p == NULL) return;
133             if (p->left == NULL && p->right == NULL) {
134                 codes[p->symbol] = str;
135             }
136             
137             generateCodes(p->left, str + "0");
138             generateCodes(p->right, str + "1");
139         }
140 
141         vector<Node*> symbolInfo;
142         unordered_map<char, string> codes;
143         Node* root;
144 };
145 
146 int main() {
147     Huffman huffman;
148     huffman.init("input.txt");
149     huffman.print();
150 
151     string str = "abcdabcdab";
152     string encode = huffman.encode(str);
153     cout << str << endl << encode << endl;
154     cout << huffman.decode(encode) << endl;
155     return 0;
156 }

这里用了stl的make_heap之类的函数,也尝试用priority_queue。但是两指重构的比较器的形式不同。注意priority_queue的比较器是作为模板参数传进去的,而且是定义成类。

可以用两个简单队列实现O(n)的算法,前提是一开始频率已经排好序。用vector是可以模拟queue,但是pop_front()的效率比较低。

 huffman的正确性证明可以看这篇:http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/,讲得相当清晰了。

原文地址:https://www.cnblogs.com/linyx/p/3986437.html