项目简介:
首先咱们先聊聊什么是数据压缩:信息时代,数值、文字、语言、音乐、图形、动画、静图像、电视视频图像等多种媒体信息的数量惊人,数据的表达、组织、存储和传输都有很大难度。大数据量的图像信息会给存储器的存储容量,通信干线信道的带宽,以及计算机的处理速度增加极大的压力。单纯靠增加存储器容量,提高信道带宽以及计算机的处理速度等方法来解决这个问题是不现实的,这时就要考虑压缩。压缩的关键在于编码,如果在对数据进行编码时,对于常见的数据,编码器输出较短的码字;而对于少见的数据则用较长的码字表示,就能够实现压缩。
然后咱再聊聊什么是哈弗曼编码:哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最优码,一般就叫做Huffman编码
项目思想:
1、在哈夫曼算法中,初始时有n棵二叉树,要经过n-1次合并最终形成哈夫曼树。经过 n-1 次合并产生 n-1 个新结点,且这n-1 个新结点都是具有两个孩子的分支结点。可见:哈夫曼树中共有 n+n-1=2n-1个结点,且其所有的分支结点的度均不为1。
2.如何编码:
输入一个字符串,不同字符出现的不同的次数称为这个字符的权值。
构造哈夫曼树——HT[i]
进行哈夫曼编码——HC[i]
查HC[i] ,得到各字符的哈夫曼编码
3.如何解码:
构造哈夫曼树
依次读入二进制码,与编码时构建的每一个二进制码进行匹配
4.如果小伙伴们还有不理解的地方可以去看我代码里的注释,十分详细!
主页面:
输入原文,给出编码:
输入编码,给出原文:
实现代码:
#include <bits/stdc++.h>
#include <windows.h>
using namespace std;
#define maxsize 100 /* 编码函数中,被编译的字符串的最大长度 */
/*----------------结构体-------------------*/
//哈夫曼结点
typedef struct {
int weight; //权值
int parent; //双亲结点的序号
int lch; //左孩子的序号
int rch; //右孩子的序号
char data; //字符
} HTNode, *HuffmanTree;
//哈夫曼编码表,是一个字符类型的二维数组。
typedef char **HuffmanCode;
//输入的字符及其权值
typedef struct {
int weight; //权值
char data; //字符
} iNode;
/*----------------全局变量-------------------*/
HuffmanCode HC;
HuffmanTree HT;
/*----------------查找两个权值最小的节点在HT中的序号的函数-------------------*/
void Select(HuffmanTree &HT,int k,int &s1,int &s2) {
int min1=1000,min2=1000;
for(int i=1; i<=k; i++) { //第一次筛选最小权值
if(HT[i].parent==0) {
if(HT[i].weight<=min1) {
min1=HT[i].weight;
s1=i;
}
}
}
HT[s1].parent=k+1; //把最小权值对应的那个节点的双亲节点设置为k+1,下一次再选最小值时,就搜不到它了
for(int i=1; i<=k; i++) { //第二次筛选最小权值
if(HT[i].parent==0) {
if(HT[i].weight<=min2) {
min2=HT[i].weight;
s2=i;
}
}
}
HT[s2].parent=k+1; //把最小权值对应的那个节点的双亲节点设置为k+1
}
/*----------------构建哈夫曼树函数-------------------*/
void CreateHuffmanTree(int n,iNode *a) { //这里的n指的是数组a中存的字符的个数
//HuffmanTree HT;
if(n<=1) return ; //如果节点数小于等于1,则直接返回主函数。
int m=2*n-1; //构造成的哈夫曼树的总节点树为2n-1
HT=new HTNode[m+1]; //HT[m]表示根节点,HT[0]这个位置空着不用
for(int i=1; i<=m; i++) { //将所有结点的左右孩子和双亲结点的都置为0
HT[i].lch=0;
HT[i].rch=0;
HT[i].parent=0;
}
for(int i=1; i<=n; i++) {
HT[i].weight=a[i-1].weight;
HT[i].data=a[i-1].data;
}
//到此,哈夫曼树的初始化结束
//下面开始构造这个哈夫曼树
for(int i=n+1; i<=m; i++) { //合并过程中会产生n-1个结点,把他们从第n+1个位置上开始排列
int s1,s2; //从Select函数返回的两个权值最小的节点在HT中的序号
Select(HT,i-1,s1,s2); //在HT[k](1≤k≤i-1)中选择两个其双亲域为0, 且权值最小的结点,并返回它们在HT中的序号s1和s2
HT[i].lch=s1;
HT[i].rch=s2; //把s1和s2分别设置为i节点的左右孩子
HT[i].weight=HT[s1].weight+HT[s2].weight; //它的权值是两个孩子结点权值的和
}
//return HT;
}
/*----------------生成哈夫曼编码函数-------------------*/
void MakeHuffmanCode(HuffmanTree HT,int n) {
//HuffmanCode HC;
//从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
HC=new char *[n+1]; //这个语句其实是定义了n+1个指针,也就是说定义了一个包含了n+1个指针的数组,而且每一个指针又代表一个数组,因为数组名就是指针
//new char[n+1]这样是一个一维的数组,而char *[n+1]是一个二维的数组。
char *cd;
cd=new char[n]; //分配临时存放编码的动态数组空间
cd[n-1]='\0'; // \0是字符串的结束符,在这里充当编码结束符
for(int i=1; i<=n; i++) {
int start;
start=n-1; //从末尾开始,也就是从‘\0’开始
int c; //c代表当前节点的序号
c=i;
int f; //f是当前节点的双亲结点的序号
f=HT[c].parent;
while(f!=0) { //从叶子节点开始,一直回溯到根节点
--start; //往上走一个,则start向前指一个位置
if(HT[f].lch==c) { //如果当前节点是双亲节点的左孩子
cd[start]='0'; //编码设置为字符0
} else { //如果当前节点是双亲节点的右孩子
cd[start]='1'; //编码设置为字符1
}
c=f;
f=HT[f].parent; //继续向上回溯
}
HC[i]=new char[n-start]; //为第i个字符串编码分配空间
strcpy(HC[i],&cd[start]);//把从数组中第start个位置所在的地址开始一直到字符串结束符所在的地址之间的这些字符,赋值给从 以HC[i]为数组名的数组的首地址开始的这一段存储空间
//&是取地址运算符
//使用格式:char* strcmp(char* buffer,char*str)
//功能:把从str地址开始且含有NULL结束符的字符串复制到以buffer开始的地址空间,buffer地址空间中必须有足够的空间来容纳str的字符串
}
delete cd; //释放临时空间
//return HC; //返回生成的哈夫曼编码表
}
/*----------------显示菜单函数-------------------*/
void ShowMenu() {
system("cls");
printf("\n\n\n\n\n\n");
printf("\t\t ★ *-*-*-*-*-*-*-*-*-*-*-欢迎进入*-*-*-*-*-*-*-*-*-*-*-★ \n");
printf("\t\t ★ 文件压缩与解压系统 ★ \n");
printf("\t\t ★ ★ \n");
printf("\t\t ★ 1.我要压缩 ★ \n");
printf("\t\t ★ 2.我要解压 ★ \n");
printf("\t\t ★ 0.退出系统 ★ \n");
printf("\t\t ★*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*★ \n");
printf("\n");
printf("\t\t请您选择(0--3),执行其中一个程序:");
}
/*----------------压缩函数-------------------*/
int Coding() {
system("cls");
cout<<"请输入要压缩的字符串:" ;
string input;
cin>>input; //input表示输入的字符串
int n=input.length(); //n表示输入字符串的长度
iNode a[n]; //保存输入的字符及其权值的数组
for(int i=0; i<n ; i++) { //初始化数组a
a[i].data='*';
a[i].weight=0;
}
int p=0; //用来标记数组a中已经存储的字符的个数
for(int i=0; i<n; i++) { //遍历数组的所有字符
int k;
for(k=0; k<p; k++) { //遍历数组a
if(a[k].data==input[i]) { //如果这个字符已经在数组a中存在
a[k].weight++; //那么这个字符的权值+1;
break;
}
}
if(k==p) { //k和p相等,说明这个字符在数组中没有出现过
a[p].data=input[i];
a[p].weight++;
p++;
}
}
cout<<endl;
cout<<"您输入的字符串中出现的所有字符,及其出现的次数如下所示:"<<endl;
for(int i=0; i<p; i++) {
cout<<a[i].data<<":"<<a[i].weight<<"次\t";
}
CreateHuffmanTree(p,a); //构建哈夫曼树
MakeHuffmanCode(HT,p); //生成哈夫曼编码
cout<<endl<<"各个字符对应的哈夫曼编码如下所示:"<<endl;
for(int m=1; m<=p; m++) {
cout<<HT[m].data<<":"<<HC[m]<<"\t";
}
//开始编码
int in=0;
cout<<endl<<"字符串"<<input<<"的编码结果为:"<<endl;
for(int i=0; i<n; i++) {
for(int j=1; j<=p; j++) {
if(input[i]==HT[j].data) {
cout<<HC[j];
break;
}
}
}
cout<<endl<<"请按回车键返回主菜单!";
return p; //返回已经编码完成的字符的个数
}
/*----------------解压函数-------------------*/
void Dedcoding(int p) { //参数p是指已经编码完成的字符的个数
system("cls");
if(HC==NULL) {
cout<<"您还未进行编码,不能进行解码!"<<endl;
} else {
int flag=1;
cout<<"各个字符对应的哈夫曼编码如下所示:"<<endl;
for(int m=1; m<=p; m++) {
cout<<HT[m].data<<":"<<HC[m]<<"\t";
}
while(flag) {
cout<<endl<<"请输入要解码的二进制串:" ;
string input;
string output; //解码出的字符串
cin>>input; //input表示输入的字符串
int n=input.length(); //n表示输入字符串的长度
int in=0; //in表示输入的二进制串的下标
int w,k,i;
int out=0; //out表示解码完成的字符串的下标
while(in<n) {
//cout<<"no";
for(i=1; i<=p; i++) {
int len=strlen(HC[i]); //len问当前字符的二进制编码的长度
for(k=0,w=in; k<len; k++,w++) { // k为HC[i]的下标
if(input[w]!=HC[i][k]) { //如果输入的二进制字符与当前字符的二进制编码不匹配
w=in; //下标返回初始值
break; //跳出当前字符,进行下一个字符
}
}
if(k==len) { //k==len说明当前字符的二进制编码与当前二进制段相匹配
output[out]=HT[i].data; //把当前字符记录当最后的结果集中
out++;
break;
}
}
in=w;
}
cout<<"解码的结果为:";
for(int i=0; i<out; i++) //输出结果
printf("%c",output[i]);
printf("\n");
cout<<"请问您是否需要继续解码?(0.否,1.是):";
cin>>flag;
}
}
cout<<"请按回车键返回主菜单!";
}
/*----------------主函数-------------------*/
int main() {
int flag=1;
ShowMenu(); //显示菜单
scanf("%d",&flag);
getchar();
int p; //需要编码的字符的个数
while(flag) {
switch(flag) {
case 1: //进行压缩
p=Coding();
getchar();
getchar();
break;
case 2:
Dedcoding(p); //进行解压
getchar();
getchar();
break;
case 0: //退出系统
break;
default:
printf("\t\t请输入正确的数字!\n\t\t程序将于三秒后跳转到主菜单");
Sleep(3000);
}
ShowMenu();
scanf("%d",&flag);
getchar();
}
return 0;
}