赫夫曼(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++)的更多相关文章

  1. C#数据结构-赫夫曼树

    什么是赫夫曼树? 赫夫曼树(Huffman Tree)是指给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小.哈夫曼树(也称为最优二叉树)是带权路径长度最短的树,权值较大的结点 ...

  2. Java数据结构和算法(四)赫夫曼树

    Java数据结构和算法(四)赫夫曼树 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 赫夫曼树又称为最优二叉树,赫夫曼树的一个 ...

  3. Android版数据结构与算法(七):赫夫曼树

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 近期忙着新版本的开发,此外正在回顾C语言,大部分时间没放在数据结构与算法的整理上,所以更新有点慢了,不过既然写了就肯定尽力将这部分完全整理好分享出 ...

  4. javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题

    赫夫曼树及其应用 赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用. 最优二叉树(Huffman树) 1 基本概念 ① 结点路径:从树中一个结点到另一个结点的之间的分支 ...

  5. 【算法】赫夫曼树(Huffman)的构建和应用(编码、译码)

    参考资料 <算法(java)>                           — — Robert Sedgewick, Kevin Wayne <数据结构>       ...

  6. 赫夫曼树JAVA实现及分析

    一,介绍 1)构造赫夫曼树的算法是一个贪心算法,贪心的地方在于:总是选取当前频率(权值)最低的两个结点来进行合并,构造新结点. 2)使用最小堆来选取频率最小的节点,有助于提高算法效率,因为要选频率最低 ...

  7. puk1521 赫夫曼树编码

    Description An entropy encoder is a data encoding method that achieves lossless data compression by ...

  8. 数据结构-哈夫曼树(python实现)

    好,前面我们介绍了一般二叉树.完全二叉树.满二叉树,这篇文章呢,我们要介绍的是哈夫曼树. 哈夫曼树也叫最优二叉树,与哈夫曼树相关的概念还有哈夫曼编码,这两者其实是相同的.哈夫曼编码是哈夫曼在1952年 ...

  9. 高级数据结构---赫(哈)夫曼树及java代码实现

    我们经常会用到文件压缩,压缩之后文件会变小,便于传输,使用的时候又将其解压出来.为什么压缩之后会变小,而且压缩和解压也不会出错.赫夫曼编码和赫夫曼树了解一下. 赫夫曼树: 它是一种的叶子结点带有权重的 ...

随机推荐

  1. CentOS中配置SoftWareRaid磁盘冗余阵列

    (以vmware workstation为例) 1.关机添加一块硬盘 2.使用fdisk -l 可以看到 /dev/sdb硬盘设备 3.fdisk /dev/sdb配置磁盘分区,准备4个磁盘分区,用于 ...

  2. Asp.Net Core轻松入门之WebHost的配置

    在本篇文章中,我来讲一讲如何利用WebHost来加载配置文件和设置启动的Url 在前面的文章中讲过,ASP.Net Core应用程序会自动加载appsettings.json中的配置文件,那么如果配置 ...

  3. WPF自学入门(二)WPF-XAML布局控件

    上一篇介绍了xaml基本知识,我们已经知道了WPF简单的语法.那么接下来,我们要认识一下WPF的布局容器.布局容器可以使控件按照分类显示,我们一起来看看WPF里面可以使用哪些布局容器用来布局. 在WP ...

  4. 关于protected关键字

    protected,算是默认的访问作用域的超集,他们在相同包下时,都可以访问所声明的成员:但对于不同包的访问,默认访问域就不行,protected也必须是通过继承关系来访问. TestBase bas ...

  5. 【深入理解JVM】类加载器与双亲委派模型

    原文链接:http://blog.csdn.net/u011080472/article/details/51332866,http://www.cnblogs.com/lanxuezaipiao/p ...

  6. Python基础_函数闭包、调用、递归

    这节的主要内容是函数的几个用法闭包,调用.递归. 一.函数闭包 对闭包更好的理解请看:https://www.cnblogs.com/Lin-Yi/p/7305364.html 我们来看一个简单的例子 ...

  7. 【BZOJ4569】萌萌哒(并查集,倍增)

    [BZOJ4569]萌萌哒(并查集,倍增) 题面 BZOJ 题意: 有一个长度为\(n\)的数 给定\(m\)个限制条件 每次限制\(l1-r1\)与\(l2-r2\)是相同的 求出方案数 题解 如果 ...

  8. 【NOI2001】炮兵阵地(状态压缩,动态规划)

    题面 题面中有图片的存在,所以就贴个地址把 题解 简单题,,,, 原来一直觉得不会做... 现在发现是一道傻逼题 暴力压两行的状态 发现就需要滚一维. 然后暴力检查一下状态的可行性 DP检查MAX就可 ...

  9. QT 中setUserData和setProperty问题

    在5.9的官方文档里是找不到QObject中的setUserData 的,相反他提供了setProperty(). void setUserData(uint id, QObjectUserData ...

  10. Java集合框架(一)

    原文  http://www.jianshu.com/p/e31fb2600e4f 集合类存放于java.util包中,集合类存放的都是对象的引用,而非对象本身,出于表达上的便利,我们称集合中的对象就 ...