版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kafmws/article/details/84224666
哈夫曼树
哈夫曼树即最优二叉树,算法如下:
(1)在当前未使用结点中找出两个权重最小的作为左右孩子,计算出新的根结点
(2)新的根结点继续参与过程(1),直至所有结点连接为一棵树
如下图,symbol为具体字符,Frequency为出现频率(权重)
特点:只有度数为0和2的结点
C语言静态链表实现哈夫曼树
实现功能:输入一段英文文本,统计各字符出现次数作为权重,以当前字符集生成哈夫曼树,给出所有字符及指定编码与文本编码,再将编码后的文本解码为原文
数据结构定义
下标 | 数据域 | 父结点下标 | 左子树下标 | 右子树下标 |
---|
typedef char ElementType;
typedef struct {
ElementTree data; 结点数据
int weight; 结点权重
int parent; 双亲下标
int left, right; 左右子树下标
}HuffmanTree;
- 统计字符及初始化静态链表
用下标从32 ~ 126的数组记录ASCII码32 ~ 126的字符,包含了英文文本的绝大多数字符
int num = 1;当前静态链表有效长度 [1,num)
char text[2005];//文本源
char *textAnalyze() {//返回字符集中字符个数的数组
char *chars = (char *)malloc(sizeof(char)*127);//32~126
char c = 0;
memset(chars, 0, sizeof(char)*127);
scanf("%[^\n]", text);
int i = 0;
while (c = text[i++]) {
chars[c]++;//计数
}
for (i = 32; i <= 126; i++) {
if (chars[i]) {
printf("字符%c 出现%d次\n", i, chars[i]);
}
}
return chars;
}
void initElement(HuffmanTree *nodes, char *chars) {
memset(nodes, 0, sizeof(char) * sizeof(HuffmanTree) * 200);
for (int i = 32; i <= 126; i++) {
if (chars[i]) {
nodes[num].data = i;
nodes[num].weight = chars[i];
nodes[num].parent = nodes[num].left = nodes[num].right = 0;
num++;//全局变量记录当前结点总数
}
}
free(chars);
}///////初始化静态链表完毕
eg:
输入为affgghhhjjj
此时静态链表为
下标 | 数据域 | 权重 | 父结点下标 | 左子树下标 | 右子树下标 |
---|---|---|---|---|---|
1 | a | 1 | 0 | 0 | 0 |
2 | f | 2 | 0 | 0 | 0 |
3 | g | 2 | 0 | 0 | 0 |
4 | h | 3 | 0 | 0 | 0 |
5 | j | 3 | 0 | 0 | 0 |
建立哈夫曼树
void createHuffmanTree(HuffmanTree *nodes) {
每次连接两个结点,生成一个新结点,连接完成应该生成n-1个结点
故 n个结点建立的哈夫曼树应当有2n-1个结点
int end = num + num - 3;//计算总结点数
int *min = NULL;
while (num != end+1) {
min = searchOrder(nodes);
//制作新结点
nodes[num].weight = nodes[min[0]].weight + nodes[min[1]].weight;
nodes[num].left = min[0];
nodes[num].right = min[1];
//填补原结点
nodes[min[0]].parent = num;
nodes[min[1]].parent = num;
num++;
free(min);
}
}
其中searchOrder( )返回当前权重最小值与次小值的下标
int *searchOrder(HuffmanTree *nodes) {// num>=2
int *nums = (int *)malloc(sizeof(int) * 2);
int i = 1;
for (; i < num&&nodes[i].parent != 0; i++);//nodes[i].parent == 0 可用
1*- nums[0] = i;// 0 pre 1 later
for (i++; i < num&&nodes[i].parent != 0; i++);
nums[1] = i;//找到初始两下标
for (i++; i < num; i++) {
if (nodes[i].parent == 0) {//未使用
if (nodes[i].weight < nodes[nums[1]].weight) {// <min
nums[1] = i;
}
else if (nodes[i].weight < nodes[nums[0]].weight) {
nums[0]=nums[1],nums[1] = i;
}
}//按出现顺序生成最优二叉树
}
return nums;
}
此时的哈夫曼树为
下标 | 数据域 | 权重 | 父结点下标 | 左子树下标 | 右子树下标 |
---|---|---|---|---|---|
1 | a | 1 | 6 | 0 | 0 |
2 | f | 2 | 6 | 0 | 0 |
3 | g | 2 | 7 | 0 | 0 |
4 | h | 3 | 7 | 0 | 0 |
5 | j | 3 | 8 | 0 | 0 |
6 | 3 | 8 | 1 | 2 | |
7 | 5 | 9 | 3 | 4 | |
8 | 6 | 9 | 5 | 6 | |
9 | 11 | 0 | 7 | 8 |
可以看出叶子结点左右孩子均为0,根结点父结点域为0
哈夫曼编码
前缀编码:每个字符的编码都不为其余编码的前缀
非前缀编码:存在某字符的编码是其余某编码的前缀
(没错就是这么扭曲)
所有参与编码的字符都在叶子结点上,因此保证编码为前缀编码
哈夫曼编码:走左子树为0,走右子树为1。从树根走到叶子结点组成的01序列
哈夫曼编码是前缀编码
- 遍历哈夫曼树得到每个叶子结点的编码
typedef struct {
ElementTree data;字符
char hfCode[115];该字符对应的编码序列
}HfCode;
HfCode codes[111];存储每个字符的哈夫曼编码
int charNum = 0;//字符集中的字符个数 [0,num)
char encodedText[4005];//编码后的文本
void encodeAll(HuffmanTree *nodes, int index, char *order, int cnt) {
if (nodes[index].left == nodes[index].right) {
printf("%c : ", nodes[index].data);
order[cnt] = 0;
puts(order);
codes[charNum].data = nodes[index].data;
strcpy(codes[charNum++].hfCode,order);
}
if (nodes[index].left) {
order[cnt] = '0';
encodeAll(nodes, nodes[index].left, order, cnt+1);
order[cnt] = '1';
encodeAll(nodes, nodes[index].right, order, cnt+1);
}
}
- 从叶子结点走到根得到该叶子的编码
void getCodeByChar(HuffmanTree *nodes, char leaf) {//得到某个叶子节点的编码
int index;
int end = num / 2;
for (index = 0; index <= end; index++) {
if (nodes[index].data == leaf)
break;
}
if (index > end) {
printf("输入有误!");
return;
}
char order[115];
int cnt = 0;
while (nodes[index].parent) {不为根
order[cnt++] = nodes[nodes[index].parent].left == index ? '0' : '1';
index = nodes[index].parent;
}
printf("%c : ", leaf);
for (cnt--; cnt >= 0; cnt--) {
printf("%c", order[cnt]);
}
printf("\n");
}
在建立哈夫曼树并得到各字符编码的基础上对整个文本进行编码/解码就十分容易了
void encodeText(HuffmanTree *nodes) {//编码
int i, j, len = strlen(text);
printf("该信息为:\n%s\n", text);
printf("该信息的哈夫曼编码为:\n");
for (i = 0; i < len; i++) {
for (j = 0; j < charNum&&codes[j].data != text[i]; j++);
strcat(encodedText, codes[j].hfCode);
}
printf("%s\n",encodedText);
}
void decodeText(HuffmanTree *nodes, char *unknown) {//解码
int len = strlen(unknown);
int root = num - 1;
int i, index;
for (i = 0, index = root; i < len; i++) {
index = unknown[i] == '0' ? nodes[index].left : nodes[index].right;
if (nodes[index].left == 0) {
printf("%c", nodes[index].data);
index = root;
}
}
}
解码的主要思路是从哈夫曼树的根开始,遍历整个01序列,按照编码的方式,0向左走,1向右走,走到叶子结点输出,即译出一个字符,循环变量重新回到根结点继续解译下一个字符。因为前缀编码的前提保证,不会有歧义。
2019/11/19