超详细讲解哈夫曼树(Huffman Tree)以及哈夫曼编码的构造原理、方法,并用代码实现。

1哈夫曼树基本概念

路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径。

结点的路径长度:两结点间路径上的分支数

树的路径长度:从树根到每一个结点的路径长度之和。记作: TL 

权(weight)又称权重:将树中结点赋给一个有着某种含义的数值,(具体的意义根据树使用的场合确定)则这个数值称为该结点的权。比如之前提到的判断树中5%表示对应分数段人在总人数中的比例
结点的带权路径长度:从根结点到该结点之间的路径长度与结点上权的乘积

树的带权路径长度:树中所有叶子结点的带权路径长度之和。

树的路径长度:从树根到每一个结点的路径长度之和。

 哈夫曼树:最优树,带权路径长度(WPL)最短的树

“带权路径长度最短”是在“度相同”的树中比较而得的结果,因此有最优二叉树、最优三叉树之称。

哈夫曼树:最优二叉树,带权路径长度(WPL)最短的二叉树,因为构造这种树的算法是由哈夫曼教授于1952年提出的,所以被称为哈夫曼树,相应的算法称为哈夫曼算法。

2.哈夫曼树构造算法

哈夫曼算法(构造哈夫曼树的方法)

(1)根据n个给定的权值(W1,W2,..., Wn)构成n棵二叉树的森林F=(T1, T2,.., Tn),其中Ti只有一个带权为Wi;的根结点。

构造森林全是根

(2)在F中选取两棵根结点的权值最小的树作为左右子树,构造一棵新的二叉树,且设置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。

选用两小造新树

(3)在F中删除这两棵树,同时将新得到的二叉树加入森林中。

删除两小添新人

(4)重复(2)和(3),直到森林中只有一棵树为止,这棵树即为哈夫曼树。

重复2、3剩单根

总结 

1、在哈夫曼算法中,初始时有n棵二叉树,要经过n-1次合并最终形成哈夫曼树。

2、经过n-1次合并产生n-1个新结点,且这n-1个新结点都是具有两个孩子的分支结点。

可见:哈夫曼树中共有n+n-1 =2n-1个结点,且其所有的分支结点的度均不为1。

3.构建哈夫曼树代码实现

3.1哈弗曼树中结点结构

构建哈夫曼树时,首先需要确定树中结点的构成。

由于哈夫曼树的构建是从叶子结点开始,不断地构建新的父结点,直至树根,所以结点中应包含指向父结点的指针。但是在使用哈夫曼树时是从树根开始,根据需求遍历树中的结点,因此每个结点需要有指向其左孩子和右孩子的指针。

//哈夫曼树结点结构
typedef struct {int weight;//结点权重int parent, left, right;//父结点、左孩子、右孩子在数组中的位置下标
}HTNode, *HuffmanTree;

3.2哈弗曼树中的查找算法

构建哈夫曼树时,需要每次根据各个结点的权重值,筛选出其中值最小的两个结点,然后构建二叉树。

查找权重值最小的两个结点的思想是:从树组起始位置开始,首先找到两个无父结点的结点(说明还未使用其构建成树),然后和后续无父结点的结点依次做比较,有两种情况需要考虑:

  • 如果比两个结点中较小的那个还小,就保留这个结点,删除原来较大的结点;
  • 如果介于两个结点权重值之间,替换原来较大的结点;
