哈夫曼树解压与压缩
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
哈夫曼树的压缩与解压
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中只含有一颗二叉树为止。
函数String EnCode(Char Type ch):表示哈夫曼树已存在,返回字符ch的编码。
函数LinkList<CharType>UnCode(String strCode):表示对哈夫曼树进展译码,返回编码前的字符序列。
根据算法可以看出,在具有n个结点权值的哈夫曼树的构造过程中,每次都是从F中删去两棵树,增加一棵树,即每次完毕后减少一棵树,经过n-1次处理后,F中就只剩下一棵树了。
另外,每次合并都要产生一个新的结点,合并n-1次后共产生了n-1个新结点,并且这n-1个新节点都是具有左右子树的分支结点。
如此最终得到的哈夫曼树中共有2n-1个结点,并且其中没有度为1的分支结点,最后一次产生的新结点就是哈夫曼树的根结点。
源代码中创建了一个哈夫曼树结点类,其中有数据成员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进展译码,返回编码前的字符序列。
函数press()用哈夫曼编码压缩文件。
函数Depress()解压缩用哈夫曼编码压缩的文件。
在主函数中有两个选项,一个是选择编码压缩,一个是解压。
在函数中使用了文件输入输出流,我们可以选择要压缩的文件名输入,在选出压缩文件保存的地方和文件类型,将压缩所得到的文件存储在另一个文件中,解压也是如此。
2.源代码
#ifndef __HUFFMAN_TREE_NODE_H__
#define __HUFFMAN_TREE_NODE_H__
// 哈夫曼树结点类模板
template <class WeightType>
struct HuffmanTreeNode
{
WeightType weight; // 权数据域
unsignedint parent, leftChild, rightChild; // 双亲,左右孩子域
HuffmanTreeNode(); // 无参数的构造函数模板
HuffmanTreeNode(WeightType w, int p = 0, int lChild = 0, int rChild = 0);
// 权,双亲与左右孩子构造结构
};
// 哈夫曼树结点类模板的实现局部
template <class WeightType>
HuffmanTreeNode<WeightType>::HuffmanTreeNode()// 操作结果:构造空结点
{
parent = leftChild = rightChild = 0;
}
template <class WeightType>
HuffmanTreeNode<WeightType>::HuffmanTreeNode(WeightType w, int p, int lChild, int rChild) // 操作结果:构造一个权为w,双亲为p,左孩子为lChild,右孩子为rChild的结点
{
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 CharType, class WeightType>
class HuffmanTree
{
protected:
HuffmanTreeNode<WeightType> *nodes; // 存储结点信息,nodes[0]未用
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<CharType> Decode(String strCode); // 译码
HuffmanTree(const HuffmanTree<CharType, WeightType> ©); // 复制构造函数模板
HuffmanTree<CharType, WeightType> &operator=(const HuffmanTree<CharType, WeightType>& copy); // 重载赋值运算符
};
// 孩子兄弟表示哈夫曼树类模板的实现局部
template <class CharType, class WeightType>
void HuffmanTree<CharType, WeightType>::Select(int cur, int &r1, int &r2)
// 操作结果: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
}
elseif (r2 == 0)
{
r2 = pos; // r2为空,将pos赋值给r2
}
elseif(nodes[pos].weight < nodes[r1].weight)
{
r1 = pos; // nodes[pos]权值比nodes[r1]更小,将pos赋为r1
}
elseif (nodes[pos].weight < nodes[r2].weight)
{
r2 = pos; // nodes[pos]权值比nodes[r2]更小,将pos赋为r2
}
}
}
template <class CharType, class WeightType>
void HuffmanTree<CharType, WeightType>::CreatHuffmanTree(CharType ch[], WeightType w[], int n) // 操作结果:由字符,权值和字符个数构造哈夫曼树
{
num = n; // 叶结点个数
int m = 2 * n - 1; // 结点个数
nodes = new HuffmanTreeNode<WeightType>[m + 1]; // nodes[0]未用
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<char> charCode; // 暂存叶结点字符编码信息
for (unsignedint 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为根
}
template <class CharType, class WeightType>
HuffmanTree<CharType, WeightType>::HuffmanTree(CharType ch[], WeightType w[], int n)
// 操作结果:由字符,权值和字符个数构造哈夫曼树
{
CreatHuffmanTree(ch, w, n); // 由字符,权值和字符个数构造哈夫曼树
}
template <class CharType, class WeightType>
HuffmanTree<CharType, WeightType>::~HuffmanTree()
// 操作结果:销毁哈夫曼树
{
if (nodes != NULL) delete []nodes; // 释放结点信息
if (LeafChars != NULL) delete []LeafChars; // 释放叶结点字符信息
if (LeafCharCodes != NULL) delete []LeafCharCodes; // 释放叶结点字符编码信息
}
template <class CharType, class WeightType>
String HuffmanTree<CharType, WeightType>::Encode(CharType ch)
// 操作结果:返回字符编码
{
for (int pos = 1; pos <= num; pos++)
{ // 查找字符的位置
if (LeafChars[pos] == ch) return LeafCharCodes[pos];// 找到字符,得到编码
}
throw Error("非法字符,无法编码!"); // 抛出异常
}
template <class CharType, class WeightType>
LinkList<CharType> HuffmanTree<CharType, WeightType>::Decode(String strCode)
// 操作结果:对编码串strCode进展译码,返回编码前的字符序列
{
LinkList<CharType> charList; // 编码前的字符序列
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 <class CharType, class WeightType>
HuffmanTree<CharType, WeightType>::HuffmanTree(const HuffmanTree<CharType, WeightType> ©) // 操作结果:由哈夫曼树copy构造新哈夫曼树——复制构造函数模板
{
num = copy.num; // 叶结点个数
curPos = copy.curPos; // 译码时从根结点到叶结点路径的当前结点
int m = 2 * num - 1; // 结点总数
nodes = new HuffmanTreeNode<WeightType>[m + 1]; // nodes[0]未用
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 <class CharType, class WeightType>
HuffmanTree<CharType, WeightType> &HuffmanTree<CharType, WeightType>::operator=(const HuffmanTree<CharType, WeightType>& copy)
// 操作结果:将哈夫曼树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<WeightType>[m + 1]; // nodes[0]未用
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_PRESS_H__
#define __HUFFMAN_PRESS_H__
#include"huffman_tree.h"// 哈夫曼树类
struct BufferType// 字符缓存器
{
char ch; // 字符
unsignedint bits; // 实际比特数
};
class Huffmanpress// 哈夫曼压缩类
{
protected:
HuffmanTree<char, unsignedlong> *pHuffmanTree;
FILE *infp,*outfp; // 输入/出文件
BufferType buf; // 字符缓存
void Write(unsignedint bit); // 向目标文件中写入一个比特
void WriteToOutfp(); // 强行将字符缓存写入目标文件
public:
Huffmanpress(); // 无参数的构造函数
~Huffmanpress(); // 析构函数
void press(); // 压缩算法
void Depress(); // 解压缩算法
Huffmanpress(const Huffmanpress ©); // 复制构造函数
Huffmanpress &operator=(const Huffmanpress& copy);// 赋值运算符
};
Huffmanpress::Huffmanpress()
{
pHuffmanTree = NULL; // 空哈夫曼树
}
Huffmanpress::~Huffmanpress()
{
if (pHuffmanTree != NULL)
delete []pHuffmanTree; // 释放空间
}
void Huffmanpress::Write(unsignedint 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 Huffmanpress::WriteToOutfp() // 操作结果:强行将字符缓存写入目标文件{
unsignedint len = buf.bits; // 缓存实际比特数
if (len > 0)
{ // 缓存非空, 将缓存的比特个数增加到, 自动写入目标文件
for (unsignedint i = 0; i < 8 - len; i++) Write(0);
}
}
void Huffmanpress::press()// 操作结果:用哈夫曼编码压缩文件
{
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;
constunsignedlong n = 256; // 字符个数
char ch[n]; // 字符数组unsignedlong w[n]; // 字符出现频度(权) unsignedlong 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[(unsignedchar)cha]++; // 字符cha出现频度自加
size++; // 文件大小自加
cha=fgetc(infp); // 取出源文件下一个字符
}
if (pHuffmanTree != NULL) delete []pHuffmanTree; // 释放空间
pHuffmanTree = new HuffmanTree<char, unsignedlong>(ch, w, n); // 生成哈夫曼树
rewind(outfp); // 使目标文件指针指向文件开始处fwrite(&size, sizeof(unsignedlong), 1, outfp); // 向目标文件写入源文件大小
for (i = 0; i < n; i++)
{ // 向目标文件写入字符出现频度
fwrite(&w[i], sizeof(unsignedlong), 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<strTmp.Length(); i++)
{ // 向目标文件写入编码
if (strTmp[i] == '0')
Write(0); // 向目标文件写入
else Write(1); // 向目标文件写入
}
cha = fgetc(infp); // 取出源文件的下一个字符
}
WriteToOutfp(); // 强行写入目标文件
fclose(infp); fclose(outfp); // 关闭文件
cout << "处理完毕." << endl;
}
void Huffmanpress::Depress()// 操作结果:解压缩用哈夫曼编码压缩的文件
{
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;
constunsignedlong n = 256; // 字符个数
char ch[n]; // 字符数组
unsignedlong w[n]; // 权
unsignedlong i, size = 0;
char cha;
rewind(infp); // 使源文件指针指向文件开始处fread(&size, sizeof(unsignedlong), 1, infp); // 读取目标文件的大小
for (i = 0; i < n; i++)
{
ch[i] = (char)i; // 初始化ch[i]
fread(&w[i], sizeof(unsignedlong), 1, infp); // 读取字符频度
}
if (pHuffmanTree != NULL)
delete []pHuffmanTree; // 释放空间
pHuffmanTree = new HuffmanTree<char, unsignedlong>(ch, w, n); // 生成哈夫曼树unsignedlong len = 0; // 解压的字符数
cha = fgetc(infp); // 取出源文件的第一个字符
while (!feof(infp))
{ // 对压缩文件字符进展解码,并将解码的字符写入目标文件
String strTmp = ""; // 将cha转换二进制形式的串unsignedchar c = (unsignedchar)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<char> lkText = pHuffmanTree->Decode(strTmp);// 译码
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;
}
Huffmanpress::Huffmanpress(const Huffmanpress ©)
// 操作结果:由哈夫曼压缩类对象copy构造新哈夫曼压缩类对象——复制构造函数
{
if (copy.pHuffmanTree != NULL)
{ // copy为非空哈夫曼压缩类对象
pHuffmanTree = new HuffmanTree<char, unsignedlong>(*copy.pHuffmanTree);// 生成哈夫曼树}
else pHuffmanTree = NULL; // 生成空哈夫曼压缩类对象
}
Huffmanpress &Huffmanpress::operator=(const Huffmanpress& copy)
// 操作结果:将哈夫曼压缩类对象copy赋值给当前哈夫曼压缩类对象——赋值运算符
{
if (© != this)
{
if (pHuffmanTree != NULL) delete []pHuffmanTree; // 释放空间
if (copy.pHuffmanTree != NULL)
{ // copy为非空哈夫曼压缩类对象
pHuffmanTree = new HuffmanTree<char, unsignedlong>(*copy.pHuffmanTree); // 生成哈夫曼树}
else pHuffmanTree = NULL; // 生成空哈夫曼压缩类对象
}
return *this;
}
#endif
#include"utility.h"// 实用程序软件包
#include"huffman_press.h"// 哈夫曼压缩算法
int main(void)
{
try// 用try封装可能出现异常的代码
{
Huffmanpress 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:
obj.press(); // 压缩
break;
case 2:
obj.Depress(); // 解压缩
}
}
}
catch (Error err) // 捕捉并处理异常
{
err.Show(); // 显示异常信息
}
system("PAUSE"); // 调用库函数system()
return 0; // 返回值, 返回操作系统
}
3.测试结果
由于测试的文件字符太多,如此就截下来一局部的压缩后的编码。
下面的是压缩过后的编码截图。
下面为解压后的文件截图,解压后与压缩前的是一样的。