目录

什么是哈夫曼树

哈夫曼树的定义

哈夫曼树的构造

图解操作

代码实现

代码解析

哈夫曼树的特点

哈夫曼编码

不等长编码

二叉树用于编码

哈夫曼编码实例


什么是哈夫曼树

我们先举个例子:

要将百分制的考试成绩转化成五分制的成绩

if(score < 60)grade = 1;
else if(score < 70)grade = 2;
else if(score < 80)grade = 3;
else if(score < 90)grade = 4;
elsegrade = 5;

这种情况其实是一棵判定树:

这种方式要看各成绩段的学生分布,如果60以下的同学比较多,那么判断的次数就会很少;但是如果90多的同学比较多的情况下,那么要判断4次的情况就会很多,整体的判断效率不高。

我们考虑学生成绩分布的概率:

分数段 0-59 60-69 70-79 80-89 90-100
比例 0.05 0.15 0.40 0.30 0.10

那么判断效率就为:

现在我们想要让判断的效率更高一点,修改一下判定树:

这样的判断效率就为:

写成代码就为:

if(score < 80)
{if(score < 70){if(score < 60){grade = 1;}else{grade = 2;}}else{grade = 3;}
}
else if(score < 90)
{grade = 4;
}
else
{   grade = 5;
}

如何根据结点不同的查找频率构造更有效的搜索树? 

就涉及到了我们要讲的哈夫曼树

哈夫曼树的定义

带权路径长度(WPL):设二叉树有n个叶子结点,每个叶子结点带有权值,从根结点到每个叶子结点的长度为,则每个叶子结点的带权路径长度之和就为:

最优二叉树或哈夫曼树:WPL最小的二叉树。

例:有五个叶子结点,它们的权值为{1,2,3,4,5},用此权值序列可以构造出形状不同的多个二叉树。

哈夫曼树的构造

给出一个权值序列,构造出一棵哈夫曼树。

例:{1,2,3,4,5}

每次把权值最小的两棵二叉树合并,具体:

图解操作

哈夫曼树的构造是比较简单的,要找出两个最小值,就可以运用我们前面学过的最小堆来找了,这比从小到大排好序的效率会更高。下面我们来看一下代码的实现。

代码实现

typedef struct TreeNode *HuffmanTree;
struct TreeNode
{int Weight;HuffmanTree Left,Right;
}HuffmanTree Huffman(MinHeap H)
{    /*假设H->Size个权值已经存在H->Elements[]->Weight里*/int i;HuffmanTree T;BuildMinHeap(H);/*将H->Elements[]按权值调整为最小堆*/for (i=1;i<H->Size;i++){/*做H->Size-1次合并*/T=malloc( sizeof( struct TreeNode));/*建立新结点*/ T->Left=DeleteMin(H);/*从最小堆中删除一个结点,作为新T的左子结点*/T->Riqht=DeleteMin(H);/*从最小堆中删除一个结点,作为新T的右子结点*/T->Weight=T->Left->Weiqht + T->Right->Weight;/*计算新权值*/Insert(H,T);/*将新T插入最小堆*/}T=DeleteMin(H);return T;
}

代码解析

这段代码定义了一个结构体类型TreeNode和一个指向TreeNode类型的指针HuffmanTree。TreeNode结构体包含三个成员变量:Weight表示权值,Left表示左子树指针,Right表示右子树指针。

HuffmanTree指针类型可以指向TreeNode类型的对象,用于表示哈夫曼树的结点。

函数的输入参数是一个最小堆H,其中存储了每个字符出现的频率。

变量 i 的作用是用于循环合并最小堆中的结点,每次循环合并两个权值最小的结点,直到只剩下一个根结点。

T的作用是用于创建新的Huffman树结点,每次合并两个最小权值的结点时,都会创建一个新的结点T,并将两个最小权值结点作为T的左右子结点,然后将T插入到最小堆中。

最终,最小堆中只剩下一个根结点,即为Huffman树的根结点,返回该结点即可。

函数首先调用BuildMinHeap函数将H中的元素按照权值调整为最小堆。

然后进入for循环,将最小堆中的所有结点合并成一棵哈夫曼树:

首先,建立一个新的结点T,作为合并后的新结点。

然后,从最小堆中删除两个权值最小的结点,分别作为新结点T的左子结点和右子结点。

接着,计算新结点T的权值,即左子结点和右子结点的权值之和。

最后,将新结点T插入最小堆中。

这个过程会重复执行H->Size-1次,因为最终的哈夫曼树只有一个根结点,所以需要将所有结点合并成一个。

