[数据结构] 哈夫曼树HuffmanTree、哈夫曼编码的c/c++语言实现

什么是哈夫曼树

先给出定义

定义 : 给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

举个例子:

已知有4个叶子结点,他们的值分别是a=1, b=2, c=4, d=10;
用这4个叶子结点构建成一个二叉树

我是树
这个27称为带权路径长度,即所有叶结点的权值(就是a,b,c,d的值)乘上其到根结点的路径长度(即经过了几条线)
当这个带权路径长度最小的时候,我们就称他为哈夫曼树,也称最优二叉树

数学方法构建哈夫曼树

构建方法描述

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:
(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

例子

假设有5个叶子结点,权值由下图所示:
在这里插入图片描述

第一步:先找出权值最小的两个结点

我是树

第二步以这两个结点为叶子,构建一个二叉树,并且把这两个结点去除,再找出权值最小的两个结点

我是树
这里有两个权值为8的结点,我们只需要任意取一个权值为8的结点就行;
从这里也可以看出,哈夫曼树是不唯一的,但是他们带权路径长度都相同;

第三步:重复以上步骤,直到只剩下最后一个结点为止

我是树
这棵哈夫曼树到此构建完成,这棵树的带权路径长度为76.

用c/c++语言实现哈夫曼树

以下面的树为例
我是表
首先定义下哈夫曼树的存储结构;
这里采用的是一个大小为2n-1的向量来存储哈夫曼树中的结点
假设有n个叶子结点,哈夫曼树的总结点数为2n-1

typedef  struct
{  int weght;   //结点的权值,为了方便直接用int
   int parent,lchild,rchild;  //左右孩子和双亲
}HTNode,*HuffmanTree;
createHuffmanTree函数
//HT为树,n为叶子结点数,w[]叶子的权值
void CreateHuffmanTree (HuffmanTree HT,int n,int w[]){
	if(n<=1) // 如果结点小于等于1,直接退出
		return;
	m=2*n-1;  //总共有2*n-1个结点
	HT=new HTNode[m+1]; //为了方便,不用0号单元,HT[m]表示根结点,下表从1开始   
	
/*给所有结点的左,右孩子以及双亲初始化为0;*/
	for(i=1;i<=m;++i)
	  {
	  		HT[i].lchild=0;
	  		HT[i].rchild=0;
	  		HT[i].parent=0;
	  }

/*给前 n 个结点设置权重*/
	for(i=1;i<=n;i++)
	{
		HT[i].weight = w[i];
	}
/*给第 n 个结点之后的结点赋值,以及设置左右孩子和双亲*/    
	for( i=n+1;i<=m;++i){ 
/*
	调用Select函数
	在前 i-1 个没有双亲的结点中返回权重最小的结点序号s1,s2
*/
	 Select(HT,i-1, s1, s2);
	 
/*
	将第 s1,s2 号结点的双亲设置为第 i 个结点
	同时意味着下次调用Select函数将忽略s1,s2
*/ 
	 HT[s1].parent=i;
	 HT[s2] .parent=i;
	 
/*s1,s2分别作为i的左右孩子*/
	 HT[i].lchild=s1;
	 HT[i].rchild=s2; 
	 
/*i 的权值为左右孩子权值之和*/ 
	 HT[i].weight=HT[s1].weight + HT[s2] .weight;
	 }
}

实现Select函数

void Select(HuffmanTree HT,int n,int &index1,int &index2){
	if(n<=1)
		return;
	for(int k=1;k<=n;k++){   //给index1赋初值
		if(HT[k].parent == 0){
			index1 = k++;
			break;
		}
	}
	for(;k<=n;k++){
		if(HT[k].parent == 0){    //给index2赋初值
			index2 = k++;
			break;
		}
	}
	for(k=1;k<=n;k++){     //找到最小的权值,赋给index1
		if(HT[k].parent == 0){
			if(HT[index1].weight > HT[k].weight){
				index2 =index1;
				index1 =k;

			}
		}
	}
	HT[index1].parent = -1;  //将最小的权值结点双亲改为-1,防止被index2选取
	for(int i=1;i<=n;i++){   //找到第二小的权值,赋给index2
		if(HT[i].parent == 0){
			if(HT[index2].weight > HT[i].weight){
				index2 =i;
			}
		}
	}
}

哈夫曼树编码的实现

  什么是哈夫曼编码

在远程通讯中,要将字符转换成二进制的字符串来传送
假如有一串字符ABACCDAAAC

常规的编码是 A = 00; B = 01 ;C = 10; D = 11;
00 01 00 10 10 11 00 00 00 00 01;

而如果采用哈夫曼编码的方式:A = 0;B = 110 ; C =10; D =111 ;
0 110 0 10 10 111 0 0 0 0 10;

这个差值会在字符变多,文本变长的情况越拉越大,而使用不等长编码的关键就在于采用 前缀编码,否者会使数据错乱,前缀编码可以采用二叉树设计;
哈夫曼编码就是使出现次数较多的字符采用尽可能短的编码方式;

哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)。

  哈夫曼编码的图示

