目录


哈夫曼树的基本概念

------------哈夫曼树的构造方法

------------------------哈夫曼编码

------------------------------------全部代码


哈夫曼树的基本概念

 哈夫曼树通常以二叉树的形式出现,所以也称最优二叉树,是一类带权路径长度最短的树

首先得知道下以下几个术语:

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

        路径长度:路径上的分支数目称作路径长度

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

:赋予某个实体的一个量

   结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积

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

哈夫曼树的构造方法

哈夫曼树的实现利用了贪心算法的理念,就是先给定的若干结点的权进行分割,让它们变为单个的森林或者说是单个的树,因为树肯定是森林,而后在其中选择两个权值最小的结点生成新的结点,而后删除被选择的结点,让生成的结点参与森林中进行选拔,直至无结点进行选拔。

不好意思结点搞的太多了。。。 每次生成都是到最后的原因是因为后面的代码,不然最后的结果不是一样的,当然哈夫曼树并不唯一,但是为了让最后答案一样,我选择这样画,这个过程也是后面构建哈夫曼树代码的过程,后面代码看不懂的可以结合这个图一起理解。

接下来就是哈夫曼树的存储结构了,因为每个结点需要权值,孩子结点与双亲结点的地址和结点的数据,所以我们需要用一个结构体来存储。而且这是二叉树的顺序存储。

typedef struct
{char data;             //结点的数据int parent,lch,rch;    //双亲结点和孩子结点的下标int weight;            //结点的权值
}htNode,*HuffmanTree;

这个就是这个结构体的内部图,有了存储结构后,我们要做的就是初始化,因为是顺序存储,所以我们要知道给的这些结点构成哈夫曼树需要多少空间,因为我们要节省空间,所以需要用动态分配的方法来解决。

而我们知道,假设有n个结点构成哈夫曼树则会生成2n-1个结点,这是因为每个结点都会有个双亲,而根结点没有双亲,生成结点和边数相同为n-1,所以为2n-1,而我们在构成哈夫曼树时0下标是不用的,所以我们真正要申请的空间为2n个。

知道这些后我们就可以来初始化哈夫曼树了,我们在各自输入权值和结点数据后还需把各个结点的孩子与双亲的值置为-1(置为-1的好处就是能当个flag,也就是现在都没有双亲,都是森林,而且在生成的过程中不会出现和它相同的值):以下是初始化的代码

int initHuffmanTree(huffmanTree& HT)
{HT = (htNode*)malloc(sizeof(htNode) * (2 * NODENUM));         //给HT分配2 * NODENUM个htNOde大小的htNode类型的数组for (int i = 1; i <= 2 * NODENUM - 1; i++)                        //下标从1开始到2 * NODENUM{HT[i].parent = HT[i].lch = HT[i].rch = -1;                  //双亲和孩子的值都置为-1}printf("please input some weight!\n");for (int i = 1; i <= NODENUM; i++)                                //权值只有1-n个{scanf("%d",&HT[i].weight);                                 //给每个结点赋予权值}char c = getchar();                                            //这个来接收上面的回车printf("please input some data!\n");for (int i = 1; i <= NODENUM; i++){//scanf("%c ",&HT[i].data);char a = getchar();if(a == '\n')                                              //遇到回车就结束break;elseHT[i].data = a;                                         //给每个结点赋予数据}return 1;
}

这是初始化后的结果。初始化完后我们就可以开始构建哈夫曼树啦,构建的思想和刚才那张图一样,就是找到最小的两个结点,然后权值相加,然后最小的两个结点的parent的值为新生成结点的下标,新生成结点的孩子的值为两个最小结点的下标。举个例子你就懂了

eq:现在最小的为1,4,则它们相加的结果要放在下标为11的位置,并且parent的值改为11,而11的孩子的值为1,2。更新后如下:

经过n-1次操作后,最终就成了哈夫曼树 ,下面为最终结果,有颜色的为生成结点部分:

可以直观的看到,在最后一个结点,下标为19parent值为-1,所以这个就是根结点,表示没有双亲。

