第四章#4.3哈夫曼树以及案例介绍

###哈夫曼树###

 

显然两种判断树的效率不同,所以哈夫曼树就是研究最优二叉树

路径:从树的一个结点到另一个结点的分支构成了两个结点间的路径(简单来说就是两个结点之间的连线)

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

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

结点数目相同的二叉树中,完全二叉树是路径最短的二叉树,反之不成立

权(weight):将树中结点赋给一个有着某种含义的数值,则这个数值称为该节点的权

结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积

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

 

由此可以引出哈夫曼树的定义

哈夫曼树最优树:带权路径长度(WPL)最短的树;前提是树的度是一样的(要么比较二叉树,要么比较三叉树)

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

构造哈夫曼树的算法就是哈夫曼算法,哈夫曼树不唯一

那么显然权越大的叶子离根越近路径越短

###构造哈夫曼树###

利用贪心算法:构造哈夫曼树时首先选择权值小的叶子结点,权值大的最后构造,这样就离根近了

  1. 根据n个给定的权值{wi}构造n棵二叉树森林F={Ti},其中Ti是只有一个带权为wi的根结点

  2. 在F中选用两个最小作为左右子树造一棵新二叉树,然后把这两个小树从森林中去除,再在森林中加上这棵新树(其权值为两小树的权值和)

  3. 重复第2步,直到森林中只有一棵树为止,这棵大树就是哈夫曼树了

    构造森林全是根 选用两小造新树 删除两小添新树 重复2步到哈夫

哈夫曼树种只有度(该结点有几个分支)为0和度为2的结点,没有度为1的结点

包含n棵树的森林要经过n-1次合并才可以形成哈夫曼树,共产生n-1个新的结点(如图空白结点),所以n个叶子结点的哈夫曼树中共有2n-1个结点

###哈夫曼树构造###

哈夫曼树构造算法,采用顺序存储结构——一维结构数组

n个叶子的哈夫曼树有2n-1个结点,因此构造结构数组2n个单位,第一个不用

//结点类型定义
typedef struct {
	int weight;
    int parent, lch, rch;
}HTNode, *HuffmanTree;
  • 第一步 构造森林全是根,它们的孩子全部都是0,0位置没用,因此表示为没有孩子

  • 第一步 构造森林全是根,它们的孩子全部都是0,0位置没用,因此表示为没有孩子

  • 第二步 选择两小造新树,给最小的两个的parent赋值成新根坐标,表示它们有双亲因此不是根

  • 第三步 删除两小添新树,给新节点赋予权重为两小之和

  • 第四步 重复二部到哈夫,当表中所有结点只有一个parent为0时结束,此时坐标为2n-1

//哈夫曼树构造算法实现
void CreatHuffmanTree(HuffmanTree, int n) {//构造哈夫曼树——哈夫曼算法
    if (n<=1) return;
    int m=2*n-1;//数组共有m个元素
    HuffmanTree HT=new HTNode[m+1]; //0号不用,HT[m]表示根结点
    for (int i=1; i<=m; i++) {
        HT[i].lch=HT[i].rch=HT[i].parent=0;
    }//将2n-1个元素的lch,rch,parent初始化为0
    for (int i=1; i<n; i++) cin >> HT[i].weight;//输入n个元素的权重
    //初始化步骤结束,接下来开始建立哈夫曼树
    //在parent为0的元素中选取2个最小的
    for (int i=n+1; i<=m; i++) {//合并产生n-1个结点——构造哈夫曼树
    	Select(HT, i-1, s1, s2); 
    //在HT[K](1<=K<=i-1)中选择两个其双亲域是0,且权重最小的结点,返回它们的序列号s1,s2
      	HT[s1].parent=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的权重为左右孩子权重之和
    }
}

###哈夫曼树的应用###

哈夫曼编码

在传输时,将字符转换成二进制字符串,如传输ABACCDA

如果令:A----00, B----01, C----10, D----11

这种编码方式将编码设置为长度相同的二进制编码,常常浪费空间和时间

解决方法就是将编码设置为长度不等的二进制编码,即让待传字符串中出现次数较多的字符采用尽可能短的编码,则转换的二进制字符串就可以减少传输空间和传输时间

 

 为了解决重码问题,关键就是要设计长度不等的编码,且必须使得任一字符的编码都不是另一字符编码的前缀,这种编码称为前缀编码

 

 

 

那么给定一颗哈夫曼编码二叉树,如何求哈夫曼编码?

  • 从叶子向上爬到根,再倒过来,就是哈夫曼编码

  • 看当前结点的parent结点中,自己是左孩子还是右孩子,如果是左孩子则为0,否则为1

void CreatHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n) {
    //从叶子到根逆向求每一个字符的哈夫曼编码,储存在编码表HC中
    HC=new char*[n+1]; 
    //分配n个字符编码的头指针矢量,指针数组,new一块n个单位char*类型的空间
    cd=new char[n];//分配临时存放编码的动态数组空间
    //n个元素,哈夫曼树有可能n层,那分支线最多n-1条,最后一格存放'\0'
    cd[n-1]='\0';//编码结束符
    for (int i=1; i<=n; i++) {//逐个字符求哈夫曼编码
        int 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的当前行中
        //strcpy是用于把一个char*类型复制到另一个地址空间
        //PS:string类转成char*类型方法:char* c; string s="1234"; c = s.c_str(); 
    }
    delete cd;//释放临时空间
}//CreatHuffmanCode

哈夫曼编码的解码

  1. 构造哈夫曼树

  2. 依次读入二进制码

  3. 读入0则走向左孩子;读入1则走向右孩子

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

  5. 然后再从根出发继续译码,直到结束

 

 算法这里不去介绍了,结合上面,以及先前章节容易实现

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值