最后,从最小堆中删除最后一个结点,即哈夫曼树的根结点,

并返回该结点作为哈夫曼树的根。

哈夫曼树构造完成。整体的时间复杂度为

哈夫曼树的特点

  • 没有度为1的结点

哈夫曼树是一棵最优二叉树,每个叶子结点都对应着一个字符,

而每个非叶子结点都是两个子结点的父结点,表示两个字符的合并。

如果存在度为1的结点,那么这个结点只有一个子结点,就不能表示两个字符的合并,因此不符合哈夫曼树的定义。

  • n个叶子结点的哈夫曼树共有2n-1个结点

我们最开始学二叉树时,在其中提到了二叉树的几个重要性质。

对任何非空二叉树T,若n0表示叶节点的个数,n2是度为2的非叶节点个数,那么两者满足关系n0 = n2 +1。

因为哈夫曼树没有度为1的结点,叶结点为n个,那么度为2的非叶结点个数就为n-1个;

故而总结点数就等于n + (n - 1) = 2n - 1。

  • 哈夫曼树的任意非叶结点的左右子树交换之后仍是哈夫曼树

交换哈夫曼树中任意非叶结点的左右子树时,它的深度和权值并没有发生改变,因此仍然满足哈夫曼树的定义。

  • 对同一组权值{},存在不同构的两棵哈夫曼树

对一组权值{1,2,3,3},不同构的两棵哈夫曼树:

哈夫曼编码

不等长编码

抛出问题:给定一段字符串,如何对字符进行编码,可以使得该字符串的编码存储空间最少?

【例】假设有一段文本,包含58个字符,并由以下7个字符构成:a,e,i,s,t,空格(sp),换行(nl);这7个字符出现的次数不同。如何对这7个字符进行编码,使得总编码空间最少?

【分析】

(1)用等长ASCII编码(ASCII占1个字节,8个比特位):58 * 8 = 464位;

(2)用等长3位编码(因为只有7个字符,3位的编码足够表达8个对象):58 * 3 = 174位;

(3)不等长编码:出现频率高的字符用的编码短些,出现频率低的字符则可以编码长些

怎么进行不等长编码呢?

我们不妨假设:

a:0

e:0

s:10

t:11

......

那么在这样的不等长编码下,1011是什么字符串的编码?

a e a a:1 0 1 1

a e t:1 0 1 1

s t:1 0 1 1

这样就出现了同一个编码,却译出不同的几个字符串了,即存在二义性。

要避免二义性,就要满足一个条件:前缀码

前缀码prefix code:任何字符的编码都不是另一字符编码的前缀

例如,s的前缀为1,而1就可以理解为a,这就不符合前缀码的条件了。

二叉树用于编码

为了保证我们的编码不出现二义性,我们可以用二叉树来编码。

用二叉树编码:

(1)左分支:0

(2)右分支:1

(3)字符只在叶结点上

【例】现有四个字符的频率:a:4,u:1,x:2,z:1。

前面说过的前缀码,是当字符出现在叶结点时;如果字符出现在非叶结点上,就说明它不满足前缀码的条件:

所以这就是为什么用二叉树来编码时,字符只在叶结点上。

接下来的问题是,怎么样构造才能使得付出的代价最小?

看到刚才举的例子:

这就和我们讲哈夫曼树时是差不多的情况,我们根据频率来把二叉树重构一下:

这样不存在二义性,代价也最小。

哈夫曼编码实例

【例】

a e i s t sp nl
10 15 12 3 4 13 1

用上面学过的构造哈夫曼树的方法,每次选取最小的两个值构造二叉树,整个过程如下

最终构造出来的哈夫曼编码树:


end


学习自:MOOC数据结构——陈越、何钦铭

