哈夫曼

什么是堆


优先队列(Priority Queue):特殊的"队列",取出元素的顺序是依照元素的优先权(关键字)的大小,而不是元素进入队列的先后顺序。


问题:如何组织优先队列?


  • 一般的数组、链表?

  • 有序的数组、链表?

  • 二叉搜索树?AVL树?


对于堆来说,主要就是两个操作,插入和删除,而无论是一般的数组、链表,还是有序的数组、链表其中至少有一个操作是需要O(n) 的时间来完成的。可以考虑能否采用二叉树存储结构?如果采用这种存储结构的话,我们更应该关注插入还是删除操作?树结点顺序怎么安排?树结构怎样?


堆的两个特性:


  • 结构性:用数组表示的完全二叉树

  • 有序性:任一结点的关键字是其子树所有结点的最大值(或最小值)

  • 堆的抽象数据类型


    以最大堆为例,其主要操作有:


    • MaxHeap Create(int MaxSize):创建一个空的最大堆

    • Boolean IsFull(MaxHeap H):判断最大堆H是否已满

    • void Insert(MaxHeap H, ElementType item):将元素item插入最大堆H

    • Boolean IsEmpty(MaxHeap H):判断最大堆是否为空

    • ElementType DeleteMax(MaxHeap H):返回H中最大元素(高优先级)


     最大堆的创建


    • 注意到,把MaxData换成小于堆中所有元素的MinData,同样适用于创建最小堆。


  • 最大堆的插入


    思路:首先默认插入位置在完全二叉树的下一个位置,通过向下过滤结点的方式,从其父结点到根结点的有序序列中寻找合适的位置进行插入操作

  • void Insert(MaxHeap H, ElementType item)   {      // 将元素item插入最大堆H,其中H->Elements[0]已经定义为哨兵

  •  int i;    
  •  if(IsFull(H))      
  • {        
  •  printf("最大堆已满");      
  •    return;    
  •  }    
  •  i = ++H->Size; // i指向插入后堆中的最后一个元素的位置       
  • for(; H->Elements[i/2] < item; i /= 2)      
  •    H->Elements[i] = H->Elements[i/2]; // 向下过滤结点,这种方式比交换数据来得快  

       H->Elements[i] = item; // 将item插入    

  •    i = ++H->Size; // i指向插入后堆中的最后一个元素的位置      

  • for(; H->Elements[i/2] < item; i /= 2)        

  •  H->Elements[i] = H->Elements[i/2]; // 向下过滤结点,这种方式比交换数据来得快  

  •    H->Elements[i] = item; // 将item插入   }上述代码中,H->Elements[0]是哨兵元素,它不小于堆中的最大元素,控制循环结束。时间复杂度O(logN)。


    最大堆的删除

  • 思路:取出根结点(最大值),同时删除它,方法就是用堆中的最后一个元素代替之(和插入操作一样,这里的代替只是形式上方便理解的说辞,实际上我们只是用一个临时变量保存其值而已,这比真实的替代更省时),但是其位置不一定正确,因此需要从根结点开始向上过滤下层结点

  • 最大堆的建立

    • 建立最大堆:将已经存在的N个元素按最大堆的要求存放在一个一维数组中


      • 方法一:通过插入操作,将N个元素一个个相继插入到一个初始为空的堆中去,其时间代价为O(NlogN)

      • 方法二:线性时间复杂度下建立最大堆


      将N个元素按输入顺序存入,先满足完全二叉树的结构特性

      调整各结点位置,以满足最大堆的有序特性。


      重点说下方法二,从完全二叉树的倒数第二层开始调整,因为其左、右子树只有一个结点,本身构成了一个堆,因此可以用过滤的方式以当前层为根,将根放到合适的位置。经过一轮调整,可以从倒数第三层开始(其左、右子树仍然各自构成一个堆),续行此法,直到完全二叉树的Root放置到合适的位置为止。


      可以证明(由每层的结点数和该层的最多交换次数找出一般规律,利用错位相消可解出闭形式)这种方法的时间复杂度是线性的,即T(N) = O(N)。

       
    • 7、哈夫曼树与哈夫曼编码


      什么是哈夫曼树


      我们需要先引入一个概念——带权路径长度(WPL):


      设二叉树有n个叶子结点,每个叶子结点带有权值Wk,从根结点到每个叶子结点的长度为Lk,则每个叶子结点的带权路径长度之和就是WPL = Σ(k=1~n)WkLk


      最优二叉树或哈夫曼树就是WPL最小的二叉树,因此哈夫曼树说白了就是根据结点不同的查找频率构造的最有效的搜索树。


      哈夫曼树的构造


      基本思路:每次把权值最小的两颗二叉树合并,把两者的和作为当前树新的权值,再取两个权值最小的二叉树合并,续行此法,直至结点取空。

      下面是时间复杂度为O(NlogN)的做法:

    哈夫曼树的特点


    • 没有度为1的结点

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

    • 哈夫曼树的任意非叶结点的左右子树交换后仍是哈夫曼树

    • 对同一组权值{W1,W2,...,Wn},是否存在不同构的两颗哈夫曼树呢?

    • 答案是肯定的,比如对于一组权值{1,2,3,3},不同构的两颗哈夫曼树如下:图片

    • 容易算出二者的WPL值均为18,之所以出现这样的结果是因为3个权值为3的结点合并的时机不同导致的。


      哈夫曼编码


      考虑这样一个问题:用位串来编码英语字母表里的字母(不区分大小写),可以用长度为5的位串来表示每个字母,这样用来编码数据的总数是5乘以文本中的字符数,有没有可能找出这些字母的编码方案,使得在编码数据时使用的位更少?若可能,那么就可以节省存储空间。


      哈夫曼编码作为一种不等长的编码形式,一个需要解决的关键性问题就是如何避免二义性。为了保证无二义地解码,我们采用前缀码——任何字符的编码都不是另一个字符编码的前缀。用二叉树进行编码:左右分支0、1;字符只在叶节点上。





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值