中南大学 信息论与编码实验2-实验报告
中南大学
----------信息论与编码
题目关于编码的实验
学生姓名
指导教师
学院信息科学与工程学院
学号
专业班级
2015年12月
目录
一、实验目的 (3)
二、实验原理 (3)
三、实验内容 (5)
四、实验要求 (5)
五、代码调试结果 (6)
六、实验代码 (8)
一、实验目的
1.掌握香农码和 Huffman 编码原理和过程。
2.熟悉matlab软件的基本操作,练习使用matlab实现香农码和Huffman
编码。
3.熟悉C/C++语言,练习使用C/C++实现香农码和Huffman编码。
4.应用Huffman编码实现文件的压缩和解压缩。
二、实验原理
香农编码:香农第一定理指出了平均码长与信源之间的关系,同时也指出了可以通过编码使平均码长达到极限值,这是一个很重要的极限定理。如何构造这种码?香农第一定理指出,选择每个码字的长度K
i
满足下式
I(x
i )≤K﹤I(x
i
)+1, i
就可以得到这种码。这种编码方法就是香农编码。
香农第一定理:
设离散无记忆信源为
S P=s1 s2 .............sq p(s1) p(s2) .....p(sq)
熵为H(S),其N 次扩展信源为
熵为H(SN)。码符号集X=(x1,x2,…,xr)。先对信源S N 进行编码,总可以
找到一种编码方法,构成惟一可以码,使S 中每个信源符号所需的平均码长满
足:
当N→∞时
L 是平均码长λ是ai 对应的码字长度
哈夫曼编码:要完成哈夫曼的编码和解码需要首先建立哈夫曼树,之后对所有字符根据权重进行编码,最后再对文件内容进行编码和解码。
首先定义适合哈夫曼树的节点类型,需要定义的有当前节点的字符,当前节点的左子、右子和父亲指针。在建立哈夫曼树之前还需要对出现的字符和权重进行统计和记录,并且定义一个可以筛选出最小权重的函数。
初始化树节点之后开始建立哈夫曼树。先在所有可能出现的字符中筛选出当前权重最小的两个字符,将这两个字符分别作为新节点的左子和右子建立一个小的二叉树,并将两个字符的权重之和赋值给新节点,将新二叉树放入筛选字符中,再将筛选过的两个字符从筛选列表中淘汰掉。依次对列表中剩下的字符进行权重最小的筛选,直到根节点(如果编码表共有N个字符,则2*N-1就为最终根节点)为止,也就是当筛选列表为空的时候,哈夫曼树即建立完成。
对于哈夫曼编码树来说,由于哈夫曼编码是前缀码,所以所有要编码的字符最终都将是这颗树的叶子节点,而其它节点并没有真正的字符意义。即当哈夫曼编码树建立之后,对树的所有叶子节点进行打印可知道是否有字符遗漏或多余。
建立哈夫曼编码表。
建立编码表时要根据每个出现的字符的权重对建立的哈夫曼树的每个叶子节点进行编码。编码时要从叶子节点出发向根节点进行逆向编码。判断如果当前节点为左子则对其编码‘0’,如果当前节点为右子则对其编码‘1’。以此类推进行编码直到根节点为止。此时的编码是逆向的,所以需要将码值逆向存储。依次对每一个叶子节点进行编码操作,即可得到当前哈夫曼树的编码表。
对于码值的逆向存储可以使用栈结构,先将一个码的每一步编码存入栈,再在一个码结束后出栈至空。当然也可以定义一个字符型数组,将值从后向前存入数组,再将数组有值部分粘贴到新的数组中进行存储。本次采用了后者,因为个人认为为此一步操作建立栈结构不划算,而且前一个设计也已经熟练掌握了栈的方法,此处进行新的尝试会更好。
对文件进行编码。
首先需要建立一个原始文件,在文件中输入需要编码的内容。之后将文件打开,将其中的内容存储到字符串中以便程序编码调用。开始对需要编码的字符进行编码,将字符逐一读取与刚刚建立的编码表中的每个叶子节点代表的字符进行比较,找出相同的对象,并将当前节点的编码打印到屏幕,并将编码存入到新建
的密码文件当中。
对文件进行解码。
先打开密码文件,将之前编码后得到的密文内容存储到字符串中以便解码调用。开始对密文的字符串进行解码,树索引从根节点开始走,当密文中的当前字符是‘0’的时候,则索引走向左子节点;当是‘1’的时候,则走向右子节点。以此类推,一直走到叶子节点为止,则当前叶子节点所代表的字符即为前一段密文的解码结果,。再对下一个字符依次从根节点开始解码,如此循环对每一段密文进行解码直到解码结束。将解码打印到屏幕,并将解码结果存入到新的解码文件当中。
在解码之前,还应该先确认之前是否建立了哈夫曼树并且是否构建了编码表。不过由于本次将‘a’到‘z’都进行了编码,所以此步省略了,因为编码表是唯一的。需要的时候可以在Encoder 函数中先进行判定。
将编码和解码写在了一起,可以在运行时进行选择调用。
三、实验内容
1.使用matlab实现香农码和Huffman 编码,并自己设计测试案例。
2.使用C\C++实现香农码和Huffman编码,并自己设计测试案例。
3.可以用任何开发工具和开发语言,尝试实现Huffman 编码应用在数据文
件的压缩和解压缩中,并自己设计测试案例。
四、实验要求
1. 提前预习实验,认真阅读实验原理以及相应的参考书。
2. 认真高效的完成实验,实验中服从实验室管理人员以及实验指导老师的
管理。
3. 认真撰写实验报告,内容可以自己编排,可以考虑包括以下一些方面:原理概述、程序设计与算法描述、源程序及注释(程序太长可以只选取
重要部分)、运行输出结果实例、调试和运行程序过程中产生的问题及
采取的措施、对实验的讨论分析、总结。
五、代码调试结果
香农编码:
哈夫曼编码:
C语言之香农编码:
C语言之哈夫曼编码:
六、实验代码
Matlab实现
香农编码:
clear;
A=input('please input a number:') %提示输入界面
A=fliplr(sort(A));%降序排列
[m,n]=size(A);
for i=1:n
B(i,1)=A(i);%生成B的第1列
end
%生成B第2列的元素
a=sum(B(:,1))/2;
for k=1:n-1
if abs(sum(B(1:k,1))-a)<=abs(sum(B(1:k+1,1))-a) break;
end
end
for i=1:n%生成B第2列的元素
if i<=k
B(i,2)=0;
else
B(i,2)=1;
end
end
%生成第一次编码的结果
END=B(:,2)';
END=sym(END);
%生成第3列及以后几列的各元素
j=3;
while (j~=0)
p=1;
while(p<=n)
x=B(p,j-1);
for q=p:n
if x==-1
break;
else
if B(q,j-1)==x
y=1;
continue;
else
y=0;
break;
end
end
end
if y==1
q=q+1;
end
if q==p|q-p==1
B(p,j)=-1;
else
if q-p==2
B(p,j)=0;
END(p)=[char(END(p)),'0'];
B(q-1,j)=1;
END(q-1)=[char(END(q-1)),'1'];
else
a=sum(B(p:q-1,1))/2;
for k=p:q-2
if abs(sum(B(p:k,1))-a)<=abs(sum(B(p:k+1,1))-a);
break;
end
end
for i=p:q-1
if i<=k
B(i,j)=0;
END(i)=[char(END(i)),'0'];
else
B(i,j)=1;
END(i)=[char(END(i)),'1'];
end
end
end
end
p=q;
end
C=B(:,j);
D=find(C==-1);
[e,f]=size(D);
if e==n
j=0;
else
j=j+1;
end
end
B
END
for i=1:n
[u,v]=size(char(END(i)));
L(i)=v;
end
avlen=sum(L.*A)
哈夫曼编码:p=input('please input a number:') %提示输入界面
n=length(p);
for i=1:n
if p(i)<0
fprintf('\n The probabilities in huffman can not less than 0!\n');
p=input('please input a number:') %如果输入的概率数组中有小于0 的值,则重新输入概率数组
end
end
if abs(sum(p)-1)>0
fprintf('\n The sum of the probabilities in huffman can more than 1!\n');
p=input('please input a number:') %如果输入的概率数组总和大于1,则重新输入概率数组
end
q=p;
a=zeros(n-1,n); %生成一个n-1 行n 列的数组
for i=1:n-1
[q,l]=sort(q); %对概率数组q 进行从小至大的排序,并且用l 数组返回一个数组,该数组表示概率数组q 排序前的顺序编号
a(i,:)=[l(1:n-i+1),zeros(1,i-1)]; %由数组l 构建一个矩阵,该矩阵表明概率合并时的顺序,用于后面的编码
q=[q(1)+q(2),q(3:n),1]; %将排序后的概率数组q 的前两项,即概率最小的两个数加和,得到新的一组概率序列
end
for i=1:n-1
c(i,1:n*n)=blanks(n*n); %生成一个n-1 行n 列,并且每个元素的的长度为n 的空白数组,c 矩阵用于进行huffman 编码,并且在编码中与a 矩阵有一定的对应关系
end
c(n-1,n)='0'; %由于a 矩阵的第n-1 行的前两个元素为进行huffman 编码加和运算时所得的最
c(n-1,2*n)='1'; %后两个概率,因此其值为0 或1,在编码时设第n-1 行的第一个空白字符为0,第二个空白字符1。
for i=2:n-1
c(n-i,1:n-1)=c(n-i+1,n*(find(a(n-i+1,:)==1))-(n-2):n*(find(a(n-i+1,:)==1))); %矩阵c 的第n-i 的第一个元素的n-1 的字符赋值为对应于a 矩阵中第n-i+1 行中值为1 的位置在c 矩阵中的编码值
c(n-i,n)='0'; %根据之前的规则,在分支的第一个元素最后补0
c(n-i,n+1:2*n-1)=c(n-i,1:n-1); %矩阵c 的第n-i 的第二个元素的n-1 的字符与第n-i 行的第一个元素的前n-1 个符号相同,因为其根节点相同c(n-i,2*n)='1'; %根据之前的规则,在分支的第一个元素最后补1
for j=1:i-1
c(n-i,(j+1)*n+1:(j+2)*n)=c(n-i+1,n*(find(a(n-i+1,:)==j+1)-1)+1:n*find(a(n-i+1,:)= =j+1)); %矩阵c 中第n-i 行第j+1 列的值等于对应于a 矩阵中第n-i+1 行中值为j+1 的前面一个元素的位置在c 矩阵中的编码值
end
end %完成huffman 码字的分配
for i=1:n
h(i,1:n)=c(1,n*(find(a(1,:)==i)-1)+1:find(a(1,:)==i)*n); %用h 表示最后的huffman 编码,矩阵h的第i 行的元素对应于矩阵c 的第一行的第i 个元素ll(i)=length(find(abs(h(i,:))~=32)); %计算每一个huffman 编码的长度
end
disp('二元Huffman编码平均码长')
l=sum(p.*ll);%计算平均码长
disp(l)
%fprintf('\n huffman code:\n');
h
disp('信源熵')
hh=-sum(p.*log2(p)) %计算信源熵
%fprintf('\n the huffman effciency:\n');
disp('编码效率')
t=hh/l %计算编码效率
C语言实现
香农编码:
#include
#include
#include
#include
class T
{
public:
T() {}
~T();
void Create();
void Coutpxj();
void Coutk();
void Coutz();
void Print();
protected:
int n;
double *p;
double *pxj;
int *k;
double *mz;
};
void T::Create()
{
cout<<"输入信源符号个数:";
cin>>n;
p=new double[n];
cout<<"请输入这几个"< int i; for(i=0;i cin>>p[i]; pxj=new double[n]; k=new int[n]; mz=new double[n]; double sum=0.0; for(i=0;i sum+=p[i]; if(sum!=1.0) throw 1; else { for(i=0;i { int k=i; for(int j=i+1;j if(p[k] double m=p[i]; p[i]=p[k]; p[k]=m; } } } T::~T() { delete p; delete pxj; delete k; delete mz; } void T::Coutpxj() { pxj[0]=0; for(int i=1;i { pxj[i]=0; for(int j=0;j pxj[i]+=p[j]; } } void T::Coutk() { for(int i=0;i { double d=(-1)*(log(p[i])/log(2)); if(d-(int)d>0) k[i]=(int)d+1; else k[i]=(int)d; } } void T::Print() { cout<<"Xi"< < < < < int i; for(i=0;i { cout<<"X"< < < mz[i]=pxj[i]; for(int j=0;j { if(2*mz[i]-1>=0) { cout<<1; mz[i]=2*mz[i]-1; } else { cout<<0; mz[i]=2*mz[i]; } } cout< } double K=0.0,H=0.0,Y; for(i=0;i { K+=(double)p[i]*k[i]; H+=(-1)*p[i]*(log10(p[i])/log10(2.0)); } Y=H/K; cout<<"平均码长:"< cout<<"信源熵:"< cout<<"编码效率:"< } void main() { T t;int e; try { t.Create(); t.Coutpxj(); t.Coutk(); t.Print(); } catch(int e) {if(e==1) cout<<"输入错误,请重新运行";} } 哈夫曼编码:#include #include #include #define MAXLEN 100 typedef struct { int weight; int lchild; int rchild; int parent; char key; }htnode; typedef htnode hfmt[MAXLEN]; int n; void inithfmt(hfmt t)//对结构体进行初始化 { int i; printf("\n"); printf("------------------------------------------------------------------\n"); printf("******************************输入区******************************\n"); printf("\n请输入n="); scanf("%d",&n); getchar(); for(i=0;i<2*n-1;i++)//对结构体进行初始化 { t[i].weight=0; t[i].lchild=-1; t[i].rchild=-1; t[i].parent=-1; } printf("\n"); } void inputweight(hfmt t)//输入函数 { int w;//w表示权值 int i; char k;//k表示获取的字符 for(i=0;i { printf("请输入第%d个字符:",i+1); scanf("%c",&k); getchar(); t[i].key=k; printf("请输入第%d个字符的权值:",i+1); scanf("%d",&w); getchar(); t[i].weight=w; printf("\n"); } } void selectmin(hfmt t,int i,int *p1,int *p2)//选中两个权值最小的函数{ long min1=999999; long min2=999999; int j; for(j=0;j<=i;j++)//选择最小权值字符的下标返回 if(t[j].parent==-1) if(min1>t[j].weight) { min1=t[j].weight; *p1=j; } for(j=0;j<=i;j++)//选择次小权值字符的下标还回 if(t[j].parent==-1) if(min2>t[j].weight && j!=(*p1))//注意 j!=(*p1)) { min2=t[j].weight; *p2=j; } } void creathfmt(hfmt t)//创建哈夫曼树的函数 { int i,p1,p2; inithfmt(t); inputweight(t); for(i=n;i<2*n-1;i++) { selectmin(t,i-1,&p1,&p2); t[p1].parent=i; t[p2].parent=i; t[i].lchild=p1; t[i].rchild=p2; t[i].weight=t[p1].weight+t[p2].weight; } } void printhfmt(hfmt t)//打印哈夫曼树 { int i; printf("------------------------------------------------------------------\n"); printf("**********************哈夫曼编数结构:*****************************\n"); printf("\t\t权重\t父母\t左孩子\t右孩子\t字符\t"); for(i=0;i<2*n-1;i++) { printf("\n"); printf("\t\t%d\t%d\t%d\t%d\t%c",t[i].weight,t[i].parent,t[i].lchild,t [i].rchild,t[i].key); } printf("\n------------------------------------------------------------------\n"); printf("\n\n"); } void hfmtpath(hfmt t,int i,int j)//编码的重要哈夫曼树路径递归算法 { int a,b; a=i; b=j=t[i].parent; if(t[j].parent!=-1) { i=j; hfmtpath(t,i,j); } if(t[b].lchild==a) printf("0"); else printf("1"); } void phfmnode(hfmt t)//对字符进行初始编码 { int i,j,a; printf("\n------------------------------------------------------------------\n"); printf("**************************哈夫曼编码******************************"); for(i=0;i { j=0; printf("\n"); printf("\t\t%c\t",t[i].key,t[i].weight); hfmtpath(t,i,j); } printf("\n------------------------------------------------------------------\n"); } void encoding(hfmt t)//对用户输入的电文进行编码 { char r[1000];//用来存储输入的字符串 int i,j; printf("\n\n请输入需要编码的字符:"); gets(r); printf("编码结果为:"); for(j=0;r[j]!='\0';j++) for(i=0;i if(r[j]==t[i].key) hfmtpath(t,i,j); printf("\n"); } void decoding(hfmt t)//对用户输入的密文进行译码 { char r[100]; int i,j,len; j=2*n-2;//j初始从树的根节点开始 printf("\n\n请输入需要译码的字符串:"); gets(r); len=strlen(r); printf("译码的结果是:"); for(i=0;i