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不是一笔庞大的数字。

 #include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <queue>
#include <fstream>
#include <sstream>
#include <string> using namespace std; class Huffman {
public:
Huffman() {}
~Huffman() {
freeTree(root);
} void init(string filename) {
ifstream in(filename.c_str());
string line;
while(getline(in, line)) {
stringstream ss(line);
char symbol;
float p;
ss >> symbol >> p;
symbolInfo.push_back(new Node(symbol, p));
}
root = buildTree2();
generateCodes(root, "");
} void print() const {
for (auto it = codes.begin(); it != codes.end(); ++it) {
cout << it->first << ": " << it->second << endl;
}
} string encode(string input) {
stringstream ans;
for (int i = ; i < input.length(); ++i) {
ans << codes[input[i]];
}
return ans.str();
} string decode(string input) {
if (root == NULL) return "";
stringstream ans;
for (int i = ; i < input.length(); ) {
Node* p = root;
for ( ; p != NULL; ++i) {
if (p->left == NULL && p->right == NULL) {
ans << p->symbol;
break;
}
if (input[i] == '') {
p = p->left;
} else if (input[i] == '') {
p = p->right;
} else {
return "";
}
}
}
return ans.str();
}
private:
struct Node {
char symbol;
float p;
Node* left;
Node* right;
Node(char s, float p, Node* l = NULL, Node* r = NULL):symbol(s), p(p), left(l), right(r) {}
}; static bool nodeCompare(Node* n1, Node* n2) {
return n1->p > n2->p;
} // O(nlgn)
Node* buildTree() {
if (symbolInfo.empty()) return NULL;
make_heap(symbolInfo.begin(), symbolInfo.end(), nodeCompare);
while (symbolInfo.size() > ) {
// get the smallest
Node* n1 = symbolInfo.front();
pop_heap(symbolInfo.begin(), symbolInfo.end(), nodeCompare);
symbolInfo.pop_back();
// get the second smallest
Node* n2 = symbolInfo.front();
pop_heap(symbolInfo.begin(), symbolInfo.end(), nodeCompare);
symbolInfo.pop_back(); Node* n3 = new Node('@', n1->p + n2->p, n1, n2);
symbolInfo.push_back(n3);
push_heap(symbolInfo.begin(), symbolInfo.end(), nodeCompare);
}
return symbolInfo[];
} class Comparator {
public:
bool operator() (const Node* n1, const Node* n2) const {
return n1->p > n2->p;
}
}; Node* buildTree2() {
if (symbolInfo.empty()) return NULL;
priority_queue<Node*, vector<Node*>, Comparator> queue(symbolInfo.begin(), symbolInfo.end());
while (queue.size() > ) {
Node* n1 = queue.top();
queue.pop();
Node* n2 = queue.top();
queue.pop();
Node* n3 = new Node('@', n1->p + n2->p, n1, n2);
queue.push(n3);
}
return queue.top();
} void freeTree(Node* p) {
if (p == NULL) return;
freeTree(p->left);
freeTree(p->right);
delete p;
p = NULL;
} void generateCodes(Node* p, string str) {
if (p == NULL) return;
if (p->left == NULL && p->right == NULL) {
codes[p->symbol] = str;
} generateCodes(p->left, str + "");
generateCodes(p->right, str + "");
} vector<Node*> symbolInfo;
unordered_map<char, string> codes;
Node* root;
}; int main() {
Huffman huffman;
huffman.init("input.txt");
huffman.print(); string str = "abcdabcdab";
string encode = huffman.encode(str);
cout << str << endl << encode << endl;
cout << huffman.decode(encode) << endl;
return ;
}

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

