哈夫曼树

哈夫曼树,最优二叉树,带权路径长度(WPL)最短的树。它没有度为1的点,是一棵严格的二叉树(满二叉树)。

何谓‘带权路径长度’

了解哈夫曼树,我们首先要知道树的几个相关术语,并了解什么是WPL。

  • 路径:从树中一个结点到另一个结点之间的分支构成两个结点之间的路径
  • 路径长度:路径上的分支数目
  • 树的路径长度:从树根到每一个结点的路径长度之和
  • 树的带权路径长度:树中所有叶子结点的带权路径之和WPL=∑k=1nwklkWPL=\sum_{k=1}^n{w_kl_k}WPL=∑k=1n​wk​lk​,其中wkw_kwk​为第k个结点的权值
  • 最优二叉树(哈夫曼树):WPL最小的二叉树

注:树的WPL这个概念非常重要,这个公式直接产生了哈夫曼编码数据压缩的应用

哈夫曼算法

  1. 根据给定的n个权值{w1w_1w1​,w2w_2w2​,···,wnw_nwn​}构成n棵二叉树的集合F={T1T_1T1​,T2T_2T2​,···,TnT_nTn​},其中每棵二叉树TiT_iTi​中只有一个带权为wiw_iwi​的根结点,其左右子树均空
  2. 在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左。右子树上根结点的权值之和。
  3. 在F中删除这两棵树,同时将新得到的二叉树加入F中。
  4. 重复(2)(3),直到F只含一棵树为止。这棵树即为哈夫曼树

哈夫曼编码

在谈论哈夫曼编码之前,我们先来了解一下编码的相关概念。

等长的二进制编码

对于一个无记忆离散信源中每一个符号,若采用相同长度的不同码字代表相应的符号,就称为等长编码。如:A/B/C/D采用00/01/10/11编码就是一个等长的编码。

不等长的二进制编码

在数据传输过程中,我们需要压缩文件,就可以通过不等长编码的方式。

哈夫曼编码就是一种不等长的编码,它根据字频分析结果决定每个字符的编码长度,出现概率高的字符编码短,出现概率低的编码长。哈夫曼研究这种最优树的目的是为了解决当年远距离通信(主要是电报)的数据传输的最优化问题。

但是文件压缩了之后我们需要解压才能得到想要的文件,编码了之后我们还需解码。下面我们来看一下一个不等长编码的二进制编码的示例:同样我们给对A/B/C/D编码,分别为0/00/1/01,很容易看出如果我们收到电文0000,我们则无法得到唯一的译文(0000可以翻译成AAAA、BB等等)

一般来说,若要实现无失真的编码,这不但要求信源符号与码字是一一对应的,而且要求码符号序列的反变换也是唯一的。也就是说,一个码的任意一串有限长的码符号序列(码字)只能被唯一地翻译成所对应的信源符号序列。不然我们的译文就会有二义性,这样的编码是没有意义的。

为了解决不等长二进制编码译码的二义性问题,我们需要引入一个概念前缀码

前缀码是一种不等长的编码,它保证任何一个字符的编码都不是另一个编码的前缀。比如A(0),B(10),C(110),D(1111)就是一组前缀码。

哈夫曼树构造哈夫曼编码

在编写一个具体的算法之前,我们总要先思考算法的载体——数据如何被存储。哈夫曼算法的关键数据结构为哈夫曼树,所以接下来我们来思考哈夫曼树的存储。

哈夫曼树的存储

我们采用孩子双亲表示法存储哈夫曼树

typedef struct{unsigned int weight;unsigned int parent, lchild, rchild;
} HTNode, *HuffmanTree; //动态分配数组存储哈夫曼树

定义一个char型二级指针存储哈夫曼编码

typedef char **HuffmanCode;

哈夫曼树的构造

哈夫曼树的构造思想就是利用二叉树的非叶子结点来实现二叉树的结构(:我在说什么

void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n)
{if(n<=1) return;// 初始化哈夫曼树m = 2*n-1;HT = (HuffmanTree)malloc((m+1)*sizeof(HTNode));HuffmanTree p; int i; for(p = HT, i = 1; i <= n; ++i, ++p, ++w) *p = {*w, 0, 0, 0};for(; i <= m; ++i; ++p) *p = {0, 0, 0, 0};// 构造哈夫曼树for(i = n+1; i <= m; ++i){select(HT, i-1, s1, s2); //选择parent为0且权值最小的两个结点s1,s2HT[s1].parent = i; HT[s2].parent = i;HT[i].lchild = s1; HT[i].rchild = s2;HT[i].weight = HT[s1].weight + HT[s1].weight;}
}

