版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_39956356/article/details/80270851
基本概念:哈夫曼树、WPL、哈夫曼编码
哈夫曼(Huffman)树又称最优二叉树或最优搜索树,是一种带权路径长度最短的二叉树。
树的带权路径长度(WPL):就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的带权路径长度记为WPL=(W1*L1+W2*L2+W3*L3+…+Wn*Ln),N个权值Wi(i=1,2,…n)构成一棵有N个叶结点的二叉树。
哈夫曼编码广泛地用于数据文件压缩的编码方法。通讯的报文。
哈夫曼树的存储表示
这里需要知道双亲的位置–采用的int整型变量。
typedef struct
{
int weight; //权值
unsigned int parent, lchild, rchild; //双亲及左右孩子的下标
}HTNode, *HuffmanTree;
typedef char *HuffmanCode; //哈夫曼编码
这里有一个HuffmanCode 被定义成char* ;为了之后的二维字符串数组
“000” “01” “1” “001”
选取两个最小、次小的结点
这里我分析一下最小值是怎么想的,次之和最小差不多,仅仅除去最小值而已。
这里就用两个for循环就可以了。
/*****************************************************************
** *HT:哈夫曼树
** s1 :最小数的位置
** s2 :次小数的位置
** n :权值的个数(叶子结点)一棵n叶子结点的哈夫曼共有2n-1个结点
******************************************************************/
void select(HuffmanTree *HT, int n, int *s1, int *s2)
{
int i = 0;
int min; //记录最小权值
for (i = 1; i <= n; i++) //遍历全部结点,找出单节点
{
if (0 == (*HT)[i].parent) //如果此结点的父亲没有,那么把结点号赋值给min,跳出循环
{
min = i;
break;
}
}
for (i = 1; i <= n; i++) //继续遍历全部结点,找出权值最小的单节点
{
if (0 == (*HT)[i].parent) //如果此结点的父亲为空,则进入 if
{
if ((*HT)[i].weight < (*HT)[min].weight) //如果此结点的权值比 min 结点的权值小,那么更新 min 结点,否则就是最开始的 min
{
min = i;
}
}
}
*s1 = min; //s1指向最小权值的结点
for (i = 1; i <= n; i++) //遍历全部结点
{
if ((*HT)[i].parent == 0 && i != (*s1)) //找出下一个单节点,且没有被 s1指向,那么i 赋值给 min,跳出循环
{
min = i;
break;
}
}
for (i = 1; i <= n; i++) //继续遍历全部结点,找到权值最小的那一个
{
if (0 == (*HT)[i].parent && i != (*s1))
{
if ((*HT)[i].weight < (*HT)[min].weight) //如果此结点的权值比 min 结点的权值小,那么更新 min 结点,否则就是最开始的min
{
min = i;
}
}
}
*s2 = min; //s2指向次小权值的叶子结点
}
创建哈夫曼树与哈夫曼编码
创建哈夫曼树:
1:分配m+1个空间(度0+度2)
2:n个度0叶子结点初始化赋值{w[i-1], 0, 0, 0}
3:n-1个度2结点初始化赋值{0, 0, 0, 0}
4:选择两个最小的结点,修改孩子的双亲,双亲的左右孩子,双亲的权值
5:输出各节点信息及WPL值
哈夫曼编码:
1:多分配一个(n+1)二维字符数组
2:创建一个临时字符串,存放一个节点的编码(如 000),记得释放
3:编码循环左为0,右为1
4:分配一个HCi,把temp拷贝到HC[i]
5:输出哈夫曼编码
HC[i]存放每个节点的信息:
这里给出编码for循环的思路:
/*****************************************************************
** *HT:哈夫曼树
** *HC:哈夫曼编码二维字符数组
** *w :存放权值的数组
** n :权值的个数(叶子结点)一棵n叶子结点的哈夫曼共有2n-1个结点
******************************************************************/
void HuffmanCoding(HuffmanTree *HT, HuffmanCode *HC, int *w, int n)
{
if (n < 1)
return ;
int m; //哈夫曼树的总结点树
m = 2 * n - 1; //一棵有n个叶子结点的哈夫曼树共有2n - 1个结点
*HT = (HuffmanTree)malloc((m+1)*sizeof(HTNode)); //0号单元没有用
int i;
for (i = 1; i <= n; i++) //初始化叶子结点--n个
{
(*HT)[i].weight = w[i-1];
(*HT)[i].parent = 0;
(*HT)[i].lchild = 0;
(*HT)[i].rchild = 0;
}
for (; i <= m; i++) //初始化度2结点(双亲节点及根结点)--(n-1)个
{
(*HT)[i].weight = 0;
(*HT)[i].parent = 0;
(*HT)[i].lchild = 0;
(*HT)[i].rchild = 0;
}
int s1, s2;
for (i = n + 1; i <= m; i++) //开始创建后(n-1)个结点
{
select(HT, i-1, &s1, &s2); //i逐渐增加,比较对象个数增加(对于上一轮被更改的双亲的结点,找最小值就不考虑了,但是它的位置任然固定)
(*HT)[s1].parent = i; //在这里两个较小的结点双亲改变,所以接下来的select()就会除掉这两个点
(*HT)[s2].parent = i;
(*HT)[i].lchild = s1;
(*HT)[i].rchild = s2;
(*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;
printf("%d (%d %d)\n", (*HT)[i].weight, (*HT)[s1].weight, (*HT)[s2].weight); //双亲权值 左孩子权值 右孩子权值
}
printf("index weight parent lChild rChild\n"); //输出每个节点的所有信息
for (i = 1; i <= m; ++i)
{
printf("%d\t", i); //结点所在的序号
printf("%d\t", (*HT)[i].weight);
printf("%d\t", (*HT)[i].parent);
printf("%d\t", (*HT)[i].lchild);
printf("%d\n", (*HT)[i].rchild);
}
printf("\nWPL = %d\n\n", (*HT)[m].weight); //输出WPL值,WPL值必定是最后结点的weight,且它的parent为0
HC = (HuffmanCode *)malloc((n + 1) * sizeof(char *)); //分配编码空间
char *temp = (char *)malloc(n * sizeof(char)); //临时储存,以备复制给HC[i]
temp[n - 1] = '\0'; //倒序输出
int start, c, f;
for (i = 1; i <= n; i++) //叶子结点需要编码
{
start = n - 1;
for (c = i, f = (*HT)[i].parent; f != 0; c = f, f = (*HT)[f].parent) //先用c记录当前位置,f记录c的双亲的位置,不断往上同时走
{
if (c == (*HT)[f].lchild) //左边编码为0
temp[--start] = '0';
else //右边编码为1
temp[--start] = '1';
}
HC[i] = (char *)malloc((n-start)* sizeof(char)); //动态分配HC[i],由于每个结点编码长度不一致,start值每次循环都不一样(编码一个0或1,start减一)
strcpy(HC[i], &temp[start]); //单个叶子节点编码拷贝
}
free(temp); //释放临时的temp空间
for (i = 1; i <= n; i++)
{
printf("HuffmanCode of\t %3d is %s\n", (*HT)[i].weight, HC[i]); //输出各结点的编码
}
}
输出结果
工程文件VS2017与感谢
链接: https://pan.baidu.com/s/1fzILwqlj79pGt7C3uPhwSA 密码: rkcd
这里感谢以下博主文章对我的启发与思考:
https://www.cnblogs.com/kubixuesheng/p/4397798.html
若你觉得文章对你有启发,请注明出处。