哈夫曼编码与译码

        最近完成了数据结构的课程设计,想着写一下博客,一是再次熟悉一下,让自己在过两天的答辩中更加行云流水,二是可以分享给大家,互相学习。

        本次实验的题目是:哈夫曼编码与译码。哈夫曼编码是一种可变长度且无损压缩的编码方式。在哈夫曼树中,出现频率高的字符将被编码为较短的二进制编码,而出现频率低的字符将被编码为较长的二进制编码。通过这种方式,可以有效地减少数据的存储空间。

        我们将哈夫曼树看成一个数组,该数组的元素是一个结构体。该结构体包含:权值、字符、左右子树。

typedef struct{
	char ch;//存储字符 
	int weight;//权值 
	int parent;//双亲
	int lchild;//左孩子
	int rchild;//右孩子	 
}HTnode,*HuffmanTree;

1.哈夫曼树的构建

        我们如果要完成哈夫曼编码,首先肯定需要构建一棵哈夫曼树。

        什么是哈夫曼树

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

        步骤

        1.构造森林全是根:根据给定的N个权值{W1,W2,W3,W4,...,Wn}构成n棵二叉树的森林F={T1,T2,T3,...,Tn},其中Ti只有一个带权为Wi的根结点。

        2.选用两小造新树:在F中选取两棵根结点的权值最小的数做为左右子树,构造一棵新的二叉树,且新二叉树的根结点权值为左右子树上权值的和。

        3.删除两小添新人:在F中删除那两棵树,同时将新构造的新树加入到森林中。

        4.重复2.3 剩单根:重复2和3的操作,直到森林中只有一棵二叉树为止,这棵树就叫做哈夫曼树。

        事例如下:对于给定的权值2,3,4,7,9.第一步:构建5个只有根结点的二叉树。第二步:选取权值最小的两个:2和3构建新的二叉树,新树的根结点权值为2+3=5.然后将2和3从森林中删去,将新树加入到森林中。最后,重复第二步,直到该森林只剩下一棵树。

        代码实现 

//构造哈夫曼树 
void CreateHuffmanTree(HuffmanTree &HT,int n)
{
	if(n <= 1){
		cout << "无法构建哈夫曼树" << endl;
		return;
	}
	int m = 2 * n;//确定存储空间的大小
	
	//创建2*n的空间,下标为0的空间不存储东西,从下标为1开始存储
	HT = new HTnode[m];
	
	//输入需要构建哈夫曼树的字符 
	for(int i = 1;i <= n;i++){
		cout << "请输入第" << i << "字符:";
		cin >> HT[i].ch;
	}
	
	//输入各字符(叶子结点)的权值 
	for(int i = 1;i <= n;i++){
		cout << "请输入第" << i <<"个字符的权值:"; 
		cin >> HT[i].weight;
	}
	
	//将所有单元的双亲、左孩子、右孩子都初始化为0
	for(int i = 0;i <= m;i++){
		HT[i].parent = 0;
		HT[i].lchild = 0;
		HT[i].rchild = 0;
	}

	/*****************至此,初始化完毕*******************/ 
	
	//接下来通过n-1次合并来创建哈夫曼树
	
	int s1,s2;//定义s1变量记录最小权值的下标,s2变量记录第二小权值的下标
	 
	for(int i = n + 1;i <= m;i++){
		Select(HT,i - 1,s1,s2);
		
		//定义变量记录双亲为0的个数
		int count = 0;
		for(int j = 1;j < m;j++){
			if(HT[j].parent==0){
				count++;
			}
		} 
		if(count == 1){
			break;
		}
		
		//修改最小两个权值所代表字符的双亲 
		HT[s1].parent = i;
		HT[s2].parent = i;
		
		//最小两个权值的字符成为i的左右孩子 
		HT[i].lchild = s1;
		HT[i].rchild = s2;
		
		//i的权值等于左右孩子权值的和 
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	} 
}

        在构建哈夫曼树中我们需要寻找权值最小的两个树时,使用了Select.该代码的实现如下:

