博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
霍夫曼树
阅读量:7041 次
发布时间:2019-06-28

本文共 5025 字,大约阅读时间需要 16 分钟。

一、哈夫曼树的概念和定义

  什么是哈夫曼树?让我们先举一个例子。

  判定树:在很多问题的处理过程中,需要进行大量的条件判断,这些判断结构的设计直接影响着程序的执行效率。例如,编制一个程序,将百分制转换成五个等级输出。大家可能认为这个程序很简单,并且很快就可以用下列形式编写出来:

if(score<60)    cout<<"Bad"<
View Code
  若考虑上述程序所耗费的时间,就会发现该程序的缺陷。在实际中,学生成绩在五个等级上的分布是不均匀的。当学生百分制成绩的录入量很大时,上述判定过程需要反复调用,此时程序的执行效率将成为一个严重问题。
但在实际应用中,往往各个分数段的分布并不是均匀的。下面就是在一次考试中某门课程的各分数段的分布情况: 
 
 
  下面我们就利用哈夫曼树寻找一棵最佳判定树,即总的比较次数最少的判定树。第一种构造方式:
 
 
 
  第二种构造方式:
 
 
 
  这两种方式,显然后者的判定过程的效率要比前者高。在也没有别地判定过程比第二种方式的效率更高。我们称判定过程最优的二叉树为最优二叉树

  定义哈夫曼树之前先说明几个与哈夫曼树有关的概念:

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

  路径长度:路径上的分枝数目称作路径长度。

  树的路径长度:从树根到每一个结点的路径长度之和。

  结点的带权路径长度:在一棵树中,如果其结点上附带有一个权值,通常把该结点的路径长度与该结点上的权值之积称为该结点的带权路径长度(weighted path length)

  什么是权值?( From 百度百科 )

    计算机领域中()权值就是定义的路径上面的值。可以这样理解为节点间的距离。通常指字符对应的二进制编码出现的概率。至于树中的权值可以理解为:权值大表明出现概率大!一个结点的权值实际上就是这个结点子树在整个树中所占的比例.abcd四个的权值为7,5,2,4. 这个7,5,2,4是根据实际情况得到的,比如说从一段文本中统计出abcd四个字母出现的次数分别为7,5,2,4. 说a结点的权值为7,意思是说a结点在系统中占有7这个份量.实际上也可以化为百分比来表示,但反而麻烦,实际上是一样的.

  树的带权路径长度:如果树中每个叶子上都带有一个权值,则把树中所有叶子的带权路径长度之和称为树的带权路径长度。

  设某二叉树有n个带权值的叶子结点,则该二叉树的带权路径长度记为:

                                  

公式中,Wk为第k个叶子结点的权值;Lk为该结点的路径长度。

  示例:

 
  一般来说,用n(n>0)个带权值的叶子来构造二叉树,限定二叉树中除了这n个叶子外只能出现度为2的结点。那么符合这样条件的二叉树往往可构造出许多颗, 其中带权路径长度最小的二叉树就称为最优二叉树

  二、哈夫曼树的构造

  根据哈弗曼树的定义,一棵二叉树要使其WPL值最小,必须使权值越大的叶子结点越靠近根结点,而权值越小的叶子结点越远离根结点。哈弗曼依据这一特点提出了一种构造最优二叉树的方法,其基本思想如下:

   注意:如果有几棵树根节点权值相等,并且存在一棵树只有一个节点,应该讲这课节点较小的树先出队列。

  下面演示了用Huffman算法构造一棵Huffman树的过程:

三、哈夫曼树的在编码中的应用

  在电文传输中,需要将电文中出现的每个字符进行二进制编码。在设计编码时需要遵守两个原则:
  (1)发送方传输的二进制编码,到接收方解码后必须具有唯一性,即解码结果与发送方发送的电文完全一样;
  (2)发送的二进制编码尽可能地短。下面我们介绍两种编码的方式。
1. 等长编码
  这种编码方式的特点是每个字符的编码长度相同(编码长度就是每个编码所含的二进制位数)。假设字符集只含有4个字符A,B,C,D,用二进制两位表示的编 码分别为00,01,10,11。若现在有一段电文为:ABACCDA,则应发送二进制序列:00010010101100,总长度为14位。当接收方接收到这段电文后,将按两位一段进行译码。这种编码的特点是译码简单且具有唯一性,但编码长度并不是最短的。