数据结构学习记录——哈夫曼树(什么是哈夫曼树、哈夫曼树的定义、哈夫曼树的构造、哈夫曼树的特点、哈夫曼编码)相关推荐

  1. 数据结构学习记录(二)——折半查找二叉判定树的画法

    以下给出我在学习中总结的一种比较简便的构造折半二叉判定树的思路以及方法: 思路分析: 在计算mid值时,使用的时mid=(low+high)/2  .这里由于mid为int类型,自动默认为向下取整,因 ...

  2. 数据结构学习记录---天勤线性表综合应用题(2)

    记录考研数据结构学习过程中的代码实现 (参考天勤书本) (天勤P40T1(6)法一)删除链表中重复元素(常规方法发现与前相同则删除) (天勤P40T1(6)法二)删除链表中重复元素(全部不重复元素移动 ...

  3. 【数据结构学习记录22】——有向无环图及其应用

    有向无环图及其应用 一.有向无环图的概念 二.拓扑排序(AOV网) 1.概念 2.偏序与全序 a).偏序 b).全序 c).偏序与全序的区别 3.拓扑有序 4.拓扑排序的过程 三.关键路径(AOE网) ...

  4. 数据结构学习记录连载1

    1.题目:基本要求:1) 建立线性表的顺序表类SeqList, 提高要求:在顺序表类SeqList增加一个删除函数,要求删除顺序表中等于item的所有元素. 1.SeqList.h: /* * Cop ...

  5. 【数据结构学习记录13】——稀疏矩阵的表示与运算

    稀疏矩阵的表示与运算 一.介绍 二.实现稀疏矩阵的原理 1.稀疏矩阵的顺序存储 2.稀疏矩阵的转置T 3.求转置的方法 4.快速求转置法 5.稀疏矩阵加法(减法同理) 6.稀疏矩阵的乘法 三.稀疏矩阵 ...

  6. 数据结构学习记录(三)链表的定义和操作

    顺序存储结构如数组,进行插入与删除操作,往往会比较复杂,因为牵扯到了大量数组元素的后移操作,导致算法时间复杂度很高,但是链式存储结构的很好的解决了这一问题,链表在进行插入与删除操作时,只需遍历一遍线性 ...

  7. 数据结构学习记录1——error: expected identifier before numeric constant未解决

    程序的多文件组织 最简单的多文件组织,一个项目中有3个文件: (1) .h 头文件:定义数据类型.声明自定义函数.定义宏等 (2).cpp 源文件1:用于实现头文件中声明的自定义函数 (3).cpp ...

  8. 数据结构学习——哈夫曼树

    数据结构学习记录DAY13 :哈夫曼树(上) 哈(赫)夫曼树和哈(赫)夫曼编码 路径 一个结点到另外一个结点的通路,称为路径 (祖先结点到子孙结点) 路径长度: 每经过一个结点,路径长度就增加1,不包 ...

  9. 前端技术学习记录:react+dvajs+ant design实现暴走计算器的页面重构(二)

    前端技术学习记录:react+dvajs+ant design实现暴走计算器的页面重构(二) 前言 定义 Model connect 起来 更新state 拥抱变化 主题切换 更换页面 获取当前设备类 ...

最新文章

  1. 如何使用vue.js 实现前台html页面和后台的数据绑定
  2. 雷达多普勒频率计算公式_手持式雷达流速仪的监测应用方案
  3. DIV CSS布局-固定页面开度布局
  4. MySQL学习笔记_6_SQL语言的设计与编写(下)
  5. 【百度地图API】如何判断点击的是地图还是覆盖物?
  6. 伪随机数生成器——random模块的用法
  7. Dockerfile构建LNMP分离环境部署wordpress
  8. 3.中小型企业通用自动化运维架构 -- Ansible playbook
  9. project sms / BSS / OSS / ESS / dianxin / youbian / iccid / puk / pin
  10. ap9h4qmo.exe
  11. Freeze the Discriminator a Simple Baseline for Fine-Tuning GANs
  12. 语音识别-特征提取 (一)
  13. Android 的 Activity 教程
  14. Python爬虫实例(2)--beautifulsoup的应用
  15. 工作中常用的linux命令
  16. 快速画正弦波、方波、三角波——Visio制图总结(六)
  17. PHP来客在线客服系统源码 带安装教程
  18. 新未来,决战支付大数据
  19. 小米申请注册多个 “铁蛋”商标
  20. 代入消元法 matlab,求助 如何用matlab计算期权价格

热门文章

  1. Stewart平台及其数学运算
  2. oracle 建立外键 引用条件约束 不能添加,Oracle外键约束(Foreign Key)的几个操作选项...
  3. 中间件_Redis_02_Redis的数据类型
  4. LCD 3LCD DLP LED投影仪成像原理
  5. 新手劝退!为什么学3D建模建议先3dsmax,而不是Maya
  6. 克莱因瓶计算机模拟,一个永远装不满水的克莱因瓶? 科学家们现今也没有造出这种瓶子!...
  7. 电脑重启bootmgr_开机提示bootmgr is compressed无法启动的方法,看完就明白了
  8. C++之string类字符串连接
  9. Computer Networking A Top-Down Approach 笔记(一)
  10. 第三届全国高校计算机能力挑战赛Java程序设计赛总结