//HT数组中存放的哈夫曼树,end表示HT数组中存放结点的最终位置,s1和s2传递的是HT数组中权重值最小的两个结点在数组中的位置
void Select(HuffmanTree HT, int end, int *s1, int *s2)
{int min1, min2;//遍历数组初始下标为 1int i = 1;//找到还没构建树的结点while(HT[i].parent != 0 && i <= end){i++;}min1 = HT[i].weight;*s1 = i;i++;while(HT[i].parent != 0 && i <= end){i++;}//对找到的两个结点比较大小,min2为大的,min1为小的if(HT[i].weight < min1){min2 = min1;*s2 = *s1;min1 = HT[i].weight;*s1 = i;}else{min2 = HT[i].weight;*s2 = i;}//两个结点和后续的所有未构建成树的结点做比较for(int j=i+1; j <= end; j++){//如果有父结点,直接跳过,进行下一个if(HT[j].parent != 0){continue;}//如果比最小的还小,将min2=min1,min1赋值新的结点的下标if(HT[j].weight < min1){min2 = min1;min1 = HT[j].weight;*s2 = *s1;*s1 = j;}//如果介于两者之间,min2赋值为新的结点的位置下标else if(HT[j].weight >= min1 && HT[j].weight < min2){min2 = HT[j].weight;*s2 = j;}}
}

3.3 构建算法实现

//HT为地址传递的存储哈夫曼树的数组,w为存储结点权重值的数组,n为结点个数
void CreateHuffmanTree(HuffmanTree *HT, int *w, int n)
{if(n<=1) return; // 如果只有一个编码就相当于0int m = 2*n-1; // 哈夫曼树总节点数,n就是叶子结点*HT = (HuffmanTree) malloc((m+1) * sizeof(HTNode)); // 0号位置不用HuffmanTree p = *HT;// 初始化哈夫曼树中的所有结点for(int i = 1; i <= n; i++){(p+i)->weight = *(w+i-1);(p+i)->parent = 0;(p+i)->left = 0;(p+i)->right = 0;}//从树组的下标 n+1 开始初始化哈夫曼树中除叶子结点外的结点for(int i = n+1; i <= m; i++){(p+i)->weight = 0;(p+i)->parent = 0;(p+i)->left = 0;(p+i)->right = 0;}//构建哈夫曼树for(int i = n+1; i <= m; i++){int s1, s2;Select(*HT, i-1, &s1, &s2);(*HT)[s1].parent = (*HT)[s2].parent = i;(*HT)[i].left = s1;(*HT)[i].right = s2;(*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;}
}

4.哈夫曼编码

4.1哈夫曼编码基本概念

在远程通讯中,要将待传字符转换成由二进制的字符串

若将编码设计为长度不等的二进制编码,即让待传字符串中出现次数较多的字符采用尽可能短的编码,则转换的二进制字符串便可能减少。

关键:要设计长度不等的编码,则必须使任一字符的编码都不是另一个字符的编码的前缀,这种编码称做前缀编码

问题:什么样的前缀码能使得电文总长最短?

哈夫曼编码方法:

1、统计字符集中每个字符在电文中出现的平均概率(概率越大,要求编码越短)

2、利用哈夫曼树的特点:权越大的叶子离根越近;将每个字符的概率值作为权值,构造哈夫曼树。则概率越大的结点,路径越短。

3、在哈夫曼树的每个分支上标上0或1:

结点的左分支标0,右分支标1

把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符的编码。

 两个问题:

1.为什么哈夫曼编码能够保证是前缀编码?

因为没有一片树叶是另一片树叶的祖先,所以每个叶结点的编码就不可能是其它叶结点编码的前缀。(字符都是叶子结点,根到一个字符不会路过另一个字符T)

2.为什么哈夫曼编码能够保证字符编码总长最短?

因为哈夫曼树的带权路径长度最短,故字符编码的总长最短。

性质1哈夫曼编码是前缀码

性质2哈夫曼编码是最优前缀码

4.2哈夫曼编码代码实现

使用程序求哈夫曼编码有两种方法:

  1. 从叶子结点一直找到根结点,逆向记录途中经过的标记。例如,图 3 中字符 c 的哈夫曼编码从结点 c 开始一直找到根结点,结果为:0 1 1 ,所以字符 c 的哈夫曼编码为:1 1 0(逆序输出)。
  2. 从根结点出发,一直到叶子结点,记录途中经过的标记。例如,求图 3 中字符 c 的哈夫曼编码,就从根结点开始,依次为:1 1 0。

采用方法 1 的实现代码为:

//HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数
void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC,int n){*HC = (HuffmanCode) malloc((n+1) * sizeof(char *));char *cd = (char *)malloc(n*sizeof(char)); //存放结点哈夫曼编码的字符串数组cd[n-1] = '\0';//字符串结束符for(int i=1; i<=n; i++){//从叶子结点出发,得到的哈夫曼编码是逆序的,需要在字符串数组中逆序存放int start = n-1;//当前结点在数组中的位置int c = i;//当前结点的父结点在数组中的位置int j = HT[i].parent;// 一直寻找到根结点while(j != 0){// 如果该结点是父结点的左孩子则对应路径编码为0,否则为右孩子编码为1if(HT[j].left == c)cd[--start] = '0';elsecd[--start] = '1';//以父结点为孩子结点,继续朝树根的方向遍历c = j;j = HT[j].parent;}//跳出循环后,cd数组中从下标 start 开始,存放的就是该结点的哈夫曼编码(*HC)[i] = (char *)malloc((n-start)*sizeof(char));strcpy((*HC)[i], &cd[start]);}//使用malloc申请的cd动态数组需要手动释放free(cd);
}

采用第二种算法的实现代码为:

//HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数
void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC,int n){*HC = (HuffmanCode) malloc((n+1) * sizeof(char *));int m=2*n-1;int p=m;int cdlen=0;char *cd = (char *)malloc(n*sizeof(char));//将各个结点的权重用于记录访问结点的次数,首先初始化为0for (int i=1; i<=m; i++) {HT[i].weight=0;}//一开始 p 初始化为 m,也就是从树根开始。一直到p为0while (p) {//如果当前结点一次没有访问,进入这个if语句if (HT[p].weight==0) {HT[p].weight=1;//重置访问次数为1//如果有左孩子,则访问左孩子,并且存储走过的标记为0if (HT[p].left!=0) {p=HT[p].left;cd[cdlen++]='0';}//当前结点没有左孩子,也没有右孩子,说明为叶子结点,直接记录哈夫曼编码else if(HT[p].right==0){(*HC)[p]=(char*)malloc((cdlen+1)*sizeof(char));cd[cdlen]='\0';strcpy((*HC)[p], cd);}}//如果weight为1,说明访问过一次,即是从其左孩子返回的else if(HT[p].weight==1){HT[p].weight=2;//设置访问次数为2//如果有右孩子,遍历右孩子,记录标记值 1if (HT[p].right!=0) {p=HT[p].right;cd[cdlen++]='1';}}//如果访问次数为 2,说明左右孩子都遍历完了,返回父结点else{HT[p].weight=0;p=HT[p].parent;--cdlen;}}
}

