看完后评论评论
数据结构与算法设计课程设计
专业 班级 学号 姓名(签名) 完成日期 指导教师(签名)
【设计题目】字符与霍夫曼代码的转换
【问题描述】
用霍夫曼编码的方式压缩数据,为实现霍夫曼代码与原文件ASIC码之间的对照,把压缩过程中原文件生成的霍夫曼代码保存与一个文件中。给出用霍夫曼压缩的代码,要把霍夫曼代码转换成明文件保存于文件中。
【软件功能】
读取文本文档中的字符转换成霍夫曼代码保存于另一个文件中。并能够读取霍夫曼代码转换成字符保存于另一个文件中。
【算法思想】 首先定义256个统计字符出现频数的静态数组,其数组的下标对应着每一个字符。
对读入的字符进行统计时,直接根据字符的ASIIC码找到相应的元素。
生成霍夫曼代码:
字符出现的频数统计完成后检查每个字符出现的频数,把频数不为零的元素添加到搜索二叉树中。添加完成后,遍历搜索二叉树,每遍历一个节点添加到霍夫曼树中。当霍夫曼树建立完成后遍历霍夫曼树,记录遍历节点所对应的霍夫曼编码,每遍历一个叶子节点,对叶子节点的数据域赋上霍夫曼编码。
搜索二叉树和霍夫曼树中节点的数据域为指针,直接指向定义的统计字符出现频数的静态数组元素。所以对搜索二叉树和霍夫曼树节点的数据域的操作就等于直接对定义的静态数组的成员操作。
字符与霍夫曼代码的对照表单独保存于一个文件中。其文件名由保存霍夫曼代码的文件名决定。
重新读取文件,读取的字符直接根据其ASIIC码直接找到保存该字符的数组元素。然后把其代表的霍夫曼编码写入文件。
霍夫曼代码转换成字符:
首先读取字符与霍夫曼代码的对照表,根据霍夫曼编码中1出现的次数作为下表保存在静态数组中。
读取霍夫曼代码,读取的时候如果读取的字符是‘1’则继续读,如果为‘0’则暂时停止。根据读取1的个数找到相应的静态数组元素,把相应的字符写入文件中。然后字把统计1出现的整数赋0,重新读取字符。
【类的设计】
统计字符串的类
class CharacterStat{
public:
static char a;
char character; //字符
int frequence; //频数
char * HuffStat; //字符对应的霍夫曼代码,采用指针的形式,在赋值的时候分配内存
int iHuff; //霍夫曼编码转换成的整形数
public:
CharacterStat();
~CharacterStat();
};
搜索二叉树的节点类
class BSTNode{
friend class BST;
friend class huffmantree;
CharacterStat * data;//数据域,采用指针的形式,因为是指向已分配好的地址。
BSTNode *leftchild;
BSTNode *rightchild;
int Threaded; //线索化标记(中序线索化)
public:
BSTNode(CharacterStat *p=NULL);//构造函数
~BSTNode();
};
搜索二叉树类
class BST{
BSTNode *root;
public:
void Destory();
BSTNode * GetRoot();
BST(CharacterStat * p=NULL );
bool AddANode( CharacterStat *);
};
霍夫曼类
class huffmantree{
BSTNode *root;//霍夫曼树的跟节点
public:
bool DestoryHuffman();
huffmantree(CharacterStat * p=NULL); //默认参数,建立对象时给对象初始化
bool AddToRoot(CharacterStat *); //把一个节点增加到已建好的霍夫曼树中
bool BuiltHuffmanTree(BSTNode *);//对一个空的霍夫曼树建立霍夫曼树,传过来的指针为搜索二叉树的跟节点
bool InorderBST(BSTNode *);//中序遍历搜索二叉树,把搜索二叉树中的节点增加到霍夫曼树中
bool InorderHuffman(BSTNode *);//中序遍历霍夫曼树,主要目的在于给字符附上霍夫曼编码
BSTNode * getroot();//得到霍夫曼树的跟节点
};
【存储结构设计】
统计字符的对象:采用静态存储方式,定义255个对象(ASIIC中有255个字符,包括扩展字符)。
搜索二叉树节点对象:采用堆存储方式,动态的分配内存,而且对象中的数据域只存放指针。
其逻辑关系满足搜索二叉树的定义。
字符与霍夫曼编码的对应关系存放于保存霍夫曼编码文件路径下,其文件名为保存霍夫曼代码文件名后加HMcode.如保存霍夫曼代码的文件名为huff.txt,则其对应关系文件名为huffHMcode.txt
要进行霍夫曼编码的文件为用户指定路径下的文件。
对用霍夫曼编码保存的文件为用户指定路径下的文件。
【模块流程图】
1.文本文件转换为霍夫曼编码
已定义好统计字符频数的静态数组frequencearray[255]。
统计字符出现的频数
用户指定路径,并用只读的方式打开指定路径下的文件。
进入读文件循环,条件为没有读到文件结尾。
Fin>>a;frequencearray[a]++;//相应的字符频数加1
统计完成,把频数不为零的字符添加到搜索二叉树中。
比较搜索二叉树当前的元素
比当前元素大 其他情况
当前元素的右孩子 其他 当前元素的左 其他 指针为空 孩子指针为空
添加到节点 , 当前指针指向 添加到节点 当前指针指向 打断循环 它继续比较 打断循环 它继续比较
搜索二叉树建立完成,把搜索二叉树转换成霍夫曼扩展二叉树。
中序遍历搜索二叉树,复制遍历的节点A添加到扩展二叉树中。
New node,添加到扩展二叉树的root节点。Node->leftchild=root;node->rightchild=&A
遍历霍夫曼树,生成霍夫曼编码;先定义CString string;int i=0;
向 左 向 右
string[i]=‘ 0 ’ ,然后赋给所指向的 string[i++]=‘ 1 ’ ,继续遍历,如果遍历完 统计字符对象 成,把string赋给所只相当统计字符对象
重新读取文件,译成霍夫曼编码写入新文件中。
Fin>>a;fout<<frequencearray[a].huffstat;
ASIIC与霍夫曼代码的转换完毕 |
2.霍夫曼代码转换为原文件
打开字符与霍夫曼代码的对照表文件,读取统计对象。
根据霍夫曼编码转换成的整数大小建立搜索二叉树
关闭文件,打开霍夫曼代码文件。
读取霍夫曼代码,(把霍夫曼代码按整形数输入)定义一个int i=0
读入整形数1 读取整形数0 其他
I++ 在搜索二叉树中找相应 输出文件错误,提示不是霍夫 的源码写入另一个文件 曼代码文件,并退出程序。
转换完毕 |
主函数 |
显示霍夫曼编码文件 |
显示原文件 |
对指定文件进行霍夫曼解码 |
对指定文件进行霍夫曼编码 |
【界面设计】
主页面:
用户选择操作代码,分别进入不同的界面:
1. 原文件转换为霍夫曼代码;2.霍夫曼代码转换为原文件
3. 显示原文件 4.显示霍夫曼编码文件
Esc键退出
进入不同界面时,提示当前的操作。如输入文件名,按任意键等。
【用户手册】
1、 程序上机调试报告
【语法错误及其排除】
1. 对同一个指针在不同的函数进行释放内存。解决方法,对同一类指针释放内存统一化。
2. 错误的认为把指针所指的内存释放掉了其指针就为NULL指针了。解决方法,给指针重新赋上NULL。
【算法错误及其排除】
2、 程序测试结果
【测试数据】
原文件的内容:
【输出结果】
转换为霍夫曼编码后文件的内容:
把霍夫曼编码转换为原文件为:
【程序性能评价】
1.在控制台下运行稳定,能够读取文件中的所有字符进行统计(包括不显示字符);
2.统计速度较快,因为没有循环比较过程,直接通过字符的ASIIC码找到相应的字符结构。
3.转换速度也一样较快,因为和统计的原理一样,没有循环比较过程;
4.文件中的字符与霍夫曼编码能够一字不差的进行转换;
5.在对字符出现频率进行排列是采用搜索二叉树的,并且对搜索二叉树进行线索化,很大程度上提高的遍历速度;
6.搜索二叉树和霍夫曼树的节点的数据域都为指针,指向静态数组的元素,这样即节省了空间上的开销,又方便了操作。
7.交互能力较好,避免了用户不必要的输入,如用户输入文件的扩展名;
8.错误处理能力较好,函数返回值一般用bool 用于判断函数执行的效果等;
【性能改进方向】
1. 加强错误处理能力,特别是自动处理能力;
2. 在类中多增加成员函数和变量,以便以后的升级及用在别的场合;
3. 增加显示目录的函数,使之能够查看在不用目录下的所有文本文件,以更方便操作;
4. 增加退出操作,如当输入空文件名时直接返回上一级目录;
5. 修改交互界面,使之更加友好等;
6.
【收获及体会】
最刚开始学霍夫曼编码时觉得这有点难,可当自己亲手把它做出来时就有一种熟悉的感觉。做一个程序能锻炼很多东西,在其中的收获也不少。这次做霍夫曼树的程序,让我更加清楚了自己当前的编程水平及还需提高的方向。
3、 源程序代码
#include<iostream.h>
#include<fstream.h>
#include<string.h>
#include<stdlib.h>
#include<conio.h>
#include<stdio.h>
//声明类
class BSTNode;
class BST;
class huffmantree;
//统计字符串的类
class CharacterStat{
public:
static char a;
char character; //字符
int frequence; //频数
char * HuffStat; //字符对应的霍夫曼代码,采用指针的形式,在赋值的时候分配内存
int iHuff; //霍夫曼编码转换成的整形数
public:
CharacterStat();
~CharacterStat();
};
//************************************************************
char CharacterStat::a=0;
CharacterStat::CharacterStat(){
character=(char)a;frequence=0;HuffStat=0;iHuff=-1;a++;
}
//************************************************************
CharacterStat::~CharacterStat(){
}
//搜索二叉树的节点类
class BSTNode{
friend class BST;
friend class huffmantree;
CharacterStat * data;//数据域,采用指针的形式,因为是指向已分配好的地址。
BSTNode *leftchild;
BSTNode *rightchild;
int Threaded; //线索化标记(中序线索化)
public:
BSTNode(CharacterStat *p=NULL);//构造函数
~BSTNode();
};
//************************************************************
BSTNode::BSTNode(CharacterStat * p){
data=p;
leftchild=NULL;
rightchild=NULL;
}
//************************************************************
BSTNode::~BSTNode(){
// delete leftchild;
// delete rightchild;
}
//搜索二叉树类
class BST{
BSTNode *root;
public:
void Destory();
BSTNode * GetRoot();
BST(CharacterStat * p=NULL );
bool AddANode( CharacterStat *);
};
//************************************************************
BST::BST( CharacterStat* p){
if(p)
root= new BSTNode(p);
else
root=NULL;
}
//************************************************************
bool BST::AddANode( CharacterStat * p){
if(!p){
cerr<<"传过来的指针为空,无法增加节点!!!/n";
return false;
}
BSTNode * temp=root;
BSTNode * father=NULL;
if(!root){
root=new BSTNode(p);
root->Threaded=1;
return true;
}
while(temp){
if(p->frequence>temp->data->frequence)
if(temp->rightchild&&!temp->Threaded)//右孩子存在,且没有线索化
temp=temp->rightchild;
else{
father=temp->rightchild;//线索化要指向的目的
temp->Threaded=0;//消去原有的线索化标记,接上右孩子
temp->rightchild=new BSTNode(p);
temp->rightchild->rightchild=father;
temp->rightchild->Threaded=1;//标识线索化
break;
}
else if(temp->leftchild)
temp=temp->leftchild;
else{
temp->leftchild=new BSTNode(p);
temp->leftchild->rightchild=temp;
temp->leftchild->Threaded=1;//已进行线索化了
break;
}
}
return true;
}
//************************************************************
BSTNode * BST::GetRoot(){
return root;
}
//************************************************************
//霍夫曼树类
class huffmantree{
BSTNode *root;//霍夫曼树的跟节点
public:
bool DestoryHuffman();
huffmantree(CharacterStat * p=NULL); //默认参数,建立对象时给对象初始化
bool AddToRoot(CharacterStat *); //把一个节点增加到已建好的霍夫曼树中
bool BuiltHuffmanTree(BSTNode *);//对一个空的霍夫曼树建立霍夫曼树,传过来的指针为搜索二叉树的跟节点
bool InorderBST(BSTNode *);//中序遍历搜索二叉树,把搜索二叉树中的节点增加到霍夫曼树中
bool InorderHuffman(BSTNode *);//中序遍历霍夫曼树,主要目的在于给字符附上霍夫曼编码
BSTNode * getroot();//得到霍夫曼树的跟节点
};
//************************************************************
huffmantree::huffmantree(CharacterStat * pNode){
if(pNode){
root=new BSTNode(pNode);
}
}
//************************************************************
bool huffmantree::AddToRoot(CharacterStat * pNode){
BSTNode * Root;//将成为新的根节点
if(pNode){
Root=new BSTNode(NULL);//其数据域为空,其实际数据为其左孩子指针和右孩子指针
Root->leftchild=new BSTNode(pNode);//其右孩子指向新分配的内存地址
Root->rightchild=root; //交换跟节点
root=Root; //
return true; //成功返回真
}
return false;//失败返回假
}
//************************************************************
bool huffmantree::BuiltHuffmanTree(BSTNode *Node=NULL){
if(!Node){//没有传数据域的跟指针或跟指针为空时
cerr<<"数据域为空,不能建立霍夫曼树!!!/n";
return false;
}
InorderBST(Node);//采用中序遍历添加到霍夫曼树中
return true;
}
//************************************************************
bool huffmantree::InorderBST(BSTNode * Node=NULL){
BSTNode *temp=Node;
if(!Node){
cerr<<"遍历的数据域为空!!!/n";
return false;
}
while(temp->leftchild)//找第一个要遍历的节点,因为是线索化的
temp=temp->leftchild;
while(temp){//开始遍历
AddToRoot(temp->data);
if(temp->Threaded)//如果当前节点已线索化
temp=temp->rightchild;
else{//当前节点没有线索化
temp=temp->rightchild;
while(temp&&temp->leftchild)//找最左边的节点
temp=temp->leftchild;
}
}
return true;
}
//************************************************************
bool huffmantree::InorderHuffman(BSTNode * Node=NULL){
if(!Node){
cerr<<"霍夫曼树为空,不能遍历!!!";
return false;
}
char code[256];
BSTNode * temp=Node;
for(int i=0;temp;i++){
if(temp->leftchild){
code[i]='0';code[i+1]=0;
temp->leftchild->data->HuffStat=new char[i+2];
strncpy(temp->leftchild->data->HuffStat,code,i+2);
temp->leftchild->data->iHuff=i;
temp=temp->rightchild;
code[i]='1';
}
else{
code[i]='1';code[i+1]=0;
temp->data->HuffStat=new char[i+1];
strncpy(Node->data->HuffStat,code,i+1);
temp->data->iHuff=i;
temp=temp->rightchild;
}
}
return true;
}
//************************************************************
BSTNode * huffmantree::getroot(){
return root;
}
//************************************************************
//定义全局变量
CharacterStat frequence[256];
CharacterStat huffcode[256];
BST bst;//搜索二叉树
huffmantree huffman;//霍夫曼树
//************************************************************
bool textconvertohuffman(){
char a;
char filename[26];
char fileto[30];
char filefrom[30];
ifstream fin;
ofstream fout;
while(1){
system("cls");
cerr<<"/n/n/n/t请输入要转换的文件名(不要输扩展名):";
cin>>filename;
sprintf(filefrom,"%s%s",filename,".txt");
system("cls");
fin.open(filefrom);
if(fin.fail()){
fin.close();
cout<<"/n/t所指定的文件不存在或路径错误!!!/n退出请按Esc键,按其他键重新输入文件名"<<endl;
if(getch()==27){
system("cls");
return false;
}
}
else break;
}
cout<<"/n/n/t正在统计字符出现的频率......."<<endl;
a=fin.get();
while(!fin.eof()){
frequence[a].frequence++;
a=fin.get();
}
fin.close();
system("cls");
cout<<"/n/n/t统计完毕,正在建立搜索二叉树........."<<endl;
for(int i=0;i<256;i++){
if(frequence[i].frequence)
bst.AddANode(&frequence[i]);
}
cout<<"/n/n/t搜索二叉树建立完成,正遍历搜索二叉树建立霍夫曼树......."<<endl;
huffman.InorderBST(bst.GetRoot());
cout<<"/n/n/t霍夫曼树建立完毕,正遍历霍夫曼树给字符赋上霍夫曼代码......."<<endl;
huffman.InorderHuffman(huffman.getroot());
cout<<"/n/n/t字符赋上霍夫曼代码完成"<<endl;
cerr<<"/n/n/t请输入保存霍夫曼代码的文件名(不用输扩展名):";
cin>>filename;
sprintf(fileto,"%s%s",filename,".txt");
fin.open(filefrom);
fout.open(fileto);
if(fout.fail()){
system("cls");
cerr<<"/n/n/t文件不存在或无法新建";
system("cls");
return false;
}
i=0;
a=fin.get();
while(!fin.eof()){
fout<<frequence[a].HuffStat;
i+=frequence[a].iHuff;
if(i>80){
fout<<"/n";
i=0;
}
a=fin.get();
}
fin.close();
fout.close();
sprintf(filename,"%s%s",filename,"HMcode.txt");
fout.open(filename);
for(i=0;i<256;i++){
if(frequence[i].frequence)
fout<<frequence[i].iHuff<<"/t"<<(int)frequence[i].character<<"/t"
<<frequence[i].frequence<<"/t"<<frequence[i].HuffStat<<endl;
}
fout.close();
system("cls");
cout<<"/n/n/t按回车键显示字符与霍夫曼代码的对应关系"<<endl;
if(getch()==13){
system("cls");
cout<<"/n/t字符与霍夫曼代码的对应关系如下:"<<endl;
cout<<"ASIIC码 字符 二进制整形数 霍夫曼编码"<<endl;
for(i=0;i<256;i++){
if(frequence[i].frequence){
cout<<(int)frequence[i].character<<'/t'<<frequence[i].character<<"/t"<<frequence[i].iHuff<<"/t"
<<frequence[i].HuffStat<<endl;
}
}
cout<<"按任意键确定"<<endl;
getch();
}
for(i=0;i<256;i++)//执行完成,把所分配的内存释放掉
if(frequence[i].frequence){
frequence[i].frequence=0;
frequence[i].iHuff=0;
delete frequence[i].HuffStat;
}
huffman.DestoryHuffman();
bst.Destory();
system("cls");
return true;
}
//************************************************************
bool huffmanconvertotext(){
char filename[26];
char filefrom[30];
char fileto[30];
char i;
int a=0,b,c;
ifstream fin;
ofstream fout;
system("cls");
cerr<<"/n/n/t请输入保存霍夫曼代码的文件名(不要输扩展名):";
cin>>filename;
sprintf(filefrom,"%s%s",filename,".txt");
sprintf(filename,"%s%s",filename,"HMcode.txt");
fin.open(filename);
fin>>b;//读入频数
while(!fin.eof()){
huffcode[b].iHuff=b;
fin>>c>>huffcode[b].frequence;
huffcode[b].character=c;
huffcode[b].HuffStat=new char[b+2];
fin>>huffcode[b].HuffStat;
fin>>b;
}
fin.close();
system("cls");
cerr<<"/n/t请输入保存文件的名称(不要输扩展名):";
cin>>filename;
sprintf(fileto,"%s%s",filename,".txt");
fin.open(filefrom);
fout.open(fileto);
while(!fin.eof()){
fin>>i;
if(i=='1')
a++;
else if(i=='0'){
fout<<huffcode[a].character;
a=0;
}
else{
cout<<"/n/t文件中保存的不是霍夫曼代码,退出执行!!!/n"
<<"/n/t按任意键确定"<<endl;
getch();
system("cls");
return false;
}
}
system("cls");
return true;
}
//************************************************************
bool Readfile(){
ifstream fin;
char filename[30];
char a;
system("cls");
cerr<<"/n/n/t请输入要显示的原文件名称(不要输扩展名):";
cin>>filename;
sprintf(filename,"%s%s",filename,".txt");
fin.open(filename);
a=fin.get();
while(!fin.eof()){
cout<<a;
a=fin.get();
}
cout<<"/n/n/t按任意键确定"<<endl;
getch();
return true;
}
//************************************************************
bool ReadHuffmanFile(){
ifstream fin;
char filename[30];
char a;
int i=0;
system("cls");
cerr<<"/n/n/t请输入要显示的霍夫曼编码文件名称:";
cin>>filename;
sprintf(filename,"%s%s",filename,".txt");
fin.open(filename);
fin>>a;
while(!fin.eof()){
if(a!='1'&&a!='0'){
cout<<"/n/n/t这文件不是霍夫曼编码文件,按任意键退出"<<endl;
getch();
return false;
}
cout<<a;
i++;
if(i>70){
cout<<endl;
i=0;
}
fin>>a;
}
cout<<"/n/n/t文件显示完成,按任意键确定"<<endl;
getch();
return true;
}
//************************************************************
void main(){
while(1){
system("cls");
cout<<"/n/n/t请选择操作代码:"
<<"/n/n/t1: 文本文件转换为霍夫曼代码"
<<"/n/n/t2: 霍夫曼代码转换为原文件"
<<"/n/n/t3: 显示源文件"
<<"/n/n/t4: 显示霍夫曼编码文件"
<<"/n/n/t 退出程序请按Esc键"
<<endl;
switch(getch()){
case '1':
if(textconvertohuffman())
cout<<"/n/t文件转换完成/n/n/t按任意键确定"<<endl;
else
cout<<"/n/t文件转换出错!!!/n/n/t按任意键确定"<<endl;
getch();
break;
case '2':
if(huffmanconvertotext())
cout<<"/n/t文件转换完成/n/n/t按任意键确定"<<endl;
else
cout<<"/n/t文件转换出错!!!/n/n/t按任意键确定"<<endl;
getch();
break;
case '3':if(!Readfile()){
cout<<"/n/n/t程序执行出错,按任意键确定"<<endl;
getch();
}
break;
case '4':if(!ReadHuffmanFile()){
cout<<"/n/n/t程序执行出错,按任意键确定"<<endl;
getch();
}
break;
case 27:return ;
default:cout<<"/n/n/t输入错误,按任意键重试"<<endl;
getch();break;
}
}
}
bool huffmantree::DestoryHuffman()
{
if(!root)
return false;
BSTNode *temp1=root,*temp2=root->rightchild;
while(temp2){
delete temp1->leftchild;
delete temp1;
temp1=temp2;temp2=temp2->rightchild;
}
delete temp2;
root=NULL;
return true;
}
void BST::Destory()
{
BSTNode *temp=root,*temp1;
if(!root)
return ;
while(temp->leftchild)//找第一个的节点,因为是线索化的
temp=temp->leftchild;
while(temp){
temp1=temp;
if(temp->Threaded)//如果当前节点已线索化
temp=temp->rightchild;
else{//当前节点没有线索化
temp=temp->rightchild;
while(temp&&temp->leftchild)//找最左边的节点
temp=temp->leftchild;
}
delete temp1;
}
root=NULL;
return ;
}