路径:
1.路径与长度
如下
在上图的二叉树里面,从A到B,即从根节点到它的左孩子的路径
~特别的,一个节点到另外一个节点的路径不止一条
路径的长度即一个节点到另一个节点之间经历的每一个树枝。
注意:路径的长度指的是树枝的个数而不是节点的个数
带权路径长度:
计算带权路径长度之前,我们必须给树的每一个节点赋予一个特殊的意义,一般我们会是数字,而这个数字就成为这个节点的权,带权路径长度就是路径长度×权
还是上图:
如图,蓝色字体为该节点的权
从A到I的路径长度:3
I的权为9
A到I的带权路径长度为 3 * 9 = 27
带权路径和:
树的带权路径和:树的叶子节点的带权路径和的和
叶子节点:E,H,I,J,K,F
他们的路径长度:E:2,H:3,I:3,J:3,K:3,F:2
他们的权为: E:4,H:8,I:9,J:10,K:11,F:6
带权路径和:2 * 4+3 * 8+3 * 9+3 * 10+3 * 11+2 * 6 = 8+24+27+30+33+12 = 134
接下来就是我们今天的重点了
哈夫曼树:
定义:
哈夫曼树又称最优二叉树,带权路权长度最短的二叉树,带权路径即叶节点的权值与其到根节点的路径长度的乘积
哈夫曼树的构建
- 哈夫曼树是一种特殊的二叉树,用于构建数据的最优前缀编码。
- 对于给定的一组权重(通常是字符出现的频率),哈夫曼树可以生成一个最优的前缀编码,使得频率高的字符拥有较短的编码,频率低的字符拥有较长的编码。
哈夫曼树构建的精髓在于每次选择权最小的两个节点构成一颗子树,并且将构成的子树的根节点重新按照从小到大的顺序放在原本的节点选项中,重复这个过程,最后得到的树就叫做哈夫曼树
我们以2 3 5 6 9 13 22 33为例子
第一步,首先选择权值最小的2,3,而后,构成第二排所示的5-2-3.然后将该子树的根节点5 按照顺序放入原本序列中
第二步,找到新数列中此时最小的两个节点5,6,而后5,6构成新的子树,将5,6的和11按照顺序放在数列中,将根节点11作为数列的一部分
第三步:找到此时最小的两个权值9和13,并且组成一颗新的子树放在其中:
第四步:找到此时最小的两个权值11和22,构成一颗新的子树,并且将根节点和33匹配。
哈夫曼编码
哈夫曼编码遵从顺着哈夫曼树的路径,如果路径经过的节点是往左边走,则在树枝上面标记0,如果经过的路径是往右走,则在树枝的上面标记1
于是我们在结构体中,会构建两个tag用来记录。
然后,我们开始读取哈夫曼编码,哈夫曼编码的读取:
只需要读取从根节点开始,到需要读取的节点路径上经过的tag
在本图中,
B:0
I:011
G:10
K:100
接下来,我们来看代码实现
附录(源代码实现)
“Huffman.h”
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdbool.h>
#define Maxleaf 30
#define Maxnode 1000
#define Max 100000
#define MaxSize 100
// 定义结构体
typedef struct HufmTree{
char ch; // 字符型节点名
float weight; // 权值变量
/* 左右孩子和双亲指针 */
int lChild;
int rChild;
int parent;
}HufmTree;
typedef struct Codetype {
char bits[MaxSize]; // 定义字符型数组存储节点
int start; // 字符串起点
char ch; // 存储节点名称
}Codetype;
// 接口函数
// 创建哈夫曼树(最优二叉树)
void CreatHuffman(HufmTree tree[], int n);
// 哈夫曼树的编码函数
void HuffmanCode(Codetype code[], HufmTree tree[], int n);
// 译码函数
void DeCode(HufmTree tree[], int m);
void Menu();
“Huffman.c”
// 函数的具体实现
#include "Huffman.h"
void Test() {
int n, m;
int choose;
HufmTree tree[Maxnode];
Codetype code[Maxleaf];
while (true) {
printf("\n请输入要进行的功能:");
scanf("%d", &choose);
switch (choose)
{
case 1:
printf("请输入元素个数: ");
scanf("%d", &n);
getchar();
CreatHuffman(tree, n);
printf("成功建立哈夫曼树! \n");
break;
case 2:
printf("请输出哈夫曼编码:\n");
HuffmanCode(code, tree, n);
for (int i = 0; i < n; i++) {
printf("%c :", code[i].ch);
for (int j = code[i].start; j < n; j++) {
printf("%c", code[i].bits[j]);
}
printf("\n");
}
break;
case 3:
m = 2 * n - 1;
printf("请输入编码,以#为结束标志:\n");
DeCode(tree, m);
break;
default:
return 0;
}
}
}
int main() {
Menu();
Test();
}
//测试用例
“1.c”
#include "Huffman.h"
void Test() {
int n, m;
int choose;
HufmTree tree[Maxnode];
Codetype code[Maxleaf];
while (true) {
printf("\n请输入要进行的功能:");
scanf("%d", &choose);
switch (choose)
{
case 1:
printf("请输入元素个数: ");
scanf("%d", &n);
getchar();
CreatHuffman(tree, n);
printf("成功建立哈夫曼树! \n");
break;
case 2:
printf("请输出哈夫曼编码:\n");
HuffmanCode(code, tree, n);
for (int i = 0; i < n; i++) {
printf("%c :", code[i].ch);
for (int j = code[i].start; j < n; j++) {
printf("%c", code[i].bits[j]);
}
printf("\n");
}
break;
case 3:
m = 2 * n - 1;
printf("请输入编码,以#为结束标志:\n");
DeCode(tree, m);
break;
default:
return 0;
}
}
}
int main() {
Menu();
Test();
}
运行效果
- 创建哈夫曼树
2.编码
3.译码
若输入的编码在系统中没有就显示ERROR来提示输入错误
最终效果:
特别的,我们再次看图来分析代码,不难发现,我们控制台输出的和我们画图构建出来的最优二叉树是相符合的