哈夫曼树的基本概念
哈夫曼树的构造算法
哈夫曼树的构造示例
哈夫曼编码的基本概念
哈夫曼编码的程序实现
编码实现
假设字符集及其概率分布为:
Data = {A B C D E F G}
Weight = {0.4 0.3 0.15 0.05 0.04 0.03 0.03}
构造哈夫曼树
哈夫曼树对应的结构数组为
weight | parent | lch | rch |
---|---|---|---|
0.4 | 13 | 0 | 0 |
0.3 | 12 | 0 | 0 |
0.15 | 11 | 0 | 0 |
0.05 | 9 | 0 | 0 |
0.04 | 9 | 0 | 0 |
0.03 | 8 | 0 | 0 |
0.03 | 8 | 0 | 0 |
0.06 | 10 | 7 | 6 |
0.09 | 10 | 5 | 4 |
0.15 | 11 | 8 | 9 |
0.3 | 12 | 10 | 3 |
0.6 | 13 | 11 | 2 |
1 | 0 | 1 | 12 |
求哈夫曼编码
从上面的哈夫曼树中求出对应的哈夫曼编码如下
A 0
B 11
C 01
D 10011
E 10010
F 10001
存储结构
采用静态的结构数组
typedef struct
{
int weight;
int parent, lchild, rchild;
} HTNode, *HuffmanTree;
typedef char **HuffmanCode;
文件的编码与解码
编码
- 输入各字符及其权值
- 构造哈夫曼树–HT[i]
- 进行哈夫曼编码–HC[i]
- 查找 HC[i],得到各字符的哈夫曼编码
解码
- 构造哈夫曼树
- 依次读入二进制码
- 读入 0 则走向左孩子,读入 1 则走向右孩子
- 一旦到达某叶子结点时,即可译出字符
- 然后再从根出发继续译码,直到结束
代码实现
main.c
/*
* Change Logs:
* Date Author Notes
* 2021-07-27 tyustli first version
*/
#include "tree.h"
int main(int argc, char *argv[])
{
HuffmanTree HT;
HuffmanCode HC;
int *w, n, i;
printf("请输入权值的个数(>1): ");
scanf("%d", &n);
w = (int *)malloc(n * sizeof(int));
printf("请依次输入%d个权值(整型):\n", n);
for (i = 0; i <= n - 1; i++)
scanf("%d", w + i);
HuffmanCoding(&HT, &HC, w, n);
printf("对应的哈夫曼编码为:");
for (i = 1; i <= n; i++)
printf("%s ", HC[i]);
printf("\n");
return 1;
}
/*
编译:make
运行:./obj
结果:
请输入权值的个数(>1): 4
请依次输入4个权值(整型):
1
2
3
4
对应的哈夫曼编码为:110 111 10 0
*/
/***************** end of file ******************/
tree.c
/*
* Change Logs:
* Date Author Notes
* 2021-07-27 tyustli first version
*/
#include "tree.h"
int huff_min(HuffmanTree t, int i)
{ // 返回i个结点中权值最小的树的根结点序号,函数select()调用
int j, flag;
unsigned int k = UINT_MAX; // 取k为不小于可能的值(无符号整型最大值)
for (j = 1; j <= i; j++)
if (t[j].weight < k && t[j].parent == 0) // t[j]是树的根结点
k = t[j].weight, flag = j;
t[flag].parent = 1; // 给选中的根结点的双亲赋1,避免第2次查找该结点
return flag;
}
// 在 i 个结点中选择2个权值最小的树的根结点序号,s1为其中序号小的那个
void huff_select(HuffmanTree t, int i, int *s1, int *s2)
{
int j;
*s1 = huff_min(t, i);
*s2 = huff_min(t, i);
if (*s1 > *s2)
{
j = *s1;
*s1 = *s2;
*s2 = j;
}
}
// w 存放 n 个字符的权值(均>0),构造赫夫曼树 HT,并求出 n 个字符的赫夫曼编码 HC
void HuffmanCoding(HuffmanTree *HT, HuffmanCode *HC, int *w, int n)
{
int m, i, s1, s2, start;
unsigned c, f;
HuffmanTree p;
char *cd;
if (n <= 1)
return;
m = 2 * n - 1; /* n 个 结点,合并 n - 1 次,共 2n-1 个结点 */
*HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); // 0号单元未用
if (NULL == *HT)
{
printf("malloc failed\r\n");
exit(-1);
}
/* 前 n 个结点初始化 */
for (p = *HT + 1, i = 1; i <= n; ++i, ++p, ++w)
{
(*p).weight = *w;
(*p).parent = 0;
(*p).lchild = 0;
(*p).rchild = 0;
}
/* 后 n-1 个结点初始化 */
for (; i <= m; ++i, ++p)
(*p).parent = 0;
// 建赫夫曼树
for (i = n + 1; i <= m; ++i)
{ // 在HT[1~i-1]中选择parent为0且weight最小的两个结点,其序号分别为s1和s2
huff_select(*HT, i - 1, &s1, &s2);
(*HT)[s1].parent = (*HT)[s2].parent = i;
(*HT)[i].lchild = s1;
(*HT)[i].rchild = s2;
(*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;
}
// 从叶子到根逆向求每个字符的赫夫曼编码
*HC = (HuffmanCode)malloc((n + 1) * sizeof(char *)); // 分配 n 个字符编码的头指针向量([0]不用)
cd = (char *)malloc(n * sizeof(char)); // 分配求编码的工作空间
cd[n - 1] = '\0'; // 编码结束符
for (i = 1; i <= n; i++)
{ // 逐个字符求赫夫曼编码
start = n - 1; // 编码结束符位置
for (c = i, f = (*HT)[i].parent; f != 0; c = f, f = (*HT)[f].parent)
// 从叶子到根逆向求编码
if ((*HT)[f].lchild == c)
cd[--start] = '0';
else
cd[--start] = '1';
(*HC)[i] = (char *)malloc((n - start) * sizeof(char));
// 为第i个字符编码分配空间
strcpy((*HC)[i], &cd[start]); // 从cd复制编码(串)到HC
}
free(cd); // 释放工作空间
}
/********** end of file ******************/
tree.h
/*
* Change Logs:
* Date Author Notes
* 2021-07-27 tyustli first version
*/
#include <string.h>
#include <malloc.h> // malloc()等
#include <limits.h> // INT_MAX等
#include <stdlib.h> // atoi()
typedef struct
{
int weight;
int parent, lchild, rchild;
} HTNode, *HuffmanTree;
typedef char **HuffmanCode;
void HuffmanCoding(HuffmanTree *HT, HuffmanCode *HC, int *w, int n);
/***************** end of file ******************/
makefile
objects = main.o tree.o
obj: $(objects)
cc -o obj $(objects) -lm
main.o : tree.h
tree.o : tree.h
.PHONY : clean
clean :
-rm obj $(objects)