当前位置:文档之家› 哈夫曼树解压与压缩

哈夫曼树解压与压缩

哈夫曼树的压缩与解压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<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进行译码,返回编码前的字符序列。

函数Compress()用哈夫曼编码压缩文件。

函数Decompress()解压缩用哈夫曼编码压缩的文件。

在主函数中有两个选项,一个是选择编码压缩,一个是解压。

在函数中使用了文件输入输出流,我们可以选择要压缩的文件名输入,在选出压缩文件保存的地方和文件类型,将压缩所得到的文件存储在另一个文件中,解压也是如此。

2.源代码#ifndef __HUFFMAN_TREE_NODE_H__#define __HUFFMAN_TREE_NODE_H__// 哈夫曼树结点类模板template <class WeightType>struct HuffmanTreeNode{WeightType weight; // 权数据域unsigned int 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,r2void 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> &copy); // 复制构造函数模板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}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<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双亲为posnodes[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 (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<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)// 操作结果:由哈夫曼树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 (&copy != 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_COMPRESS_H__#define __HUFFMAN_COMPRESS_H__#include"huffman_tree.h"// 哈夫曼树类struct BufferType// 字符缓存器{char ch; // 字符unsigned int bits; // 实际比特数};class HuffmanCompress// 哈夫曼压缩类{protected:HuffmanTree<char, unsigned long> *pHuffmanTree;FILE *infp,*outfp; // 输入/出文件BufferType buf; // 字符缓存void Write(unsigned int bit); // 向目标文件中写入一个比特void WriteToOutfp(); // 强行将字符缓存写入目标文件public:HuffmanCompress(); // 无参数的构造函数~HuffmanCompress(); // 析构函数void Compress(); // 压缩算法void Decompress(); // 解压缩算法HuffmanCompress(const HuffmanCompress &copy); // 复制构造函数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; // 初始化bitsbuf.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):"; // 被压缩文件小于GBcin >> 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<char, unsigned long>(ch, w, n); // 生成哈夫曼树rewind(outfp); // 使目标文件指针指向文件开始处fwrite(&size, sizeof(unsigned long), 1, outfp); // 向目标文件写入源文件大小for (i = 0; i < n; i++){ // 向目标文件写入字符出现频度fwrite(&w[i], sizeof(unsigned long), 1, outfp);}buf.bits = 0; // 初始化bitsbuf.ch = 0; // 初始化chrewind(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 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<char, unsigned long>(ch, w, n); // 生成哈夫曼树unsigned long len = 0; // 解压的字符数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<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;}HuffmanCompress::HuffmanCompress(const HuffmanCompress &copy)// 操作结果:由哈夫曼压缩类对象copy构造新哈夫曼压缩类对象——复制构造函数{if (copy.pHuffmanTree != NULL){ // copy为非空哈夫曼压缩类对象pHuffmanTree = new HuffmanTree<char, unsigned long>(*copy.pHuffmanTree);// 生成哈夫曼树}else pHuffmanTree = NULL; // 生成空哈夫曼压缩类对象}HuffmanCompress &HuffmanCompress::operator=(const HuffmanCompress& copy)// 操作结果:将哈夫曼压缩类对象copy赋值给当前哈夫曼压缩类对象——赋值运算符{if (&copy != this){if (pHuffmanTree != NULL) delete []pHuffmanTree; // 释放空间if (copy.pHuffmanTree != NULL){ // copy为非空哈夫曼压缩类对象pHuffmanTree = new HuffmanTree<char, unsigned long>(*copy.pHuffmanTree);// 生成哈夫曼树}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:press(); // 压缩break;case 2:obj.Decompress(); // 解压缩}}}catch (Error err) // 捕捉并处理异常{err.Show(); // 显示异常信息}system("PAUSE"); // 调用库函数system()return 0; // 返回值, 返回操作系统}3.测试结果由于测试的文件字符太多,则就截下来一部分的压缩后的编码。

相关主题