基于哈夫曼树的文本数据压缩

基于哈夫曼树的文本数据压缩

课题内容:
1、学习哈夫曼编码原理和哈夫曼树的构造方法;
2、针对序列(whatever is worth doing is worth doing well,包含空格),利用matlab绘制对应的哈夫曼树以及计算编码后的序列。

(本课题是西安理工大学MATLAB课程设计课题之一,由本人编辑,仅供参考)

在这里我们先了解一下哈夫曼树构建原理

思路1.掌握知识点,明确要解决的问题:

本课题主要要求掌握两个知识点:
1、哈夫曼编码原理
2、哈夫曼树的构造方法
本课题主要解决两个问题:
1、绘制哈夫曼树
2、对哈夫曼树每个结点进行编码并导出
对于构造与绘制哈夫曼树:
需要掌握哈夫曼树原理,将特殊序列中的字符依据出现概率排序后由小到大从树的底部向上构造一个完整的哈夫曼树。
对于哈夫曼树编码:
对已经构造完成的树进行编码,对每一个根节点的左右叶子结点定义编码为0和1,对每一个出现的字符进行遍历显示。

对于构造哈夫曼树:
哈夫曼树(即二叉树)原理[[1]]:1952年, David A. Huffman提出了一个不同的算法,这个算法可以为任何的可能性提供出一个理想的树。香农-范诺编码(Shanno-Fano)是从树的根节点到叶子节点所进行的的编码,哈夫曼编码算法却是从相反的方向,暨从叶子节点到根节点的方向编码的。
为每个符号建立一个叶子节点,并加上其相应的发生频率
当有一个以上的节点存在时,进行下列循环:
把这些节点作为带权值的二叉树的根节点,左右子树为空
选择两棵根结点权值最小的树作为左右子树构造一棵新的二叉树,且至新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
把权值最小的两个根节点移除
将新的二叉树加入队列中.
最后剩下的节点暨为根节点,此时二叉树已经完成。

对于哈夫曼编码[[2]]:
在数据通信中,需要将传送的文字转换成二进制的字符串,用0,1码的不同排列来表示字符。例如,需传送的报文为“AFTER DATA EAR ARE ART AREA”,这里用到的字符集为“A,E,R,T,F,D”,各字母出现的次数为{8,4,5,3,1,1}。现要求为这些字母设计编码。要区别6个字母,最简单的二进制编码方式是等长编码,固定采用3位二进制,可分别用000、001、010、011、100、101对“A,E,R,T,F,D”进行编码发送,当对方接收报文时再按照三位一分进行译码。显然编码的长度取决报文中不同字符的个数。若报文中可能出现26个不同字符,则固定编码长度为5。然而,传送报文时总是希望总长度尽可能短。在实际应用中,各个字符的出现频度或使用次数是不相同的,如A、B、C的使用频率远远高于X、Y、Z,自然会想到设计编码时,让使用频率高的用短码,使用频率低的用长码,以优化整个报文编码。
为使不等长编码为前缀编码(即要求一个字符的编码不能是另一个字符编码的前缀),可用字符集中的每个字符作为叶子结点生成一棵编码二叉树,为了获得传送报文的最短长度,可将每个字符的出现频率作为字符结点的权值赋予该结点上,显然字使用频率越小权值越小,权值越小叶子就越靠下,于是频率小编码长,频率高编码短,这样就保证了此树的最小带权路径长度效果上就是传送报文的最短长度。因此,求传送报文的最短长度问题转化为求由字符集中的所有字符作为叶子结点,由字符出现频率作为其权值所产生的哈夫曼树的问题。利用哈夫曼树来设计二进制的前缀编码,既满足前缀编码的条件,又保证报文编码总长最短。

