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. Java面向对象的继承

    继承也是面向对象的又一重要特性,继承是类于类的一种关系,通俗来说狗属于动物类,那么狗这个类就继承了动物类 java中的继承是单继承的,一个类只能继承与一个父类 子类继承父类之后,子类就拥有了父类的所有 ...

  2. struts1老古董配置

    <!--Struts1 struts-config.xml Demo --><?xml version="1.0" encoding="UTF-8&qu ...

  3. Linux多台服务器之间的文件共享

    由于项目有个图片上传和导入导出的模块,所以当项目通过集群方式部署的时候就要考虑文件共享问题. 文件共享要么就是通过统一的文件系统来管理,要么就是在系统之间做文件共享,前者扩展性比较好,可以随时随地加服 ...

  4. zabbix 修改管理员用户密码

    mysql> use zabbix mysql> desc users; +----------------+---------------------+------+-----+---- ...

  5. oracle 10g 学习之数据进行增删改查、数据库事务、约束(8)

    目标 通过本章学习,您将可以: l  使用 DML 语句 l  向表中插入数据 l  更新表中数据 l  从表中删除数据 l  控制事务 l  描述约束 l  创建和维护约束 数据控制语言 l     ...

  6. ER-STUDIO 6.5工具使用帮助的中文翻译

    转自于:http://yujingwang.blog.sohu.com/63362979.html 1       资料 ER-STUDIO的帮助(英文) 2       内容 2.1         ...

  7. 【现代程序设计】homework-03

    Homework-03 队员: 11061193 薛亚杰 11061192 周敏轩    11061190 李孟 0 材料阅读 我们三个人将以下材料仔细阅读,觉得十分受益.下面是我们的总结和分享: 1 ...

  8. 在getView方法产生给用户item的视图以及数据

    在getView方法产生给用户item的视图以及数据

  9. hdu 1430+hdu 3567(预处理)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1430 思路:由于只是8种颜色,所以标号就无所谓了,对起始状态重新修改标号为 12345678,对目标状 ...

  10. Android异步加载图像(含线程池,缓存方法)

    研究了android从网络上异步加载图像: (1)由于android UI更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程首先可能会想到以下方法. 在主线程中new 一个Han ...