2. 不等长编码
  在传送电文时,为了使其二进制位数尽可能地少,可以将每个字符的编码设计为不等长的,使用频度较高的字符分配一个相对比较短的编码,使用频度较低的字符分配一个比较长的编码。例如,可以为A,B,C,D四个字符分别分0,00,1,01,并可将上述电文用二进制序列:000011010发送,其长度只有 9个二进制位,但随之带来了一个问题,接收方接到这段电文后无法进行译码,因为无法断定前面4个0是4个A,1个B、2个A,还是2个B,即译码不唯一, 因此这种编码方法不可使用。
  因此,为了设计长短不等的编码,以便减少电文的总长,
还必须考虑编码的
唯一性,即在建立不等长编码时必须使任何一个字符的编码都不是另一个字符的前缀,这宗编码称为前缀编码(prefix  code)
  (1)利用字符集中每个字符的使用频率作为权值构造一个哈夫曼树;
  (2)从根结点开始,为到每个叶子结点路径上的左分支赋予0,右分支赋予1,并从根到叶子方向形成该叶子结点的编码
例题:
  假设一个文本文件TFile中只包含7个字符{A,B,C,D,E,F,G},这7个字符在文本中出现的次数为{5,24,7,17,34,5,13}
  利用哈夫曼树可以为文件TFile构造出符合前缀编码要求的不等长编码
具体做法:
  1. 将TFile中7个字符都作为叶子结点,每个字符出现次数作为该叶子结点的权值
  2. 规定哈夫曼树中所有左分支表示字符0,所有右分支表示字符1,将依次从根结点到每个叶子结点所经过的分支的二进制位的序列作为该结点对应的字符编码
  3. 由于从根结点到任何一个叶子结点都不可能经过其他叶子,这种编码一定是前缀编码,哈夫曼树的带权路径长度正好是文件TFile编码的总长度
  通过哈夫曼树来构造的编码称为哈弗曼编码(huffman code)
import java.util.PriorityQueue;public class HuffMan {    private static int[] count = { 5, 24, 7, 17, 34, 5, 13 };    private static int N = count.length;    private static char[] chars = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };    private static PriorityQueue
priorityQueue = new PriorityQueue
(); private static class Node implements Comparable { Character c = null; double w; Node left; Node right; @Override public int compareTo(Object o) { Node n = (Node) o; if (this.w - n.w > 0) return 1; else if (this.w - n.w < 0) { return -1; } else { //如果两个Node的权值相等,将是由其他节点合并而来的节点设置为较小值,先出队列构建霍夫曼树 if (n.c == null) { return 1; } else if (this.c == null) { return -1; } else { return 0; } } } public Node(char c, double w, Node left, Node right) { super(); this.c = c; this.w = w; this.left = left; this.right = right; } public Node(double w, Node left, Node right) { super(); this.w = w; this.left = left; this.right = right; } @Override public String toString() { return "Node [c=" + c + ", w=" + w + "]"; } } public static Node create() { for (int i = 0; i < N; i++) { priorityQueue.add(new Node(chars[i], count[i], null, null)); } while (priorityQueue.size() > 1) { Node left = priorityQueue.poll(); Node right = priorityQueue.poll(); Node newNode = new Node(left.w + right.w, left, right); priorityQueue.add(newNode); } return priorityQueue.poll(); } public static void print(Node node, StringBuilder builder) { if (node.left == null && node.right == null) { System.out.println(node.c + ":" + builder); } else { print(node.left, builder.append('0')); builder.deleteCharAt(builder.length() - 1); print(node.right, builder.append('1')); builder.deleteCharAt(builder.length() - 1); } } public static void main(String[] args) { Node root = create(); print(root, new StringBuilder()); }}
View Code

转载于:https://www.cnblogs.com/wxgblogs/p/5604853.html

你可能感兴趣的文章
sql DATEPART() MONTH() convert() cast() dateadd() DATEDIFF() with(nolock)
查看>>
线程池ThreadPoolExecutor
查看>>
github中删除项目
查看>>
CentOS中/英文环境切换教程(CentOS6.8)
查看>>
Python的一个命名空间冲突,关于from-import机制
查看>>
jQuery动画详解
查看>>
3.移植驱动到3.4内核-移植DM9000C驱动
查看>>
Mysql 奇怪的连接错误
查看>>
给程序员简历的一些建议
查看>>
CSS3饼状loading效果
查看>>
docker日志
查看>>
Postman使用入门
查看>>
编程修养(一)
查看>>
Solidworks如何替换工程图参考零件
查看>>
2013年第四届蓝桥杯C/C++B组省赛题目解析
查看>>
重温.NET下Assembly的加载过程
查看>>
SpringBoot 之Spring Boot Starter依赖包及作用
查看>>
websocket 协议 使用
查看>>
微信小程序——自定义导航栏
查看>>
《JavaScript高级程序设计》笔记:DOM2和DOM3(十二)
查看>>