哈夫曼树和哈夫曼编码应用之图片压缩编码c++实现
本人正在学习数据结构,在前几天做了压缩图片的项目,感觉到有必要分享给大家。因此今天我就分享给大家c语言数据结构有关哈夫曼树压缩图片的项目实现。
一:下面先介绍有关的知识:
1.背景
压缩软件是用特定算法压缩数据的工具,压缩后的文件称为压缩包,可以对其进行解压。那么为什么要用到压缩软件呢?我们都知道,文件是用编码进行存储的,编码要用到字节,而不可避免的一个文件中会出现很多重复的字节,用压缩软件可以减少重复的字节,方便在互联网上实现更快的传输,也可以减少文件在磁盘上的占用空间,常见的压缩软件有rar,zip等。
压缩可以分为无损压缩和有损压缩两种,无损压缩后的文件,经过解压后可以完全恢复到之前的文件,rar,zip等格式都是无损压缩格式,而图片文件jpg,音乐文件mp3都是有损压缩格式。
2.编码介绍
计算机文件是由一个个字节组成的,一个字节有8位二进制编码构成,共有0-255种可能的情况。由于文件中的字节可能会重复出现,可以对不同的字节设计长度不等的编码,让出现次数较多的字节,采用较短的编码,这样可以使文件编码的总长度减少。
3.哈夫曼树和哈夫曼编码
(1)哈夫曼树
有关二叉树的知识这里就不讲解了,大家可以自行学习。这里我们统计文件中256种字节重复的次数作为权值,构造一棵有256个叶节点的二叉树,如果带权路径长度最小,我们称为这样的二叉树为最有二叉树,也叫哈夫曼(Huffman)树。
(2)哈夫曼编码
哈夫曼树从根结点到每个叶子结点都有一条路径,对路径上的分支,约定指向左子树的为0,指向右子树的为1,从根到每个叶子结点路径上的0和1构成的序列就是这个叶节点的哈夫曼编码。
如图所示:
这时编码就是:
A:000 B:001 C:010 D:011 E:11
使用哈夫曼编码给每个字节重新编码,重复次数较多的字节,哈夫曼编码较短,这样就比以前的二进制编码短了许多,因此可以实现压缩。
这里我们实现把一个bmp格式的图片进行压缩编码。
二:过程和代码实现与分析
1.流程简述:
(1)读取文件
先读取文件,生成一棵带权二叉树。树在程序中可以使用顺序结构,链式结构两种方式实现,由于这棵带权二叉树的叶子节点有256个,存储空间是固定的,则这里可以使用顺序结构来表示二叉树。
(2)定义二叉树结构
定义一个结构体HaffNode来表示二叉树的叶子节点,记录每个结点的权值,标记,父结点,左孩子和右孩子。创建一个结构体数组来存储这棵带权二叉树的每个叶子结点的信息。
(3)生成哈夫曼编码
其次,生成Huffman树后,要生成哈夫曼编码,就要先遍历这棵二叉树,这里我用的是先序遍历方法,定义一个字符串数组Code来存储每个叶子结点的哈夫曼编码。
(4)字符串转字节实现压缩
)由于哈夫曼编码是以字符串数组的形式保存的,重新编码后的数据将是一个很长的字符串。定义Str2byte函数,将字符串转化为字节,才能转化为最终的编码,将其保存到*.huf中,则实现了文件压缩。
(5)解压缩
最后,为了保证压缩后的数据能够被正常解压,除了保存压缩的数据,还应保存原文件的长度和256种字节重复出现的次数。这时就需要定义一个文件头,用于保存这些信息,再保存压缩文件时,同时向文件中写入文件头和压缩数据,保证文件能够被还原。
2.代码实现与分析
(1)打开Microsoft Visual Studio 2010,创建一个解决方案,名字为"HuffmanSLN",在HuffmanSLN解决方案下面新建一个空的win32控制台工程,名字为"HfmCompressCPro"。
(2)打开文件
在源文件(Source Files)中新建"main.cpp"文件,作为程序运行的入口函数。
导入<iostream>头文件,声明using bamespace std,使用cout输出信息,用cin接受键盘输入文件信息。
代码如下:
#include <iostream>
#include <stdlib.h>
using namespace std;int main()
{cout<<"--------------Huffman文件压缩编码---------------"<<endl;cout<<"请输入文件名:";char filename[256];//文件名cin>>filename;return 0;
}
(3)读取原文件
以二进制流的方式,只读打开文件,一个个地逐个读取字节数据,统计文件中256种字节重复出现的次数,保存到一个数组中
int weight[256]中,然后将扫描的结果在控制台打印下来。
代码如下:
#include <iostream>
#include <stdlib.h>
using namespace std;int main()
{cout<<"--------------Huffman文件压缩编码---------------"<<endl;cout<<"请输入文件名:";char filename[256];//文件名cin>>filename;char ch;int weight[256]={0};//以二进制流的方式打开文件FILE* in = fopen(filename,"rb");if(in == NULL){printf("打开文件失败");return 0;}//扫描文件,获得权重while(ch = getc(in) != EOF){weight[ch]++;}//关闭文件fclose(in);//显示256个字节出现的次数cout<<"Byte "<<"Weight"<<endl;for(int i=0;i<256;i++){printf("0x%02X %d\n",i,weight[i]);}system("pause");return 0;
}
这里我们的图片在E盘的根目录下:
即下面的一张图:
运行结果如下:
(4)生成哈夫曼树
在Haffman.h文件中,定义一个存储哈夫曼树结点信息的结构体,有权值,标记(若当前结点未加入结构体,flag=0;若当前结点加入结构体,flag=1),双亲结点下标,左孩子结点下标,右孩子结节下标。
在Haffman.cpp文件中创建构造哈夫曼树的函数,在Haffman.h文件中声明。
Haffman.h文件
typedef struct
{int weight; //权值int flag; //标记int parent; //双亲结点下标int leftChild; //左孩子下标int rightChild; //右孩子下标
}HaffNode;//创建叶结点个数为n,权值数组为weight的哈夫曼树haffTree
void Haffman(int weight[],int n,HaffNode haffTree[]);
Haffman.cpp文件
#include "Huffman.h"
#define MaxValue 10000//创建叶结点个数为n,权值数组为weight的哈夫曼树haffTree
void Haffman(int weight[],int n,HaffNode haffTree[])
{int i,j,m1,m2,x1,x2;//哈夫曼树haffTree初始化,n个叶结点的二叉树共有2n-1结点for(i = 0;i < 2 * n - 1;i++){if(i < n) //如果是小于n的结点(叶子结点),则把每个字节的重复次数作为权值赋给这些该结点haffTree[i].weight = weight[i];elsehaffTree[i].weight = 0; //其他非叶子结点权值设为0haffTree[i].parent = -1;haffTree[i].flag = 0;haffTree[i].leftChild = -1;haffTree[i].rightChild = -1;}//构造哈夫曼树haffTree的n-1个非叶结点for(i = 0;i < n - 1;i++){//这里假设先进行一次循环,i=0,后面的代码注释方便大家理解m1=m2=MaxValue;x1=x2=0;//找到权值最小和次小的子树,就是找到权值最小的两个结点for(j = 0;j < n + i;j++) //这里先让i=0;则对于n个叶子结点,按权值最小和次小的顺序连接结点生成哈夫曼树{//这里比较每个结点的权值,如果小于上一个结点(已查找的最小权值结点)权值且该结点没有被访问if(haffTree[j].weight < m1 && haffTree[j].flag == 0) {m2 = m1; //令m2为上一个结点(非最小结点的前面的权值)的下标x2 = x1; //x2为上一个结点(非最小结点的前面的权值)下标m1 = haffTree[j].weight; //m1记录该结点的权值x1 = j; //x1为该结点的下标}//这里比较每个结点的权值,如果小于非最小结点的前面的结点权值且该结点没有被访问else if(haffTree[j].weight < m2 && haffTree[j].flag == 0) {m2 = haffTree[j].weight; //m2记录该结点的权值x2 = j; //x2记录该结点的下标}//比较完所有的结点后,x1就为最小权值结点的下标,x2就为次小权值结点的下标}//将找出的两棵权值最小和次小的子树合并为一棵子树haffTree[x1].parent = n + i; //x1就为最小权值结点的下标haffTree[x2].parent = n + i; //x2就为次小权值结点的下标haffTree[x1].flag = 1; //x1被访问haffTree[x2].flag = 1; //x2被访问haffTree[n+i].weight = haffTree[x1].weight + haffTree[x2].weight;haffTree[n+i].leftChild = x1; //左孩子存储结点x1haffTree[n+i].rightChild = x2; //右孩子存储结点x2}
}
在main.cpp中编测试一下,改为如下形式:
#include <iostream>
#include <stdlib.h>
#include "Haffman.h"
#define MaxValue 10000
using namespace std;int main(){cout<<"--------------Huffman文件压缩编码---------------"<<endl;cout<<"请输入文件名:";char filename[256];//文件名cin>>filename;char ch;int i;int n = 256;int weight[256]={0};//以二进制流的方式打开文件FILE* in = fopen(filename,"rb");if(in == NULL){printf("打开文件失败");return 0;}//扫描文件,获得权重while(ch = getc(in) != EOF){weight[ch]++;}//关闭文件fclose(in);//测试哈夫曼树构造函数HaffNode *myHaffNode = (HaffNode *)malloc(sizeof(HaffNode)*(2*n-1));if(n >MaxValue){printf("给出的n越界,修改MaxValue");exit(1);}Haffman(weight,n,myHaffNode);printf("字节种类 权值 标记 双亲结点下标 左孩子结点下标 右孩子结点下标\n");for(i = 0;i < n;i++){printf("pHT[%d]\t%d\t%d\t%d\t%d\t%d\n",i,myHaffNode[i].weight,myHaffNode[i].flag,myHaffNode[i].parent,myHaffNode[i].leftChild,myHaffNode[i].rightChild);}system("pause");return 0;
}
运行结果如下:
(5)生成哈夫曼编码
按照先序遍历的算法对上面生成的哈夫曼树进行遍历,生成哈夫曼编码。
在Haffman.h文件中创建结构体哈夫曼数组,用于存储编码的起始下表和权值。
在Haffman.cpp文件中创建构造哈夫曼树编码的函数,在Haffman.h文件中声明。
Haffman.h文件
typedef struct
{int weight; //权值int flag; //标记int parent; //双亲结点下标int leftChild; //左孩子下标int rightChild; //右孩子下标
}HaffNode;typedef struct
{int bit[10000]; //数组int start; //编码的起始下标int weight; //字符的权值
}Code;//创建叶结点个数为n,权值数组为weight的哈夫曼树haffTree
void Haffman(int weight[],int n,HaffNode haffTree[]);//有n个结点的哈夫曼树haffTree构造哈夫曼编码haffCode
void HaffmanCode(HaffNode haffTree[],int n,Code haffNode[]);
Haffman.cpp文件
//有n个结点的哈夫曼树haffTree构造哈夫曼编码haffCode
void HaffmanCode(HaffNode haffTree[],int n,Code haffCode[])
{Code *cd = (Code *)malloc(sizeof(Code));int i,j,child,parent;//求n个叶结点的哈夫曼编码for(int i = 0;i < n;i++){cd->start = n-1; //不等长编码的最后一位为n-1cd->weight = haffTree[i].weight; //取得编码对应的权值child = i;parent = haffTree[child].weight;//由叶结点向上直到根节点while(parent != -1){if(haffTree[parent].leftChild == child) //判断左孩子是否存在cd->bit[cd->start] = 0;else //判断右孩子是否存在cd->bit[cd->start] = 1;cd->start--;child = parent;parent = haffTree[child].parent;}for(j = cd->start+1;j < n;j++)haffCode[j].bit[j] = cd->bit[j];haffCode[i].start = cd->start + 1;haffCode[i].weight = cd->weight;}
}
现在在主函数中测试一下:
#include <iostream>
#include <stdlib.h>
#include "Haffman.h"
using namespace std;
#define MaxValue 10000int main(){cout<<"--------------Huffman文件压缩编码---------------"<<endl;cout<<"请输入文件名:";char filename[256];//文件名cin>>filename;char ch;int i,j;int n = 256;int weight[256]={0};//以二进制流的方式打开文件FILE* in = fopen(filename,"rb");if(in == NULL){printf("打开文件失败");fclose(in);return 0;}//扫描文件,获得权重while(ch = fgetc(in) != EOF){weight[ch]++;}//关闭文件fclose(in);//显示256个字节出现的次数cout<<"Byte "<<"Weight"<<endl;for(i=0;i<256;i++){printf("0x%02X %d\n",i,weight[i]);}HaffNode *myHaffNode = (HaffNode *)malloc(sizeof(HaffNode)*(2*n-1));Code *myHaffCode = (Code *)malloc(sizeof(Code)*n);if(n >MaxValue){printf("给出的n越界,修改MaxValue");exit(1);}//测试哈夫曼树构造函数Haffman(weight,n,myHaffNode);//测试哈夫曼编码函数HaffmanCode(myHaffNode,n,myHaffCode);//输出每个字节叶结点的哈夫曼编码printf("编码信息为:");for(i = 0;i < n;i++){//printf("0x%02X ",i);printf("Weight = %d,Code = ",myHaffCode[i].weight);for(j = myHaffCode[i].start;j < n;j++)printf("%d",myHaffCode[i].bit[j]);printf("\n");} system("pause");return 0;
}
运行结果如下:
这里我们发现权值为141291的字节没有显示编码,原因是这个编码个数太长了,在这里显示不出来。
(6)压缩源文件
创建Compress.h和Compress,cpp文件,定义Compress函数用于压缩原文件。
由于编码是以字符数组的形式保存的,重新编码后的数据将是一个很长的字符串,先计算需要的空间,然后把编码按位进行存放到字符数组中,或者直接存放,这里采用直接存放的方式。
int k,ji=0;int jiShu[1000];//输出每个字节叶结点的哈夫曼编码printf("编码信息为:");for(i = 0;i < n;i++){for(j = myHaffCode[i].start;j < n;j++)for(k = 0;k < myHaffCode[k].weight+1;k++){printf("%d",myHaffCode[i].bit[j]);jiShu[ji] = myHaffCode[i].bit[j];ji++;}}
结果如下:
(6)写入文件
建立一个新文件,文件名为"原文件名字+.huf",将压缩后的数据写入文件。
为了保证压缩后的数据能够被正确解压,必须把相关的解压缩规则写进去,就是把权值信息写入进去,还有文件类型,长度,权值。
在Huffman.h中定义一个文件头结构和InitHead函数声明,在Huffman.cpp中写入函数。
代码如下:
Huffman.h文件
struct HEAD
{char type[4]; //文件类型int length; //原文件长度int weight[256]; //权值数组
}
Huffman.cpp文件
//记录文件信息
int initHead(char pFileName[256],HEAD &sHead)
{int i;//初始化文件头strcpy(sHead.type,"bmp");sHead.length = 0; //原文件长度for(i = 0;i<256;i++){sHead.weight[i] = 0;}//以二进制流的方式打开文件FILE* in = fopen(pFileName,"rb");if(in == NULL){printf("打开文件失败");fclose(in);return 0;}char ch;//扫描文件,获得权重while(ch = fgetc(in) != EOF){sHead.weight[ch]++;sHead.length++;}//关闭文件fclose(in);in = NULL;return 0;
}
现在我们得到文件的信息和编码,就可以得到压缩后的文件,直接在主函数中写代码:
#include <iostream>
#include <stdlib.h>
#include <io.h>
#include "Haffman.h"
//#include "Compress.h"
using namespace std;
#define MaxValue 10000int main(){cout<<"--------------Huffman文件压缩编码---------------"<<endl;cout<<"请输入文件名:";char filename[256];//文件名cin>>filename;char ch;int i,j;int n = 256;int weight[256]={0};//以二进制流的方式打开文件FILE* in = fopen(filename,"rb");int fn = _fileno(in); /*取得文件指针的底层流式文件号*/int sz = _filelength(fn);/*根据文件号取得文件大小*/printf("%d字节\n",sz);if(in == NULL){printf("打开文件失败");fclose(in);return 0;}//扫描文件,获得权重while(ch = fgetc(in) != EOF){weight[ch]++;}//关闭文件fclose(in);/*//显示256个字节出现的次数cout<<"Byte "<<"Weight"<<endl;for(i=0;i<256;i++){printf("0x%02X %d\n",i,weight[i]);}*/HaffNode *myHaffNode = (HaffNode *)malloc(sizeof(HaffNode)*(2*n-1));Code *myHaffCode = (Code *)malloc(sizeof(Code)*n);if(n >MaxValue){printf("给出的n越界,修改MaxValue");exit(1);}//测试哈夫曼树构造函数Haffman(weight,n,myHaffNode);//测试哈夫曼编码函数HaffmanCode(myHaffNode,n,myHaffCode);/*printf("字节种类 权值 标记 双亲结点下标 左孩子结点下标 右孩子结点下标\n");for(i = 0;i < n;i++){printf("pHT[%d]\t%d\t%d\t%d\t%d\t%d\n",i,myHaffNode[i].weight,myHaffNode[i].flag,myHaffNode[i].parent,myHaffNode[i].leftChild,myHaffNode[i].rightChild);}*/HEAD sHead;initHead(filename,sHead);int cc=0;//生成文件名char newFileName[256] = {0};strcpy(newFileName,filename);strcat(newFileName,".huf");//以二进制流的方式打开文件FILE* out = fopen(newFileName,"wb");//写文件头//fwrite(&sHead,sizeof(HEAD),1,out);//int k,ji=0;//int jiShu[1000];//输出每个字节叶结点的哈夫曼编码//printf("编码信息为:");for(i = 0;i < n;i++){for(j = myHaffCode[i].start;j < n;j++){//printf("%d",myHaffCode[i].bit[j]);//写压缩后的编码cc+=sizeof(myHaffCode[i].bit[j]);}} fclose(out);out = NULL;cout << "生成压缩文件:" << newFileName <<endl;printf("\n");printf("%d字节",sizeof(newFileName)+cc);double bi=(sizeof(newFileName)+cc)/(double)sz;printf("压缩比例为:%.2f",bi);system("pause");return 0;
}
结果如下:
而我们也生成了该buf的压缩文件:
需要详细代码请私信我:
qq:1657264184
微信:liang71471494741
哈夫曼树和哈夫曼编码应用之图片压缩编码c++实现相关推荐
- 蓝桥哈夫曼树C语言,实验四 哈夫曼树及哈夫曼编码
实验目的## 掌握哈夫曼树的概念.哈夫曼编码及其应用. 掌握生成哈夫曼树的算法. 会用哈夫曼树对传输报文进行编码. 掌握二叉树的二叉链表存储方式及相应操作的实现. ##实验内容## 用哈夫曼编码进行通 ...
- python哈夫曼树_python霍夫曼树
class Node(): data=0 left=None right=None father=None def __init__(self,data,left,right): self.data= ...
- 一文看懂哈夫曼树与哈夫曼编码
转自:http://www.cnblogs.com/Jezze/archive/2011/12/23/2299884.html 在一般的数据结构的书中,树的那章后面,著者一般都会介绍一下哈夫曼(HUF ...
- 树:哈夫曼树和哈夫曼编码的详细介绍以及代码实现
闲扯前言 哈夫曼编码的代码实现对于初学数据结构的同学可能会有些困难,没有必要灰心,其实没啥,学习就犹如攀登一座又一座的山峰,每当我们攻克一个难点后,回首来看,也不过如此嘛.我们要做的就是不断的去攀越学 ...
- 听说你还不懂哈夫曼树和哈夫曼编码
基本概念 哈夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,在实际中有广泛的用途. 基本概念 路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径. 路径长度:路径上的分 ...
- 数据结构图文解析之:哈夫曼树与哈夫曼编码详解及C++模板实现
0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...
- 【Java数据结构与算法】第十二章 哈夫曼树和哈夫曼编码
第十二章 哈夫曼树和哈夫曼编码 文章目录 第十二章 哈夫曼树和哈夫曼编码 一.哈夫曼树 1.基本术语 2.构建思路 3.代码实现 三.哈夫曼编码 1.引入 2.介绍 3.代码实现哈夫曼编码综合案例 一 ...
- 【数据结构】树与树的表示、二叉树存储结构及其遍历、二叉搜索树、平衡二叉树、堆、哈夫曼树与哈夫曼编码、集合及其运算
1.树与树的表示 什么是树? 客观世界中许多事物存在层次关系 人类社会家谱 社会组织结构 图书信息管理 分层次组织在管理上具有更高的效率! 数据管理的基本操作之一:查找(根据某个给定关键字K,从集合R ...
- 【数据结构】-哈夫曼树以及哈夫曼编码
哈夫曼树的几个定义 哈夫曼树又叫最优二叉树:特点是带权路径最短 带权路径长度:该结点到根结点的路径长度乘以该结点的权值. 树的带权路径长度(WPL):所有叶子结点到根结点的带全路径长度之和. 最优二叉 ...
- C++ 实现哈夫曼树和哈夫曼编码
C++ 实现哈夫曼树和哈夫曼编码 一.哈夫曼树的定义 二.哈夫曼树的构造算法 三.哈夫曼编码 四.哈夫曼算法实现 1.定义一个结点类 2.定义一个哈夫曼编码类 3.定义一个哈夫曼树类 4.设置初始值 ...
最新文章
- python为什么慢_python-为什么startswith比切片慢
- MATLAB编程练习题
- 树的先序遍历,中序遍历,后续遍历(递归和非递归实现)
- 页面缓存js问题解决
- java序列化_技术干货 | JAVA反序列化漏洞
- Doclava:来自Google的自定义Javadoc Doclet
- Restful API 设计
- adb概览及协议參考
- Ubuntu下安装php7.1的gd,mysql,pdo_mysql扩展库
- 公众号排版文章批量导出-免费公众号文章批量导出排版
- python怎么爬虎牙_Python_虎牙妹子爬虫实现
- 音视频技术开发周刊 | 241
- 校园火灾项目结合Focus
- 心情随笔(一):五月随笔满满的正能量
- 宝贝宝贝用计算机弹奏,原神宝贝宝贝琴谱 原神琴谱两只老虎爱跳舞怎么弹
- 2021年IT行业现状及就业前景怎样?
- Cesium 根据经纬度获取地形高程
- JNOJ 江南在线评测系统 搭建
- python数据分析实战之信用卡违约风险预测
- 海量存储检索原理系列文章
热门文章
- HTML页面从JS获取数据
- knife4j word导出模板改造经验
- 图形学书籍 Real-Time Rendering 3.4 可编程着色和 API 的演变(根据谷歌翻译修改)
- 地球最强安卓模拟器BlueStacks蓝叠全版本ROOT支持最新4.x
- 360 默认极速模式
- 融资金额暴涨近40%,IT运维迎来“至简”时代
- 右手坐标系和左手坐标系(转)很详细,有图示
- linux tc限制网卡速度,在Linux下用tc限制接口带宽
- 渐进式web应用程序_渐进式Web应用程序终极指南
- 点聚WebOffice-使用(更新中)