数据结构实验2
题目要求
实现一个哈夫曼编码系统,系统包括以下功能:
(1) 字符信息统计:读取待编码的源文件SourceFile.txt,统计出现的字符及其频率。
(2) 建立哈夫曼树:根据统计结果建立哈夫曼树以及哈夫曼码表,将各字符对应的编码表保存在文件Code.txt中。
(3) 对源文件进行编码:根据哈夫曼码表,将SourceFile.txt中的字符转换成相应的编码文件ResultFile.txt。
(4) 选做内容:
完成译码功能:对任意一个给定的由01组成的文件,根据哈夫曼码表翻译成由字符组成的源文件。
分析
这是一个即实用又有趣的题目,但是设计到很多的知识点,非常耗时。若用c++实现最好有c++ STL 使用基础。
我将程序分成几个主要部分:
- 统计文章的字符出现频率。
- 构造哈夫曼树并生成哈夫曼编码。
- 将文章按照生成的哈弗曼编码加密成只含有0和1的问。
- 保存编码到文件,读取编码并还原哈夫曼树。
- 读取经过特定编码加密的文件,并又这个编码树还原成可读的内容。
简单实现哈夫曼方法请看:我的哈夫曼算法
知识点
【文件读写 】
如读取原文,保存编码, 难点是将数据序列化和反序列化。
【c++ map】
用来保存各个字符出现的次数,若不会使用,将难以下手或代码量倍增。
【c++ primary_queue】
用于构造哈夫曼树,能极大提高效率和缩短代码长度
笔记
第一次写时用char来保存字符,但是后来发现原文中若出现中文,char类型没法保存,最后溢出了。于是用整数来保存
字符,输出时简单的类型转换即可。
? 以下代码实现题目基本要求和选做内容
MYCODE
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<string>
#include<algorithm>
#include<fstream>
#include<queue>
#include<map>
using namespace std;
const char *sourcefile = "D://SourceFile.txt"; //原文
const char *mapfile = "D://mapfile.txt"; //存储字典的文件
const char *encryptfile = "D://entryfile.txt"; //存储原文加密后形成的文本
ofstream mapf;
map<int , int> iimap; //保存字符在原文中出现的字典,用int来保存字符
map<int , string> ccmap; //保存字符对应hufuman编码的字典
//哈夫曼树节点结构
struct node{
int val; //字符
int times; //出现次数
string code; //哈夫曼编码
node* left;
node* right;
node(int c, int t, node* l, node* r){
val = c;
times = t;
right = r;
left = l;
}
bool operator < (const node &n){
return this->times > n.times;
}
};
struct cmp{
bool operator()(const node* a, const node* b){
return a->times > b->times;
}
};
//保存节点的优先队列,times小的节点优先输出
priority_queue<node*, vector<node*>, cmp> pq;
//读取原文并且统计每个字符出现的次数
void Read_and_static(){
ifstream fout;
string buf;
fout.open(sourcefile);
while (getline(fout, buf)){
iimap['\n']++; //getline后换行符不会出现在buf中,getline一次则说明有一个换行符
for (int i = 0; i < buf.length(); i++){
cout << buf[i];
iimap[ buf[i] ]++;
}
}
fout.close();
}
//将iimap的数据放到优先队列pq中,需要先统计原文得到iimap。
void init_queue(){
for (auto i : iimap){
node *tn = new node(i.first, i.second, NULL, NULL);
pq.push(tn);
}
}
//生成哈夫曼树,返回头结点。
node* build_huffmantree1(){
init_queue();
while (pq.size()>1){
node* t1 = pq.top();
pq.pop();
node* t2 = pq.top();
pq.pop();
node* newnode = new node(-1,t1->times + t2->times, t1, t2);
pq.push(newnode);
}
node* head = pq.top();
return head;
}
//显示 pq 的内容,即各个字符以及其出现的次数
void display(){
priority_queue<node*, vector<node*>, cmp> temp = pq;
while (temp.empty() == false){
node* t = temp.top();
printf("%c ---- %d \n", t->val, t->times);
temp.pop();
}
return;
}
//从头结点开始,递归遍历哈夫曼树,从而为节点设置哈夫曼编码,
void set_code(node* n){
if (n->left == NULL && n->right == NULL){
cout << char(n->val) << " " << n->code << endl;
return;
}
if (n->left != NULL){
n->left->code += n->code + "0";
set_code(n->left);
}
if (n->right != NULL){
n->right->code += n->code + "1";
set_code(n->right);
}
return;
}
//递归遍历一个哈弗曼树,将编码字典保存到文件和ccmap中
void Save_code(node *n){
if (n->left == NULL && n->right == NULL){
cout << n->val << " --> " << n->code << endl;
ccmap[n->val] = n->code;
mapf << n->val << endl << n->code << endl;
return;
}
if (n->left != NULL) Save_code(n->left);
if (n->right != NULL) Save_code(n->right);
}
//按照特定格式读取件中保存的哈弗曼字典,返回一个map<int,string>表示字符对应的哈夫曼编码
map<int, string> read_codefile(){
ifstream code;
code.open(mapfile);
int t = 0;
string tkey,val;
map<int, string>tcode;
while (getline(code, tkey) && getline(code,val)){
int key = atoi(tkey.c_str());
tcode[key] = val;
}
code.close();
return tcode;
}
//更具指定的字典构造一个哈夫曼树,返回头节点
node* build_huffmantree2(map<int,string> code){
init_queue();
node *head = new node(-1, 0, NULL, NULL);
for (auto i : code){
string code = i.second;
int val = i.first;
node *p = head;
for (int i = 0; i < code.length(); i++){
if (code[i] == '0'){
if (p->left == NULL){
p->left = new node(-1, 0, NULL, NULL);
}
p = p->left;
}
else{
if (p->right == NULL){
p->right = new node(-1, 0, NULL, NULL);
}
p = p->right;
}
}
p->code = code;
p->val = val;
}
return head;
}
//将文本加密为哈夫曼编码
void encryption(){
ifstream from(sourcefile);
ofstream to(encryptfile);
string temp;
while (getline(from, temp)){
if (temp.length() == 0) to << ccmap['\n'];
for (int i = 0; i < temp.length(); i++){
to << ccmap[temp[i]];
}
}
from.close();
to.close();
return;
}
//将哈夫曼编码还原为原文,前提条件是ccmap已经按照相应的字典初始化
void decode(node *head){
ifstream from(encryptfile);
char tc;
node *p = head;
while (from.get(tc)){
if (tc == '0')p = p->left;
else p = p->right;
if (p->left == NULL && p->right == NULL){
cout << char(p->val);
p = head;
}
}
return;
}
int main(){
bool create = false;
if (create){ //加密源文件(读取源文件,生成编码字典和一个加密后的文件)
Read_and_static(); //得到了字符——出现次数 字典 iimap
node *head = build_huffmantree1(); //又iimap构建一棵树,但是树的节点未设置编码
set_code(head); //为哈夫曼树的节点附上编码
mapf.open(mapfile);
Save_code(head); //将编码保存到文件中
mapf.close();
encryption(); //将原文按照字典解析成编码,放到文件中,相当于加密
}
else{ //解密源文件(读取加密文件和编码字典,还原并输出原文)
ccmap = read_codefile(); //读取编码字典
node *head = build_huffmantree2(ccmap); //构造树
set_code(head); //设置编码
decode(head);
}
return 0;
}
能力有限,若发现不知之处欢迎留言