霍夫曼编码是用于数据压缩存储的。根据的原理是:任一个字符编码绝对不是另一个字符编码的前缀,并且出现次数最多的字符,所用编码的位数最小。以此来达到数据压缩的目的。
霍夫曼树有两种实现方式:一种是基于链表的实现方式,另一种是基于动态数组的实现方式。
本文是基于动态数组的实现方式:
代码及结果展示:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define null NULL
/*
哈夫曼树的构建是从叶子结点开始的,不断的构建新的父结点。
而在使用哈夫曼树时是从根节点开始的.因此每个结点需要有指向左右孩子的指针。
//哈夫曼树的两种实现方式,一种是动态数组实现方式
// 一种是链表实现方式
//本篇是动态数组实现方式
*/
typedef struct HuffmanNode{
int weight; //结点的权重
int parent,left,right; //父结点,左孩子,右孩子在数组中的位置下标。
}HuffmanNode;
//在哈夫曼树的动态数组中查找两个最小的元素值 end是hf数组中存放最后一个元素的位置 s1 s2是最小的两个元素的下标
void Select(HuffmanNode *hf,int end,int *s1,int *s2){
int min1,min2; //min1 存放最小值, min2 存放次小值
int i = 0;
//在现有hf数组中查找 parent == -1的两个结点
while( hf[i].parent != -1 && i <= end ){
i++;
}
min1 = hf[i].weight;
*s1 = i;
i++;
while(hf[i].parent != -1 && i<= end) {
i++;
}
//保证 min1 的值始终小于 min2
if(hf[i].weight < min1){
//值要交换一下
min2 = min1;
min1 = hf[i].weight;
//数组下标也得交换一下
*s2 = *s1;
*s1 = i;
}else{
min2 = hf[i].weight;
*s2 = i;
}
i++;
//把数组中后面的元素遍历完,保证min1是最小值,min2是第二小值。
while(i <= end){
//如果有父节点直接跳过
if(hf[i].parent != -1){
//有父节点直接跳过后面的所有代码,i也得加加,不然就是死循环。
i++;
continue;
}
//这种情况min1 min2变量里面的值都得变
if(hf[i].weight < min1){
min2 = min1;
*s2 = *s1;
min1 = hf[i].weight;
*s1 = i;
}
//如果值介于两者之间,只改变min2即可
if(min1 < hf[i].weight && hf[i].weight < min2){
min2 = hf[i].weight;
*s2 = i;
}
i++;
}
}
//创建Huffman树 array即为每个元素的权重数组 ,n代表的是结点个数,也是权重数组的长度。
HuffmanNode* CreateHuffmanTree(int *array,int n){
HuffmanNode *hf;
if(n == 0) return null;
if(n == 1) {
hf = (HuffmanNode *)malloc(sizeof(HuffmanNode));
hf->weight = array[0];
hf->parent = -1;
hf->left = -1;
hf->right = -1;
return hf;
}
int m = 2*n - 1; //哈夫曼树的总的节点数
//为哈夫曼树分配所需的结点空间
hf = (HuffmanNode *)malloc(m*sizeof(HuffmanNode));
//初始化前n个哈夫曼结点 一定要用hf[i]去赋值,不能hf++,因为这样会改变hf指针的指向。
for(int i = 0;i < n; i++){
hf[i].weight=array[i];
hf[i].parent= -1;
hf[i].left= -1;
hf[i].right= -1;
}
//初始化后n-1个哈夫曼结点
for(int i = n ; i < m; i++){
hf[i].weight=0;
hf[i].parent= -1;
hf[i].left= -1;
hf[i].right= -1;
}
//构建哈夫曼树
for(int i = n;i < m ; i++){
int min1,min2; //min1是最小元素, min2是倒数第二小元素
Select(hf,i-1,&min1,&min2);
//哈夫曼树非叶子结点的构建
hf[i].weight = hf[min1].weight + hf[min2].weight;
hf[i].left = min1;
hf[i].right = min2;
//哈夫曼叶子结点中父指针的改变
hf[min1].parent=i;
hf[min2].parent=i;
}
return hf;
}
//哈夫曼编码的两种方式: 1.第一种是从叶子结点开始逆序找到根节点
// 2.从根节点开始顺序找到叶子结点
//第一种方式从叶子结点开始逆序找到根节点
/*
hf是存储哈夫曼树的动态数组
n是哈夫曼树中叶子结点的个数
*/
char** HuffmanCoding1(HuffmanNode *hf,int n){
char **s = (char **)malloc(n*sizeof(char*));
//用于临时存放哈夫曼编码
char *cd = (char *)malloc(n*sizeof(char));
cd[n-1]='\0';
for(int i = 0;i < n; i++){
//哈夫曼编码存储的开始位置
int start = n-1;
//当前儿子结点的数组下标
int k = i;
//父亲结点的数组下标
int parent = hf[i].parent;
//代表当前结点不是根节点
while(parent != -1){
//当前结点是父结点的左孩子
if(k == hf[parent].left){
cd[--start]='0';
}else{
cd[--start]='1';
}
//以父结点为孩子结点,继续朝树根的方向遍历
k = parent;
parent = hf[parent].parent;
}
//申请数组空间用来存放哈夫曼编码
s[i] = (char *)malloc(sizeof((n-start)*sizeof(char)));
strcpy(s[i],&cd[start]);
}
//临时的存储空间用完了要释放掉。
free(cd);
return s;
}
//哈夫曼编码的第二种方式: 从根节点开始找。
char** HuffmanCoding2(HuffmanNode *hf,int n){
//存储哈夫曼编码的二维动态数组
char **huffmanCode = (char **)malloc(n*sizeof(char *));
//临时存放哈夫曼编码
char *cd = (char *)malloc(n*sizeof(char));
int cdstart = 0;
//动态数组中最后一个结点是哈夫曼树的根节点
//将所有结点的权值置为0 --> 此种方式能获得正确的哈夫曼编码,但是会把哈夫曼树中的权重指标毁了。
int q = 2*n-1;
for(int i = 0 ; i < q ; i++){
hf[i].weight = 0;
}
int m = q-1;
while(m != -1){ //数组存储从0开始的坏处。
//证明当前结点一次都没有访问过
if(hf[m].weight == 0){
//重置访问次数
hf[m].weight = 1;
//有左孩子、让m指针指向其左孩子
if(hf[m].left != -1){
m=hf[m].left;
cd[cdstart++]='0';
}else{
//没有左孩子,肯定也没有右孩子,叶子结点
cd[cdstart]='\0';
huffmanCode[m] = (char *)malloc((cdstart+1)*sizeof(char));
strcpy(huffmanCode[m],cd);
}
}else if(hf[m].weight == 1){
hf[m].weight = 2;
//之前访问过一次了,第二次访问判断其有没有右孩子
//有右孩子,让m指针指向其右孩子
if(hf[m].right != -1){
m=hf[m].right;
cd[cdstart++]='1';
}
}else{
//访问次数为2,说明左右孩子都遍历完了,返回父节点
hf[m].weight = 0;
m=hf[m].parent;
--cdstart;
}
}
return huffmanCode;
}
//哈夫曼编码的打印 n是叶结点的个数
void printHuffmanCode(char **s,int *array,int n){
printf("huffman编码是:\n");
for(int i = 0; i < n ; i++ ){
printf("权重为%d的HuffmanCode是%s\n",array[i],s[i]);
}
}
int main(int argc, char *argv[])
{
//哈夫曼的数组实现方式
int array[6]={2,8,7,6,5,100};
int n = 6;
//创建huffman树
HuffmanNode *hf = CreateHuffmanTree(array,n);
//哈夫曼编码
char **huffmanCode = HuffmanCoding2(hf,n);
//打印huffuman编码
printHuffmanCode(huffmanCode,array,n);
return 0;
}
根据霍夫曼树得到霍夫曼编码的两种方式:1.从叶子结点遍历到根节点,但需要逆序存储。
2.从根节点遍历到叶子结点。(损坏了动态数组中的huffman的权重值变量)