Huffman的更多相关文章

  1. 哈夫曼(huffman)树和哈夫曼编码

    哈夫曼树 哈夫曼树也叫最优二叉树(哈夫曼树) 问题:什么是哈夫曼树? 例:将学生的百分制成绩转换为五分制成绩:≥90 分: A,80-89分: B,70-79分: C,60-69分: D,<60 ...

  2. (转载)哈夫曼编码(Huffman)

    转载自:click here 1.哈夫曼编码的起源: 哈夫曼编码是 1952 年由 David A. Huffman 提出的一种无损数据压缩的编码算法.哈夫曼编码先统计出每种字母在字符串里出现的频率, ...

  3. [老文章搬家] 关于 Huffman 编码

    按:去年接手一个项目,涉及到一个一个叫做Mxpeg的非主流视频编码格式,编解码器是厂商以源代码形式提供的,但是可能代码写的不算健壮,以至于我们tcp直连设备很正常,但是经过一个UDP数据分发服务器之后 ...

  4. jpeg huffman coding table

    亮度DC系数的取值范围及序号:                                                               序号(size) 取值范围 0 0  1 - ...

  5. 优先队列实现Huffman编码

    首先把所有的字符加入到优先队列,然后每次弹出两个结点,用这两个结点作为左右孩子,构造一个子树,子树的跟结点的权值为左右孩子的权值的和,然后将子树插入到优先队列,重复这个步骤,直到优先队列中只有一个结点 ...

  6. Huffman树进行编码和译码

    //编码#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> ...

  7. Huffman Tree

    哈夫曼(Huffman)树又称最优二叉树.它是一种带权路径长度最短的树,应用非常广泛. 关于Huffman Tree会涉及到下面的一些概念: 1. 路径和路径长度路径是指在树中从一个结点到另一个结点所 ...

  8. Huffman的应用_Huffman编码

    //最优二叉树 #include <iostream> #include <iomanip> using namespace std; //定义结点类型 //[weight | ...

  9. Huffman树实现_详细注释

    //最优二叉树 #include <iostream> #include <iomanip> using namespace std; //定义结点类型 //[weight | ...

  10. Huffman编码

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstdio> #include <cstri ...

随机推荐

  1. 转mysql复制主从集群搭建

    最近搭了个主从复制,中间出了点小问题,排查搞定,记录下来 1环境:虚拟机:OS:centos6.5Linux host2 2.6.32-431.el6.x86_64 #1 SMP Fri Nov 22 ...

  2. cmd的rd命令简单解析

    我们知道在Windows下cmd命令行中"rd 文件夹名称"可以删除空目录,"del 文件名"可以删除文件,那么怎么删除一个非空文件夹呢,命令如下: 比如删除文 ...

  3. Delphi经验总结(2)

    Q: 怎么来改变ListBox的字体呢?就修改其中的一行. A: 先把ListBox1.Style 设成lbOwnerDrawFixed 然后在 OnDrawItem 事件下写下如下代码 proced ...

  4. DP:Apple Catching(POJ 2385)

    牛如何吃苹果 问题大意:一个叫Bessie的牛,可以吃苹果,然后有两棵树,树上苹果每分钟会掉一个,这只牛一分钟可以在两棵树中往返吃苹果(且不吃地上的),然后折返只能是有限次W,问你这只叫Bessie的 ...

  5. Xshell 中文乱码

    Xshell对于嵌入式开发来说,是个非常不错的工具.但或许都有过被中文显示为乱码的问题感觉有点不爽.解决方法其实很简单的,即把xshell编码方式改成UTF-8即可. [文件]–>[打开]–&g ...

  6. Android之圆角矩形

    安卓圆角矩形的定义 在drawable文件夹下,定义corner.xml <?xml version="1.0" encoding="utf-8"?> ...

  7. hdu 1005:Number Sequence(水题)

    Number Sequence Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)T ...

  8. M方法和D方法的区别

    M方法和D方法的区别 ThinkPHP 中M方法和D方法都用于实例化一个模型类,M方法 用于高效实例化一个基础模型类,而 D方法 用于实例化一个用户定义模型类. 使用M方法 如果是如下情况,请考虑使用 ...

  9. BZOJ 1072: [SCOI2007]排列perm 状态压缩DP

    1072: [SCOI2007]排列perm Description 给一个数字串s和正整数d, 统计s有多少种不同的排列能被d整除(可以有前导0).例如123434有90种排列能被2整除,其中末位为 ...

  10. SQLServer2005利用维护计划自动备份数据库

    经常性忘了给数据库备份,结果当数据库发生问题的时候,才发现备份是1个月以前的,那个后悔与懊恼还加惭愧啊,别提有对难受了.要认为的记住去备份比较难,每天事情又那么多,所以有了这个自动备份就不用愁了.先拷 ...