当前位置: 首页 > news >正文

哈夫曼树及哈夫曼编码

目录

一. 前言

二. 哈夫曼树的构造

三. 哈夫曼编码


一. 前言

        在学习哈夫曼树之前,我们先了解几个基本概念。

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

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

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

4.通过分析可以知道,结点数相同的二叉树中,完全二叉树是路径长度最短的二叉树。但路径长度最短的二叉树不一定是完全二叉树。

5.权:将树中结点赋给一个有着某种含义的数值,这个数值称为该结点的权。

6.结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积。

7.树的带权路径长度:树中所有叶子结点的带权路径长度

在有了上面的知识作基础之后,我们就能理解哈夫曼树的定义了。

所谓哈夫曼树就是指带权路径长度最短的树,也叫最优树。当它为最优二叉树的时候就是指的带权路径长度(WPL)最短的二叉树。

并且我们可以知道它的特点,就是满二叉树不一定是最优二叉树。最优二叉树中权越大的数离根越近。并且具有相同带权结点的哈夫曼树不唯一。

二. 哈夫曼树的构造

        实现哈夫曼树的算法步骤可以分为以下几步:

1)首先根据n个给定的权值来构造n棵二叉树的森林F。森林F中的每一棵树都只有一个结点,也就是根结点。可以使用这样一个口诀来加以记忆这一步骤:构造森林全是根。

2)接着在这个森林F中选取两棵根结点的权值最小的树作为左右子树,用来构造一棵新的二叉树。并且设置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。记忆口诀为:选用两小造新树。

3)然后在这个森林F中删除这两棵树,同时将新得到的二叉树加入到这个森林F中。记忆口诀为:删除两小添新人。

4)重复2)3)两个步骤,直到森林中只有一棵树为止,这棵树就是哈夫曼树。

我们可以看下面一个例子来巩固掌握哈夫曼树的实现:

 首先就是构造森林全是根。然后找到权值最小的两棵树,也就是d和e,它们的权值最小为2和4。将它们构成一个新的二叉树加入到森林中,然后重复这两步的操作。得到的最后只有一棵树,也就是我们所求的哈夫曼树。

并且通过分析,我们可以发现,假设初始时有n棵二叉树,其中的二叉树两两进行合并,那么就需要经过n-1次合并最终才形成哈夫曼树,经过n-1次合并产生n-1个新结点,且这n-1个新结点都是具有两个孩子的分支结点。

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

在知道了哈夫曼树的原理和实现之和,我们该怎么用代码表示出来呢?

下面我将给出哈夫曼构造算法的完整代码,包括找两个最小权值的根结点(也就是还没合并的两个二叉树,还没有双亲)的函数,以及打印哈夫曼树中序号为1的结点数据。同学们也可自行在我的代码基础上添加一些其他功能。

//构造哈夫曼树的结点类型
typedef struct HTNode{int weight;    //权值int parent,lchild,rchild;    //结点的左右孩子以及双亲的位置序号
}HTNode,*HuffmanTree;//函数的声明
void Choice(HuffmanTree HT,int n,int&s1,int&s2);//最后结果会返回一个哈夫曼树的函数
HuffmanTree CreatHuffmanTree(int n){    if(n<=1) exit;   //如果结点数小于2,会退出m=2*n-1;    //带权的结点数为n,那么整个哈夫曼树就会有2*n-1个结点。HuffmanTree HT=new HTNode[m+1];  //一般不使用序号为0的位置,所以m再加1for(int i=1;i<=m;i++){        //将里面所有的元素的这些属性先赋值为0HT[i].lchild=0;HT[i].rchild=0;HT[i].parent=0;}for(int i=1;i<=n;i++){    //输入前n个树的权值scanf("%d",&HT[i].weight);}for(i=n+1;i<=m;i++){    //开始进行合并,总共合并n-1次Choice(HT,i-1,s1,s2);   //这个choice函数就是在HT[k](1<=k<=i-1)中选择两个双亲为0,也就是没有被合并的根结点,并且权值最小的结点,返回它们在HT中的序号s1和s2.HT[s1].parent=i;    //其中序号为s1的根结点有了双亲HT[s2].parent=i;    //跟s1是兄弟关系HT[i].lchild=s1;    //给这个n+1的位置上的树分配左右孩子HT[i].rchild=s2;HT[i].weight=HT[s1].weight+HT[s2].weight;   //权值为这两个子树之和}return HT;  //在完成了上面的哈夫曼树构造之后,返回一个哈夫曼树
}void Choice(HuffmanTree HT,int n,int&s1,int&s2){int min1=10000;int min2=10000;s1=s2=0;    //先给这两个变量初始化for(int i=1;i<=n;i++){if(HT[i].parent==0&&HT[i].weight<min1){   //用min1来存放其中权值最小的,min2来存放其中权值第二小的。所以一旦有比min1还要小的数,就先            //将min1现在的值赋值给min2,接着再把这个更小的数赋值给min1.min2=min1;s2=s1;min1=HT[i].weight;s1=i;}else if(HT[i].parent==0&&HT[i].weight<min2){    //只比第二小的数小,则只修改min2的值min2=HT[i].weight;s2=i;}}
}int main(){int n=4;    //带权的结点数为4HuffmanTree HT=CreatHuffmanTree(n);    //用HT来接收在CreatHuffmanTree函数中已经构造好了的哈夫曼树printf("%d",HT[1].weight);    //简单测试下,打印一号位置的权值
}

