给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为赫夫曼树(Huffman Tree)。赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
先说几个基本的名词:
构造一颗赫夫曼树
假设有n个权值,则构造出的赫夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则赫夫曼树的构造规则为:
(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的赫夫曼树
翠花说先上几个基本的名词:
定义的头文件:huffman.h
#include
#include
#include
//错误
#define ERROR 0
//成功
#define SUCCESS 1
/* 状态码识别类型 */
typedef int Status;
#define MAXSIZE 1024
typedef char Element;
typedef struct {
// 节点值
Element data;
// 权重
unsigned int weight;
// 父结点,左孩子,右孩子
unsigned int parent,lchild,rchild;
}HTNode;
typedef HTNode* HuffmanTree;
typedef Element* HCNode;
/*
用以存储根据huffman树来生成每个字符的huffman编码值
例如:
data: , code: 10
data:a, code: 111
data:h, code: 110
data:i, code: 001
data:o, code: 0101
data:s, code: 0100
data:x, code: 0111
data:y, code: 0110
data:z, code: 000
*/
typedef HCNode* HuffmanCode;
/*
对于有n个叶子节点的赫夫曼树,共有2n-1个节点。
由二叉树性质:对于任何一个二叉树T,如果其终端节点数为n_0,度为2的节点数为n_2,则n_0 = n_2 +1。
赫夫曼树是个严格的二叉树,不存在度为1的节点。
so,赫夫曼树的节点 = 度为2的节点 + 叶子节点,即2n-1
*/
HuffmanTree buildTree(char *str);
void Create_Huffmantree(int n, int m, HuffmanTree HT);
Status HuffmanCoding(HuffmanTree HT, HuffmanCode *HC);
Status EnCoding(HuffmanCode HC, char *str, char **code);
Status DeCoding(HuffmanTree HT, char *code, char **translation);
huffman.c
#include "huffman.h"
// 记录有效的字符个数
int valid_char_num = 0;
// 记录有效字符的指针
char *valid_char_pointer;
HuffmanTree buildTree(char *str) {
// ASCII编码就只能表示256个字符
valid_char_pointer = (char *)malloc(256 * sizeof(char));
// 用来记录每个字符出现的次数
int char_count_arr[256] = {0};
//并计算每个符号出现的次数
for(int i=0; str[i]!='\0'; i++) {
// 以字符的ascill码作为索引,在数组中的对应位置记录次数。
char_count_arr[(unsigned char) str[i]]++;
}
// 循环存放有效字符
for(int i = 0; i < 256; i++) {
if(char_count_arr[i] != 0) {
valid_char_pointer[valid_char_num++] = (char)i;
}
}
// 加上结束标志。
valid_char_pointer[valid_char_num] = '\0';
// 总共需要m个节点来存储整个树
int m = valid_char_num * 2 - 1;
// 申请一连串的存储空间
HTNode *ht_nodes = (HTNode *)calloc(m, sizeof(HTNode));
if(!ht_nodes) {
exit(1);
}
// 节点进行初始化
for(int i = 0; i < m; i++) {
if(i < valid_char_num) {
ht_nodes[i].data = valid_char_pointer[i];
ht_nodes[i].weight = char_count_arr[(unsigned char) valid_char_pointer[i]];
} else {
ht_nodes[i].data = '\0';
ht_nodes[i].weight = 0;
}
ht_nodes[i].lchild = ht_nodes[i].rchild = ht_nodes[i].parent = -1;
}
HuffmanTree ht = ht_nodes;
Create_Huffmantree(valid_char_num, m, ht);
return ht;
}
/*
寻找权值最小的两个数,然后合并放入n ~ m-1个结点之中。对叶子节点进行操作合并。
n是叶子结点的个数,m=2n-1,从下标0开始存储
*/
void Create_Huffmantree(int n, int m, HuffmanTree HT) {
for (int i = n; i < m; i++) {
// 32767是整型在16位计算机中的最大值,w1记录最小权值,w2存储第二小的权值
int w1 =32767, w2 = 32767;
// p1记录最小权值下标,p2存储第二小的权值下标
int p1 = -1, p2 = -1;
// 必须是小于i,若等于i,那么最小的权值就是当前i节点的权值:0。
// 同样不能是n,因为合并后的节点同样要参与比较。
for (int j = 0; j < i; j++) {
// 若为叶子节点
if (HT[j].parent == -1) {
if (HT[j].weight <= w1) {
w2 = w1;
w1 = HT[j].weight;
p2 = p1;
p1 = j;
} else if (HT[j].weight < w2) {
w2 = HT[j].weight;
p2 = j;
}
}
}
if (p1 > -1 && p2 > -1) {
// 将最小的两个节点权值相加赋给合并的节点
HT[i].weight = w1 + w2;
// 左孩子为最小的,右孩子为第二小的
HT[i].lchild = p1;
HT[i].rchild = p2;
HT[p1].parent = i;
HT[p2].parent = i;
}
printf("\n------huffman tree Construction process------%d-th time------\n",i - valid_char_num + 1);
for(int i = 0; i < valid_char_num * 2 - 1; i++) {
printf("data:%c, weight:%d, parrent:%d, lchild: %d, rchild:%d\n", HT[i].data, HT[i].weight, HT[i].parent, HT[i].lchild, HT[i].rchild);
}
printf("\n");
}
}
/* 从叶子节点开始逆向进行赫夫曼编码*/
/*HC用来储存huffman编码值*/
Status HuffmanCoding(HuffmanTree HT, HuffmanCode *HC) {
int n = valid_char_num;
if(!(*HC = (HuffmanCode)malloc(n * sizeof(HCNode)))) {
return ERROR;
}
/* 声明一个动态字符数组code,用来临时存储赫夫曼编码,
n个叶子节点的huffman tree,深度最多就为n(没验证过,只为理解),
那一个字符的编码长度最大也就n-1个。所以有n个存储空间就够了,最后一个存放\0。
*/
HCNode code;
if(!(code = (HCNode)malloc(n * sizeof(char)))) {
return ERROR;
}
/* 加上一个字符串结束符号*/
code[n - 1] = '\0';
for(int i = 0; i < n; i++) {
// 记录当前元素的位置
int current = i;
int father = HT[i].parent;
// 从后向前记录的索引位置
int end = n - 1;
// 这里的huffman tree根节点的parent域为-1
while(father != -1) {
// 若当前节点等于当前节点的父节点的左孩子,编码为0,右孩子编码为1
if(current == HT[father].lchild) {
code[--end] = '0';
} else {
code[--end] = '1';
}
// 父节点为当前元素节点。
current = father;
// 父节点指向父节点的父节点
father = HT[father].parent;
}
/* HC[i]用于最终存储huffman码,是char类型的数组,有n个char类型的数据*/
if(!(*(*HC + i) = (HCNode)malloc((n - 1 - end) * sizeof(char)))) {
return ERROR;
}
/* 从临时空间中复制到HC[i]中*/
strcpy(*(*HC + i), code + end);
}
/*释放临时存储空间*/
free(code);
code = NULL;
printf("\n===============huffman code==================\n");
for(int i = 0; i < n; i++) {
printf("data:%c, code: %s\n", HT[i].data, *(*HC + i));
}
return SUCCESS;
}
Status EnCoding(HuffmanCode HC, char *str, char **code) {
printf("\n===============Encode==================\n");
if(!(*code = (char *)malloc(MAXSIZE * sizeof(char)))) {
return ERROR;
}
**code = '\0';
int len = strlen(str);
for(int i = 0; i < len; i++){
for(int j = 0; j < valid_char_num; j++) {
if(*(str + i) == valid_char_pointer[j]) {
//printf("%s", *(HC + i));
strcpy(*code + strlen(*code), *(HC + j));
break;
}
}
}
return SUCCESS;
}
Status DeCoding(HuffmanTree HT, char *code, char **translation) {
printf("\n===============Decode==================\n");
if(!(*translation = (char *)malloc(sizeof(char)))) {
return ERROR;
}
// 编码的长度
int n = strlen(code);
int j = 0;
for(int i = 0; i < n; ) {
// 每次都从根节点开始
HTNode t = HT[valid_char_num * 2 - 2];
// 只要存在孩子节点就判断第i个编码是0还是1
while((int)t.lchild > -1 || (int)t.rchild > -1) {
if(code[i++] == '0') {
// 编码为0将t的左孩子赋值给t
t = HT[t.lchild];
} else {
// 编码为1将t的右孩子赋值给t
t = HT[t.rchild];
}
}
*(*translation + j++) = t.data;
}
// 加上一个字符串结束标志
*(*translation + j) = '\0';
return SUCCESS;
}
main:
#include "huffman.h"
int main(int argc, char *argv[]) {
//char *str = "aabc ebac fv afvvvv";
char *str = "shi xiao zha zha ya";
HuffmanTree ht = buildTree(str);
HuffmanCode hc;
HuffmanCoding(ht, &hc);
char *code = NULL;
EnCoding(hc, str, &code);
printf("%s 得到的huffman编码为:%s\n", str,code);
char *translation;
char *cd = "010011000110011100111101011000011011110000110111100110111";
DeCoding(ht, cd, &translation);
printf("huffman编码 %s \n译文:%s\n", cd, translation);
return 0;
}
运行结果:
代码有些是东拼西凑从别处摘的,参考的多篇博客,某些出处都忘了,所有就都不贴了,抱歉。
错误不足之处还请指正。3Q!