下面对这段代码的一些细节略加解释:

  1. 传参时使用引用实现了直接对函数外变量的修改,解决了C函数按值传递且只有单返回值的弊端
  2. *w表示待编码的字符数组,n表示字符个数,m=2n-1表示哈夫曼树的结点个数,至于分配m+1个结点的空间,则是为了将结点标号和数组下标对应起来,数组的0号元素弃置不用(树的常见存储
  3. malloc函数的返回值为void*,需要将其强转成HTNode的指针,即HuffmanTree类型
  4. HuffmanTree p是为遍历并初始化动态分配的内存块而设的HTNode指针int i是为了循环而设的辅助变量

哈夫曼编码

以下代码依据从叶子到根逆向求每个字符的哈夫曼编码

// 哈夫曼编码
HC = (HuffmanCode)malloc((n+1)*sizeof(char *));
cd = (char*)malloc(n*sizeof(char));
cd[n-1] = "\0";
for(int i = 1; i <= n; ++i)
{start = n-1;for(int c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent)if(HT[f].lchild == c) cd[--start] = "0";else cd[--start] = "1";HC[i] = (char*)malloc((n-start)*sizeof(char));strcpy(HC[i], &cd[start]);
}
free(cd);

注记:

  1. 同理,开辟 n+1个char* 的空间为了将数组下标i与第i个待编码的字符对应起来
  2. cd为辅助数组,用于给单个字符编码,由于一开始不知道每个字符的编码长度,所以使用cd开足够大的编码空间n,最后将编码完成后就将确定长度的编码串拷贝到哈夫曼表HC

最后献上Huffman编码算法的完整代码

typedef struct{unsigned int weight;unsigned int parent, lchild, rchild;
} HTNode, *HuffmanTree; //哈夫曼树结点和根结点typedef char **HuffmanCode; //哈夫曼编码表void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n)
{if(n<=1) return;// 初始化哈夫曼树m = 2*n-1;HT = (HuffmanTree)malloc((m+1)*sizeof(HTNode));HuffmanTree p; int i; for(p = HT, i = 1; i <= n; ++i, ++p, ++w) *p = {*w, 0, 0, 0};for(; i <= m; ++i; ++p) *p = {0, 0, 0, 0};// 构造哈夫曼树for(i = n+1; i <= m; ++i){select(HT, i-1, s1, s2); //选择parent为0且权值最小的两个结点s1,s2HT[s1].parent = i; HT[s2].parent = i;HT[i].lchild = s1; HT[i].rchild = s2;HT[i].weight = HT[s1].weight + HT[s1].weight;}// 哈夫曼编码HC = (HuffmanCode)malloc((n+1)*sizeof(char *));cd = (char*)malloc(n*sizeof(char));cd[n-1] = "\0";for(int i = 1; i <= n; ++i){start = n-1;for(int c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent)if(HT[f].lchild == c) cd[--start] = "0";else cd[--start] = "1";HC[i] = (char*)malloc((n-start)*sizeof(char));strcpy(HC[i], &cd[start]);}free(cd);
}
习题

例1 电文的译码:分解电文中字符串,从根结点出发,按字符0/1确定左、右孩子,直到叶子结点。

char text[maxn] = "101110110111";for(int i = 0; i < text.length(); i++)
{HuffmanTree p; // 辅助变量指向哈夫曼树首地址HuffmanCoding(p, HC, text, text.length()); //HC为哈夫曼编码表p += text.length(); // 将指针指向哈夫曼树头结点while(p->lchild || p->rchild){if(text[i] == 0) p = p->lchild;else p = p->rchild;j += 1;}
}

例2 已知某系统在通信联络中只可能出现8种字符,其概率分布为0.05, 0.29, 0.07, 0.08, 0.14, 0.23, 0.03, 0.11, 试设计哈夫曼编码。

分析:首先设8个字符的权分别为w={5, 29, 7, 8, 14, 23, 3, 11}, n = 8, 则m = 15, 即在有8个叶子结点的哈夫曼树上有15个结点,然后构造一棵哈夫曼树,最后就得到哈夫曼编码。

哈夫曼树的应用场景

其实哈夫曼树使用场景还真不少,例如apache负载均衡的按权重请求策略的底层算法、咱们生活中的路由器的路由算法、利用哈夫曼树实现汉字点阵字形的压缩存储、快速检索信息等等底层优化算法,其实核心就是因为目标带有权重、长度远近这类信息才能构建赫夫曼模型。


ps:哈夫曼树最优子结构性质的证明可以详见这篇博文算法学习之哈夫曼编码算法

哈夫曼树及哈夫曼编码相关推荐

  1. 蓝桥哈夫曼树C语言,实验四 哈夫曼树及哈夫曼编码

    实验目的## 掌握哈夫曼树的概念.哈夫曼编码及其应用. 掌握生成哈夫曼树的算法. 会用哈夫曼树对传输报文进行编码. 掌握二叉树的二叉链表存储方式及相应操作的实现. ##实验内容## 用哈夫曼编码进行通 ...

  2. python哈夫曼树_python霍夫曼树

    class Node(): data=0 left=None right=None father=None def __init__(self,data,left,right): self.data= ...

  3. 一文看懂哈夫曼树与哈夫曼编码

    转自:http://www.cnblogs.com/Jezze/archive/2011/12/23/2299884.html 在一般的数据结构的书中,树的那章后面,著者一般都会介绍一下哈夫曼(HUF ...

  4. 树:哈夫曼树和哈夫曼编码的详细介绍以及代码实现

    闲扯前言 哈夫曼编码的代码实现对于初学数据结构的同学可能会有些困难,没有必要灰心,其实没啥,学习就犹如攀登一座又一座的山峰,每当我们攻克一个难点后,回首来看,也不过如此嘛.我们要做的就是不断的去攀越学 ...

  5. 听说你还不懂哈夫曼树和哈夫曼编码

    基本概念 哈夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,在实际中有广泛的用途. 基本概念 路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径. 路径长度:路径上的分 ...

  6. 数据结构图文解析之:哈夫曼树与哈夫曼编码详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  7. 【Java数据结构与算法】第十二章 哈夫曼树和哈夫曼编码

    第十二章 哈夫曼树和哈夫曼编码 文章目录 第十二章 哈夫曼树和哈夫曼编码 一.哈夫曼树 1.基本术语 2.构建思路 3.代码实现 三.哈夫曼编码 1.引入 2.介绍 3.代码实现哈夫曼编码综合案例 一 ...

  8. 【数据结构】树与树的表示、二叉树存储结构及其遍历、二叉搜索树、平衡二叉树、堆、哈夫曼树与哈夫曼编码、集合及其运算

    1.树与树的表示 什么是树? 客观世界中许多事物存在层次关系 人类社会家谱 社会组织结构 图书信息管理 分层次组织在管理上具有更高的效率! 数据管理的基本操作之一:查找(根据某个给定关键字K,从集合R ...

  9. 【数据结构】-哈夫曼树以及哈夫曼编码

    哈夫曼树的几个定义 哈夫曼树又叫最优二叉树:特点是带权路径最短 带权路径长度:该结点到根结点的路径长度乘以该结点的权值. 树的带权路径长度(WPL):所有叶子结点到根结点的带全路径长度之和. 最优二叉 ...

  10. C++ 实现哈夫曼树和哈夫曼编码

    C++ 实现哈夫曼树和哈夫曼编码 一.哈夫曼树的定义 二.哈夫曼树的构造算法 三.哈夫曼编码 四.哈夫曼算法实现 1.定义一个结点类 2.定义一个哈夫曼编码类 3.定义一个哈夫曼树类 4.设置初始值 ...

最新文章

  1. CSDN付费专栏写作感悟及成长之路、兼论学习会员模式的创作者协同效应
  2. CentOS7中使用Docker安装SVN以及配置账号权限
  3. RHEL7.0系统相关配置
  4. 人类如何面对AI挑战
  5. 8.用MyEclipse进行JSP开发
  6. (Docker实战) 第1篇:Centos7 环境准备和安装Docker-ce
  7. 问题解决:Sublime 乱码显示GBK编码文件解决
  8. PHP JSON文件解析并获取key、value,判断key是否存在
  9. git提取和拉取区别_每天一Git之起步 - 关于版本控制
  10. armbian安装图形桌面_archlinux / parabola 图形用户界面安装教程
  11. 给大家讲一个被社区团购小程序套路的经历吧
  12. java 图形界面---字体的设置
  13. jquery easyui-----------tree
  14. Android技术精髓-Bitmap详解
  15. Kaggle实战之食尸鬼、地精、鬼魂分类
  16. 如何在excel中打钩
  17. idea toggle offline mode
  18. 阿里云API网关配置详解
  19. wps两个段落之间间隔太大,将段落中的行距设为0也没用
  20. 产品 电信nb接口调用_电信物联网平台NBIoT使用Postman模拟测试接口

热门文章

  1. Linux之echo命令红底白字闪烁效果实现
  2. html给单选框赋值,layui radio性别单选框赋值方法
  3. 关于在ios中使用png与jpg图片的区别
  4. 混合的app自动化_混合网络自动化
  5. OpenGL着色器语言GLSL语法总结
  6. Fishbank阿尔法区块链鱼 (区块链应用)
  7. 移动端实时姿态识别算法
  8. 使用Flask-QRcode生成动态二维码
  9. 如何使用cmd进入打印机选项_怎么用cmd运行功能添加WiFi打印机
  10. 判断浏览器Version等相关参数的js脚本