三. 哈夫曼编码

        哈夫曼编码就是将要发送的数据(可能是数字也有可能是字符)转换成由二进制组成的字符串。举个简单例子:例如发送ABC,将A的编码设置成1,B的编码设置成2,C的编码设置成3。那么最后发送的就是123。哈夫曼编码要求传送的字符串中出现次数较多的字符采用尽可能短的编码,那么转换成的二进制字符串便可能减少,并且是前缀编码。例如1是2的前缀编码,而0不是01的前缀编码,会造成重码的问题。

实现哈夫曼编码的方法:

在知道了哈夫曼编码的原理和方法之后,我们如何用代码实现呢?

如下所示:

//从叶子到根逆向求每一个字符的哈夫曼编码,并存储到编码表HC中
void CreatHuffmanCode(HuffmanTree HT,HuffmanCode&HC,int n){HC=new char*[n+1];    //分配n个字符编码的头指针矢量cd=new char[n];       //分配临时存放编码的动态数组空间cd[n-1]='\0';        //最后一个位置存放结束符,编码最长为n-1,因此空间不会小for(int i=1;i<=n;i++){    //逐个字符求哈夫曼编码int start=n-1;int c=i;int f=HT[i].parent;while(f!=0){        //从叶子结点开始往上回溯,直到根结点--start;if(HT[f].lchild==c) cd[start]='0';   //结点c是f的左孩子,则生成代码0else cd[start]='1';        //右孩子生成代码1c=f;                //继续向上回溯f=HT[f].parent;}HC[i]=new char[n-start];        //为第i个字符的编码分配空间strcpy(HC[i],&cd[start]);       //将所求得编码从临时空间cd中赋值到HC当前位置上}delete cd;    //释放临时空间
}

 

 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 前端小白安装node、vue、Express、Electron及(Electron桌面端exe应用开发)
  • 干货满满,从零到一:编程小白如何在大学成为编程大神?
  • 滴滴官宣潘展乐为滴滴网约车“快”乐大使
  • AI产品经理的职责与能力:将AI技术转化为实际价值
  • 算法小白的进阶之路(力扣1~5)
  • 复制知乎文字内容
  • 本地VSCode连接远程linux环境服务器的docker
  • 【Linux】文件系统
  • TypeScript中 ?, ??, !, !! 的使用
  • 6小时之可笑中文乱码bug
  • 面试:MySQL 数据库中的 count(1)、count(*)、count(字段)有什么区别?
  • ssh免密认证配置
  • 解决vue 初始化页面闪动问题
  • c++STL容器中vector的使用,模拟实现及迭代器使用注意事项和迭代器失效问题
  • HTB Driver红队笔记靶机精讲笔记
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • mysql innodb 索引使用指南
  • PAT A1017 优先队列
  • Redis 懒删除(lazy free)简史
  • spring-boot List转Page
  • 如何优雅地使用 Sublime Text
  • elasticsearch-head插件安装
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • #php的pecl工具#
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (21)起落架/可伸缩相机支架
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (二)构建dubbo分布式平台-平台功能导图
  • (四)图像的%2线性拉伸
  • (转)用.Net的File控件上传文件的解决方案
  • .form文件_一篇文章学会文件上传
  • .netcore 如何获取系统中所有session_如何把百度推广中获取的线索(基木鱼,电话,百度商桥等)同步到企业微信或者企业CRM等企业营销系统中...
  • .net利用SQLBulkCopy进行数据库之间的大批量数据传递
  • .NET上SQLite的连接
  • .NET使用存储过程实现对数据库的增删改查
  • [ 转载 ] SharePoint 资料
  • [\u4e00-\u9fa5] //匹配中文字符
  • [14]内置对象
  • [20190416]完善shared latch测试脚本2.txt
  • [240527] 谷歌 CEO 承认 AI 编造虚假信息问题难解(此文使用 @gemini 命令二次创作)| ICQ 停止运作
  • [240607] Jina AI 发布多模态嵌入模型 | PHP 曝新漏洞 | TypeScript 5.5 RC 发布公告
  • [CF543A]/[CF544C]Writing Code
  • [codevs 1288] 埃及分数 [IDdfs 迭代加深搜索 ]
  • [CSDN首发]鱿鱼游戏的具体玩法详细介绍
  • [flink总结]什么是flink背压 ,有什么危害? 如何解决flink背压?flink如何保证端到端一致性?
  • [Flutter]打包IPA
  • [Java]快速入门二叉树,手撕相关面试题
  • [java基础揉碎]方法的重写/覆盖
  • [POJ2728] Desert King
  • [Python]pyenv 环境配置
  • [Qt] Qt Creator中配置 Vs-Code 编码风格
  • [Redis]Redis的数据类型
  • [RN] React Native 常用命令行
  • [ROS]安装tutlebot时无法下载解决方法
  • [SQL基础教程] 3-4 对查询结果进行排序/ORDER BY