作者:shmily
目录
实验内容
赫夫曼编码的设计与实现
赫夫曼树:假设有n个权值 {W1,W2,…,Wn}
,试构造一颗有n个叶子结点的二叉树,每个叶子结点带权为Wi,则其中带权路径长度WPL最小的二叉树称为。
例如下图:有3棵二叉树,都有4个叶子结点a,b,c,d,分别带权7,5,2,4,它们的带权路径长度分别为:
(a)WPL = 72 + 52 + 22 + 42 = 36
(b)WPL = 73 + 53 + 21 + 42 = 46
©WPL = 71 + 52 + 23 + 43 = 35
其中,©树的WPL最小,它便是赫夫曼树,即其带权路径长度在所有带权为7、5、2、4的4个叶子结点的二叉树中居最小。
构造赫夫曼数可以利用赫夫曼最早给出的一个带有一般规律的算法,即赫夫曼算法。步骤如下:
(1)根据给定的n个权值 {W1,W2,…, Wn}
构成n棵二叉树的集合 F={T1,T2,…,Tn}
,其中每棵二叉树Ti中只有一个带权为Wi的根结点,其左右子树均为空;
(2)在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和;
(3)在F中删除这两棵树,同事将新得到的二叉树加入F中;
(4)重复(2)和(3),直到F只含一棵树为止。这棵树便是赫夫曼树。
例如下图展示了赫夫曼树的构造过程:
赫夫曼编码是赫夫曼树的应用,通常采用前缀编码,使得解码时不会产生混淆。具体方法为;从叶子出发走一条从叶子到根的路径,如上图字符C所在的叶子到根结点的路径为字符串011,再将所得的字符串反转得到110,便是该字符C的编码。继续分析上例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcWPmNLr-1585656751344)(C:\Users\lenovo\Desktop\3.PNG)]
赫夫曼译码是编码的逆向工程,具体做法为:从根出发走一条从根到叶子的路径,如根据字符C的编码110,从根出发,按字符’0’或’1’确定找左孩子或右孩子,直至叶子结点,便求得该编码字符串对应的字符。
源代码:
#include <vector>
#include <iostream>
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
//haffman 树的结构
typedef struct
{
//叶子结点权值
unsigned int weight;
//指向双亲,和孩子结点的指针
unsigned int parent;
unsigned int lChild;
unsigned int rChild;
} Node, *HuffmanTree;
//动态分配数组,存储哈夫曼编码
typedef char *HuffmanCode;
//选择两个parent为0,且weight最小的结点s1和s2的方法实现
//n 为叶子结点的总数,s1和 s2两个指针参数指向要选取出来的两个权值最小的结点
void select(HuffmanTree *huffmanTree, int n, int *s1, int *s2)
{
//标记 i
int i = 0;
//记录最小权值
int min;
//遍历全部结点,找出单节点
for(i = 1; i <= n; i++)
{
//如果此结点的没有父节点,那么把结点号赋值给 min,跳出循环
if((*huffmanTree)[i].parent == 0)
{
min = i;
break;
}
}
//继续遍历全部结点,找出权值最小的单节点
for(i = 1; i <= n; i++)
{
//如果此结点的父亲为空,则进入 if
if((*huffmanTree)[i].parent == 0)
{
//如果此结点的权值比 min 结点的权值小,那么更新 min 结点,否则就是最开始的 min
if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight)
{
min = i;
}
}
}
//找到了最小权值的结点,s1指向
*s1 = min;
//遍历全部结点
for(i = 1; i <= n; i++)
{
//找出下一个单节点,且没有被 s1指向,那么i 赋值给 min,跳出循环
if