哈夫曼树解压与压缩
哈夫曼树的压缩与解压
1.算法简要描述
1.哈夫曼算法
1.哈弗曼算法是根据给定的n个权值{w1,w2,w3.......wn},构造由n棵
二叉树构成的深林F={T1,T2,。。。。Tn},其中每个二叉树Ti分别都是只
含有一个权值wi的根结点,其左右子树为空(i=1,,,,,,2)。
2.在深林F中选取其根结点的权值最小的两棵二叉树,分别作其左右子树
构造一颗新的二叉树,并置这棵新的二叉树根结点的权值为其左右子树
的根结点之和。
3.从F中删去这两棵二叉树,同时刚新生成的二叉树加入到深林F中。
4.重复2,3,步骤,直至深林F中只含有一颗二叉树为止。
2.哈夫曼树的实现
函数String EnCode(Char Type ch):表示哈夫曼树已存在,返回字符ch的编码。
函数LinkList
源代码中创建了一个哈夫曼树结点类,其中有数据成员weight,parent,leftChild,rightChild分别代表了权值,双亲,左孩子,右孩子。
在哈夫曼树类中有数据成员*nodes,*LeafChars,*LeafCharCodes,curPos,num,分别用来存储结点信息,叶结点字符信息,叶结点字符编码信息,译码时从根结点到叶结点路径的当前结点,叶结点个数。哈夫曼树类中含有多个函数,有构造函数,析构函数等。由函数HuffmanTree(CharType ch[],WeightType w[],int n)来构造由字符,权值,和字符个数构造哈夫曼树,在根据哈夫曼算法很容易实现哈夫曼类的函数以及构造函数。在在算法中,求叶结点字符的编码时,需要从叶结点出发走一条从高叶结点到根结点的路径,而编码却是从根结点出发到叶结点的路径,由左分支为编码0,右分支为编码1,得到的编码,因此从叶结点出发到根结点的路径得到的编码是实际编码的逆序,并且编码长度不确定,又由于可以再线性链表中构造串,因此将编码的信息储存在一个线性立案标准,每得到一位编码都将其插入在线性链表的最前面。
在求某个字符的编码是由函数EnCode(CharType ch)来求,返回字符编码。在进行译码时,用一个线性链表存储字符序列,由函数Decode(String strCode)来求,对编码串strCode进行译码,返回编码前的字符序列。函数Compress()用哈夫曼编码压缩文件。函数Decompress()解压缩用哈夫曼编码压缩的文件。
在主函数中有两个选项,一个是选择编码压缩,一个是解压。在函数中使用了文件输入输出流,我们可以选择要压缩的文件名输入,在选出压缩文件保存的地方和文件类型,将压缩所得到的文件存储在另一个文件中,解压也是如此。
2.源代码
#ifndef __HUFFMAN_TREE_NODE_H__
#define __HUFFMAN_TREE_NODE_H__
// 哈夫曼树结点类模板
template
struct HuffmanTreeNode
{
WeightType weight; // 权数据域
unsigned int parent, leftChild, rightChild; // 双亲,左右孩子域
HuffmanTreeNode(); // 无参数的构造函数模板
HuffmanTreeNode(WeightType w, int p = 0, int lChild = 0, int rChild = 0);
// 已知权,双亲及左右孩子构造结构
};
// 哈夫曼树结点类模板的实现部分
template
HuffmanTreeNode
{
parent = leftChild = rightChild = 0;
}
template
HuffmanTreeNode
{
weight = w; // 权
parent = p; // 双亲
leftChild = lChild; // 左孩子
rightChild = rChild; // 右孩子
}
#endif
#ifndef __HUFFMAN_TREE_H__
#define __HUFFMAN_TREE_H__
#include"string.h"// 串类
#include"huffman_tree_node.h"// 哈夫曼树结点类模板
// 哈夫曼树类模板
template
class HuffmanTree
{
protected:
HuffmanTreeNode
CharType *LeafChars; // 叶结点字符信息,LeafChars[0]未用
String *LeafCharCodes; // 叶结点字符编码信息,LeafCharCodes[0]未用
int curPos; // 译码时从根结点到叶结点路径的当前结点int num; // 叶结点个数
// 辅助函数模板:
void Select(int cur, int &r1, int &r2); // nodes[1 ~ cur]中选择双亲为,权值最小的两个结点r1,r2
void CreatHuffmanTree(CharType ch[], WeightType w[], int n);
// 由字符,权值和字符个数构造哈夫曼树
public:
// 哈夫曼树方法声明及重载编译系统默认方法声明:
HuffmanTree(CharType ch[], WeightType w[], int n); // 由字符,权值和字符个数构造哈夫曼树virtual ~HuffmanTree(); // 析构函数模板
String Encode(CharType ch); // 编码
LinkList
HuffmanTree(const HuffmanTree
};
// 孩子兄弟表示哈夫曼树类模板的实现部分
template
void HuffmanTree
// 操作结果:nodes[1 ~ cur]中选择双亲为,权值最小的两个结点r1,r2
{
r1 = r2 = 0; // 0表示空结点
for (int pos = 1; pos <= cur; pos++)
{ // 查找树值最小的两个结点
if (nodes[pos].parent != 0) continue; // 只处理双亲不为的结点
if (r1 == 0)
{
r1 = pos; // r1为空,将pos赋值给r1
}
else if (r2 == 0)
{
r2 = pos; // r2为空,将pos赋值给r2
}
else if(nodes[pos].weight < nodes[r1].weight)
{
r1 = pos; // nodes[pos]权值比nodes[r1]更小,将pos赋为r1
}
else if (nodes[pos].weight < nodes[r2].weight)
{
r2 = pos; // nodes[pos]权值比nodes[r2]更小,将pos赋为r2
}
}
}
void HuffmanTree
// 操作结果:由字符,权值和字符个数构造哈夫曼树
{
num = n; // 叶结点个数
int m = 2 * n - 1; // 结点个数
nodes = new HuffmanTreeNode
LeafChars = new CharType[n + 1]; // LeafChars[0]未用
LeafCharCodes = new String[n + 1]; // LeafCharCodes[0]未用
int pos; // 临时变量
for (pos = 1; pos <= n; pos++)
{ // 存储叶结点信息
nodes[pos].weight = w[pos - 1]; // 权值
LeafChars[pos] = ch[pos - 1]; // 字符
}
for (pos = n + 1; pos <= m; pos++)
{ // 建立哈夫曼树
int r1, r2;
Select(pos - 1, r1, r2); // nodes[1 ~ pos - 1]中选择双亲为,权值最小的两个结点r1,r2
// 合并以r1,r2为根的树
nodes[r1].parent = nodes[r2].parent = pos; // r1,r2双亲为pos
nodes[pos].leftChild = r1; // r1为pos的左孩子
nodes[pos].rightChild = r2; // r2为pos的右孩子
nodes[pos].weight = nodes[r1].weight + nodes[r2].weight;//pos的权为r1,r2的权值之和}
for (pos = 1; pos <= n; pos++)
{ // 求n个叶结点字符的编码
LinkList
for (unsigned int child = pos, parent = nodes[child].parent; parent != 0;
child = parent, parent = nodes[child].parent)
{ // 从叶结点到根结点逆向求编码
if (nodes[parent].leftChild == child) charCode.Insert(1, '0');// 左分支编码为'0'
else charCode.Insert(1, '1'); // 右分支编码为'1'
}
LeafCharCodes[pos] = charCode; // charCode中存储字符编码
}
curPos = m; // 译码时从根结点开始,m为根
}
HuffmanTree
{
CreatHuffmanTree(ch, w, n); // 由字符,权值和字符个数构造哈夫曼树
}
template
HuffmanTree
// 操作结果:销毁哈夫曼树
{
if (nodes != NULL) delete []nodes; // 释放结点信息
if (LeafChars != NULL) delete []LeafChars; // 释放叶结点字符信息
if (LeafCharCodes != NULL) delete []LeafCharCodes; // 释放叶结点字符编码信息
}
template
String HuffmanTree
// 操作结果:返回字符编码
{
for (int pos = 1; pos <= num; pos++)
{ // 查找字符的位置
if (LeafChars[pos] == ch) return LeafCharCodes[pos];// 找到字符,得到编码}
throw Error("非法字符,无法编码!"); // 抛出异常
}
template
LinkList
// 操作结果:对编码串strCode进行译码,返回编码前的字符序列
{
LinkList
for (int pos = 0; pos < strCode.Length(); pos++)
{ // 处理每位编码
if (strCode[pos] == '0') curPos = nodes[curPos].leftChild; // '0'表示左分支
else curPos = nodes[curPos].rightChild; // '1'表示左分支
if (nodes[curPos].leftChild == 0 && nodes[curPos].rightChild == 0)
{ // 译码时从根结点到叶结点路径的当前结点为叶结点
charList.Insert(charList.Length() + 1, LeafChars[curPos]);
curPos = 2 * num - 1; // curPos回归根结点}
}
return charList; // 返回编码前的字符序列
}
template
HuffmanTree
// 操作结果:由哈夫曼树copy构造新哈夫曼树——复制构造函数模板
{
num = copy.num; // 叶结点个数
curPos = copy.curPos; // 译码时从根结点到叶结点路径的当前结点
int m = 2 * num - 1; // 结点总数
nodes = new HuffmanTreeNode
LeafChars = new CharType[num + 1]; // LeafChars[0]未用
LeafCharCodes = new String[num + 1]; // LeafCharCodes[0]未用
int pos; // 临时变量
for (pos = 1; pos <= m; pos++)
{ // 复制结点信息
nodes[pos] = copy.nodes[pos]; // 结点信息
}
for (pos = 1; pos <= num; pos++)
{ // 复制叶结点字符信息与叶结点字符编码信息
LeafChars[pos] = copy.LeafChars[pos]; // 叶结点字符信息
LeafCharCodes[pos] = copy.LeafCharCodes[pos];// 叶结点字符编码信息}
}
template
HuffmanTree WeightType>::operator=(const HuffmanTree // 操作结果:将哈夫曼树copy赋值给当前哈夫曼树——重载赋值运算符 { if (© != this) { if (nodes != NULL) delete []nodes; // 释放结点信息 if (LeafChars != NULL) delete []LeafChars; // 释放叶结点字符信息 if (LeafCharCodes != NULL) delete []LeafCharCodes; // 释放叶结点字符编码信息 num = copy.num; // 叶结点个数 curPos = copy.curPos; // 译码时从根结点到叶结点路径的当前结点 int m = 2 * num - 1; // 结点总数 nodes = new HuffmanTreeNode LeafChars = new CharType[num + 1]; // LeafChars[0]未用 LeafCharCodes = new String[num + 1]; // LeafCharCodes[0]未用 int pos; // 临时变量 for (pos = 1; pos <= m; pos++) { // 复制结点信息 nodes[pos] = copy.nodes[pos]; // 结点信息 } for (pos = 1; pos <= num; pos++) { // 复制叶结点字符信息与叶结点字符编码信息 LeafChars[pos] = copy.LeafChars[pos]; // 叶结点字符信息 LeafCharCodes[pos] = copy.LeafCharCodes[pos];// 叶结点字符编码信息} } return *this; } #endif #ifndef __HUFFMAN_COMPRESS_H__ #define __HUFFMAN_COMPRESS_H__ #include"huffman_tree.h"// 哈夫曼树类 struct BufferType// 字符缓存器 { char ch; // 字符 unsigned int bits; // 实际比特数 }; class HuffmanCompress// 哈夫曼压缩类 { protected: HuffmanTree FILE *infp,*outfp; // 输入/出文件 BufferType buf; // 字符缓存 void Write(unsigned int bit); // 向目标文件中写入一个比特 void WriteToOutfp(); // 强行将字符缓存写入目标文件public: HuffmanCompress(); // 无参数的构造函数 ~HuffmanCompress(); // 析构函数 void Compress(); // 压缩算法 void Decompress(); // 解压缩算法 HuffmanCompress(const HuffmanCompress ©); // 复制构造函数 HuffmanCompress &operator=(const HuffmanCompress& copy);// 赋值运算符 }; HuffmanCompress::HuffmanCompress() { pHuffmanTree = NULL; // 空哈夫曼树 } HuffmanCompress::~HuffmanCompress() { if (pHuffmanTree != NULL) delete []pHuffmanTree; // 释放空间 } void HuffmanCompress::Write(unsigned int bit) // 操作结果:向目标文件中写入一个比特 { buf.bits++; // 缓存比特数自增 buf.ch = (buf.ch << 1) | bit; // 将bit加入到缓存字符中 if (buf.bits == 8) { // 缓存区已满,写入目标文件 fputc(buf.ch, outfp); // 写入目标文件 buf.bits = 0; // 初始化bits buf.ch = 0; // 初始化ch } } void HuffmanCompress::WriteToOutfp() // 操作结果:强行将字符缓存写入目标文件{ unsigned int len = buf.bits; // 缓存实际比特数 if (len > 0) { // 缓存非空, 将缓存的比特个数增加到, 自动写入目标文件 for (unsigned int i = 0; i < 8 - len; i++) Write(0); } } void HuffmanCompress::Compress()// 操作结果:用哈夫曼编码压缩文件 { char infName[256], outfName[256]; // 输入(源)/出(目标)文件名 cout << "请输入源文件名(文件小于GB):"; // 被压缩文件小于GB cin >> infName; // 输入源文件名 if ((infp = fopen(infName, "r+b")) == NULL) throw Error("打开源文件失败!"); // 抛出异常fgetc(infp); // 取出源文件第一个字符if (feof(infp)) throw Error("空源文件!"); // 抛出异常 cout << "请输入目标文件:"; cin >> outfName; if ((outfp = fopen(outfName, "w+b")) == NULL) throw Error("打开目标文件失败!"); // 抛出异常 cout << "正在处理,请稍候..." << endl; const unsigned long n = 256; // 字符个数 char ch[n]; // 字符数组 unsigned long w[n]; // 字符出现频度(权) unsigned long i, size = 0; char cha; for (i = 0; i < n; i++) { // 初始化ch[]和w[] ch[i] = (char)i; // 初始化ch[i] w[i] = 0; // 初始化w[i] } rewind(infp); // 使源文件指针指向文件开始处 cha = fgetc(infp); // 取出源文件第一个字符 while (!feof(infp)) { // 统计字符出现频度 w[(unsigned char)cha]++; // 字符cha出现频度自加 size++; // 文件大小自加 cha=fgetc(infp); // 取出源文件下一个字符 } if (pHuffmanTree != NULL) delete []pHuffmanTree; // 释放空间 pHuffmanTree = new HuffmanTree fwrite(&size, sizeof(unsigned long), 1, outfp); // 向目标文件写入源文件大小 for (i = 0; i < n; i++) { // 向目标文件写入字符出现频度 fwrite(&w[i], sizeof(unsigned long), 1, outfp); } buf.bits = 0; // 初始化bits buf.ch = 0; // 初始化ch rewind(infp); // 使源文件指针指向文件开始处 cha = fgetc(infp); // 取出源文件的第一个字符 while (!feof(infp)) { // 对源文件字符进行编码,并将编码写入目标文件 String strTmp = pHuffmanTree->Encode(cha);// 字符编码 for (i = 0; i { // 向目标文件写入编码 if (strTmp[i] == '0') Write(0); // 向目标文件写入 else Write(1); // 向目标文件写入 } cha = fgetc(infp); // 取出源文件的下一个字符 } WriteToOutfp(); // 强行写入目标文件 fclose(infp); fclose(outfp); // 关闭文件 cout << "处理结束." << endl; } void HuffmanCompress::Decompress()// 操作结果:解压缩用哈夫曼编码压缩的文件 { char infName[256], outfName[256]; // 输入(压缩)/出(目标)文件名 cout << "请输入压缩文件名:"; cin >> infName; if ((infp = fopen(infName, "r+b")) == NULL) throw Error("打开压缩文件失败!"); // 抛出异常 fgetc(infp); // 取出压缩文件第一个字符 if (feof(infp)) throw Error("压缩文件为空!"); // 抛出异常 cout << "请输入目标文件名:"; cin >> outfName; if ((outfp = fopen(outfName, "w+b")) == NULL) throw Error("打开目标文件失败!"); // 抛出异常 cout << "正在处理,请稍候..." << endl; const unsigned long n = 256; // 字符个数 char ch[n]; // 字符数组 unsigned long w[n]; // 权 unsigned long i, size = 0; char cha; rewind(infp); // 使源文件指针指向文件开始处fread(&size, sizeof(unsigned long), 1, infp); // 读取目标文件的大小 for (i = 0; i < n; i++) { ch[i] = (char)i; // 初始化ch[i] fread(&w[i], sizeof(unsigned long), 1, infp); // 读取字符频度 } if (pHuffmanTree != NULL) delete []pHuffmanTree; // 释放空间 pHuffmanTree = new HuffmanTree cha = fgetc(infp); // 取出源文件的第一个字符 while (!feof(infp)) { // 对压缩文件字符进行解码,并将解码的字符写入目标文件 String strTmp = ""; // 将cha转换二进制形式的串 unsigned char c = (unsigned char)cha; // 将cha转换成unsigned char类型 for (i = 0; i < 8; i++) { // 将c转换成二进制串 if (c < 128) Concat(strTmp, "0"); // 最高位为 else Concat(strTmp, "1"); // 最高位为 c = c << 1; // 左移一位 } LinkList String strTemp(lkText); for (i = 1; i<=strTemp.Length(); i++) { // 向目标文件写入字符 len++; // 目标文件长度自加 fputc(strTemp[i-1], outfp); // 将字符写入目标文件中 if (len == size) break; // 解压完毕退出内循环 } if (len == size) break; // 解压完毕退出外循环 cha = fgetc(infp); // 取出源文件的下一个字符} fclose(infp); fclose(outfp); // 关闭文件 cout << "处理结束." << endl; } HuffmanCompress::HuffmanCompress(const HuffmanCompress ©) // 操作结果:由哈夫曼压缩类对象copy构造新哈夫曼压缩类对象——复制构造函数 { if (copy.pHuffmanTree != NULL) { // copy为非空哈夫曼压缩类对象 pHuffmanTree = new HuffmanTree } else pHuffmanTree = NULL; // 生成空哈夫曼压缩类对象 } HuffmanCompress &HuffmanCompress::operator=(const HuffmanCompress& copy) // 操作结果:将哈夫曼压缩类对象copy赋值给当前哈夫曼压缩类对象——赋值运算符 { if (© != this) { if (pHuffmanTree != NULL) delete []pHuffmanTree; // 释放空间 if (copy.pHuffmanTree != NULL) { // copy为非空哈夫曼压缩类对象 pHuffmanTree = new HuffmanTree // 生成哈夫曼树 } else pHuffmanTree = NULL; // 生成空哈夫曼压缩类对象 } return *this; } #endif #include"utility.h"// 实用程序软件包 #include"huffman_compress.h"// 哈夫曼压缩算法 int main(void) { try// 用try封装可能出现异常的代码 { HuffmanCompress obj; int select = 0; while (select != 3) { cout << endl << "1. 压缩"; cout << endl << "2. 解压"; cout << endl << "3. 退出"; cout << endl << "请选择:"; cin >> select; // 输入选择 while (cin.get() != '\n'); // 忽略用户输入的其他字符 switch (select) { case 1: https://www.360docs.net/doc/3a4381914.html,press(); // 压缩 break; case 2: obj.Decompress(); // 解压缩 } } } catch (Error err) // 捕捉并处理异常 { err.Show(); // 显示异常信息 } system("PAUSE"); // 调用库函数system() return 0; // 返回值, 返回操作系统 } 3.测试结果 由于测试的文件字符太多,则就截下来一部分的压缩后的编码。下面的是压缩过后的编码截图。 下面为解压后的文件截图,解压后与压缩前的是一样的。