【数据结构与算法】-哈夫曼树(Huffman Tree)与哈夫曼编码相关推荐

  1. 数据结构C#版笔记--啥夫曼树(Huffman Tree)与啥夫曼编码(Huffman Encoding)

    哈夫曼树Huffman tree 又称最优完全二叉树,切入正题之前,先看几个定义 1.路径 Path 简单点讲,路径就是从一个指定节点走到另一个指定节点所经过的分支,比如下图中的红色分支(A-> ...

  2. 哈夫曼树(Huffman Tree),与哈夫曼编码

    目录 一.哈夫曼树 1.什么是哈夫曼树? 2.哈夫曼树关键字说明 3.用代码实现哈夫曼树思路分析 4.代码实现 二.哈夫曼编码 1.哈夫曼编码基本介绍 2.原理剖析 3.代码实现 一.哈夫曼树 1.什 ...

  3. 哈夫曼树(Huffman Tree)

    定义 哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树.所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数).树的路 ...

  4. 数据结构与算法(C++)– 树(Tree)

    数据结构与算法(C++)– 树(Tree) 1.树的基础知识 树(tree): 一些节点的集合,可以为空集 子树(sub tree): 树的子集 根(root): 树的第一个节点 孩子和父亲(Chil ...

  5. Python---哈夫曼树---Huffman Tree

    今天要讲的是天才哈夫曼的哈夫曼编码,这是树形数据结构的一个典型应用. !!!敲黑板!!!哈夫曼树的构建以及编码方式将是我们的学习重点. 老方式,代码+解释,手把手教你Python完成哈夫曼编码的全过程 ...

  6. 哈夫曼树(Huffman Tree)的介绍、画法、哈夫曼树的可视化显示(Python代码实现)

    https://blog.csdn.net/hanhanwanghaha宝藏女孩 欢迎您的关注! 欢迎关注微信公众号:宝藏女孩的成长日记 如有转载,请注明出处(如不注明,盗者必究) 目录 一.概念 二 ...

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

    目录 什么是哈夫曼树 哈夫曼树的定义 哈夫曼树的构造 图解操作 代码实现 代码解析 哈夫曼树的特点 哈夫曼编码 不等长编码 二叉树用于编码 哈夫曼编码实例 什么是哈夫曼树 我们先举个例子: 要将百分制 ...

  8. 【算法学习笔记】哈夫曼树的构建和哈夫曼编码的实现代码

    介绍 哈夫曼(Haffman)这种方法的基本思想如下: ①由给定的n个权值{W1,W2,-,Wn}构造n棵只有一个叶子结点的二叉树,从而得到一个二叉树的集合F={T1,T2,-,Tn}. ②在F中选取 ...

  9. 【赫夫曼树详解】赫夫曼树简介及java代码实现-数据结构07

    赫夫曼树(最优二叉树) 1. 简介 定义: 赫夫曼树是n个带权叶子结点构成的所有二叉树中,带权路径长度(WPL)最小的二叉树. 叶子结点的带权路径: 叶子结点权值*到根节点的路径长度(叶结点的层数) ...

最新文章

  1. SAP PM入门系列30 - IW39 Display Orders
  2. ASA 独立实现WEB URL过滤!!!
  3. Vue本地执行build之后打开dist目录下index.html正常访问
  4. st edmunds和emmanuel college
  5. Swagger 2——@ApiOperation注解、@ApiModel注解、@ApiImplicitParams注解、@ApiImplicitParam注解无效解决方案
  6. js中字符串和数组的使用
  7. JavaScript图片 向下闪缩放的效果
  8. 亚马逊靠“新闻稿”推动创新,跃居市值第一
  9. 如何从文件名字符串中获取文件扩展名_Linux操作系统:文件系统的功能和命名...
  10. Hibernate学习笔记_核心幵发接口及三种对象状态
  11. Kubernetes详解(九)——资源配置清单创建Pod实战
  12. 索尼工厂被迫停止生产,日本地震带来的冲击可能不止于此
  13. 高品质的算法混响插件-Initial Audio AR1 Reverb v1.0.1 WiN-MAC
  14. 我要彻底搞懂SSD网络结构(1)VGG部分
  15. Matlab运行.m文件
  16. 每天一个linux命令——cat
  17. 如何利用家庭闲置宽带赚钱,甜糖 x86 docker 从零开始搭建
  18. bzoj 1779: [Usaco2010 Hol]Cowwar 奶牛战争 (网络流)
  19. 用excel打开一个xls文件进度到36%就不动了
  20. 做相关性分析时,如何排除奇异值Outliers,以增加相关分析的准确性

热门文章

  1. 如何应对软件需求不明确、需求频繁更改和需求的无底洞
  2. 绝望!新 AlphaGo 放弃人类,柯洁:人类太多余了
  3. perf的基本使用方法
  4. Ogre学习笔记(8):骨骼动画
  5. 申请鸿蒙开发者干什么,申请鸿蒙开发者有啥用?
  6. java的数组,一起“干完”这份300页1000道面试题
  7. karaf maven
  8. High-resolution Face Swapping via Latent Semantics Disentanglement
  9. ✨✨✨ ❃ ♕ ꕥ Xpath解析html获取表情符号,丰富你的文章 ꧁ ꧂꧁ ꧂
  10. iPhone 6s GPU性能为何能够有大幅提升