//获取最小两个权值的下标
void Select(HuffmanTree HT, const int length, int &s1, int &s2)
{
	int min1 = 99999,min2 = 99999;
	//通过for循环寻找最小权值的下标s1 
	for(int i = 1;i <= length;i++){
		if(min1 > HT[i].weight && HT[i].parent == 0){
			min1 = HT[i].weight;
			s1 = i;
		}
	}
	
	//通过for循环寻找第二小权值的下标s2
	for(int i = 1;i <= length;i++){
		if(min2 > HT[i].weight && HT[i].parent == 0 && HT[i].weight != min1){
			min2 = HT[i].weight;
			s2 = i;
		}
	}
}

 2.哈夫曼编码

        哈夫曼编码是一种可变长度且无损压缩的编码方式。

        哈夫曼编码就是从根结点出发,在左路径上为0,右路经上为1.在每一个路径上标记完之后,从根结点出发到叶子结点所经过的二进制编码就是哈夫曼编码。

        但是如果我们要通过代码实现以上是比较困难的,所有我们下面通过逆序的方法来进行哈夫曼编码。 实际操作是从叶子结点出发,如果该结点是双亲的左孩子,则记为0,如果是右孩子则为1.,直到我们走到了根结点。

//哈夫曼编码
void CreatHuffmanCode(HuffmanTree HT,char str[],int n)
{
	char cd[100];
	cd[99] = '\0';
	for(int i = 1;i <= n;i++){
		int c = i,p = HT[i].parent,start = 99;
		while(p != 0){
			start--;
			if(c == HT[p].lchild){
				cd[start] = '0';
			} else {
				cd[start] = '1';
			}
			c = p;
			p = HT[p].parent;
		}
		cout << str[i] << " 的编码是:" ;
		for(int j = start;j <= 99;j++){
			if(cd[j] == '0' || cd[j] == '1'){
				cout << cd[j];
			}
		}
		cout << endl;
		memset(cd, '\0', n);
	}
 } 

 3.译码

        哈夫曼译码我觉得比上面两个都简单,如果你已经看到这里了,就在坚持一下吧!

        译码的话,他是从根结点出发,当读取到0时,我们就走向左子树;当读取到1时,我们就走向右子树。直到我们走到了叶子结点就结束,然后再从根结点开始走。

//哈夫曼译码 
void HuffmanCode(HuffmanTree HT,int n)
{
	char code[100];
	cout << "请输入你需要译码的二进制编码:(以#结尾)" << endl;
	cin >> code; 
	
	//下面来找到该哈夫曼树的根节点 
	int temp = 9999;
	for(int i = 1;i <= n * 2 - 1;i++){
		if(HT[i].parent == 0){
			temp = i;
		}
	}
	cout << "根的下标为:" << temp << endl;
	
	int a = temp;//定义变量来标记现在走到结点
	for(int i = 0;code[i] != '#';i++){
		if(code[i] == '0'){
			a = HT[a].lchild;
		} else if(code[i] == '1'){
			a = HT[a].rchild;
		}
		if(HT[a].lchild == 0){
			cout << HT[a].ch;
			a = temp;//走到头之后再从根节点开始 
		}
	}
}

完整代码实现 

#include <stdio.h>
#include <iostream>
#include <string.h>
#include <stdlib.h>
using namespace std;

typedef struct{
	char ch;//存储字符 
	int weight;//权值 
	int parent;//双亲
	int lchild;//左孩子
	int rchild;//右孩子	 
}HTnode,*HuffmanTree;

HuffmanTree HT;


void Select(HuffmanTree HT, const int length, int &s1, int &s2);//获取最小两个权值的下标
void CreateHuffmanTree(HuffmanTree &HT,int n);//构造哈夫曼树
void PrintHuffmanTree(HuffmanTree HT, int n);//输出哈夫曼树
void CreatHuffmanCode(HuffmanTree HT,char str[],int n);//哈夫曼编码
void HuffmanCode(HuffmanTree HT,int n);//哈夫曼译码 


//获取最小两个权值的下标
void Select(HuffmanTree HT, const int length, int &s1, int &s2)
{
	int min1 = 99999,min2 = 99999;
	//通过for循环寻找最小权值的下标s1 
	for(int i = 1;i <= length;i++){
		if(min1 > HT[i].weight && HT[i].parent == 0){
			min1 = HT[i].weight;
			s1 = i;
		}
	}
	
	//通过for循环寻找第二小权值的下标s2
	for(int i = 1;i <= length;i++){
		if(min2 > HT[i].weight && HT[i].parent == 0 && HT[i].weight != min1){
			min2 = HT[i].weight;
			s2 = i;
		}
	}
}


