专栏目录(数据结构与算法解析):https://blog.csdn.net/qq_40344524/article/details/107785323
哈夫曼树
哈夫曼树也叫最优二叉树,是带权路径长度最短的树。
路径长度
根结点到第L层结点路径长度为L-1
带权路径长度(WPL)
树中所有叶子节点到根节点的路径长度与该叶子节点权值的乘积之和
特性:
• 哈夫曼树不一定是唯一的。
• 哈夫曼树的左右子树可以互换,这样做并不影响树的带权路径长度。
• 带权值的节点都是叶子节点,不带权值的节点都是某棵子二叉树的根节点。
• 权值越大的节点越靠近哈夫曼树的根节点,权值越小的节点越远离哈夫曼树的根节点。
• 哈夫曼树中只有叶子节点和度为2的节点,没有度为1的节点。
• 一棵有n个叶子节点的哈夫曼树共有2n-1个节点。
构建步骤:
结点权重:1,3,6,7,8,12,5,9,11
1、 按照从小到大的顺序排序:
2、 选最小的值合成一棵树:
3、 将新的数据重新排序:
4、选最小的两个值合成树:
5、重新排序:
6、选最小的值合成树:
7、重新排序:
8、选最小的值合成树:
9、重新排序:
10、选最小的值合成树:
11、重新排序:
12、选最小的值合成树:
13、重新排序:
14、选最小的值合成树:
15、重新排序:
16、选最小的值合成树:
最终形态
哈夫曼树的应用场景很广泛,最为人熟知的就是哈夫曼编码,哈夫曼编码解决了远距离数据传输过程中数据的优化问题,是传输的报文尽可能短,哈夫曼编码的实现原理和编码过程如下:
规定左分支编码为字符0,右分支编码为字符1,将从根节点到叶子节点的路径上分支字符组成的字符串作为叶子节点字符的编码,这便是赫夫曼编码。
以上述创建的哈夫曼树为例替换分支编码生成新的哈夫曼编码树:
权值为12的结点编码为:10,
权值为8的结点编码为:000,
权值为9的结点编码为:010,
权值为11的结点编码为:011,
权值为6的结点编码为:110,
权值为7的结点编码为:111,
权值为5的结点编码为:0011,
权值为1的结点编码为:00100,
权值为3的结点编码为:00101,
接下来我们通过代码来实现哈夫曼树和哈夫曼编码:
哈夫曼树的实现
结点结构体:
typedef struct node{
char ch; //存储该节点表示的字符,只有叶子节点用的到
int weight; //记录该节点的权值
struct node *leftSon; //记录左孩子结点的地址
struct node *rightSon; //记录右孩子结点的地址
}Node;
构建哈夫曼树:
//创建哈夫曼树,n--带权值的个数,a--权值数组
struct Node* CreateHuffman(ElemType a[], int n)
{
int i, j;
struct Node**b, *q;
b = malloc(n*sizeof(struct Node));
for (i = 0; i < n; i++) //初始化b指针数组,使每个指针元素指向a数组中对应的元素结点
{
b[i] = malloc(sizeof(struct Node));
b[i]->data = a[i];
b[i]->left = b[i]->right = NULL;
}
for (i = 1; i < n; i++)//进行 n-1 次循环建立哈夫曼树
{
//k1表示森林中具有最小权值的树根结点的下标,k2为次最小的下标
int k1 = -1, k2;
for (j = 0; j < n; j++)//让k1初始指向森林中第一棵树,k2指向第二棵
{
if (b[j] != NULL && k1 == -1)
{
k1 = j;
continue;
}
if (b[j] != NULL)
{
k2 = j;
break;
}
}
for (j = k2; j < n; j++)//从当前森林中求出最小权值树和次最小
{
if (b[j] != NULL)
{
if (b[j]->data < b[k1]->data)
{
k2 = k1;
k1 = j;
}
else if (b[j]->data < b[k2]->data)
k2 = j;
}
}
//由最小权值树和次最小权值树建立一棵新树,q指向树根结点
q = malloc(sizeof(struct Node));
q->data = b[k1]->data + b[k2]->data;
q->left = b[k1];
q->right = b[k2];
b[k1] = q;//将指向新树的指针赋给b指针数组中k1位置
b[k2] = NULL;//k2位置为空
}
free(b); //删除数组b
return q; //返回哈夫曼树的树根指针
}
哈夫曼编码实现
实现哈夫曼编码:
//哈夫曼编码
void HuffManCoding(struct Node* FBT, int len)//len初始值为0
{
static int a[10];
if (FBT != NULL)
{
if (FBT->left == NULL && FBT->right == NULL)
{
int i;
printf("权值为%d的结点的编码:", FBT->data);
for (i = 0; i < len; i++)
printf("%d", a[i]);
printf("\n");
}
else
{
a[len] = 0;
HuffManCoding(FBT->left, len + 1);
a[len] = 1;
HuffManCoding(FBT->right, len + 1);
}
}
}
总结
哈夫曼树的产生就是为了简化编码,获取最优编码,为了实现带权路径长度最短的目的,在构造哈夫曼树时权值越大的结点离根越近。哈夫曼树的出现时计算机编码的一大进步,哈夫曼树在编码译码领域得到了很广泛的应用。
哈夫曼树就介绍到这里,下一节学习另一种特殊的树——B-树。
以上是我的一些粗浅的见解,有表述不当的地方欢迎指正,谢谢!
表述能力有限,部分内容讲解的不到位,有需要可评论或私信,看到必回…