哈夫曼树编码与译码(完整C/C++实现代码)

哈夫曼编码的设计与应用

问题需求分析

用哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)。
霍夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1L1+W2L2+W3L3+…+WnLn),N个权值Wi(i=1,2,…n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,…n)。

数据结构的定义

哈夫曼树结构体包含如下内容:节点权重,当前节点的父节点,左右子树,节点信息。
哈夫曼编码结构体包含如下内容:存编码的数组,编码数组的开始标志。

功能详细设计

构建哈夫曼树:使用一维数组,每个节点存储权重,父节点,左子树,右子树,以及数值的信息。遍历整个数组,根据每个节点的权重去找到最小以及第二小的节点,然后对各自的父节点,左右子树赋值,建立两个节点的联系将他们的联系填入该结构体数组中。
哈夫曼树如下图:
a权重45; b权重15; c权重12; d权重16; e权重9; f权重5
在这里插入图片描述
哈夫曼树结构体数组如下图(使用a, b, 58举例):
在这里插入图片描述
根据哈夫曼树建立哈夫曼编码:从树的根节点开始,根据每个叶子节点与父节点之间的联系,使用哈夫曼编码结构体中的编码数组储存树种每个叶子节点的编码, 往左边遍历是0, 往右边遍历是1, 举例: a, 由建立好的哈夫曼树可得, 当遍历查找到a的时候, a到root的路径是: 100->86->58->a, 所以可得编码就是000;

根据哈夫曼编码将指定的编码转化为字符串:读取到哈夫曼编码后,当编码为0表示节点是左子树,当编码为1的时候表示右子树,根据已经建立好的哈夫曼树,利用顺序遍历的方式,根据左0右1,寻找哈夫曼树中的叶子节点,当某一节点在哈夫曼树中左右子树都为空的时候,表示该节点即使叶子节点,然后输出对应叶子节点在哈夫曼编码数组中的位置, 其原理与编码的方式恰恰相符, 而是根据01数字查找对应的叶子节点.

函数调用图:

在这里插入图片描述

编写代码体会

遇到的问题是数组下标没有弄好,导致建立哈夫曼树时出现各种错误, 进行编码过程没有将lchild与rchild的”变化写在一起”导致出现错误,其实最大的问题是:没有将代码的逻辑思路考虑清楚,还有边界条件值,最蠢的是没有打一下草稿,写一下伪代码,把程序的思想理解,导致编程的过程中出现各种问题,以后写代码之前还是把逻辑理清楚再动手写代码,最后再将代码写到电脑上测试。

完整代码

#include<iostream>
#include<string>
#include<fstream> 
using namespace std;
struct Htnode{
	int weight,parent,lchild,rchild;
	char c; 
};
struct Htcode{
	int bit[25],start;
};
int type(int a[]){
	string s;
	int sum = 0; 
	cout<<"输入需要编码的字符串"<<endl;
	cin>>s;
	for(int i = 0; i < s.size(); i++){
		a[s[i]-'a']++;
	}
	cout<<"出现的字母种类以及频率:"<<endl; 
	for(int i = 0; i < 26; i++){
		if(a[i] != 0){
			char c = char(i+'a');
			cout<<"i:"<<i<<" c:"<<c<<":"<<a[i]/(s.size()*1.0)<<endl;
			sum++;	
		}
	}
	return sum;
} 
//通过数组的方式构建哈夫曼树 
void HufmanTree(Htnode h[],int n, int a[]){
	int i,j,max1,max2,x1,x2;
	for(i=0;i<2*n;i++){
		h[i].weight=0;
		h[i].parent=-1;
		h[i].lchild=-1;
		h[i].rchild=-1;
		h[i].c='\0';
	}
	for(i=0;i<26;i++){
		if(a[i] != 0){
			h[i].c = i + 'a';
			h[i].weight = a[i]; 
		}
	}
	for(i=0;i<n-1;i++){
		max1=1000,max2=1000;
		x1=-1,x2=-1; 
		for(j=0;j<n+i;j++){
			if(h[j].weight<max1 && h[j].parent==-1){
				max2=max1;
				x2=x1;
				max1=h[j].weight;
				x1=j;
			}else if(h[j].weight<max2 && h[j].parent==-1){
				max2=h[j].weight;
				x2=j;
			}
		}
		//根据每个节点信息, 将其信息存储到节点数组中
		h[x1].parent=n+i; h[x2].parent=n+i; 
		h[n+i].weight=h[x1].weight+h[x2].weight;
		h[n+i].lchild=x1;h[n+i].rchild=x2;
	}
	for (i=0;i<2*n-1;i++){
		cout<<h[i].weight<<" "<<h[i].parent<<" "<<h[i].lchild<<" "<<h[i].rchild<<" "<<h[i].c<<endl; 
	}
}
//哈夫曼编码
//根据叶子节点的位置, 将其path路径01数字填充到编码数组中
void  HuffmandeCode(Htnode h[], int n, int a[], Htcode hcode[]){
	HufmanTree(h, n, a);
	ofstream out;
	out.open("HuffmandeCode.txt", ios::out);
	int i,j;
	for(i=0;i<n;i++){
		j=0;
		int parent=h[i].parent;//记录当前节点的父亲 
		int c=i;
		while(c!=-1){//parent造成根节点不会被访问 
			hcode[i].bit[j++]=h[parent].lchild==c?0:1;//从叶子节点到根节点, 应该使用栈结构 
			c=parent;
			parent=h[parent].parent;
		}
		hcode[i].start=j-1;
	}
	for(i=0;i<n;i++){
		cout<<h[i].c<<":";
		for(j=hcode[i].start-1;j>=0;j--){
			cout<<hcode[i].bit[j];
			out<<hcode[i].bit[j];
		}
		cout<<endl;
	}
	out.close(); 
}
string load(){
	ifstream in("HuffmandeCode.txt");
	string str;
	char buffer[256];
	if(!in.is_open()){
		cout<<"加载文件错误"<<endl; 
		return NULL;
	} 
	cout << "载入编码文件" << endl;
	in.getline(buffer, 100, ' ');
	return string(buffer);
}

//哈夫曼译码
void  HuffmanenCode(string s,int n,Htnode h[]){
	int i=0,j=0,lchild=2*n-2,rchild=2*n-2;
	while(s[i]!='\0'){
		if(s[i]=='0'){
		//出现的问题是,最初将lchild,rchild分开计算,导致在左右子树间相互变化出现
		//lchild,rchild不同同时表示同一个节点,最后想到lchild=rchild就能解决问题
			lchild=h[lchild].lchild;
			rchild=j=lchild;
		}
		if(s[i]=='1'){
			rchild=h[rchild].rchild;
			lchild=j=rchild;
		}
		if(h[lchild].lchild==-1 && h[rchild].rchild==-1){
			cout<<h[j].c;
			lchild=rchild=2*n-2;
			j=0;
		}
		i++;
	}
}
int main(){
	Htnode h[30];
	Htcode hcode[10];
	int a[26]={0};
	string s;
	int n = type(a);
	cout<<"n:"<<n<<endl;
	HuffmandeCode(h, n, a, hcode);
	HuffmanenCode(load(),n,h);
	return 0;
}

上面有错, 还请指出, 如果认为我写的还不错, 还请点个赞, 多多支持一下, O(∩_∩)O~~

评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值