//构造哈夫曼树 
void CreateHuffmanTree(HuffmanTree &HT,int n)
{
	if(n <= 1){
		cout << "无法构建哈夫曼树" << endl;
		return;
	}
	int m = 2 * n;//确定存储空间的大小
	
	//创建2*n的空间,下标为0的空间不存储东西,从下标为1开始存储
	HT = new HTnode[m];
	
	//输入需要构建哈夫曼树的字符 
	for(int i = 1;i <= n;i++){
		cout << "请输入第" << i << "字符:";
		cin >> HT[i].ch;
	}
	
	//输入各字符(叶子结点)的权值 
	for(int i = 1;i <= n;i++){
		cout << "请输入第" << i <<"个字符的权值:"; 
		cin >> HT[i].weight;
	}
	
	//将所有单元的双亲、左孩子、右孩子都初始化为0
	for(int i = 0;i <= m;i++){
		HT[i].parent = 0;
		HT[i].lchild = 0;
		HT[i].rchild = 0;
	}

	/*****************至此,初始化完毕*******************/ 
	
	//接下来通过n-1次合并来创建哈夫曼树
	
	int s1,s2;//定义s1变量记录最小权值的下标,s2变量记录第二小权值的下标
	 
	for(int i = n + 1;i <= m;i++){
		Select(HT,i - 1,s1,s2);
		
		//定义变量记录双亲为0的个数
		int count = 0;
		for(int j = 1;j < m;j++){
			if(HT[j].parent==0){
				count++;
			}
		} 
		if(count == 1){
			break;
		}
		
		//修改最小两个权值所代表字符的双亲 
		HT[s1].parent = i;
		HT[s2].parent = i;
		
		//最小两个权值的字符成为i的左右孩子 
		HT[i].lchild = s1;
		HT[i].rchild = s2;
		
		//i的权值等于左右孩子权值的和 
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	} 
}

//输出哈夫曼树 
void PrintHuffmanTree(HuffmanTree HT, int n) 
{
    cout << "哈夫曼树的结构如下:" << endl;
    cout << "字符\t权值\t双亲结点\t左孩子结点\t右孩子结点" << endl;
    for (int i = 1; i <= n; i++) {
        cout << HT[i].ch << "\t" << HT[i].weight << "\t" << HT[i].parent << "\t\t" 
		<< HT[i].lchild << "\t\t" << HT[i].rchild << endl;
		if(i == n / 2 + 1){
			cout << "**************************" << endl; 
		}
    }
}

//哈夫曼编码
void CreatHuffmanCode(HuffmanTree HT,char str[],int n)
{
	char cd[100];
	cd[99] = '\0';
	for(int i = 1;i <= n;i++){
		int c = i,p = HT[i].parent,start = 99;
		while(p != 0){
			start--;
			if(c == HT[p].lchild){
				cd[start] = '0';
			} else {
				cd[start] = '1';
			}
			c = p;
			p = HT[p].parent;
		}
		cout << str[i] << " 的编码是:" ;
		for(int j = start;j <= 99;j++){
			if(cd[j] == '0' || cd[j] == '1'){
				cout << cd[j];
			}
		}
		cout << endl;
		memset(cd, '\0', n);
	}
 } 
 
//哈夫曼译码 
void HuffmanCode(HuffmanTree HT,int n)
{
	char code[100];
	cout << "请输入你需要译码的二进制编码:(以#结尾)" << endl;
	cin >> code; 
	
	//下面来找到该哈夫曼树的根节点 
	int temp = 9999;
	for(int i = 1;i <= n * 2 - 1;i++){
		if(HT[i].parent == 0){
			temp = i;
		}
	}
	cout << "根的下标为:" << temp << endl;
	
	int a = temp;//定义变量来标记现在走到结点
	for(int i = 0;code[i] != '#';i++){
		if(code[i] == '0'){
			a = HT[a].lchild;
		} else if(code[i] == '1'){
			a = HT[a].rchild;
		}
		if(HT[a].lchild == 0){
			cout << HT[a].ch;
			a = temp;//走到头之后再从根节点开始 
		}
	}
}
int main()
{
	int n;
	cout << "请输入有几个字符:" << endl; 
	cin >> n;
	CreateHuffmanTree(HT,n);
	//输出哈夫曼树进行验证 
	PrintHuffmanTree(HT,2*n-1);
	
//	cout << "1111111111111111" << endl;
	char str[n + 1];
	for(int i = 1;i <= n;i++){
		str[i] = HT[i].ch;
	}
	CreatHuffmanCode(HT,str,n);
	cout << "CreatHuffmanCode运行成功" << endl;
	
	HuffmanCode(HT,n);

}
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值