哈夫曼树及其应用

哈夫曼树是一种特殊的二叉树,用于构造最短带权路径长度的树,常用于数据压缩。通过贪心算法构建,权值大的叶子节点离根节点更近。哈夫曼编码是利用哈夫曼树生成的前缀编码,减少传输字符的平均长度,提高空间效率。构建哈夫曼树涉及将权值最小的节点合并,形成新的结点,直至只剩下一棵树。哈夫曼编码算法则从叶子结点到根节点逆序标记0和1,生成无前缀冲突的编码。
摘要由CSDN通过智能技术生成

哈夫曼树及其应用

哈夫曼树的基本概念及特点

又称最优二叉树

  • 路径——从树中一个结点到另一结点之间的分支构成这两个结点间的路径。
  • 结点的路径长度——两结点间路径上的分支数。

例子:

在这里插入图片描述

  • 树的路径路径长度——从根到每一个结点的路径长度之和。记作:TL

例子:

在这里插入图片描述

结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树(充分条件)

  • 权(weight)——将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
  • 结点的带权路径长度——从根结点到该结点之间路径长度与该结点的权的乘积。
  • 树的带权路径长度——树中所有叶子结点的带权路径长度之和。
    W P L = ∑ k = 1 n w k l k WPL = \sum_{k=1}^n w_kl_k WPL=k=1nwklk
    w w w:权值; l l l:结点到根的路径长度

例子:

在这里插入图片描述

  • 哈夫曼树:最优树——带权路径长度(WPL)最短的树。

注:“带权路径长度最短”是在“度相同”的树中比较而得的结果,因此有最优二叉树、最优三叉树等等。

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

在这里插入图片描述


  • 特点:满二叉树不一定是哈夫曼树,哈夫曼树中权值越大的叶子离根越近,具有相同带权结点的哈夫曼树不唯一。

构造哈夫曼树

哈夫曼树中权值越大的叶子离根越近——贪心算法:构造哈夫曼树时首先选择权值小的叶子结点。

  • 哈夫曼算法(构造哈夫曼树的方法)
    • (1)根据n个给定的权值 { w 1 , w 2 , … , w n } \{w_1,w_2,…,w_n\} {w1,w2,,wn}构成n棵二叉树的森林 F = { T 1 , T 2 , . . . , T n } F=\{T_1,T_2,...,T_n\} F={T1,T2,...,Tn},其中 T i T_i Ti只有一个带权为 w i w_i wi的根结点。构造森林全是根
    • (2)在 F F F中选取两棵根结点的权值最小的树作为左右子树,构造一棵新的二叉树,且设置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。选用两小造新树
    • (3)在F中删除这两棵树,同时将新得到的二叉树加入森林中。删除两小添新人
    • (4)重复(2)和(3),直到森林中只有一棵树为止,这棵树即为哈夫曼树。重复2、3剩单根

哈夫曼树中没有度为1的结点,包含 n n n个叶子结点的哈夫曼树中共有 2 n − 1 2n-1 2n1个结点

例子:

在这里插入图片描述

哈夫曼树的算法实现

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

  • 结点类型定义
typedef struct{
    int weight;
    int parent,lch,rch;
}HTNode,*HuffmanTree;

HuffmanTree H;

  • 1.初始化 H T [ 1......2 n − 1 ] : l c h = r c h = p a r e n t = 0 HT [1......2n-1]:lch=rch=parent=0 HT[1......2n1]:lch=rch=parent=0
  • 2.输入初始 n n n个叶子结点:置 H T [ 1...... n ] HT[1......n] HT[1......n]的weight值;
  • 3.进行以下 n − 1 n-1 n1次合并,依次产生 n − 1 n-1 n1个结点 H T [ i ] , i = n + 1......2 n − 1 HT[i],i=n+1......2n-1 HT[i],i=n+1......2n1:
    • a)在 H T [ 1... i − 1 ] HT[1...i-1] HT[1...i1]中选两个未被选过(从 p a r e n t : = = 0 parent: == 0 parent:==0的结点中选)的 w e i g h t weight weight最小的两个结点 H T [ s 1 ] HT[s1] HT[s1] H T [ s 2 ] , s 1 、 s 2 HT[s2],s1、s2 HT[s2],s1s2为两个最小结点下标;
    • b)修改 H T [ s 1 ] HT[s1] HT[s1] H T [ s 2 ] HT[s2] HT[s2] p a r e n t parent parent值: H T [ s 1 ] . p a r e n t = i ; H T [ s 2 ] . p a r e n t = i HT[s1].parent=i;HT[s2].parent=i HT[s1].parent=i;HT[s2].parent=i
    • C)修改新产生的 H T [ i ] HT[i] HT[i]:
      • H T [ i ] . w e i g h t = H T [ s 1 ] . w e i g h t + H T [ s 2 ] . w e i g h t HT[i].weight=HT[s1].weight+HT[s2].weight HT[i].weight=HT[s1].weight+HT[s2].weight;
      • H T [ i ] . l c h = s 1 ; H T [ i ] . r c h = s 2 HT[i].lch=s1; HT[i].rch=s2 HT[i].lch=s1;HT[i].rch=s2;

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(int i=1;i<=n;i++) cin>>HT[i].weight; //输入n个元素
    // 合并产生n-1个结点——构造Huffman树
    for(i=n+1;i<=m;i++){
        Select(HT,i-1,s1,s2); //在HT[k](1<=k<=i-1)中选择两个其双亲域为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、统计字符集中每个字符在电文中出现的平均概率(概率越大,要求编码越短)。
    2、利用哈夫曼树的特点:权越大的叶子离根越近;将每个字符的概率值作为权值,构造哈夫曼树。则概率越大的结点,路径越短。
    3、在哈夫曼树的每个分支上标上0或1:
      结点的左分支标0,右分支标1
      把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符的编码。

例子:

在这里插入图片描述

  • 两个问题:

在这里插入图片描述


  • 哈夫曼编码算法实现:
    先建立一个哈夫曼树,从叶子到根查找,然后逆序。
void CreatHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n){
//从叶子结点到根逆序求每个字符的哈夫曼编码,存储在编码表HC中
    HC=new char *[n+1]; //分配n个字符编码的头指针矢量
    cd=new char [n]; //分配临时存放编码的动态数组空间
    cd[n-1]='\0';  //编码结束符
    for(int 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; //释放临时空间
}//CreatHuffmanCode
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Galactus_hao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值