哈夫曼压缩原理就是构建二叉树,出现频率高的字母用更少的位数来表示,实现压缩的效果

比如字符串abcbbc

构建哈夫曼树

这样构建出编码表b->0,a->10,  c->11

原本6个字符要48位来表示,现在只需要9位来表示即可

1.    首先将文本文件的每一个字符进行统计,构建编码表,这个编码表大概50几k

void readTxt(string file,Character *cList){ifstream infile;infile.open(file.data());  //将文件流对象与文件连接起来assert(infile.is_open());  //若失败,则输出错误消息,并终止程序运行char c;infile >> noskipws;while (!infile.eof()){addList(c,cList);infile>>c;}infile.close();            //关闭文件输入流}void addList(char c,Character *cList){//为空就退出if(!c) return;totalNum++;int i=0;for(;i<cnumber;i++){if(c==cList[i].data){break;}}cList[i].number++;if(i==cnumber){cList[cnumber].data = c;cnumber++;}if(i>0 && cList[i].number>cList[i-1].number){int num = cList[i].number;char ch = cList[i].data;cList[i].data = cList[i-1].data;cList[i].number = cList[i-1].number;cList[i-1].data = ch;cList[i-1].number = num;}}

1.    根据huffman树原理,构建编码表(如下图所示),词频越高则用更小的编码表示

//换行符string enterCode;typedef struct node{char ch;                         //存储该节点表示的字符,只有叶子节点用的到int val;                         //记录该节点的权值structnode *self,*left,*right;   //三个指针,分别用于记录自己的地址,左孩子的地址和右孩子的地址friend booloperator <(const node &a,constnode &b) //运算符重载,定义优先队列的比较结构{return a.val>b.val;          //这里是权值小的优先出队列}}node;int codeTablePlace = 0;node *myroot;priority_queue<node> p;               //定义优先队列char res[10000];                         //用于记录哈夫曼编码void dfs(node *root,int level,CodeTableItem *codetable,Character *cList)        //哈夫曼编码表{if(root->left==root->right)       //叶子节点的左孩子地址一定等于右孩子地址,且一定都为NULL;叶子节点记录有字符{if(level==0)                  //“AAAAA”这种只有一字符的情况{res[0]='0';level++;}res[level]='\0';              //字符数组以'\0'结束//printf("%c=>%s\n",root->ch,res);//搜索在统计表中的位置for(int j=0;j<cnumber;j++){if(root->ch==cList[j].data){codetable[j].data = root->ch;codetable[j].code =res;}}//        codetable[codeTablePlace].data = root->ch;//        codetable[codeTablePlace].code = res;if(int(root->ch)==10)enterCode =res;// codeTablePlace++;}else{res[level]='0';               //左分支为0dfs(root->left,level+1,codetable,cList);res[level]='1';               //右分支为1dfs(root->right,level+1,codetable,cList);}}void huffman(Character *hash,CodeTableItem *codeTable)               //构建哈夫曼树{node *root,fir,sec;for(int i=0;i<cnumber;i++){root=(node *)malloc(sizeof(node));         //开辟节点root->self=root;                           //记录自己的地址,方便父节点连接自己root->left=root->right=NULL;               //该节点是叶子节点,左右孩子地址均为NULLroot->ch=hash[i].data;                            //记录该节点表示的字符root->val=hash[i].number;                         //记录该字符的权值p.push(*root);                             //将该节点压入优先队列}//下面循环模拟建树过程,每次取出两个最小的节点合并后重新压入队列//当队列中剩余节点数量为1时,哈夫曼树构建完成while(p.size()>1){fir=p.top();p.pop();     //取出最小的节点sec=p.top();p.pop();     //取出次小的节点root=(node *)malloc(sizeof(node));         //构建新节点,将其作为fir,sec的父节点root->self=root;                           //记录自己的地址,方便该节点的父节点连接root->left=fir.self;     //记录左孩子节点地址root->right=sec.self;    //记录右孩子节点地址root->val=fir.val+sec.val;//该节点权值为两孩子权值之和p.push(*root);           //将新节点压入队列}fir=p.top();p.pop();         //弹出哈夫曼树的根节点myroot = fir.self;dfs(fir.self,0,codeTable,CList);             //输出叶子节点记录的字符和对应的哈夫曼编码}

1.     接下来是压缩的关键部分,首先读入一行的字符,通过编码表将该行加密成01串。根据01串构造一个个字符,一个char型字符是1个字节,也就是8个01就可以通过移位和加一来构建一个char类型,然后将该字符写入文件。这里要注意的是一行最后要加上换行符号的编码,直接读取一行是不会读取到换行符的。其次是一行最后的一个字符一般是没有构建完的,这样就会在最后一个字符的ASCII码的二进制表示前面引入几个0,应该将该字符记录下来,在新的一行构建字符的时候将该字符补全。

/*加密文件*/string tempstr="";ofstream out;//记录上一行尾部没有填充完的字符leftChar和还剩多少位填完leftBit;char leftChar=0;int leftBit=0;void encodeLine(string line,string path,CodeTableItem *cTable){if (line=="" ) {return;}if (out.is_open()){//将一行文本加密成二进制字符串string str= "";for(int i=0;i<line.size();i++){for(int j=0;j<cnumber;j++){if(line[i]==cTable[j].data){str+=cTable[j].code;break;}}}//加上换行符str+=enterCode;int count = 0;unsigned char c=leftChar;//先把上一行字符填充完for(int i=0;i<leftBit;i++){c<<=1;if(str[i]=='1'){c = c + 1;}}//cout<<str<<"-";//tempstr+=c;char ch1 = c;out<<ch1;//加密这一行剩下的for(int i=leftBit;i<str.size();i++){c<<=1;count++;if(str[i]=='1'){c = c + 1;}//            //一个字节存满,将改字节保存if(count==8){ch1 = c;out<<ch1;//tempstr+=c;c = 0;count=0;}}//记录没有保存的字符leftChar = c;leftBit = 8-count;out<<tempstr;//cout<<"1";}}void encodeHuffman(string file,string path,CodeTableItem *cTable){//读取文件,一个个字符加密ifstream infile;infile.open(file.data());  //将文件流对象与文件连接起来assert(infile.is_open());  //若失败,则输出错误消息,并终止程序运行infile >> noskipws;string line;while (std::getline(infile, line)) {encodeLine(line,path,cTable);}infile.close();}

解密过程也很简单,就是加密的逆过程读取一个字符将其解密成01串,然后顺着01串遍历huffman树,如果是叶子节点就输出字符,并且回到根节点准备下一个字符的解密。

//解密

void decodeHuffman(string file){node *p = myroot;ifstream infile;infile.open(file.data());  //将文件流对象与文件连接起来assert(infile.is_open());  //若失败,则输出错误消息,并终止程序运行unsigned char c;infile >> noskipws;int wordcount=0;infile>>c;while(!infile.eof()){infile>>c;string str = "";int beichushu = 128;for(int i=0;i<8;i++){int bit = (int)c/beichushu;if(bit==1){p = p->right;str+= "1";}else{str+= "0";p = p->left;}if(p->left==NULL){//cout<<p->ch;out<<p->ch;//当执行到最后一个字符就结束,不要继续输出后面多余字符wordcount++;if(wordcount==totalNum)return;p = myroot;}c <<= 1;}//cout<<str;//cout<<wordcount;}infile.close();            //关闭文件输入流}

mac上只有zip,所以直接用命令行查看压缩时间。压缩cacm.all压缩时间

我的算法与zip的对比

规模

我的压缩时间

zip时间

我的压缩率

zip压缩率

2.2M

0.9s

0.11s

63.6%

31.8%

22M

14.3s

1.7s

65.3%

35%

220M

148.6s

10.8s

65.5%

32%

2200M

1522.1s

63.2s

65.7

30%

将他们时间取对数后发现,我的代码几乎是线性递增的,我这个文件是有原来的cacm文件多次复制张贴而来,所以构成的哈夫曼树是一样的,压缩率也就差不多一样。而zip就比较神奇,也是预料之中,可能是它出现次数越多的字母的权重越大,是时间在逐渐减少,而且压缩率也降低。

完整代码

由于忙着赶作业写得有点乱,代码也没有美化和优化,现在闲下来反而不想弄了

#include <iostream>#include <iostream>#include <fstream>#include <cassert>#include <string>#include <time.h>#include<queue>#include<stdlib.h>#include<bitset>using namespace std;class Character{public:char data;int number;Character(){data = ' ';number = 0;}};class CodeTableItem{public:char data;string code;};//字符统计表Character CList[200];int cnumber=0,totalNum =0;void addList(char c,Character *cList){//为空就退出if(!c) return;totalNum++;int i=0;for(;i<cnumber;i++){if(c==cList[i].data){break;}}cList[i].number++;if(i==cnumber){cList[cnumber].data = c;cnumber++;}if(i>0 && cList[i].number>cList[i-1].number){int num = cList[i].number;char ch = cList[i].data;cList[i].data = cList[i-1].data;cList[i].number = cList[i-1].number;cList[i-1].data = ch;cList[i-1].number = num;}}void readTxt(string file,Character *cList){ifstream infile;infile.open(file.data());  //将文件流对象与文件连接起来assert(infile.is_open());  //若失败,则输出错误消息,并终止程序运行char c;infile >> noskipws;while (!infile.eof()){addList(c,cList);infile>>c;}infile.close();            //关闭文件输入流}//换行符string enterCode;typedef struct node{char ch;                         //存储该节点表示的字符,只有叶子节点用的到int val;                         //记录该节点的权值structnode *self,*left,*right;   //三个指针,分别用于记录自己的地址,左孩子的地址和右孩子的地址friend booloperator <(const node &a,constnode &b) //运算符重载,定义优先队列的比较结构{return a.val>b.val;          //这里是权值小的优先出队列}}node;int codeTablePlace = 0;node *myroot;priority_queue<node> p;               //定义优先队列char res[10000];                         //用于记录哈夫曼编码void dfs(node *root,int level,CodeTableItem *codetable,Character *cList)        //哈夫曼编码表{if(root->left==root->right)       //叶子节点的左孩子地址一定等于右孩子地址,且一定都为NULL;叶子节点记录有字符{if(level==0)                  //“AAAAA”这种只有一字符的情况{res[0]='0';level++;}res[level]='\0';              //字符数组以'\0'结束//printf("%c=>%s\n",root->ch,res);//搜索在统计表中的位置for(int j=0;j<cnumber;j++){if(root->ch==cList[j].data){codetable[j].data = root->ch;codetable[j].code =res;}}//        codetable[codeTablePlace].data = root->ch;//        codetable[codeTablePlace].code = res;if(int(root->ch)==10)enterCode =res;// codeTablePlace++;}else{res[level]='0';               //左分支为0dfs(root->left,level+1,codetable,cList);res[level]='1';               //右分支为1dfs(root->right,level+1,codetable,cList);}}void huffman(Character *hash,CodeTableItem *codeTable)               //构建哈夫曼树{node *root,fir,sec;for(int i=0;i<cnumber;i++){root=(node *)malloc(sizeof(node));         //开辟节点root->self=root;                           //记录自己的地址,方便父节点连接自己root->left=root->right=NULL;               //该节点是叶子节点,左右孩子地址均为NULLroot->ch=hash[i].data;                            //记录该节点表示的字符root->val=hash[i].number;                         //记录该字符的权值p.push(*root);                             //将该节点压入优先队列}//下面循环模拟建树过程,每次取出两个最小的节点合并后重新压入队列//当队列中剩余节点数量为1时,哈夫曼树构建完成while(p.size()>1){fir=p.top();p.pop();     //取出最小的节点sec=p.top();p.pop();     //取出次小的节点root=(node *)malloc(sizeof(node));         //构建新节点,将其作为fir,sec的父节点root->self=root;                           //记录自己的地址,方便该节点的父节点连接root->left=fir.self;     //记录左孩子节点地址root->right=sec.self;    //记录右孩子节点地址root->val=fir.val+sec.val;//该节点权值为两孩子权值之和p.push(*root);           //将新节点压入队列}fir=p.top();p.pop();         //弹出哈夫曼树的根节点myroot = fir.self;dfs(fir.self,0,codeTable,CList);             //输出叶子节点记录的字符和对应的哈夫曼编码}/*加密文件*/string tempstr="";ofstream out;//记录上一行尾部没有填充完的字符leftChar和还剩多少位填完leftBit;char leftChar=0;int leftBit=0;void encodeLine(string line,string path,CodeTableItem *cTable){if (line=="" ) {return;}if (out.is_open()){//将一行文本加密成二进制字符串string str= "";for(int i=0;i<line.size();i++){for(int j=0;j<cnumber;j++){if(line[i]==cTable[j].data){str+=cTable[j].code;break;}}}//加上换行符str+=enterCode;int count = 0;unsigned char c=leftChar;//先把上一行字符填充完for(int i=0;i<leftBit;i++){c<<=1;if(str[i]=='1'){c = c + 1;}}//cout<<str<<"-";//tempstr+=c;char ch1 = c;out<<ch1;//加密这一行剩下的for(int i=leftBit;i<str.size();i++){c<<=1;count++;if(str[i]=='1'){c = c + 1;}//            //一个字节存满,将改字节保存if(count==8){ch1 = c;out<<ch1;//tempstr+=c;c = 0;count=0;}}//记录没有保存的字符leftChar = c;leftBit = 8-count;out<<tempstr;//cout<<"1";}}void encodeHuffman(string file,string path,CodeTableItem *cTable){//读取文件,一个个字符加密ifstream infile;infile.open(file.data());  //将文件流对象与文件连接起来assert(infile.is_open());  //若失败,则输出错误消息,并终止程序运行infile >> noskipws;string line;while (std::getline(infile, line)) {encodeLine(line,path,cTable);}infile.close();}//解密void decodeHuffman(string file){node *p = myroot;ifstream infile;infile.open(file.data());  //将文件流对象与文件连接起来assert(infile.is_open());  //若失败,则输出错误消息,并终止程序运行unsigned char c;infile >> noskipws;int wordcount=0;infile>>c;while(!infile.eof()){infile>>c;string str = "";int beichushu = 128;for(int i=0;i<8;i++){int bit = (int)c/beichushu;if(bit==1){p = p->right;str+= "1";}else{str+= "0";p = p->left;}if(p->left==NULL){//cout<<p->ch;out<<p->ch;//当执行到最后一个字符就结束,不要继续输出后面多余字符wordcount++;if(wordcount==totalNum)return;p = myroot;}c <<= 1;}//cout<<str;//cout<<wordcount;}infile.close();            //关闭文件输入流}void testWrite(string savePath){string str = "";for(int i=0;i<1000;i++){str+='a';}ofstream ofile;              //定义输出文件ofile.open(savePath);     //作为输出文件打开for(int i=0;i<10;i++){ofile<<str;}ofile.close();}int main(int argc, const char * argv[]) {srand((unsigned)time(NULL));//用时间做种,每次产生随机数不一样clock_t start,finish;double time;start = clock();string file ="/Users/ish/Documents/code/c++/Huffman/cacm.all";string encodeFile="/Users/ish/Documents/code/c++/Huffman/encode.all";string decodefile="/Users/ish/Documents/code/c++/Huffman/decode.all";//构建统计表readTxt(file,CList);//动态初始化编码表CodeTableItem *codeTable =new CodeTableItem[cnumber];//构建树并且建立编码表huffman(CList,codeTable);//输出编码表//    for(int i=0;i<cnumber;i++){//        cout<<codeTable[i].data<<"->"<<codeTable[i].code<<endl;//    }//加密out.open(encodeFile);encodeHuffman(file, encodeFile,codeTable);out.close();//解密out.open(decodefile);decodeHuffman(encodeFile);out.close();finish=clock();time = static_cast<double>(finish - start)/CLOCKS_PER_SEC*1000; //mscout<<"运行时间为:"<<time<<"ms"<<endl;return 0;}

c++实现哈夫曼huffman压缩文本相关推荐

  1. 利用哈夫曼编码压缩文本

    文章目录 使用哈夫曼编码进行压缩文本 文本内容 读取文件内容至内存中 遍历文本内容,获取每个字符对应出现概率值 建立哈夫曼树 获取哈夫曼编码 将转换后的编码写入新文件 检测压缩率 利用编码文件进行还原 ...

  2. 【数据结构和算法笔记】哈夫曼树的概念,构造和应用(利用哈夫曼编码压缩文本)

    目录 哈夫曼树定义: 构造哈夫曼树: 哈夫曼编码 前缀编码: 应用(压缩文本) 哈夫曼树定义: 构造哈夫曼树: 哈夫曼编码 前缀编码:  哈夫曼编码是前缀编码 哈夫曼树的性质 哈夫曼树的任意非叶结点的 ...

  3. 对文件进行哈夫曼编码压缩与译码的C++实现 以及压缩率计算 ——HIT杨朔

    哈夫曼编码压缩原理:由于每个字符在内存中都是以ASCII码进行存储,所以每个字符都占用了八个01位,利用哈夫曼树对每个字符进行01编码,根据字符在文章中出现的频率调整01串长度,出现频率高的字符在哈夫 ...

  4. 武汉理工大学数据结构综合实验——二叉树与赫夫曼图片压缩

    文章目录 实验目的 主要仪器设备及耗材 一.实验要求 二.分析与设计 1.数据结构的设计 2.核心算法设计 生成Huffman树的算法 生成Huffman编码的算法 压缩编码的算法 3.测试用例设计 ...

  5. 基于霍夫曼(Huffman)图像编码的图像压缩和重建-含Matlab代码

    目录 一.引言 二.霍夫曼Huffman编码 2.1 霍夫曼编码流程 2.2 输入数据的编码 三.霍夫曼解码 四.实验结果 五.参考文献 六.Matlab代码(GUI界面)获取 一.引言 随着通信与信 ...

  6. C语言哈夫曼树压缩/解压器

    C语言哈夫曼树压缩/解压器 小编是大一的菜鸡,这个题目是数据结构的一个实验题,为了完成这个作业,查找了各种资料,借鉴了很多人的代码,前后折腾了三天左右.代码可能跟网上的不一样,大佬路过请不要踩我. 温 ...

  7. 哈夫曼图片压缩及解压

    哈夫曼图片压缩及解压 文件 功能 Huffman 哈夫曼编码 compress 解压 //Compress.h #ifndef COMPRESS_H #define COMPRESS_H typede ...

  8. C语言霍夫曼编码压缩,数据结构大作业——哈夫曼编码压缩BMP格式文件

    数据结构大作业--哈夫曼编码压缩BMP格式文件 首先需要了解BMP图像格式 BMP图像格式详解 其次需要了解哈夫曼编码如何对BMP文件进行压缩 哈夫曼压缩与解压缩 编程部分 使用的头文件 虽然这里用了 ...

  9. C语言哈夫曼编码压缩解压

    C语言哈夫曼编码压缩解压 一.实验目的 掌握哈夫曼编码基本运算以及存储结构表示. 二.实验内容: 1.系统要求包含以下功能 1)初始化:从终端读入字符集大小n,以及n个字符和n个权值(或者读入字符集和 ...

最新文章

  1. 3D视觉创新应用(三维重建)竞赛作品系列——多楼层室内环境下的三维几何重建
  2. 字段对应数组_字段不同的多个工作薄汇总? 还要固定字段位置 !难不倒我!...
  3. 天猫上线“商家售后服务评价”功能,消费者体验将纳入商家考核指标
  4. java之DocumentBuilderFactory解析xml
  5. 终端terminal的颜色配置
  6. Ajax技术的一些总结
  7. 【GPU编程】体绘制传输函数-分类(Volume Rendering Transfer function:Pre- VS Post-Classification)
  8. ipv4转换ipv6工具_IPv4与IPv6的区别,不仅仅是地址变长而已
  9. java怎么中断流_JAVA代码运行中断
  10. php模板引擎如何实现,PHP模板引擎如何实现
  11. linux双括号文本比较,Linux Shell 双括号运算符使用
  12. Selenium---环境配置
  13. 工程介绍好处费性质_中间人拿工程好处费是否违法
  14. 【Python】Python简介
  15. 美男子第一次的JAVA博客
  16. pandas库与numpy库
  17. 韩顺平的php东方航空_韩顺平PHP从入门到精通视频教程
  18. FFmpeg 软编码H.264与H.265
  19. 维基百科数据导入mysql数据库并进行检索操作
  20. 蓝桥杯 历届试题 史丰收速算

热门文章

  1. 实在智能“信创RPA”完成国产化流程全适配,加入ISV华为鲲鹏展翅伙伴
  2. 嵌入式linux网络编程之——5年程序员给你深度讲解socket套接字
  3. 如何隐藏系统保留分区
  4. D. Complete Tripartite(三分图+枚举)
  5. Android菜鸟修行
  6. 【过关斩将】2020年互联网公司运维岗位面试题 -技能篇03
  7. 计算4的1万次方的结果
  8. Java IO(二)
  9. 在运行框中用命令快速打开应用程序
  10. 简书Android APP上线了