北邮信通院数据结构实验报告三哈夫曼编码器
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
数据结构实验报告
实验名称:实验三树——哈夫曼编/解码器
学生姓名:
班级:
班内序号:
学号:
日期:2014年12月11日
1.实验要求
利用二叉树结构实现赫夫曼编/解码器。
基本要求:
1、初始化(Init):能够对输入得任意长度得字符串s进行统计,统计每个
字符得频度,并建立赫夫曼树
2、建立编码表(CreateTable):利用已经建好得赫夫曼树进行编码,并
将每个字符得编码输出。
3、编码(Encoding):根据编码表对输入得字符串进行编码,并将编码后
得字符串输出。
4、译码(Decoding):利用已经建好得赫夫曼树对编码后得字符串进行译
码,并输出译码结果。
5、打印(Print):以直观得方式打印赫夫曼树(选作)
6、计算输入得字符串编码前与编码后得长度,并进行分析,讨论赫夫曼编
码得压缩效果。
测试数据:
I lovedata Structure, I loveputer。
I willtrymy best tostudy data Structure、
提示:
1、用户界面可以设计为“菜单”方式:能够进行交互。
2、根据输入得字符串中每个字符出现得次数统计频度,对没有出现得ﻩ字符一律不用编码。
2、程序分析
2、1存储结构
Huffman树
给定一组具有确定权值得叶子结点,可以构造出不同得二叉树,其中带权路径长度最小得二叉树称为Huffman树,也叫做最优二叉树。
weightlchildrchild parent
2-1-1-1
5-1-1-1
6-1-1-1
7-1-1-1
9-1-1-1
weight lchild rchildparent 2-1-15
5-1-15
6-1-16
7-1-16
9-1-17
7017
13238
16548
2967-1
2、2 关键算法分析
(1)计算出现字符得权值
利用ASCII码统计出现字符得次数,再将未出现得字符进行筛选,将出现得字符及頻数存储在数组a[]中。
void Huffman::Init()
{
ﻩintnNum[256]= {0};//记录每一个字符出现得次数int ch = cin、get();
int i=0;
ﻩwhile((ch!='\r') && (ch!='\n'))
ﻩ{
ﻩﻩnNum[ch]++; //统计字符出现得次数
ﻩstr[i++] = ch; //记录原始字符串ﻩch = cin、get(); //读取下一个字符
ﻩ}
str[i]='\0';
n = 0;
for ( i=0;i<256;i++)
ﻩ{
ﻩﻩif(nNum[i]>0) //若nNum[i]==0,字符未出现
ﻩ{
l[n] = (char)i;
ﻩa[n] = nNum[i];
n++;
ﻩ}
}
}
时间复杂度为O(1);
(2)创建哈夫曼树:
算法过程:
Huffman树采用顺序存储---数组;
数组得前n个结点存储叶子结点,然后就是分支结点,最后就是根结点;
首先初始化叶子结点元素—循环实现;
以循环结构,实现分支结点得合成,合成规则按照huffman树构成规则进行。
关键点:选择最小与次小结点合成。
void Huffman::CreateHTree()
{
ﻩHTree = newHNode [2*n-1];//根据权重数组a[0、、n-1] 初始化Huffman树for (int j = 0;j < n;j++)
{
ﻩHTree[j]、weight =a[j];
ﻩﻩHTree[j]、LChild = HTree[j]、RChild = HTree[j]、parent = -1;
ﻩ}
intx,y;
for (int i = n; i < 2*n-1; i++)//开始建Huffman树{
SelectMin(HTree, i, x, y);ﻩ//从1~i中选出两个权值最小得结点
HTree[x]、parent = HTree[y]、parent = i;
ﻩHTree[i]、weight = HTree[x]、weight+ HTree[y]、weight;
HTree[i]、LChild = x;
HTree[i]、RChild = y;
ﻩﻩHTree[i]、parent = -1;
ﻩ}
}
时间复杂度为O(n2)
void Huffman::SelectMin( HNode *hTree,int n, int &i1,int &i2 ) {
int i;
//找一个比较值得起始值
for(i=0; i<n; i++) //找i1
{ if(hTree[i]、parent==-1 )
{i1=i;break; }
}
i++;
for( ; i<n; i++) //找i2
{ if(hTree[i]、parent==-1 )
{ i2=i;break; }
}
if(hTree[i1]、weight>hTree[i2]、weight) //i1指向最小得
{int j=i2; i2=i1; i1= j; }//开始找最小得两个
i++;
for( ; i<n; i++)
{ if(hTree[i]、parent==-1
&& hTree[i]、weight < hTree[i1]、weight )
{ i2=i1; i1= i;}
else if( hTree[i]、parent==-1
&& hTree[i]、weight < hTree[i2]、weight)
{ i2=i; }
}
}
时间复杂度为O(n)
(3)创建编码表
算法过程:从叶子到根---自底向上
首先定义码表存储空间;
循环对n个叶子结点自底向上回溯到根,记下途径得左右关系,形成编码得逆序串;
将各个叶子结点对应得逆序串反序即可。
void Huffman::CreateCodeTable()
{
ﻩHCodeTable = new HCode[n]; //生成编码表
ﻩfor (int i=0;i<n;i++)ﻩ
{
ﻩHCodeTable[i]、data = l[i];
ﻩint child= i;ﻩ//孩子结点编号
ﻩint parent= HTree[i]、parent; //当前结点得父结点编号int k=0;
ﻩwhile(parent!=-1)
ﻩﻩ{
ﻩif (child==HTree[parent]、LChild) ﻩ//左孩子标‘0’ﻩﻩHCodeTable[i]、code[k]= '0';
ﻩelse
ﻩHCodeTable[i]、code[k] = '1' ; //右孩子标‘1’ﻩﻩk++;
ﻩchild = parent;ﻩ//迭代
ﻩﻩparent = HTree[child]、parent;
ﻩﻩ}
ﻩﻩHCodeTable[i]、code[k] = '\0';
Reverse(HCodeTable[i]、code); //将编码字符逆置
ﻩ}
}
时间复杂度为O(n)
(4)生成编码串
将输入得字符串得每一个字符与编码表比较
void Huffman::Encode(char *d)//编码,d为编码后得字符串{
ﻩﻩchar *s = str;
while(*s!='\0')
{
ﻩﻩﻩfor(int i=0;i<n;i++)
ﻩﻩﻩﻩif (*s == HCodeTable[i]、data )
ﻩﻩ{
ﻩﻩﻩstrcat(d, HCodeTable[i]、code);
ﻩﻩﻩbreak;
ﻩﻩﻩ}
ﻩﻩs++;
ﻩ}
}
时间复杂度为O(n)
(5)解码:
算法过程: 从根到叶子---自顶向下
基于huffman树存储数组,从根结点开始,依据输入待解码串s中码字0或1,分别向左或右跟踪至叶子结点,叶子结点对应得字符(见码表),即为解码得到得字符;
只要s串为结束,重复上述过程
void Huffman::Decode(char* s, char *d)ﻩ//解码,s为编码串,d为解码后得字符串
{
while(*s!='\0')
{
ﻩint parent = 2*n-2;ﻩﻩﻩ//根结点在HTree中得下标
ﻩwhile (HTree[parent]、LChild!=-1) //如果不就是叶子结点ﻩ{
ﻩif (*s=='0')
parent = HTree[parent]、LChild;
else
ﻩﻩparent = HTree[parent]、RChild;
ﻩs++;
ﻩﻩ}ﻩ
ﻩ*d =HCodeTable[parent]、data;
ﻩd++;
ﻩ}
}
时间复杂度为O(n)
2、3 其她
(1)哈夫曼树得输出就是以凹入表示法来实现得,具体算法如下:
void Huffman::Print(int i,int m)
{
ﻩif(HTree[i]、LChild == -1)
ﻩcout<<setfill(' ')<<setw(m+1)<<l[i]<<setfill('-')<<setw(10-m)<<'\n';
else
{
ﻩcout<<setfill(' ')<<setw(m+1)<<HTree[i]、weight<<setfill('-')<<setw(10-m)<<'\n';
ﻩPrint(HTree[i]、LChild,m+1);
ﻩﻩPrint(HTree[i]、RChild,m+1);
}
}
(2)统计字符頻数时,利用字符得ASCII码进行计数统计,调用了cin、get()函数
3、程序运行
程序框图:
程序源代码:
#includ
#include <iomanip>
using namespace std;
struct HNode
{
ﻩint weight; //结点权值
int parent;ﻩ//双亲指针ﻩintLChild;ﻩ//左孩子指针
ﻩint RChild; ﻩ//右孩子指针
struct HCode
{
char data;
charcode[100];
};
class Huffman
{
private:
HNode*HTree; //Huffman树
HCode* HCodeTable;//Huffman编码表
charstr[1024]; //输入得原始字符串ﻩchar l[256]; //叶子节点对应得字符
int a[256];//记录每个出现得字符得个数
public:
int n; //叶子节点数
ﻩvoidInit();//初始化
void CreateHTree(); ﻩ //创建huffman树
void CreateCodeTable(); //创建编码表
ﻩvoid PrintTable();
void Encode(char*d);//编码
void Decode(char *s, char*d); //解码
voidPrint(int i,int m); //打印Huffman树
voidSelectMin(HNode *hTree,int n, int &i1, int&i2);//找出最小得两个权值
void Reverse(char*s); //逆序
ﻩvoid pare(char*d);//比较压缩大小
~Huffman(); //析构
};
voidHuffman::Init()
{
int nNum[256]={0}; //记录每一个字符出现得次数
ﻩint ch =cin、get();
int i=0;
ﻩwhile((ch!='\r') && (ch!='\n'))
ﻩ{
ﻩﻩnNum[ch]++; //统计字符出现得次数str[i++] = ch;//记录原始字符串
ch = cin、get();//读取下一个字符 }
str[i]='\0';
for(i=0;i<256;i++)
ﻩ{
if(nNum[i]>0)//若nNum[i]==0,字符未出现ﻩﻩ{
ﻩﻩl[n]= (char)i;
ﻩﻩﻩa[n]= nNum[i];
ﻩﻩn++;
}
}
}
void Huffman::CreateHTree()
{
ﻩHTree=newHNode [2*n-1]; //根据权重数组a[0、、n-1] 初始化Huffman树
for (intj = 0;j < n;j++)
ﻩ{
ﻩHTree[j]、weight=a[j];
ﻩHTree[j]、LChild =HTree[j]、RChild =HTree[j]、parent =-1;ﻩ}
ﻩintx,y;
ﻩfor (int i= n;i <2*n-1;i++)ﻩ//开始建Huffman树
{
SelectMin( HTree, i,x, y); ﻩ //从1~i中选出两个权值最小得结点
ﻩHTree[x]、parent = HTree[y]、parent= i;
ﻩﻩHTree[i]、weight =HTree[x]、weight+ HTree[y]、weight;ﻩHTree[i]、LChild =x;
ﻩHTree[i]、RChild =y;
ﻩHTree[i]、parent=-1;
}
}
void Huffman::SelectMin( HNode*hTree,intn,int &i1, int &i2 )
{
inti;
//找一个比较值得起始值
for(i=0; i<n;i++) //找i1
{ if(hTree[i]、parent==-1 )
{i1=i; break; }}
i++;
for( ;i<n; i++)//找i2
{if(hTree[i]、parent==-1)
{ i2=i;break;} }
if(hTree[i1]、weight>hTree[i2]、weight)//i1指向最小得
{int j=i2; i2=i1;i1 =j; }
//开始找最小得两个
i++;
for(; i<n; i++)
{if(hTree[i]、parent==-1
&&hTree[i]、weight < hTree[i1]、weight ) { i2=i1; i1 =i; }
else if(hTree[i]、parent==-1
&& hTree[i]、weight < hTree[i2]、weight){ i2=i; }
}
}
voidHuffman::Print(int i, intm)
{
ﻩif (HTree[i]、LChild ==-1)
ﻩcout<<setfill(' ')<<setw(m+1)<<l[i]<<setfill('-')<<setw(10-m)<<'\n';
ﻩelse
{
ﻩcout<<setfill('')<<setw(m+1)<<HTree[i]、weight<<setfill('-')<<setw(10-m)<<'\n';
Print(HTree[i]、LChild,m+1);
ﻩPrint(HTree[i]、RChild,m+1);
ﻩ}
}
void Huffman::CreateCodeTable()
{
ﻩHCodeTable =new HCode[n]; //生成编码表
ﻩfor(int i=0;i<n;i++)ﻩ
{
ﻩﻩHCodeTable[i]、data =l[i];
ﻩint child= i; ﻩﻩﻩ//孩子结点编号
int parent= HTree[i]、parent;//当前结点得父结点编号
ﻩintk=0;
ﻩwhile(parent!=-1)
ﻩ{
ﻩif(child==HTree[parent]、LChild)ﻩﻩ//左孩子标‘0’
ﻩﻩﻩHCodeTable[i]、code[k] ='0';
else
ﻩﻩ HCodeTable[i]、code[k]='1' ;ﻩ//右孩子标‘1’ﻩk++;
child = parent;ﻩﻩﻩ//迭代
ﻩparent = HTree[child]、parent;
ﻩﻩ}
ﻩHCodeTable[i]、code[k]= '\0';
Reverse(HCodeTable[i]、code);//将编码字符逆置ﻩ}
}
voidHuffman::PrintTable()
{
ﻩfor(int i=0;i<n;i++)
ﻩcout<<HCodeTable[i]、data<<'\t'<<HCodeTable[i]、code<<e ndl;
}
void Huffman::Encode(char *d)//编码,d为编码后得字符串
{
ﻩchar*s=str;
ﻩﻩwhile(*s!='\0')
{
ﻩfor(inti=0;i<n;i++)
ﻩﻩif (*s==HCodeTable[i]、data )
ﻩﻩﻩ{
ﻩﻩﻩstrcat(d,HCodeTable[i]、code);
ﻩﻩﻩﻩﻩbreak;
ﻩﻩ}
ﻩﻩﻩs++;
ﻩ}
}
void Huffman::Decode(char* s,char *d)ﻩ//解码,s为编码串,d为解码后得字符串
{
ﻩwhile(*s!='\0')
ﻩ{
ﻩint parent = 2*n-2;ﻩﻩﻩﻩ//根结点在HTree中得下标
while (HTree[parent]、LChild!=-1)//如果不就是叶子结点
{
ﻩif (*s=='0')
ﻩparent=HTree[parent]、LChild;
else
ﻩparent =HTree[parent]、RChild;
ﻩﻩﻩs++;
ﻩ}ﻩ
*d=HCodeTable[parent]、data;
d++;
}
}
voidHuffman::Reverse(char*s)//换序
{
char ch;
int len =strlen(s);
for(int i=0;i<len/2;i++)
ﻩ{
ﻩﻩch =s[i];
ﻩﻩs[i] =s[len-i-1];
ﻩﻩs[len-i-1]=ch;
ﻩ}
}
voidHuffman::pare(char*d)//比较压缩大小
{
cout<<"编码前:"<<strlen(str)*8<<"bit"<<endl;
cout<<"编码后:"<<strlen(d)<<"bit"<<endl;
}
Huffman::~ Huffman()//析构函数
{
delete []HTree;
ﻩdelete[]HCodeTable;
}
void main()
{
HuffmanHFCode;
ﻩchar d[1024]={0};
ﻩchars[1024]={0};
cout<<"请输入要编码得字符串:";
HFCode、Init();
HFCode、CreateHTree();
HFCode、CreateCodeTable();
ﻩHFCode、Encode(d);
HFCode、Decode(d,s);
int m;
ﻩcout<<"欢迎使用\n"<<"1、打印哈夫曼树\n"<<"2、打印哈夫曼编码表\n"<<"3、打印编码\n"<<"4、打印解码\n"<<"5、压缩比"<<endl;
while(1)
{cin>>m;
ﻩswitch(m)
{
ﻩcase 1:
ﻩ{
ﻩﻩHFCode、Print(2*HFCode、n-2,1);
ﻩbreak;
ﻩ}
ﻩcase 2:
ﻩﻩ{
HFCode、PrintTable( );
break;
ﻩﻩ}
case 3:
{
cout<<"编码结果:"<<d<<endl;
ﻩﻩbreak;
}
ﻩcase 4:
ﻩﻩ{
ﻩﻩcout<<"解码结果:"<<s<<endl;
ﻩbreak;
ﻩ}
ﻩcase 5:
ﻩﻩ{
ﻩHFCode、pare(d);
ﻩﻩ}
}
}
}
运行结果:
4、总结
在编程时,最开始在字符统计时出现了空格无法统计得问题,后来用cin、get()函数进行统计。
最后由于有一些字符没有出现过,所以还需要进行筛选。
在输出哈夫曼树时,采用了凹入函数法进行输出,更加直观。
创建编码表时,开始就是自下到上得进行遍历,所以最后还需要进行逆序,形成最终得编码表。
创建编码树得时候,没有正确运用指针得传递,结果出现了很多问题,各种内存访问错误,最后经过细细地
从头到尾检查,发现了就是在形式参数得地方出现了错误,在获取两个最小权值得结点得时候应该用引用,改过来之后错误没有了。
打印赫夫曼树就是最难得部分,一开始没有找到合适得办法,出现了很多问题,最后采用凹入表示打印得方法,从最右边得结点开始一行一行得打印,最后问题也能解决了。
调试时,出现得问题就是在进行编码时循环出现了错误,导致运行后编码变少,通过修改问题得以解决。
通过哈夫曼编码得程序设计,更加深入得学习了哈夫曼树编码得思想,了解了不等长编码得思想,同时也通过实践明白了编码器得原理,在编码过程中,面对出现得问题,也学习了字符串得相关函数得运用,更加了解树得存储结构,受益匪浅。