描述
输入一串字符串,根据给定的字符串中字符出现的频率建立相应哈夫曼树,构造哈夫曼编码表,在此基础上可以对待压缩文件进行压缩(即编码),同时可以对压缩后的二进制编码文件进行解压(即译码)。
输入
多组数据,每组数据一行,为一个字符串(只考虑26个小写字母即可)。当输入字符串为“0”时,输入结束。
输出
每组数据输出2n+3行(n为输入串中字符类别的个数)。第一行为统计出来的字符出现频率(只输出存在的字符,格式为:字符:频度),每两组字符之间用一个空格分隔,字符按照ASCII码从小到大的顺序排列。第二行至第2n行为哈夫曼树的存储结构的终态(形如教材139页表5.2(b),一行当中的数据用空格分隔)。第2n+1行为每个字符的哈夫曼编码(只输出存在的字符,格式为:字符:编码),每两组字符之间用一个空格分隔,字符按照ASCII码从小到大的顺序排列。第2n+2行为编码后的字符串,第2n+3行为解码后的字符串(与输入的字符串相同)。
输入样例 1
aaaaaaabbbbbccdddd
aabccc
0
输出样例 1
a:7 b:5 c:2 d:4
1 7 7 0 0
2 5 6 0 0
3 2 5 0 0
4 4 5 0 0
5 6 6 3 4
6 11 7 2 5
7 18 0 1 6
a:0 b:10 c:110 d:111
00000001010101010110110111111111111
aaaaaaabbbbbccdddd
a:2 b:1 c:3
1 2 4 0 0
2 1 4 0 0
3 3 5 0 0
4 3 5 2 1
5 6 0 3 4
a:11 b:10 c:0
111110000
aabccc
//基于哈夫曼树的数据压缩算法
#include <iostream>
#include <sstream>
using namespace std;
typedef struct{
char name; //数据名称 a、b、c、d
int weight; //数据权重
int parent; //父亲结点(所有结点用数字表示 在数组中直接索引即可)
int lchild; //左子结点
int rchild; //右子结点
string Code;//哈夫曼编码
}LNode;
typedef struct{
LNode *data; //哈夫曼树内的全体数据(把data[0]空出来)
int length; //始终表示哈夫曼叶子的个数!!!不会随着构造哈夫曼树产生父结点而改变(防止二义性)
}Tree;
void MakeTree(Tree &Huffman){//此操作改变哈夫曼树前length个结点的父结点和第length+1到2*length-1个结点的权和父子结点
for(int i=1;i<=Huffman.length-1;i++){ //总共进行树叶个数-1次试验,因为n个树叶 每次连接需要连接个数都是-1,中途不改变length,最后改变
int m,n; //用于存放每次循环最小的两个位置
for(int j=1;j<=Huffman.length+i-1;j++)
if(Huffman.data[j].parent==0){m=j;break;} //遇到第一个父结点为0的位置停下
for(int j=m+1;j<=Huffman.length+i-1;j++)
if(Huffman.data[j].parent==0&&Huffman.data[j].weight<Huffman.data[m].weight) //如果遇到父结点为0且权比m的权小的,就改变m的值
m=j;
Huffman.data[m].parent=-1; //将m的父结点置为-1,防止后面的n定位到重复的值
for(int j=1;j<=Huffman.length+i-1;j++)
if(Huffman.data[j].parent==0){n=j;break;}
for(int j=n+1;j<=Huffman.length+i-1;j++)
if(Huffman.data[j].parent==0&&Huffman.data[j].weight<Huffman.data[n].weight)
n=j;
Huffman.data[m].parent=0; //将m的父结点重置为0(没必要,因为调用不到,但是保险起见)
Huffman.data[m].parent=Huffman.data[n].parent=Huffman.length+i; //改变mn结点的父结点
Huffman.data[Huffman.length+i].parent=0; //新结点的父结点为0以保证他能加入下一轮操作
Huffman.data[Huffman.length+i].lchild=m; //新结点的左子结点是比较小的结点
Huffman.data[Huffman.length+i].rchild=n; //新结点的右子结点是第二小的结点
Huffman.data[Huffman.length+i].weight=Huffman.data[m].weight+Huffman.data[n].weight;
}
for(int i=1;i<=Huffman.length*2-1;i++) //输出哈夫曼树各叶子结点的名称和权重
cout<<i<<" "<<Huffman.data[i].weight<<" "<<Huffman.data[i].parent<<" "<<Huffman.data[i].lchild<<" "<<Huffman.data[i].rchild<<endl;
}
void MakeCode(Tree &Huffman){
for(int i=1;i<=Huffman.length;i++){
int temp=i; //temp用于寻找i的祖先结点
while(Huffman.data[temp].parent!=0){
if(Huffman.data[Huffman.data[temp].parent].lchild==temp) Huffman.data[i].Code='0'+Huffman.data[i].Code; //如果父结点的左子结点是自己,就在编码前面加上0
else Huffman.data[i].Code='1'+Huffman.data[i].Code; //否则加上1
temp=Huffman.data[temp].parent; //让temp往父结点走
}
}
for(int i=1;i<=Huffman.length-1;i++)
cout<<Huffman.data[i].name<<":"<<Huffman.data[i].Code<<" ";
cout<<Huffman.data[Huffman.length].name<<":"<<Huffman.data[Huffman.length].Code<<endl;
}
void Compress(Tree &Huffman,string &str){//利用哈夫曼树对字符进行压缩编码并改变 每次对一个字符处理,将他变成编码进入str中
stringstream ss;
ss<<str;
char ch;
str=""; //str清零 这时候里面的内容已经在ss中了
while(ss>>ch)
for(int i=1;i<=Huffman.length;i++)
if(Huffman.data[i].name==ch){str+=Huffman.data[i].Code;break;}
}
void Decompress(Tree &Huffman,string &str){//利用哈夫曼树对压缩码进行解压缩并改变
stringstream ss;
ss<<str;
int temp=2*Huffman.length-1; //让temp站在树顶
char ch;
str=""; //str清零 这时候里面的内容已经在ss中了
while(ss>>ch){
if(Huffman.data[temp].lchild==0){ //如果没有孩子结点 说明temp到底了 这时候直接输出temp结点名字
str+=Huffman.data[temp].name;
temp=2*Huffman.length-1;
}
if(ch=='0') temp=Huffman.data[temp].lchild; //如果ch为0 temp往左结点走,否则往右结点走
else temp=Huffman.data[temp].rchild;
}
str+=Huffman.data[temp].name; //ch读完之后temp还处于一个树叶结点 对他进行输出
}
void Calculate(string &str){ //对一行数据的计算过程
Tree Huffman;
Huffman.length=0; //叶子长度归零
Huffman.data=new LNode[100];
stringstream ss; //数据流
ss<<str;
char ch; //读取单个字符
while(ss>>ch){
int flag=1; //用于判断数组中是否已有这个字符
for(int i=1;i<=Huffman.length;i++)
if(ch==Huffman.data[i].name){ //如果找到相同字符 就让这个结点权重+1
flag=0;
Huffman.data[i].weight++;
break;
}
if(flag==1){ //如果没找到 就定义新结点
Huffman.data[Huffman.length+1].name=ch; //数据名称定义为读入的字符
Huffman.data[Huffman.length+1].weight=1; //数组权重初始化为1
Huffman.data[Huffman.length+1].parent=Huffman.data[Huffman.length+1].lchild=Huffman.data[Huffman.length+1].rchild=0; //父子结点全部指向零
Huffman.length++;
}
}
for(int i=1;i<=Huffman.length-1;i++) //排序(冒泡)
for(int j=Huffman.length-1;j>=i;j--)
if(Huffman.data[j].name>Huffman.data[j+1].name){
char temp=Huffman.data[j].name;
Huffman.data[j].name=Huffman.data[j+1].name;
Huffman.data[j+1].name=temp;
int tempp=Huffman.data[j].weight;
Huffman.data[j].weight=Huffman.data[j+1].weight;
Huffman.data[j+1].weight=tempp;
}
for(int i=1;i<=Huffman.length-1;i++) //输出哈夫曼树各叶子结点的名称和权重
cout<<Huffman.data[i].name<<":"<<Huffman.data[i].weight<<" ";
cout<<Huffman.data[Huffman.length].name<<":"<<Huffman.data[Huffman.length].weight<<endl;
MakeTree(Huffman); //构造哈夫曼树
MakeCode(Huffman); //构造哈夫曼编码
Compress(Huffman,str);//利用哈夫曼树对字符进行压缩编码并改变
cout<<str<<endl;
Decompress(Huffman,str);//利用哈夫曼树对压缩码进行解压缩并改变
cout<<str<<endl;
}
int main(){
string str; //一行数据
while(cin>>str&&str!="0") //输入一行数据到只有零为止
Calculate(str);
return 0;
}