【数据结构】赫夫曼树的实现和模拟压缩(C++)
赫夫曼(Huffman)树,由发明它的人物命名,又称最优树,是一类带权路径最短的二叉树,主要用于数据压缩传输。
赫夫曼树的构造过程相对比较简单,要理解赫夫曼树,要先了解赫夫曼编码。
对一组出现频率不同的字符进行01编码,如果设计等长的编码方法,不会出现混淆的方法,根据规定长度的编码进行翻译,有且只有一个字符与之对应。比如设计两位编码的方法,A,B,C,D字符可以用00-11来表示,接收方只要依次取两位编码进行翻译就可以得出原数据,但如果原数据只由n个A组成的,那发出的编码就是2n个0组成,这样的压缩效率并不是十分理想(两位编码只是举例,实际上表示所有ASCII字符需要255个编码,则需要使用八位编码方式,这样如果原数据字符重复率较高的话,效果自然不理想)。如果设计的编码分别为:0,00,1和01,这样我发送了n个A组成的数据,这样一来只需要发送n个0,虽然会节省很多的数据传输量,但如果我传送了“0000”,则有多种翻译方法,可以翻译为“AAAA”,“ABB”,也不是可行的解决方案。如果有一种设计方式,用较短的编码表示出现频率较高的字符,用相对较长的编码表示出现频率相对较高的字符,而每个字符的解码不产生混淆,效果将会十分理想,而赫夫曼编码就是满足这种要求的编码方式,这种编码方式也叫前缀编码。
以一颗二叉树作为基础,规定左分支位0,右分支为1,以每个出现的字符作为各叶子节点的权值构造一颗赫夫曼树。在一颗赫夫曼树之中,从根节点出发,每经过一个节点(通过左子树或者右子树),解码出来的字符也随之一步步确定,因为从一个节点开始,通过左子树和右子树的结果是互斥的。
如左图所示,如果一开始从根节点开始,往右子树探索,则结果不可能会出现AB两种可能,再接下去往左子树探索就会得到C,往右子树就会得到D,保证在得到某一个字符之前的前缀编码不会是另一个字符的编码。回到前一段,如果我们把权值(出现频率)较高的节点放在深度较低的叶子节点上,权值较低的节点因为构造二叉树需要放的相对深一些,则可构造一颗赫夫曼数,具体方法如下:
1)根据给定的n个权值{w1,w2,...,wn}构成n棵二叉树的集合F = {T1,T2,...,Tn},其中每棵二叉树Ti中只有一个带权位wi的根节点,其中左右子树均为空。
2)在F中选取两颗根节点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根节点的权值为其左,右子树上根节点的权值之和。
3)在F中删除这两棵树,同时将新的二叉树加入F中
4)重复(2)和(3)的过程,直到F只含一棵树为止。这棵树便是赫夫曼树。
文字描述可能有点费解,以下用一些示意图表示,整个过程可表示为:

