数据结构与算法(十一)哈夫曼树及其应用

路径:树中一个结点到另一个结点之间的分支所构成的路径

路径长度:两个结点之间的分支数

树的路径长度:从根结点到每个结点的路径长度之和,记作TL

        结点数目相同的二叉树中,完全二叉树是路径长度最短

结点的带权路径长度:根到该结点之间路径长度 X 该结点的

树的带权路径长度:所有叶子结点带权路径长度之和

        记作WPL=\sum w_{k}l_{k}

哈夫曼树:最优二叉树(带权路径长度(WPL)最短的树)

        满二叉树不一定是哈夫曼树

        哈夫曼树中权越大的叶子离根越近(贪心算法)

哈夫曼算法

        1.根据n个给定的权值{w1,w2,...2n}构成n棵二叉树森林F={T1,T2,...,Tn},其中Ti只有一个带权为wi的根结点。(构造森林全是根

        2.从F中选择两个权值最小的作为左右子树来构造一颗新的二叉树(新二叉树根结点的权值为左右子树根结点之和)(选用两小造新树

        3.删除原来的两颗树,将新树加入森林(删除两小添新人

        4.重复步骤2-3,直到森林中仅剩一棵树(重复2、3剩单根

例1:a,b,c,d权值分别为7,5,2,4

        哈夫曼树只有度为0或2的结点,没有度为1的结点

        包含n个叶子结点的哈夫曼树共有2n-1个结点

        n棵二叉树要经历n-1次合并可以形成哈夫曼树

例2:a,b,c,d,e权值分别为7,5,5,2,4

 算法实现

        使用顺序存储结构实现--结构数组

                weight(权重),parent(父母),lch,rch(左右孩子)

                哈夫曼树中共2n-1个结点,不使用0为下标,需要数组大小2n

        1.初始化HT[1...2n-1]:lch=rch=parent=0

        2.输入初始n个叶子结点:设置HT[1...n]的weight值

        3.进行n-1次合并

                a.从HT[1...i-1]中选取连个未选择的(parent=0)weight最小的两个结点

                b.修改HT[S1],HT[S2]的值,HT[S1].parent=HT[S2].parent=i

                c.修改新产生的HT[i]        HT[i].weight=HT[S1].weght+HT[S2].weight;

                                                        HT[i].lch=S1;        HT[i].ch=s2;

void CreateHuffmanTree(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){
        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,s1,s2);//在HT中选择连个双亲域为0且权值最小的结点
        HT[s1].parent=i;    HT[s2].parent=i;//删除s1,s2
        HT[i].lch=s1;    HT[i].rch=s2;    //s1,s2作为左右孩子
        HT[i].weight=HT[s1].weight+HT[s2].weight;//设置新权值
    }
}

应用:哈夫曼编码

        不定长二进制编码,出现次数多的尽可能短(节约空间)!!!任意一个字符的编码不能是另一个字符编码的前缀--前缀编码

        哈夫曼树:            1.统计字符在电文中出现的平均概率

                                      2.根据平均概率构造哈夫曼树

                                      3.结点左分支标0右分支标1;把从根到每个叶子结点的路径上的标号连接起来,作为该叶子代表的字符编码

                 哈夫曼编码中每个字符(叶子结点)的编码(路径)都不会经过其他字符,所以不存在某个字符的编码是其他字符的前缀

                哈夫曼编码的实现

                        1.查找该字符的双亲结点,并确定其是左孩子还是右孩子。

                        2.在临时存储区域中标注当前结点的状态,如果为左孩子标0,右孩子标1

                        3.将临时空间向前移动一位寻找下一个双亲结点,直到找到根结点为止

以第三个字符C为例

start0123456
Loc1312113
parentLoc0131211
childTagroot101\0

                Loc为结点自己的位置,parentLoc为双亲结点的位置,childTag为该结点是左孩子还是右孩子,讲childTag连接起来就是该字符的哈夫曼编码,\0为编码结束的标志

void  CreateHuffmanCode(HuffmanTree HT,HuffmanCode &HC ,int n){
    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个字符的编码分配空间
    strcopy(HC[i],$cd[start];)//将求得的编码从临时空间cd复制到hc中
    }
    delete cd;//释放临时空间
}

 哈夫曼树的应用:

        1.编码文件        ①输入各字符的权值

                                  ②构造哈夫曼树HT[i]

                                  ③进行哈夫曼编码HC[i]

                                  ④查HC[i],得到各字符的哈夫曼编码

        2.解码文件        ①构造哈夫曼树

                                  ②一次读入二进制编码

                                  ③读入0,则走向左孩子;读入1,则走向右孩子

                                  ④一旦到达某叶子时,即可译出字符

                                  ⑤然后从根出发继续译码,直到结束

        原文为uvuwxxxvywyuyyvxxyxxuxuvvyvxy

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值