霍夫曼树以及霍夫曼编码(动态数组实现方式)

霍夫曼编码是用于数据压缩存储的。根据的原理是:任一个字符编码绝对不是另一个字符编码的前缀,并且出现次数最多的字符,所用编码的位数最小。以此来达到数据压缩的目的。

霍夫曼树有两种实现方式:一种是基于链表的实现方式,另一种是基于动态数组的实现方式。

本文是基于动态数组的实现方式:

代码及结果展示:

#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的权重值变量)

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值