整个过程下来能够保证权值较小的叶子节点比较先被选择到,从而保证一套编码下来权值较大的叶子节点编码较短(因为深度比较低),而权值较小的叶子节点编码相对就长一点。因为权值小的字符出现概率低,所以平均下来这种编码方式能够达到较高的压缩率。
接下来是算法代码的实现,经过以上的理论,我们要完成的任务有:
1)用一个初始的字符串去构建一棵赫夫曼树(统计每个字符的出现次数作为权值),前提是这个字符串中字符出现的概率必须和数据交换时候字符出现的概率大致匹配。
2)完成赫夫曼树的构建(常用的字符叶子结点深度较浅,不常用的叶子结点深度较深),并求出每个字符对应的编码,存在一个Map数据结构中,这个Map暂称编码表
3)A要把一段数据传输给B,A对B说,我给你一棵赫夫曼树,到时候你就用这棵树对我发给你的内容进行解码,然后根据编码表求出原数据的编码并发给B
4)B根据收到的编码以赫夫曼树为基础进行解码,最后得出原数据内容
首先是树节点的数据结构:
class Node{
public:
Type val;
int weight;
Node *parent,*left,*right;
Node(Type v,int w):val(v),weight(w),parent(NULL),left(NULL),right(NULL){}
Node(int w):weight(w),parent(NULL),left(NULL),right(NULL){}
};
val为初始化字符的每个字符,weight,权值,即出现次数,left和right分别指向节点的左右子树,parent指向父节点。
构建赫夫曼树的核心代码为:
void builTree(){
int n = nodes.size()*-;
for(int i = nodes.size();i<n;i++){
vector<int> ret = selMin2(nodes);
nodes.push_back(new Node(nodes[ret[]]->weight+nodes[ret[]]->weight));
nodes[ret[]]->parent = nodes.back();
nodes[ret[]]->parent = nodes.back();
nodes.back()->left = nodes[ret[]];
nodes.back()->right = nodes[ret[]];
}
root = nodes.back();
}
初始字符串经过统计之后存在nodes容器中,定义为 vector<Node*> nodes ,因为开始构建的时候nodes存储的全为叶子节点,所以预先求出整棵赫夫曼树总的节点数为 int n = nodes.size()*-; (一棵完全二叉树的总节点个数为所有叶子节点个数 x 2 - 1)其中selMin2()函数求出权值最小的且没有父节点的两个节点,返回这两个节点的下表,我自己写的复杂度为O(n),没有特别的优化过程。
完成赫夫曼树的构建后,从根节点开始求出每个叶子节点(字符)的编码:
void getcode(){
//encoding all the character recursively
if(root != NULL)
Recur(root,"");
}
void Recur(Node *node,string c){
if(!node->left && !node->right){
//leaf node
code[node->val] = c;
}
else{
Recur(node->left,c+'');
Recur(node->right,c+'');
}
}
对应的编码和解码过程代码如下:
string encoding(string plain){
string res;
for(auto elem : plain)
res+=code[elem];
return res;
}
string decoding(string enci){
string res;
if(NULL != root){
int i= ;
Node *n = root;
while(i<enci.size()){
if(enci[i] == '')
n = n->left;
else
n = n->right;
if(n && !n->left && !n->right){
res.push_back(n->val);
n = root;
}
i++;
}
}
return res;
}
编码过程中的code为unordered_map类型,即为前文所提到的编码表。
一开始用”1111111111222222222333333334444444555555666667777888990”作为初始化字符串来构建赫夫曼树,写两个工具分别用于编码和解码,效果如下图所示:

