哈弗曼树实现文件压缩,500分

W1nds 2011-12-07 04:56:59
只能发100分的帖子,解决后开贴补分

huffman.h
#include <iostream>
#include <fstream>
#include <queue>

//字符可能是0x00-0xFF 共256种类型


const int leaf=256;//定义最多可能出现不同的字符数,也就是叶子节点的最大个数
const long MAX=99999999;//代表无穷大(每个字符出现的最大次数)

struct HTnode//哈弗曼树节点的结构体
{
long weight;//记录结点的权值
int parent;//记录结点的父结点的位置?
int lchild;//结点的左孩子 值代表什么?
int rchild;//结点的右孩子
int *code;//记录该点的哈弗曼编码
int codelen;//记录该点哈弗曼编码的长度
//初始化结点,令权值无穷大,无父节点跟左右孩子
HTnode
{
weight=MAX;
parent=-1;
lchild=-1;
rchild=-1;
codelen=0;
}
};
class huffman
{
public:
huffman();
void CountProc(char *input); //压缩时统计各字符出现的次数,并将其写入对应结点的权值
void CreatHuffman();//根据各结点的权值构造哈弗曼树
void CodeProc();//压缩时利用建好的哈夫曼树计算每个字符的哈弗曼编码
void PrintCode();//列出每个字符的哈弗曼编码
void AddBit(int bit);//压缩时对于一个未满八位的cbyte添加一个bit
void ResetByte();//将cbyte清空
void Compress(char *inout,char *output);//压缩函数
void Decompress(char *input,char *output);//解压缩函数
void Compare(char *input, char *output);//原文件与压缩后的文件比较函数
void Compare2(char *input,char *output);//原文件与解压后的文件比较函数
~huffman();
private:
int root;//记录根结点的位置
int leafnum;//记录不同字符的个数
HTnode HT[leaf*2-1];//HTnode结构的数组,用来表示哈弗曼树,树的最大结点个数不超过leaf*2-1,由二叉树的性质得到
char cbyte;//压缩文件时用来缓冲bit的变量
int bitsnum;//cbyte中bit的个数
int lacknum;//压缩到最后cbyte中得bit不满8个时填充的0的个数
};

hufman.cpp
include "huffman.h"

huffman::huffman()
{
root=0;
leafnum=0;
cbyte=0;
bitsnum=0;
lacknum=0;
}

void huffman::CountProc(char *input)
{
ifstream ifs;
char c;
ifs.open(input,ios::binary);
if (!ifs)
{
AfxMessageBox(_T("failed to open the file"));
return;
}
while(ifs.get(c))//从流中读取一个字符
{
if (HT[c+leaf/2]->weight==MAX)//如果该字符是第一次出现,则初始化其权值
{
HT[c+128]->weight=0;
leafnum++;
}
HT[c+128]->weight++;//权值加1
}
ifs.close();
}
红色标记的地方为什么是c+leaf/2
CPP文件不完全,还有几个类似的问题
树的构造过程和编码过程我基本了解,就是几个地方搞不懂
若是不方便交流可以加我QQ393587787
谢谢~
...全文
243 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
oo 2011-12-08
  • 打赏
  • 举报
回复
这个就是根据编码值在你的编码树上找到编码值对应的字符
从最高位开始(即从编码树的root开始),一层一层往下找,找到是叶子结点即OK
W1nds 2011-12-08
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 oo 的回复:]

leaf 定义成256,就是一个byte的256种值 -128 ~ 127
leaf/2就是128

c + leaf/2 刚好把 -128 ~ 127 映射到 0~255

