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/,讲得相当清晰了。