赞
踩
上面这位就是本文的编码方式发明者, 哈夫曼先生
下面会介绍哈夫曼树的定义,名词解释(基本概念),构造算法和哈夫曼编码.
提示:以下是本篇文章正文内容,下面案例可供参考
哈夫曼树是一种带权路径长度最短的树 , 下面会给出何为带权路径. 我们又称其为最优树 , 是目前效率最高的判别树.
我们在构造的过程中, 会人为地将权值大的结点放到较上层,如此, 该结点的路径长度最小,权值大,所以权值*路径长度
也就最小 (贪心)
这里先给出一棵哈夫曼树,下面会根据此树解释概念
1. 路径长度: 分支上的所有路径数, 比如b的路径长为2,c为3
2. 权: 是对于实体的某一属性的量化描述 ,如下面会讲的字符出现次数
3. 树的带权路径长度: 树的每个叶子结点的带权路径长度之和(即 路径长度*权值,再求和),记作WPL,
上图WPL=1*18+2*12+3*10+3*11
4. 哈夫曼树 : 即WPL最小的树
下面说说如何 “拥有” 一颗哈夫曼树
这里我们将结点的值域data
同时也作为权值使用 , 由于哈夫曼树权重大的结点排列在顶层的原因,我们的Node类需要进行排序 , 因此需要实现一个Comparable
接口用于规定排序规则
代码如下(示例):
class Node implements Comparable<Node>{ int data ; Node leftNode; Node rightNode; public Node(int data) { this.data = data; } @Override public String toString() { return "Node{" + "data=" + data + '}'; } @Override public int compareTo(Node o) { return this.data - o.data; } //先序遍历 public void preOrder(){ System.out.print(this.data+" "); if(this.leftNode != null){ this.leftNode.preOrder(); } if(this.rightNode != null){ this.rightNode.preOrder(); } } }
关键点来了,先列举出思路
1. 定义一个辅助数组,并向其传入结点node
2. 循环,对目标数组(即传入的参数)进行排序,并取出每次排序结果中最小的两个结点
3. 创建一个新结点,将取出的两个树挂在新结点的同时,将新结点的权值置为二者权值之和(注意,不是值域)
4. 将新结点加入到辅助数组中并移出最小的那两个结点
5. 当数组的大小等于1时,循环终止
代码如下(示例):
class HuffmanTree { public static Node createHuffmanTree(int[] arr){ ArrayList<Node> nodes = new ArrayList<>(); for (int val: //向array list传值 arr) { nodes.add(new Node(val)); } while(nodes.size() > 1){ Collections.sort(nodes); //将nodes排序,排序得放里面排,因为remove后需重新排一次序 //1. 找到权值最小的两个结点 Node leftNode = nodes.get(0); Node rightNode = nodes.get(1); //2. 构建一颗新树 Node parent = new Node(leftNode.data+rightNode.data); parent.leftNode = leftNode; parent.rightNode = rightNode; //3. 将新树加入到ArrayList nodes.add(parent); //4.删除结点 nodes.remove(leftNode); nodes.remove(rightNode); } return nodes.get(0); } }
哈夫曼树可以应用于数据的压缩 , 基本思想是对出现次数多的字符编较短的码,且是一种最优前缀编码 , 也就是说每个字符的编码都不会是其它字符编码的前缀.
在设计算法的时候我们可以将字符出现的次数作为权值,将出现次数最多的字符放到树的上层,就达到了压缩的效果
1.统计各个字符出现的次数
2.以字符出现次数作为权值(键值对中的值),字符作为键值对的键构建哈夫曼树
3.将字符串转为变长编码
代码如下(示例):
public class HuffmanCodeDemo { public static void main(String[] args) { String str = "i like like like java do you like a java"; byte[] strBytes = str.getBytes(); System.out.println(strBytes.length); List<Node> nodes = HuffmanCode.getNodes(strBytes); Node root = HuffmanCode.createHuffmanCode(nodes); root.preOrder(); } public static void preOrder(Node root){ if(root != null){ root.preOrder(); } } } class HuffmanCode{ public static List<Node> getNodes(byte[] bytes){ //定义一个ArrayList,存放Node数组 ArrayList<Node> nodes = new ArrayList<>(); //统计各个字符出现的次数 Map<Byte,Integer> counts = new HashMap<>(); //统计的map for (byte b: bytes) { Integer count = counts.get(b); //在map中查找b if(count == null){ //counts中还没加入b这个字符,则加入b,并且让b的出现次数(即权重为1) counts.put(b,1); } else { counts.put(b,++count); } } //把键值对转换成Node对象,并加入List<Node> for (Map.Entry<Byte,Integer> entry: counts.entrySet()) { nodes.add(new Node(entry.getKey(),entry.getValue())); } return nodes; } public static Node createHuffmanCode(List<Node> nodes){ while(nodes.size() > 1){ //先排序 Collections.sort(nodes); //获取最小的两个结点 Node leftNode = nodes.get(0); Node rightNode = nodes.get(1); //拼接成一颗新树 //新树根节点没有值域,只有权值 Node parent = new Node(null,leftNode.weight+rightNode.weight); parent.leftNode = leftNode; parent.rightNode = rightNode; //将新树加入到nodes nodes.add(parent); nodes.remove(leftNode); nodes.remove(rightNode); } return nodes.get(0) ; //返回根结点 } } class Node implements Comparable<Node>{ Byte data; //数据域 Integer weight; //权重 Node leftNode; Node rightNode; public Node(Byte data, Integer weight) { this.data = data; this.weight = weight; } @Override public String toString() { return "Node{" + "data=" + data + ", weight=" + weight + '}'; } @Override public int compareTo(Node o) { return this.weight - o.weight; //按照权值从小到大排列 } public void preOrder(){ System.out.println(this); if(this.leftNode != null){ this.leftNode.preOrder(); } if(this.rightNode != null){ this.rightNode.preOrder(); } } }
具体编程思路
1. 最核心的部分是统计字符出现次数并将键值对转换为Node对象
思路有点难理解,需要debug慢慢调试哦~
1. 创建一个静态的StringBuilder和Map,前者用于拼接路径,后者用于构造编码表
2. 创建编码函数, 当前结点不为叶子结点时,对其左右子结点递归,
反之以键值对方式加入编码表
3.重载该方法. 优雅地传入一个哈夫曼树的根节点,返回编码表
代码如下(示例):
//用于拼接路径 static StringBuilder stringBuilder = new StringBuilder(); //用于存放哈夫曼编码 static Map<Byte,String> huffmanCode = new HashMap<Byte, String>(); //对getCodes(p1,p2,p3)重载 public static Map<Byte,String> getCodes(Node root){ if(root == null){ return null; } getCodes(root.leftNode,"0",stringBuilder); getCodes(root.rightNode,"1",stringBuilder); return huffmanCode; } /** * 传入结点,转为Huffman编码 * @param node 传入的结点 * @param code 存放路径 * @param stringBuilder 拼接 * */ public static void getCodes(Node node,String code,StringBuilder stringBuilder){ //用传入的StringBuilder再生成一个StringBuilder StringBuilder stringBuilder1 = new StringBuilder(stringBuilder); stringBuilder1.append(code); //拼接code if(node != null){ if(node.data == null){ //非叶子结点,就继续递归调用 //递归调用左右结点 getCodes(node.leftNode,"0",stringBuilder1); getCodes(node.rightNode,"1",stringBuilder1); } else { //叶子结点 huffmanCode.put(node.data,stringBuilder1.toString()); //以键值对方式加入编码表 } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。