哈夫曼树加密解密
哈夫曼数
1.哈夫曼树为何能压缩数据?
这个问题在上课时一直没弄懂,感觉老师表述的不太清楚。 下课后上网查了一下,感觉豁然开朗。
假设原编码每个字符占4bits(其实ASCII编码每个占8bits),为什么一定要固定为4bits呢?
因为计算机储存是连续的且只能储存0和1。一大段字符串对应一大段二进制编码。只有每个固定为四位,才能把每个字符分割开来。
例如,把a=010,b=01,c=00,d=10。 那么01001010,可以是aad,也可以是bcdd也可以是aba。
那么为什么哈夫曼树能够实现编码长度不固定呢?4
由于haffman树是唯一确定的,将哈夫曼树理解为一种编码对应表,便能实现压缩。
2.具体实现
先贴代码
//采用部分C++11特性,在VisualStudio2017下已通过编译
#include<iostream>
#include<string>
#include<vector>
using namespace std;
struct node {
int ascii,times;
node *lchild, *rchild;
};
node* Hafman(vector<node*> a) {
node* min1 = new node, *min2 = new node; node *p = new node;
int t = a.size();//第二个坑
for (int i = 0; i < t-1; i++) {
min1 = new node;
min1 = a[0];
int j, k, j1 = 0, k1 = 0;
for (j = 0; j < a.size(); j++) {
if (a[j]->times < min1->times) { min1 = a[j]; j1 = j; }
}
a.erase(a.begin() + j1);//从vector中释放最小值
min2 = a[0];
for (k = 0; k < a.size(); k++) {
if (a[k]->times < min2->times) { min2 = a[k]; k1 = k; }
}
a.erase(a.begin() + k1);//从vector中释放次小值
p = new node;//第一个坑
p->lchild = min1; p->rchild = min2;
p->times = min1->times + min2->times;
a.push_back(p);
}
return p;
}
void preorder(node *&T,int &WPL) {
if (T != NULL) {
if(T->lchild!=NULL && T->rchild!=NULL)
WPL = WPL + T->times;
preorder(T->lchild,WPL);
preorder(T->rchild,WPL);
}
}
void recode(node *&T, int x,string code) {
if (T != NULL) {
if (T->lchild == NULL && T->rchild == NULL &&T->ascii == x)
cout << code;
recode(T->lchild, x,code+"0");
recode(T->rchild, x,code +"1");
}
}
int main()
{
string text; int mark[128]; vector<node*> tab;
for (int i = 0; i < 128; i++) mark[i] = 0;
cin >> text;
cout <<"原字符串编码占:"<< text.length() * 4<<"bits"<<endl;
cout << "各个字符出现的频次为:" << endl;
for (int i = 0; i < text.length(); i++) {
int k = text[i];
mark[k] ++;
}
for (int i = 0; i < 128; i++) {
if (mark[i] != 0) {
cout << char(i) << ":" << mark[i]<<endl;
node *a=new node;
a->ascii = i;
a->times = mark[i];
a->lchild = NULL;
a->rchild = NULL;
tab.push_back(a);
}
}
node *root=Hafman(tab);
int x=0;
preorder(root, x);
cout << endl;
cout << "WPL="<<x<<endl<<endl;
string code;
cout << "压缩编码如下:" << endl;
for (auto a : tab)
{
cout << char(a->ascii) << "…………";
recode(root, a->ascii, code);
cout << endl;
}
cout << "压缩比为" << (double)x / (text.length() * 4) * 100 << "%" << endl;
cin >> text;
}
哈夫曼树的构建原理不再赘述。
由于哈夫曼树的原始数据需要带权重,所以定义哈夫曼树节点如下:
struct node {
int ascii,times;//times即权重,ascii为编码
node *lchild, *rchild;
};
3.测试
为了更方便的组织数据,需要一个vector容器,将所有原始节点都置于该容器内。
1.找出权重最小的两个节点。
利用经典的找最小值方法,可以找出第一个最小值。难点是找次小值。
我的思路是利用vector的功能函数,将最小值从vector中释放后,再找一次最小值。从而找到次小值。将次小值再次释放。
注意,由于使用了min1,min2记录两个值,并立即用于构建哈夫曼树,所以释放后,这两个值并没有丢失。
将min1,min2的权重相加,形成新的结点,,将min1,min2的父节点设置为新节点,并将新节点加入vector中。
不断循环上述过程,便可完成哈夫曼树的构建。
至此,大致思路就是这样。有一些坑我单独说:
1.万恶的指针
构建新节点时,需要new一个,并且把子节点设置为Null,不然指针错误,程序崩溃。
2.使用for循环时,注意控制条件的改变情况。
在上述程序中,很容易发现,n个初始元素,需要循环n-1次便可完成哈夫曼树的构建。
所以很显然,for(int i=0;i<原始vector长度;i++),错误就出在这里,最初直接使用size()函数置入for循环内,然而,由于删除了vector中的元素。size()函数的大小是会减小的。应该在循环未开始前,将t=size(),t置于for循环内。
这两个坑抹杀了我几个小时的debug时间。
T T