数据结构之哈夫曼树

一、哈夫曼树的基本概念

也叫最优二叉树

1.引子:

将很多学生的百分制成绩转化为五分制成绩
<60: D      60-69: D     70-79:C    80-89:B       90-100:A

两种判断的方式所需要的比较总的次数明显不同,在数据量很大的情况下所需要的时间也会差距很大

2.基本术语

路径:从树中的一个结点到另一个结点之间的分支构成这两个结点间的路径
节点的路径长度:两结点间路径上的分支数
树的路径的长度:从树根到每一个结点的路径长度之和
(在结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树,但是长度最短的不仅仅是完全二叉树)
:将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权
结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积
树的带权路径长度:树中跟结点到所有叶子节点的带权路径长度之和,记作:WPL 

哈夫曼树就是带权路径长度(WPL)最短的树
:带权路径长度最短是在‘度相同’的树中比较而得到的结果。要么都是二叉树,或者三叉树等等

二、哈夫曼树的结构算法

1.算法思路

贪心算法:构造哈夫曼树时首先选择权值最小的叶子节点构造。

1.根据给定的n个权值{w1,w2,w3...,wn}构成n棵二叉树森林F={T1,T2,T3....Tn},其中每一个二叉树只有一个带权为w的根节点(每一个二叉树只有一个节点)

2.从F中选取两个根结点的权值最小的树作为左右子树,构造一棵新的二叉树,且设置新的二叉树的根结点的权值作为其左右子树上根节点的权值之和

3.在F中删除这两棵树,同时将新得到的二叉树加入森林中

4.重复(2)和(3),直到森林只有一棵树为止,这棵树即为哈夫曼树

总结:1.在哈夫曼算法中,初始时有n棵二叉树,要经过n-1次合并最终形成哈夫曼树。

          2.经过n-1次合并产生n-1个新结点,且这n-1个新结点都是具有两个孩子的分支节点。一共会产生2n-1个结点。

2.算法实现

采用顺序存储结构——一维结构数组

设置从0到2n-1个结点

void CreatHuffmanTree (HuffmanTree HT,int n) {    // 构造哈夫曼树
    if(n<=1) return
    m = 2*n-1    //数组共有2n-1个元素
    HT = new HTNode[m+1]  //0号单元未用,HT[m]表示根节
    for(i=1;i<=m;++i) {    // 将2n-1个元素的lch、rch、parent 置为0
        HT[i].lch = 0;   HT[i].rch = 0;   HT[i].parent = 0
    }
    for(i=1;i<=n;++i) cin>>HT[i].weight    // 输入前n个元素weight值
    // 初始化结束,下面开始建立哈夫曼树
    for(i=n+1;i<=m;i++) {
        Select(HT,i+1,s1,s2)    // 选择parent为0且为最小的两个节点并且返回他们在HT中的需要s1和s2;
        HT[s1].parent=i; HT[s2].parent=i         // 将最小的结点的双亲负责为新结点,表示从F中删除s1和s2
        HT[i].lch=s1;    HT[i].rch=s2            // s1和s2分别作为i的左右孩子
        HT[i].weight=HT[s1].weight+HT[s2].weight // i的权值为左右孩子权值之和
    }
}

 

三、哈夫曼树应用

1.哈夫曼编码(最优的前缀码)

1.统计字符集中每个字符在电文中出现的平均概率(概率越大要求编码越短)
2.利用哈夫曼编码:权越大的叶子离根越近;将每个字符概率作为权值,构造哈夫曼树。则概率越大的结点,路径越短
3.在哈夫曼树的每个分支上标上0或1;
          把结点的左分支标0,右分支标1
          把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符编码

因为每一个字母都是位于二叉树的叶子节点,因此每一个路径都具有唯一性,不会产生重码(一段代码可能会产生多个解析结构)。因此哈夫曼编码是最优的前缀码

2.哈夫曼编码的算法实现

从叶子结点向根结点查询,判断是左结点还是右结点进行1或者0的编写,直到到达根结点停止。

void CreatHuffmanCode(Huffmantree HT,HuffmanCode &HC, int n) {
    // 从叶子结点到逆向求每个字符的哈夫曼编码,存储在编码表HC中
    HC = new char *[n+1]        // 分配n个字符编码的头指针矢量
    cd = new char [n]           // 分配临时存放编码的动态数组空间
    cd[n-1] = '\0'              // 编码结束符
    for(i=1;i<=n;++i) {         // 逐个字符求哈夫曼编码
        start = n-1;   c=i ;;f=HT[i].parent
        while(f!=0) {           // 从叶子节点开始向上回溯,直到根节点
            --start             // 回溯一次start向前指一个位置
            if(HT[f].lchild == c) { cd[start] = '0'}    // 结点c是f的左孩子,则生成代码0
            else     cd[start] = '1'                    // 结点c是f的右孩子,则生成代码1
            c = f , f = HT[f].parent                    // 继续想上回溯
        }                                               // 求出第i个字符的编码
        HC[i] = new char[n-start]                       // 为第i个字符串编码分配空间
        strcpy(HC[i], &cd[start])                       // 将求的的编码从临时空间cd复制到HC的当前行中
    }
    delete cd                                           // 释放临时空间
}// CreatHuffanCode

3.哈夫曼编码的解码算法

和编码类似,通过接收字符频率来构建哈夫曼树,根据接收到的哈夫曼编码,用哈夫曼树从根节点开始遍历,求出唯一确定的对于字符

 

posted @   铜须的编程生活  阅读(466)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示