[[1]]:链接: [https://blog.csdn.net/abcjennifer/article/details/8020695](这里引用了该作者的部分内容).
[[2]]:朱怀宏 吴楠 夏黎春作者. 利用优化哈夫曼编码进行数据压缩的探索. 南京市:南京大学出版社,2004年(引用至百度百科)

直接上代码

代码中有很多注释,是以基础知识为基础做出来的
经指正,代码中有少量变量名称定义不够明确,可以自行改正(突出变量含义的同时尽肯能保证字符长度较短)

// function HuffmanCode=coder()
clear;
clc;
% whatever is worth doing is worth doing well
array=input('请输入您的内容:','s');%输入字符序列
a=unique(array);%整合序列中出现的字符
b=length(array);%计算字符序列长度
num = zeros(1,length(a));%指定数组长度,节省空间
for j=1:length(a)%统计字符序列中各字符出现次数
    num(j)=length(strfind(array,a(j)));
end
probability(1:length(a))=num(1:length(a))/b;%统计字符序列中各字符出现概率

%/---声明一个tree(i)结构体,一个结点包括有6个变量存储单元。---/
for i=1:length(a)
    field1 = 'num'; value1 = i;% tree.num记录该结点的序号
    field2 = 'character';  value2 = a(i);% tree.character记录该结点的字符
    field3 = 'probability';  value3 = probability(i);% tree.probability记录该结点的概率值
    field4 = 'parent';  value4 = 0;% tree.parent记录该结点的父结点编号
    field5 = 'lr';  value5 = 0;% tree.lr记录该结点是左结点还是右结点(其中左结点为“0”,右结点为“1)
    field6 = 'root';  value6 = 1;% tree.root记录该结点是否为根结点标志(该结点为根结点记为“1”,否则记为“0)
    tree(i)=struct(field1,value1,field2,value2,field3,value3,field4,value4,field5,value5,field6,value6);
end

%/---找出字符序列中概率最小值和次最小值编码---/
function [l,r]=findminval(tree)  
    firpro=realmax;secpro=realmax;
    first=1;second=1;
    for ia=1:2*n-1%找出最小概率值,最小概率值所在结点和最大概率值,最大概率值所在结点
        if tree(ia).root==1%在root=1的节点中寻找最小值和次最小值
            if firpro>tree(ia).probability
                if secpro>firpro
                    second=first;secpro=firpro;
                end
                first=ia;firpro=tree(ia).probability;
            elseif secpro>tree(ia).probability
             second=ia;secpro=tree(ia).probability;
            end
        end
    end
    l=min([first,second]);r=max([first,second]);%最小值编码作为l结点,次最小值编码作为r结点
end

%/---生成哈夫曼树---/
function HuffmanTree=HuffmanTree(tree)
n=size(a,2);%得到字符个数
for ib=(n+1):(2*n-1)
    tree(ib).num=ib;%设置结点序号
    tree(ib).parent=0;%设置父结点为0
    tree(ib).lr=0;%初始化结点为左结
    tree(ib).root=0;%设置结点是否在集合
end
for ic=(n+1):(2*n-1)
    [l,r]=findminval(tree);%找到集合中两个最小的值的序号
    tree(ic).probability=tree(l).probability+tree(r).probability;%得到父结点概率值
    tree(ic).root=1;%设置新构造结点在集合中
    tree(l).parent=ic;tree(r).parent=ic;%设置父结点序号
    tree(l).lr=0;tree(r).lr=1;%设置左右标志
    tree(l).root=0;tree(r).root=0;%设置不在集合中
end
HuffmanTree=tree;
     disp(HuffmanTree);%将构建完成的树赋值给HuffmanTree
end

%/---对生成的哈夫曼树进行编码---/
code=[];%定义一个空矩阵用于暂时存放每一个字符的编码
Code=cell(1,b);%定义一个元素个数为字符个数的元胞数组用于存放输入字符串的编码
n=size(a,2);
tree(1:2*n-1)=HuffmanTree(tree);%调用构建好的哈弗曼树
t=1;
for ie=1:b
    for ig=1:n
        m=1;
        if tree(ig).character==array(ie)%寻找输入字符序列中出现的字符对应哈弗曼树中的位置序号
            code(m)=tree(ig).lr;%记录该结点lr状态
        ih=ig;
        while tree(tree(ih).parent).parent~=0%循环控制,直至父亲节点没有父亲节点时停止循环
                ih=tree(tree(ih).parent).num;%找父亲结点
                m=m+1;
                code(m)=tree(ih).lr;%记录父亲节点lr状态
        end
        end
    end
    code=fliplr(code);%将由下到上记录的结点lr状态数组置反,得到指定结点的编码
    Code{1,t}=code();%将每一个字符的编码依次赋值给元胞数组Code
    t=t+1;
    code=[];%数组置空,继续记录结点lr状态
end
HuffmanCode=Code;
for ii=1:b
    disp(array(ii))
    disp(HuffmanCode{1,ii})%显示哈夫曼编码
end

%% /---绘制哈夫曼树---/
x=(1:2*n-1);y=(1:2*n-1);xx=1.00;yy=1.00;%定义树上结点在图中的位置数组和左右结点间间距
x(2*n-1)=0;y(2*n-1)=0;%使树的根结点位置在(0,0)处
plot(x(2*n-1),y(2*n-1),'*r');%画出根结点
text(x(2*n-1),y(2*n-1),['(',num2str(tree(2*n-1).num),'/',num2str(tree(2*n-1).character),')'],'color','b');%在根节点处标注信息
axis off;%关闭坐标轴显示
axis([-1,1,-1.1,0]);%设置坐标轴范围
hold on;%保留图形
im=0;in=1;
xx=xx/2;yy=yy/2;%缩小结点间间距
for ij=(2*n-1):-1:1%循环控制,由深度较小结点至深度较大结点 
    im=im+1;
    while im==2*in
          xx=xx/2;yy=yy/2;%以结点深度控制缩小结点间间距
          in=im;
    end
    for ik=(ij-1):-1:1
        if tree(ik).parent==tree(ij).num%寻找该结点的子结点
            if tree(ik).lr==0%若该子结点为左叶子结点,则定位在父亲节点左下方
               x(ik)=x(ij)-xx;y(ik)=y(ij)-yy;
           elseif tree(ik).lr==1%若该子结点为右叶子结点,则定位在父亲节点右下方
               x(ik)=x(ij)+xx;y(ik)=y(ij)-yy;
            end    
               plot(x(ik),y(ik),'*r');%画出该结点的子结点
               line([x(ik),x(ij)],[y(ik),y(ij)],'Marker','.','LineStyle','-');%将子结点与父亲节点连线
               text(x(ik),y(ik),['(',num2str(tree(ik).num),'/',num2str(tree(ik).character),')'],'color','b');%在子结点处标注信息
               hold on;%保留图形
        end          
    end
end
end

编辑界面

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

  • 本函数输入内容可由用户自行决定,示例用到的是任务中要求的whatever is worth doing is worth doing well

运行结果

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述以上是每个字符所对应的哈弗曼编码

哈夫曼树输出

在这里插入图片描述
输出的一个显示部分数据的哈夫曼树

这将产生一个流程图:

链接
输入字符
定义结构体用于储存结点所带的各种数据
找字符中概率最小值及次最小值用于构建哈夫曼树
构建哈夫曼树
绘制哈夫曼树
哈弗曼编码

最后

这是我第一次发博,有错误之处还请各位不吝赐教,谢谢大家!

  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值