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、C++】LeetCode 018 4Sum

    Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + d = tar ...

  2. Emacs 24.3 配置JDEE(http://blog.csdn.net/csfreebird/article/details/19033939)

    最近要重回Java编程,所以打算在最新版本的Emacs 24.3上配置JDEE,听说会有些问题,特此记录安装过程. Emacs 24.3内置了CEDET, 版本是2.0, 这是一个让人困惑的事情,因为 ...

  3. UVA 10325 The Lottery( 容斥原理)

    The Sports Association of Bangladesh is in great problem with their latest lottery `Jodi laiga Jai'. ...

  4. 堆的判断(codevs 2879)

    2879 堆的判断  时间限制: 1 s  空间限制: 32000 KB  题目等级 : 黄金 Gold 题解  查看运行结果     题目描述 Description 堆是一种常用的数据结构.二叉堆 ...

  5. 一、HTML和CSS基础--HTML+CSS基础课程--第6部分

    第十一章 CSS代码缩写,占用更少的带宽 盒模型代码简写
:还记得在讲盒模型时外边距(margin).内边距(padding)和边框(border)设置上下左右四个方向的边距是按照顺时针方向设置的:上 ...

  6. telnet 测试端口是否打开

    [root@mysqld ~]# yum list |grep telnet telnet.x86_64 1:0.17-48.el6 @base telnet-server.x86_64 1:0.17 ...

  7. Android文件Apk下载变ZIP压缩包解决方案

    [root@ conf]# pwd /alidata/server/nginx/conf [root@ conf]# vi mime.types application/vnd.android.pac ...

  8. 批量update

    参见http://lj.soft.blog.163.com/blog/static/7940248120109215191358/ 例子:update CarSeriesDetail set clic ...

  9. LeetCode之LRU Cache 最近最少使用算法 缓存设计

    设计并实现最近最久未使用(Least Recently Used)缓存. 题目描述: Design and implement a data structure for Least Recently ...

  10. errno 错误码

    转自:http://baike.baidu.com/link?url=uX-q7K-TTL-5AFNT-hjhAP6fvgAwvsNkIMqJqJ3GEspYgtQXsovEEzpqmQ3ZmAgql ...