算法思想:
1、哈夫曼数的建立
我使用类的方法建立哈夫曼数的 。首先建立一个节点类,包含了一个char字符,int型的权重,int型左右孩子和父节点的下标,以及char类型的code数组,里面存放的是0101的编码,最后还有这个code的长度,用于最后计算平均码长。
char data;//字符
charcode[50];//编码
intweight;//权重
int lchild, rchild,parent;
intcodelen;//编码长度
左右孩子下标和父节点下标都初始化为-1
然后就是哈夫曼数的建立了:哈夫曼树的类包含以下:
intnum;//节点数
Nodetreenode[600];//节点数组
首先包含一个节点数组,在读取原文件并且统计数据的时候将节点的data和weight写入对应的节点中,并统计共有多少节点。
然后我们建立树的关系,我是通过记录左后孩子和父节点的数组下标来创建的。
从节点数组i=0到i=num-1都存放着叶子节点,i=num到i=2*num-2存放的是新建的父节点(因为哈夫曼数是个二叉树,总节点个数是2*num-1个)。
从父节点为-1的节点中找到权重最小的两个节点,然后把两个节点的权重相加等于父节点的权重;两个子节点的parent设为父节点所在的数组下标,父节点的左孩子是权重较小的,右孩子是权重较大的,同样记录下两个节点的下标。做完之后则哈夫曼数建立起来了
由于阐述的有点复杂,所以放上伪代码:
For i=num;i<2*num-1;i++ //因为0~num-1已经存好了,是叶子节点
Select(min1,min2,i)//从第0个开始到第i个寻找最小权重的两个节点
//min1 min2分别记录了第一小和第二小的两个节点的下标
Treenode[i].lchild=min1; Treenode[i].rchild=min2;
Treenode[min1].parent=i; Treenode[min2].parent=i;
Treenode[i].weight=Treenode[min1].weight+ Treenode[min2].weight;
做完以上步骤就将节点之间的关系连好了,哈夫曼树就创建好了,根节点就是最后一个。
2、对每个叶子节点进行编码并保存编码词典
按原理来说这个编码方法是从根节点开始往下查找,往左走就编码加0,往右走就编码加1,直到找到一个叶子节点。这么循环做直到叶子节点全部编码成功。在我的方法中是刚好反过来的,我是从叶子节点出发,如果是父节点的左子树就在编码数组中放入0,是父节点的右子树就在编码数组中放入1,直到找到根节点为止,同时还可记录编码长度。然后将所有叶子节点都循环一遍得到。值得注意的是因为是从下往上编码,所以得到的编码是和原编码相反的。
每个叶子节点得到的编码可以输入到文件中,得到编码词典。由于之前编码是反着的所以这次输入文件就从每个编码的最后一位开始输入,这样编码词典中得到的就是正确顺序的编码。
3、将文件压缩
由于存的编码是char类型的,所以我的方法是将8位的char字符转换成一个字节,然后以字节(unsigned char)的形式写入文件之中。大于8位的则超出的部分与后一个字符的编码组成8位。8位转换成字节的方式就是靠字节左移和与1进行或运算得到:
Unsigned char tobyte(unsigned char code[])//传入一个8位的编码数组
Unsigned char m;//定义一个字节
Fori=0;i<8;i++
m<<1
if(code[i]==1)//如果编码==1
m=m | 1;//字节的相应位数也变为1
return m
以上就是压缩哈夫曼的算法和步骤,得到的文件是一个编码词典和一个压缩文件。接下来要进行解压,为了方便解压,我在压缩哈夫曼的类的时候还顺便把字符和权重存到了一个文件中,且为了读的时候方便分清字符和数字,在文件开头和每次读完一组字符和权重时在结尾放上”@“字符作为标记。这样我在读取的时候读到@就可以判断下一个一定是字符,接下来的一定是权重。
4、重构哈夫曼数
首先将存有字符和权重的文件读出来,存到哈夫曼树的节点中。按照同样的方法构建树。
5、解压
解压的思想就是以二进制的方式读压缩文件,因为读的是一个字节,而这个字节在压缩的时候是由8位编码压缩而成。所以我们要把这个字节解压成8位的01编码,然后保存在字符串中。得到0101的编码字符串后,每次从根结点开始往下找,如果得到1则跳转到右节点,读入0则跳转到左节点,直到读到叶子节点为止,将读到的叶子节点的字符值再写入解压文件中。循环直至压缩文件全被读取完毕。
同样放上伪代码:
ifstream in;
in.open("文件名");
Unsigned char byte[1024];//开一个字节数组,用于保存从文件读取的字节,一次读1024个
String str;
While(!in.eof()) / /判断文件是否读完
in.read(reinterpret_cast<char*>(byte),sizeof(char)*1024);//这个read函数就是用来以二进制读取文件的,读取的内容放在byte数组,每次读取1024个。
str=tostring(byte,n) //tostring函数是将byte数组里的内容转换成二进制字符串
//用字符串判断0\1,找到字符
Int j=2*num-2;//从根节点开始读
Fori=0;i<str.length;i++
If treenode[j]不是叶子节点
If str[i]==1
J=treenode[j].rchild
Else
J=treenode[j].lchild
If treenode[i]是叶子节点
输出treenode[i].data
J=2*num-2; //初始化从根节点开始读
Tostring(unsigned char byte[],n)
For i=0;i<n;i++
String+=bytetostring(byte[i])//把byte数组内的数字依次转换成编码
Returnstring;
Bytetostring(unsigned char byte)//通过右移和取最后一位得到编码字符串
For(i=0;i<8;i++)
Int n=byte& 1
If n==1
Str=’1’+str
Else
Str=’0’+str
byte=byte>>1;
return Str;
以上就是哈夫曼字符的编码和解码的过程,以及根据此原理做的文档压缩和解压。
接下来就是做的数据和分析。