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不是一笔庞大的数字。
#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的更多相关文章
- 哈夫曼(huffman)树和哈夫曼编码
哈夫曼树 哈夫曼树也叫最优二叉树(哈夫曼树) 问题:什么是哈夫曼树? 例:将学生的百分制成绩转换为五分制成绩:≥90 分: A,80-89分: B,70-79分: C,60-69分: D,<60 ...
- (转载)哈夫曼编码(Huffman)
转载自:click here 1.哈夫曼编码的起源: 哈夫曼编码是 1952 年由 David A. Huffman 提出的一种无损数据压缩的编码算法.哈夫曼编码先统计出每种字母在字符串里出现的频率, ...
- [老文章搬家] 关于 Huffman 编码
按:去年接手一个项目,涉及到一个一个叫做Mxpeg的非主流视频编码格式,编解码器是厂商以源代码形式提供的,但是可能代码写的不算健壮,以至于我们tcp直连设备很正常,但是经过一个UDP数据分发服务器之后 ...
- jpeg huffman coding table
亮度DC系数的取值范围及序号: 序号(size) 取值范围 0 0 1 - ...
- 优先队列实现Huffman编码
首先把所有的字符加入到优先队列,然后每次弹出两个结点,用这两个结点作为左右孩子,构造一个子树,子树的跟结点的权值为左右孩子的权值的和,然后将子树插入到优先队列,重复这个步骤,直到优先队列中只有一个结点 ...
- Huffman树进行编码和译码
//编码#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> ...
- Huffman Tree
哈夫曼(Huffman)树又称最优二叉树.它是一种带权路径长度最短的树,应用非常广泛. 关于Huffman Tree会涉及到下面的一些概念: 1. 路径和路径长度路径是指在树中从一个结点到另一个结点所 ...
- Huffman的应用_Huffman编码
//最优二叉树 #include <iostream> #include <iomanip> using namespace std; //定义结点类型 //[weight | ...
- Huffman树实现_详细注释
//最优二叉树 #include <iostream> #include <iomanip> using namespace std; //定义结点类型 //[weight | ...
- Huffman编码
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstdio> #include <cstri ...
随机推荐
- canva实践小实例 —— 马赛克效果
前面给大家带来了操作像素的API,此时此刻,我觉得应该配以小实例来进行进一步的说明和演示,以便给大家带来更宽广的视野和灵感,你们看了我的那么多的文章,应该是懂我的风格,废话不多说,进入正题: 这次给大 ...
- Java for LeetCode 079 Word Search
Given a 2D board and a word, find if the word exists in the grid. The word can be constructed from l ...
- Java for LeetCode 048 Rotate Image
You are given an n x n 2D matrix representing an image. Rotate the image by 90 degrees (clockwise). ...
- maven的一些依赖
maven的一些依赖: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://w ...
- php 指针遍历、预定义数组和常用函数
<?php /*//定义 $attr = array(1,2,3); $attr[] = 1; $attr = array("one"=>"hello&quo ...
- 二进制日志BINARY LOG清理
mysql> show master logs; +------------------+-----------+ | Log_name | File_size | +------------- ...
- UVa 11991:Easy Problem from Rujia Liu?(STL练习,map+vector)
Easy Problem from Rujia Liu? Though Rujia Liu usually sets hard problems for contests (for example, ...
- 1、揭秘通用平台的 HttpClient (译)
原文链接:Demystifying HttpClient APIs in the Universal Windows Platform 正打算翻译这篇文章时,发现园子里已经有朋友翻译过了,既然已经开始 ...
- 让/etc/profile文件修改后立即生效(转)
方法1:让/etc/profile文件修改后立即生效 ,可以使用如下命令:# . /etc/profile注意: . 和 /etc/profile 有空格方法2:让/etc/profile文件修改后 ...
- 对Android项目中的文件夹进行解释
对Android项目中的文件夹进行解释: · src:里面存放的是Activity程序,或者是以后的其他组件,在此文件夹之中建立类的时候一定要注意,包名称不能是一级. · gen:此文件夹中的内容是自 ...