现在的问题是,我们知道是怎么回事,那怎么用代码来实现它呢

首先先设置2个变量存储最小值,2个变量存储最小值的下标,因为要生成n-1个结点,所以我们要操作n-1次,且生成一次我们要比较的次数就会增加1,而且不难看出来最后一次不用比较了,所以我们比较的次数要比现总结点数小1,按我们的例子来说,我们要生成19个结点,且比较18次,那我们剩下的工作就是如何找最小值并且生成新的结点,我们的逻辑思维应该是利用4个变量去记住值和下标

并且只合并parent为-1的结点,parent不为-1的我们就不用再去判断了,代码如下:

#define MAXVALUE 32767   void creatHuffmanTree(huffmanTree& HT, int n)
{if (n <= 1)                                                            //如果结点数小于等于1,不创建return;int min1, min2;                                                       //定义两个数,来存储每次选取最小两个结点的权值int rnode, lnode;                                                    //定义两个下标值,来存储每次选取最小两个结点的下标for (int i = n + 1; i <= 2 * n -1; i++)                                //要生成n-1个结点,所以要操作n—1次且从下标为n+1开始存储{int min1 = MAXVALUE; int lnode = -1;                            //让最小值初始化为极大值,这样叶子结点的最大值再大也不会超过这个值了                          int min2 = MAXVALUE; int rnode = -1;for (int j = 1; j <= i - 1; j++)                               //因为起先是在前n个中选择最小的两个结点的权值,但新生成一个后就得在前n+1个中选择最小的两个结点的权值                           {                                                               //假设n = 10 总结点数就得为19,那我们就只要比较18次就可以得出结果了,记住比较的次数比生成的总结点数少1if (HT[j].weight < min1 && HT[j].parent == -1)            //这个小于就使得当出现相同的权值时优先考虑先出现的值,可以假设下{min2 = min1;  rnode = lnode;                     //碰到比min1小的,那min1的值就给第二小的min2,下标也给它min1 = HT[j].weight; lnode = j;                      //然后最小的给min1,下标同理}else if (HT[j].weight < min2 && HT[j].parent == -1)       //这是第二小的判断{min2 = HT[j].weight;rnode = j;}}HT[lnode].parent = HT[rnode].parent = i;                     //最小两个结点的parent变为生成结点的下标HT[i].lch = lnode; HT[i].rch = rnode;                         //生成结点的左孩子为最小的min1的下标,右孩子同理HT[i].weight = HT[lnode].weight + HT[rnode].weight;             //生成结点的权值等于最小结点的权值相加}}

在此我给出第一次循环的图示,接下来就是同理了:

构成哈夫曼树就在此结束并完成了,而后就是编码。

哈夫曼编码

哈夫曼编码基于哈夫曼树而产生的一种好编码,具体干嘛的我不说了,百度一下你就知道,因为现在已经很晚了,我想一夜干完,哈哈哈

上面已经完成哈夫曼树的构成了,那么编码就是左子树上的为0,右子树上的为1,再自根结点扫描下来到叶子结点,输出的值就为哈夫曼编码。

那我们第一步得确定装编码的存储结构,可以从图中看出,需要一个大数组装很多装编码的小数组,那我们就选择指针数组,因为指针变量就类似一个数组,指针数组是装指针的数组,那就符合我们的需求了。

typedef char** huffmanCode;  //第一个*是代表它是指针变量,说明它是数组//第二个*说明它是指针数组,代表这个char类型数组里每个元素都是*huffmanCode变量

那我们的思路就是搞个临时数组把编码记录下来,然后再给这个大数组里的小数组,那我们怎么求编码呢,这就用到刚才看到那个最后一个结点的parent了,因为它的值是-1,所以它被我们定义为根结点,所以我们只要顺着parent存着的下标一步一步找上去就行了,而后就是左孩子为0,右孩子为1。下面为代码:

void createHuffmanCode(huffmanTree HT, huffmanCode& HC, int n)
{HC = (huffmanCode)malloc(sizeof(huffmanCode) * n + 1);               //申请n + 1个huffmanCode大小huffmanCode类型的临时空间//因为下标是从一开始,所以我们要申请比结点多一个的结点,和哈夫曼树的结构对应,方便输出char* cd = (char*)malloc(sizeof(char) * n);                         //申请n个char大小char类型的临时空间,这个临时数组记录每次遍历出来的编码int start = 0,c = 0,f = 0;                                           //start为cd数组记录下标,c初始为叶子结点下标,而后就是孩子结点的下标,f记录双亲结点的下标cd[n - 1] = '\0';                                                 //这个就是给printf留着的,因为printf不会生成'\0',如果用puts就不用这句语句了for (int i = 1; i <= n; i++)                                        //只要叶子结点的编码{start = n - 1;                                                 //这句要赋值n的话,start--要写在判断后方c = i;f = HT[c].parent;while (f != -1)                                                   //根节点没有双亲{start--;if (HT[f].lch == c)                                         //是左孩子就是0,右孩子就为1cd[start] = '0';elsecd[start] = '1';c = f; f = HT[c].parent;                                 //向根结点接近}HC[i] = (char*)malloc(sizeof(char) * (n - start));                //给数组里的数组申请n - start个char大小的char*类型的临时空间strcpy(HC[i], &cd[start]);                                      //cd里记录的编码给HC的第i个数组}free(cd);                                                           //释放临时空间
}

出一个循环的图,这图画的我累死。

全部代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#define MAXVALUE 32767      //极大值相当于无穷大
#define NODENUM 10          //叶子结点数
typedef struct
{char data;             //数据域int weight;                //结点的权值int parent, lch, rch;    //双亲与孩子的下标
}htNode,*huffmanTree;       typedef char** huffmanCode; //第一个*是代表它是指针变量,说明它是数组//第二个*说明它是指针数组,代表这个char类型数组里每个元素都是*huffmanCode变量int initHuffmanTree(huffmanTree& HT);                               //初始化哈夫曼树
void creatHuffmanTree(huffmanTree& HT, int n);                      //构建哈夫曼树
void createHuffmanCode(huffmanTree HT, huffmanCode &HC, int n);     //编写哈夫曼编码
int main()
{huffmanTree HT ;initHuffmanTree(HT);huffmanCode HC;creatHuffmanTree(HT,NODENUM);createHuffmanCode(HT,HC,NODENUM);/*for (int i = NODENUM + 1; i <= 2 * NODENUM - 1; i++)printf("%d ", HT[i].weight);*/for (int i = 1; i <= NODENUM; i++)                               //遍历输出编码{printf("%c:\t",HT[i].data);printf("%s\n", HC[i]);}return 0;
}
int initHuffmanTree(huffmanTree& HT)
{HT = (htNode*)malloc(sizeof(htNode) * (2 * NODENUM));         //给HT分配2 * NODENUM个htNOde大小的htNode类型的数组for (int i = 1; i <= 2 * NODENUM - 1; i++)                        //下标从1开始到2 * NODENUM{HT[i].parent = HT[i].lch = HT[i].rch = -1;                  //双亲和孩子的值都置为-1}printf("please input some weight!\n");for (int i = 1; i <= NODENUM; i++)                                //权值只有1-n个{scanf("%d",&HT[i].weight);                                 //给每个结点赋予权值}char c = getchar();                                            //这个来接收上面的回车printf("please input some data!\n");for (int i = 1; i <= NODENUM; i++){//scanf("%c ",&HT[i].data);char a = getchar();if(a == '\n')                                              //遇到回车就结束break;elseHT[i].data = a;                                         //给每个结点赋予数据}return 1;
}void creatHuffmanTree(huffmanTree& HT, int n)
{if (n <= 1)                                                            //如果结点数小于等于1,不创建return;int min1, min2;                                                       //定义两个数,来存储每次选取最小两个结点的权值int rnode, lnode;                                                    //定义两个下标值,来存储每次选取最小两个结点的下标for (int i = n + 1; i <= 2 * n -1; i++)                                //要生成n-1个结点,所以要操作n—1次且从下标为n+1开始存储{int min1 = MAXVALUE; int lnode = -1;                            //让最小值初始化为极大值,这样叶子结点的最大值再大也不会超过这个值了                          int min2 = MAXVALUE; int rnode = -1;for (int j = 1; j <= i - 1; j++)                               //因为起先是在前n个中选择最小的两个结点的权值,但新生成一个后就得在前n+1个中选择最小的两个结点的权值                           {                                                               //假设n = 10 总结点数就得为19,那我们就只要比较18次就可以得出结果了,记住比较的次数比生成的总结点数少1if (HT[j].weight < min1 && HT[j].parent == -1)            //这个小于就使得当出现相同的权值时优先考虑先出现的值,可以假设下{min2 = min1;  rnode = lnode;                     //碰到比min1小的,那min1的值就给第二小的min2,下标也给它min1 = HT[j].weight; lnode = j;                      //然后最小的给min1,下标同理}else if (HT[j].weight < min2 && HT[j].parent == -1)       //这是第二小的判断{min2 = HT[j].weight;rnode = j;}}HT[lnode].parent = HT[rnode].parent = i;                     //最小两个结点的parent变为生成结点的下标HT[i].lch = lnode; HT[i].rch = rnode;                         //生成结点的左孩子为最小的min1的下标,右孩子同理HT[i].weight = HT[lnode].weight + HT[rnode].weight;             //生成结点的权值等于最小结点的权值相加}}void createHuffmanCode(huffmanTree HT, huffmanCode& HC, int n)
{HC = (huffmanCode)malloc(sizeof(huffmanCode) * n + 1);               //申请n + 1个huffmanCode大小huffmanCode类型的临时空间//因为下标是从一开始,所以我们要申请比结点多一个的结点,和哈夫曼树的结构对应,方便输出char* cd = (char*)malloc(sizeof(char) * n);                         //申请n个char大小char类型的临时空间,这个临时数组记录每次遍历出来的编码int start = 0,c = 0,f = 0;                                           //start为cd数组记录下标,c初始为叶子结点下标,而后就是孩子结点的下标,f记录双亲结点的下标cd[n - 1] = '\0';                                                 //这个就是给printf留着的,因为printf不会生成'\0',如果用puts就不用这句语句了for (int i = 1; i <= n; i++)                                        //只要叶子结点的编码{start = n - 1;                                                 //这句要赋值n的话,start--要写在判断后方c = i; f = HT[c].parent;while (f != -1)                                                  //根节点没有双亲{start--;if (HT[f].lch == c)                                         //是左孩子就是0,右孩子就为1cd[start] = '0';elsecd[start] = '1';c = f; f = HT[c].parent;                                 //向根结点接近}HC[i] = (char*)malloc(sizeof(char) * (n - start));                //给数组里的数组申请n - start个char大小的char*类型的临时空间strcpy(HC[i], &cd[start]);                                      //cd里记录的编码给HC的第i个数组}free(cd);                                                           //释放临时空间
}

还得加紧学习,只能写到这了,有问题的请指出,问问题的可以评论哦,然后我有空再在有问题的地方再讲细点。

从严老师的教材中学来------------------------------------------------------------------------------------------

哈夫曼树编码的实现+图解(含全部代码)相关推荐

  1. 赫夫曼树编码的算法及应用习题--数据结构

    赫夫曼树编码的算法及应用习题 1.构造赫夫曼树的方法 1.根据给定的n个权值{w1,w2,---wn},构成n棵二叉树的集合F={T1,T2...,Tn},其中每棵二叉树中只有一个带权为Wi的根结点, ...

  2. java哈夫曼树编码_哈夫曼树的编码实验

    Java哈夫曼编码实验--哈夫曼树的建立,编码与解码 建树,造树,编码,解码 一.哈夫曼树编码介绍 1.哈夫曼树: (1)定义:假设有n个权值{w1, w2, ..., wn},试构造一棵含有n个叶子 ...

  3. 哈夫曼树的构建及哈夫曼树编码

    哈夫曼树的构建: 注意:(1).首先把一组数3 5 6 8 9 12 15从小到大排列 (2).选取里面最小2个,顶点出为2个数的和 (3).新产生的顶点在与原先的数字进行比较,在里面选取2个最小的数 ...

  4. labview 霍夫曼树_哈夫曼树编码实验报告_信息论与编码实验2 实验报告_信息论与编码报告...

    huffman编码C语言实验报告 今日推荐 180份文档 2014...4页 1下载券 安卓版100 doors 2攻略1... 3页 1下载券 <逃脱本色>doors....语文教育实习 ...

  5. 哈夫曼树构建与哈夫曼树编码

    图解哈夫曼树:https://blog.csdn.net/lee18254290736/article/details/77618201 构建哈夫曼及哈夫曼编码实现:https://blog.csdn ...

  6. 哈夫曼树编码与译码(完整C/C++实现代码)

    哈夫曼编码的设计与应用 问题需求分析 用哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种.Huffman于1952年提出一种编码方法 ...

  7. 哈夫曼树编码和译码c语言,C++哈夫曼树编码和译码的实现

    78 /*-----------创建工作---------------------------*/ 79     int s1,s2; 80     for (int i = n + 1; i < ...

  8. 利用哈夫曼树编码与译码

    #include<iostream> #include<string.h> #include<stdlib.h> using namespace std;typed ...

  9. 哈夫曼树和哈夫曼树编码

    在一般的数据结构的书中,树的那章后面,著者一般都会介绍一下哈夫曼(HUFFMAN) 树和哈夫曼编码.哈夫曼编码是哈夫曼树的一个应用.哈夫曼编码应用广泛,如 JPEG中就应用了哈夫曼编码. 首先介绍什么 ...

最新文章

  1. H5开发中的问题总结
  2. 33、Power Query-统计员工完成业绩的记录
  3. linux ntfs 新建,Linux在NTFS中创建的文件的权限
  4. java c 基本类型_java 基本数据类型
  5. php a标签里 href的mysql_php,正则表达式_php提取html中指定div下a标签的text和href问题,php,正则表达式 - phpStudy...
  6. 外贸用ERP仓储系统有那些好处?
  7. 为什么最近「骚扰电话」明显越来越多了?
  8. 自动布局和view 设置frame同时有效
  9. java实现23种设计模式之普通工厂模式和抽象工厂模式
  10. python计算bmi指数_python 练习题:计算的BMI指数,并根据BMI指数条件选择
  11. Calendar类方法——编写万年历的两种方式
  12. laravel8 微信小程序(实现简单签到功能)
  13. 概率论————思维导图(上岸必备)(随机事件与概率)
  14. 什么是http服务器
  15. 支持delete吗_那些年删过的库,跑过的路,你从中找到解决方法了吗?
  16. 概要设计的过程和任务
  17. 桌面日历软件有哪些?日历提醒便签软件推荐
  18. java中如何在键盘中输入一串以逗号隔开数字然后存入数组中,并输出。
  19. CARLA 笔记(02)— Ubuntu 安装 CARLA(服务端、客户端、安装 miniconda、创建虚拟环境、更换 pip 源、生成交通流、人工控制车辆按键)
  20. sdust-Java-字符串集合求并集

热门文章

  1. 信息安全专业课程(概要汇总)
  2. ui界面设计课程有哪些:ui设计所学课程
  3. 气传导蓝牙耳机品牌推荐,精选这四款好用的气传导耳机
  4. python升级版本命令-python升级命令
  5. HTML中三大基础选择器
  6. 扫雷小游戏 2.0版本
  7. 迅为iTOP4412 uboot烧写错误挽救办法
  8. com和cn域名的区别
  9. D-A-K方法求天然气偏差系数 以及等温压缩系数、天然气体积系数、天然气黏度的计算。牛顿迭代法。
  10. 每日小结(2023.3.25)