其实后面的128应该也该写成 leaf/2就好理解了。
另外,如果把c直接转成unsigned char,这样就直接是0~255范围,不需要加128来映射了,更好一点(而且char并不是在所有编译器里都缺省……
[/Quote]
谢谢
还有解码时碰到的一些问题
void huffman::Decompress(char *input,char *output)
{
queue<char> q;
char c;
ifstream ifs;
ofstream ofs;
ifs.open(input,ios::binary);
ofs.open(output,ios::binary);
if(!ifs){
//cout << "failed to open the file " << input << '!' << endl;
AfxMessageBox(_T("打开文件失败!"));
return;
}
if(!ofs){
//cout << "failed to open the file " << output << '!' << endl;
AfxMessageBox(_T("创建文件失败!"));
return;
}

ifs.get(c);
lacknum = c; //读出最后一个字节缺失的bit个数
ifs.get(c);
root = c+384; //读出根结点的位置
for(int i=0; i<leaf*2-1; i++){ //建立各结点之间的双亲孩子关系
ifs.get(c);
if(c==127)
continue;
else{
HT[i]->parent = c+384;
if(HT[c+384]->lchild==-1)//因为前面创建树时约定下标小的作为左孩子
HT[c+384]->lchild = i;
else
HT[c+384]->rchild = i;
}
}

int point = root;
//为了方便处理最后一个可能有缺失bit的字节,先将读出的数据放入队列
while(ifs.get(c))
q.push(c);

//还原文件过程
while(q.size()>1){ //还未到最后一个字节
c = q.front();//得到队头元素
for(int i=0; i<8; i++){
if(int(c&128)==0){//最高位为0
point = HT[point]->lchild;
if(HT[point]->lchild==-1 && HT[point]->rchild==-1){
ofs.put(char(point-128));
point = root;
}
c = c << 1;
}
else{
point = HT[point]->rchild;
if(HT[point]->lchild==-1 && HT[point]->rchild==-1){
ofs.put(char(point-128));
point = root;
}
c = c << 1;
}
}
q.pop();
}
c = q.front(); //最后一个字节
for(int i=0; i<8-lacknum; i++){
if(int(c&128)==0){
point = HT[point]->lchild;
if(HT[point]->lchild==-1 && HT[point]->rchild==-1){
ofs.put(char(point-128));
point = root;
}
c = c << 1;
}
else{
point = HT[point]->rchild;
if(HT[point]->lchild==-1 && HT[point]->rchild==-1){
ofs.put(char(point-128));
point = root;
}
c = c << 1;
}
}
q.pop();
ifs.close();
ofs.close();
AfxMessageBox(_T("解压缩成功!"));
}
求解释红色部分
douch 2011-12-08
  • 打赏
  • 举报
回复
因为leaf是全部节点个数,但是最开始的时候最多只是128个,后来的才是建树中新创建的
看leaf的注释
oo 2011-12-08
  • 打赏
  • 举报
回复
leaf 定义成256,就是一个byte的256种值 -128 ~ 127
leaf/2就是128

c + leaf/2 刚好把 -128 ~ 127 映射到 0~255

其实后面的128应该也该写成 leaf/2就好理解了。
另外,如果把c直接转成unsigned char,这样就直接是0~255范围,不需要加128来映射了,更好一点(而且char并不是在所有编译器里都缺省为 -128 ~ 127,有可能是0~255,只有signed char才保证是-128 ~ 127)
W1nds 2011-12-08
  • 打赏
  • 举报
回复
谢谢谢谢
结贴了
我在开贴给您400
oo 2011-12-08
  • 打赏
  • 举报
回复
剩下的bit在这个循环里会处理完(移动point指针),如果不是完整编码,会在处理下一字节内容时找到完整编码。point只有在读到一个完整编码时才会重新指向root
W1nds 2011-12-08
  • 打赏
  • 举报
回复
我大致明白了
C移位来对应到下一层,最终对应到叶子结点
可是如果 C是8位,他的前5为对应一个字符,那后三位该怎么处理,后三位如果是跟着后面读出一个字节的数据对应一个字符呢
oo 2011-12-08
  • 打赏
  • 举报
回复
0-8是一个字节就8个bit啊

因为编码是变长的,读进一个字节后,有可能包含一个编码,也可能是多个编码,也可能是一个编码的一部分,所以只能一个bit一个bit来看

假设 0001是个合法编码,读进来的字节是00010011B
从最高位开始:
开始point指到root,
读到‘0’,看point的left节点,不是叶子,point指到left节点
然后下一个bit,还是‘0’看point的left节点,不是叶子,point指到left节点
继续下一个bit,还是‘0’看point的left节点,不是叶子,point指到left节点
继续下一个bit,是‘1’看point的right节点,这次是叶子,OK,是个完整编码,查到编码对应的值,输出;point重新指想root,后续的4个bit 0011是下一个编码的一部分,重复前面的操作就好了。

W1nds 2011-12-08
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 oo 的回复:]

这个就是根据编码值在你的编码树上找到编码值对应的字符
从最高位开始(即从编码树的root开始),一层一层往下找,找到是叶子结点即OK
[/Quote]
谢谢您的指点
可是能不能再具体点
//还原文件过程
while(q.size()>1){ //还未到最后一个字节
c = q.front();//得到队头元素
for(int i=0; i<8; i++){
if(int(c&128)==0){//最高位为0
point = HT[point]->lchild;
if(HT[point]->lchild==-1 && HT[point]->rchild==-1){
ofs.put(char(point-128));
point = root;
}
c = c << 1;
}
else{
point = HT[point]->rchild;
if(HT[point]->lchild==-1 && HT[point]->rchild==-1){
ofs.put(char(point-128));
point = root;
}
就是这一段
为什么FOR是从0-8
C为什么要左移一位
ryfdizuo 2011-12-07
  • 打赏
  • 举报
回复
哎,lz自己研究吧,说上原理讲得很清楚。代码也不难。

33,010

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