此处只是一个模拟过程,所以编码结果我在代码中只是用字符串表示,实际上在传输过程中是用bit表示(原本8个字符32位现在只需要22位bit),所以我的代码省略了A把编码转为bit表示的编码,然后B根据编码信息求出01字符串。
以上为本人对赫夫曼编码压缩(个人认为还能起到加密作用,此时赫夫曼树作为密钥)的拙见,文中的错误和不妥之处欢迎大家指出斧正。
尊重知识产权,转载引用请通知作者并注明出处!
【数据结构】赫夫曼树的实现和模拟压缩(C++)的更多相关文章
- C#数据结构-赫夫曼树
什么是赫夫曼树? 赫夫曼树(Huffman Tree)是指给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小.哈夫曼树(也称为最优二叉树)是带权路径长度最短的树,权值较大的结点 ...
- Java数据结构和算法(四)赫夫曼树
Java数据结构和算法(四)赫夫曼树 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 赫夫曼树又称为最优二叉树,赫夫曼树的一个 ...
- Android版数据结构与算法(七):赫夫曼树
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 近期忙着新版本的开发,此外正在回顾C语言,大部分时间没放在数据结构与算法的整理上,所以更新有点慢了,不过既然写了就肯定尽力将这部分完全整理好分享出 ...
- javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题
赫夫曼树及其应用 赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用. 最优二叉树(Huffman树) 1 基本概念 ① 结点路径:从树中一个结点到另一个结点的之间的分支 ...
- 【算法】赫夫曼树(Huffman)的构建和应用(编码、译码)
参考资料 <算法(java)> — — Robert Sedgewick, Kevin Wayne <数据结构> ...
- 赫夫曼树JAVA实现及分析
一,介绍 1)构造赫夫曼树的算法是一个贪心算法,贪心的地方在于:总是选取当前频率(权值)最低的两个结点来进行合并,构造新结点. 2)使用最小堆来选取频率最小的节点,有助于提高算法效率,因为要选频率最低 ...
- puk1521 赫夫曼树编码
Description An entropy encoder is a data encoding method that achieves lossless data compression by ...
- 数据结构-哈夫曼树(python实现)
好,前面我们介绍了一般二叉树.完全二叉树.满二叉树,这篇文章呢,我们要介绍的是哈夫曼树. 哈夫曼树也叫最优二叉树,与哈夫曼树相关的概念还有哈夫曼编码,这两者其实是相同的.哈夫曼编码是哈夫曼在1952年 ...
- 高级数据结构---赫(哈)夫曼树及java代码实现
我们经常会用到文件压缩,压缩之后文件会变小,便于传输,使用的时候又将其解压出来.为什么压缩之后会变小,而且压缩和解压也不会出错.赫夫曼编码和赫夫曼树了解一下. 赫夫曼树: 它是一种的叶子结点带有权重的 ...
随机推荐
- Flex读取txt文件中的内容(二)
Flex读取txt文件中的内容 自动生成的文件 LoadTxt-app.xml: <?xml version="1.0" encoding="utf-8" ...
- freemarker写select组件(三)
freemarker写select组件 1.宏定义 <#macro select id datas value="" key="" text=" ...
- 用DirectShow实现视频采集-流程构建
DirectShow作为DirectX的一个子集,它为用户提供了强大.方便的多媒体开接口,并且它拥有直接操作硬件的能力,这使得它的效率远胜于用GDI等图形方式编写的多媒体程序.前面一篇文章已经对Dir ...
- 小白学爬虫-在无GUI的CentOS上使用Selenium+Chrome
爬虫代理IP由芝麻HTTP服务供应商提供各位小伙伴儿的采集日常是不是被JavaScript的各种点击事件折腾的欲仙欲死啊?好不容易找到个Selenium+Chrome可以解决问题! 但是另一个▄█▀█ ...
- jquery初始化的三种方式
第一种 $(document).ready(function(){ alert("第一种方法."); }); 第二种 $(function(){ alert("第二种方法 ...
- Windows DLL资料整理
1.使用Visual C++ 6.0创建dll 2. 函数的调用规则(__cdecl,__stdcall,__fastcall,__pascal) 要点: 1. 如果你的程序中没有涉及可变参数,最好使 ...
- Kettle根据时间戳同步数据实现
1 Kettle总体步骤 由于Kettle自身的特殊性以及在多个步骤中kettle自身处理数据库事务的特殊性,尝试了很多种方案,最终确定暂使用如下方案. 1.使用此方案可以解决kettle本身数据库事 ...
- c#多线程同步之EventWaitHandle使用
有这么一个场景,我需要借助windows剪贴板把数据插入到word域中. 实现步骤: 1.把剪贴板数据保存到变量. 2.使用剪贴板实现我们的业务. 3.把变量里的数据存回剪贴板. 但是结果却令人诧异, ...
- TypeScript入门知识三(函数新特性)
一,Rest and Spread操作符: 用来声明任意数量的方法参数也就是"..."操作符 输出结果: 18 jajj 89 function test (a, b, c) { ...
- MySQL增量订阅&消费组件Canal POC
POC的目的:1.与MYSQL的对接方式,配置文档2.订阅的延迟3.订阅后宕机消息会不会丢失4.能不能从指定的点开始重新订阅5.高并发写入的时候,日志的顺序是否还能保持,不考虑消费的情况订阅是否会延迟 ...