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. [Android Memory] App调试内存泄露之Context篇(上)

    转载自:http://www.cnblogs.com/qianxudetianxia/p/3645106.html Context作为最基本的上下文,承载着Activity,Service等最基本组件 ...

  2. Solr的函数查询(FunctionQuery)

    作用 通过函数查询让我们可以利用 numeric域的值或者与域相关的的某个特定的值的函数,来对文档进行评分. 如何使用 这里主要有两种方法可以使用函数查询,这两种方法都是通过solr http 接口的 ...

  3. php编译报错 configure: error: Please reinstall the libcurl distribution - easy.h should be in <curl-dir>/include/curl/

    ➜ php- yum install -y curl-devel

  4. C# Window Form播放音乐的4种方式

    C#播放背景音乐通常有四种方式: 1.播放系统事件声音 2.使用System.Media.SoundPlayer播放wav------------------------仅仅是对波形音乐 3.使用MC ...

  5. linux下安装pymssql

    WIN下安装PYMSSQL,由于我没有系统管理权限,无法安装, 那只好在LINUX下面安装罗.. 以下这个文章帮助我搞定. http://blog.csdn.net/five3/article/det ...

  6. THINKPHP 默认模板路径替换

    APP_PATH // 当前项目目录APP_NAME // 当前项目名称 ACTION_NAME // 当前操作名称 CACHE_PATH // 项目模版缓存目录 CONFIG_PATH //项目配置 ...

  7. JAVA学习:maven开发环境快速搭建

    转自:http://tech.it168.com/a2011/1204/1283/000001283307.shtml 最近,开发中要用到maven,所以对maven进行了简单的学习.因为有个mave ...

  8. PHP文件处理类

    /** * 文件读写类 * 读取时,支持跳过N个/行字符然后再读取M个/行字符 * 支持每次读取时使用回调函数 * * 示例: * $file = new File('a.txt', 'r'); * ...

  9. 安装完最小化 RHEL/CentOS 7 后需要做的 30 件事情(六)码农网

    30. 用密码保护 GRUB 用密码保护你的 boot 引导程序这样你就可以在启动时获得额外的安全保障.同时你也可以在实物层面获得保护.通过在引导时给 GRUB 加锁防止任何无授权访问来保护你的服务器 ...

  10. JNI,NDK

    jni的调用过程 1)安装和下载Cygwin,下载Android NDK 2)在ndk项目中JNI接口的设计 3)使用C/C++实现本地方法 4)JNI生成动态链接库.so文件 5)将动态链接库复制到 ...