如图所示 先将哈夫曼树画出来,左孩子为0,右孩子为1
在这里插入图片描述
A的编码就是: 000 ;
B为: 001;
C为: 01;
D为: 10;
E为: 11;

  哈夫曼编码的算法实现

这里我们仍然用创建哈夫曼树时的例子
在这里插入图片描述
首先创建两个数组,一个存放叶子结点的权值,

int *w[5]={0,70,50,20,40};

不用第 0 号位置,设为0;
在这里插入图片描述
一个放叶子是其双亲的左孩子还是右孩子,左孩子为零,右孩子为1;

char * cd;

在这里插入图片描述

//HT为树,n为叶子结点数
void codeArray(HuffmanTree HT,int n){
	cd = (char)malloc((2*n)*sizeof(char));
	cd[2n-1] = '#';//将最后一个赋值为"#",因为没有双亲结点
	for(int j = 1;j<=2*n;j++){
		if(HT[j].parent != 0) {
			if(HT[HT[j].parent].lchild == j)//判断第 j 个结点是否为双亲结点的左孩子
				cd[j] = '0';
			}else{
				cd[j] = '1';
			}
	}
}

完成代码即可创造如下的数组
在这里插入图片描述
创建一个指向字符指针的指针,该数组存放每个叶子结点的哈夫曼编码

Typedef char **Huffmancode;

在这里插入图片描述

void createHuffmanCode(HuffmanTree HT,int n, HuffmanCode &HC){
	HC = (HuffmanCode)malloc((n+1)*sizeof(char*));//分配数组结点空间,依旧不用0号位
	int count = 1; //用来计算每个叶子结点到根结点的路径长度
	int code = 0;  //当前节点的双亲结点位置
	for(int i=1;i<=n;i++){
		count = 1;
		for(int f=HT[i].parent;f != 0;f=HT[f].parent){
			count++;
		}
		HC[i] = (char*)malloc((count)*sizeof(char));
		
		for(code=i;count>1;count--){
			HC[i][count-1]= cd[code]; //将cd数组上的'0','1'字符倒叙放在HC上
			code = HT[code].parent;  //code赋值为当前节点的双亲结点位置
		}
	}
}

在这里插入图片描述

测试哈夫曼编码

下面给出测试的主程序

/*测试函数*/
int main(){
	HuffmanTree HT;  //创建哈夫曼树
	HuffmanCode HC;  //创建哈夫曼编码数组
	int n=4;
	int w[5]={0,70,50,20,40};   //测试的叶子结点权值
	CreateHuffmanTree (HT,n,w);
	codeArray(HT,n);
	createHuffmanCode(HT,n,HC);
	for(int i=1;i<=n;i++){  //顺序输出a,b,c,d的哈夫曼编码
		cout<<(char)('a'+i-1)<<"的哈夫曼编码:";
		int j =1;
		while(HC[i][j] == '0' || HC[i][j] == '1' ){
			cout<<HC[i][j];
			j++;
		}
		cout<<endl;
	}
	return 0;
}

测试结果
在这里插入图片描述

  • 9
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值