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()的效率比较低。

时间: 2024-10-11 22:18:22

Huffman的相关文章

Huffman树与编码

带权路径最小的二叉树称为最优二叉树或Huffman(哈夫曼树). Huffman树的构造 将节点的权值存入数组中,由数组开始构造Huffman树.初始化指针数组,指针指向含有权值的孤立节点. b = malloc(n*sizeof(BTreeNode)); for (i = 0; i < n; i++) { b[i] = malloc(sizeof(BTreeNode)); b[i]->data = a[i]; b[i]->left = NULL; b[i]->right = NU

Jcompress: 一款基于huffman编码和最小堆的压缩、解压缩小程序

前言 最近基于huffman编码和最小堆排序算法实现了一个压缩.解压缩的小程序.其源代码已经上传到github上面: Jcompress下载地址 .在本人的github上面有一个叫Utility的repository,该分类下面有一个名为Jcompress的目录便是本文所述的压缩.解压缩小程序的源代码.后续会在Utility下面增加其他一些实用的小程序,比如基于socket的文件断点下载小程序等等.如果你读了此文觉得还不错,不防给笔者的github点个star, 哈哈.在正式介绍Jcompres

数据结构之huffman树

#include <stdio.h> #include<stdlib.h> #define MAXBIT      99 #define MAXVALUE  9999 #define MAXLEAF     30 #define MAXNODE    MAXLEAF*2 -1 typedef struct  {     int bit[MAXBIT];     int start; } HCodeType;        /* 编码结构体 */ typedef struct {  

哈夫曼(Huffman)树及其应用

Huffman树又称最优树,是一类带权路径长度最短的树,带权路径长度为从该节点到树根之间的路径长度与节点上权值的成积. 那么如何构建一个Huffman树呢?就需要Huffman算法 1.利用给定的n个权值构成有n个二叉树的集合F,每个二叉树就只有一个带权值的根节点,其左右子树都为空. 2.选取两课根节点权值最小的树作为左右子树,且重置新的二叉树的根节点的权值为左右子树权值之和. 3.在集合F中删掉这两课子树,并将新得到的二叉树加入到F中去. 4.重复2.3操作直至F中只剩下一棵子树. 如下图:H

Greedy:Huffman编码

上次我在http://www.cnblogs.com/Philip-Tell-Truth/p/4787067.html这里,稍微讲了一下一种很普通的贪婪的算法,其实这个算法一开始是用来做压缩程序的,就是最著名的Huffman压缩算法 那么这个算法是怎么样的呢,我们知道,我们的字符如果以ASCII的形式表示的话(只算英文和数字),其实就是一堆二进制代码,一个char型字符占一个字节.那么我们想到怎么压缩呢? 因为我们的文档不可能打印那一些不可能出现在屏幕的字符,那么我们完全可以把这些字符去掉,然后

Huffman tree(赫夫曼树、霍夫曼树、哈夫曼树、最优二叉树)

flyfish 2015-8-1 Huffman tree因为翻译不同所以有其他的名字 赫夫曼树.霍夫曼树.哈夫曼树 定义引用自严蔚敏<数据结构> 路径 从树中一个结点到另一个结点之间的分支构成两个结点之间的路径. 路径长度 路径上的分支数目称作路径长度. 树的路径长度 树的路径长度就是从根节点到每一结点的路径长度之和. 结点的带权路径长度 结点的带权路径长度就是从该结点到根节点之间的路径长度与结点上权的乘积. 树的带权路径长度 树的带权路径长度就是树中所有叶子结点的带权路径长度之和,通常记做

Huffman编码学习笔记

主要是在学算导,觉得算导译到中国真是中国人民的福音. 一.编码 编码就是选择有意义的01串,令其首尾相接组成文本.我们并非可以随便挑选01串,原因在于它们是首尾相接的,这为我们识别造成了一些困难.比如说我们不能在文本000000中分清字符00与000. 一般我们使用的方式是定长字符:但更好的方式是前缀码,算导中写道"虽然我们这里不会证明,但与任何字符编码相比,前缀码确实可以保证达到最优数据压缩率.",这显然是一个flag,将来一定会有比前缀码更好的编码方式的. 二.Huffman编码便

哈夫曼编码(Huffman coding)的那些事,(编码技术介绍和程序实现)

前言 哈夫曼编码(Huffman coding)是一种可变长的前缀码.哈夫曼编码使用的算法是David A. Huffman还是在MIT的学生时提出的,并且在1952年发表了名为<A Method for the Construction of Minimum-Redundancy Codes>的文章.编码这种编码的过程叫做哈夫曼编码,它是一种普遍的熵编码技术,包括用于无损数据压缩领域.由于哈夫曼编码的运用广泛,本文将简要介绍: 哈夫曼编码的编码(不包含解码)原理 代码(java)实现过程 一

基于Huffman编码的压缩软件的Python实现

哈夫曼编码是利用贪心算法进行文本压缩的算法,其算法思想是首先统计文件中各字符出现的次数,保存到数组中,然后将各字符按照次数升序排序,挑选次数最小的两个元素进行连结形成子树,子树的次数等于两节点的次数之和,接着把两个元素从数组删除,将子树放入数组,重新排序,重复以上步骤.为了解压,在压缩时首先往文件中填入huffman编码的映射表的长度,该表的序列化字符串,编码字符串分组后最后一组的长度(编码后字符串长度模上分组长度),最后再填充编码后的字符串.本算法中以一个字节,8位作为分组长度,将编码后二进制

Huffman树编码-优先队列实现

Huffman编码是之前一道算法作业题,最近又要复习考试了,先把这个的代码再看一下吧. 算法原理很简单,使用优先队列将两个节点弹出,然后合并节点之后再入队列如此循环做下去即可. 主要问题在于树的修改问题,出队的树进行修改,然后将其合并成为一个新的树,在弹出的时候,树的两个节点地址已定,但是由于循环这两个地址会进行修改,所以单独写了一个函数用来进行树的复制. 1 #include<iostream> 2 #include<algorithm> 3 #include<vector