一.Huffman树
定义: 给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径达到最小,这样的二叉树称为最优二叉树,也称为霍夫曼树(Huffman树).
特点: Huffman树是带权路径长度最短的树,权值较大的节点离根节点较近
权值 = 当前节点的值 * 层数,wpl最小的值,就是Huffman树
创建步骤 举例 {13,7,8,3,29,6,1}
1.从小到大进行排序,将每一个数据视为一个节点,每一个节点都可视为一个二叉树
2.取出根节点权值两个最小的二叉树
3.组成一个新的二叉树,新的二叉树根节点的权值是前面两颗二叉树节点权值之和
4.再将这颗二叉树以根节点的权值大小进行再排序,不断重复1,2,3,4步,直到所有的数据都被处理,就得到一个Huffman树
class Node implements Comparable{ // 实现Comparable接口,可以使用Collections工具类进行排序 public int value; public Node left; public Node right; public Node(int value) { this.value = value; } public Node() { } /*用于测试Huffman树是否正确*/ public void preOrder(){ System.out.println(this); if (this.left != null){ this.left.preOrder(); } if (this.right != null){ this.right.preOrder(); } } @Override public String toString() { return "Node{" + "value=" + value + '}'; } @Override public int compareTo(Node o) { // 从小到大进行排序 return this.value - o.value; } }
/** * 创建霍夫曼树 * @param array 原数组 * @return 创建好Huffman树的root节点 */ public static Node createHuffmanTree(int[] array){ if (array.length == 0 || array == null){ System.out.println("数组为空,无法创建"); return null; } /*遍历数组中的每一个元素,构造成Node,放入到List中*/ Listnodes = new ArrayList<>(); for (int item : array) { nodes.add(new Node(item)); } while (nodes.size() > 1){ /*只要List中有元素,就一直进行权值计算*/ /*对Node进行排序*/ Collections.sort(nodes); /*取出根节点两个权值最小的二叉树*/ Node leftNode = nodes.get(0); Node rightNode = nodes.get(1); /*构建一个新的二叉树*/ Node parent = new Node(leftNode.value + rightNode.value); parent.left = leftNode; parent.right = rightNode; /*从List中删除使用过的节点*/ nodes.remove(leftNode); nodes.remove(rightNode); /*将新的节点加入到List中*/ nodes.add(parent); } /*返回Huffman树的root节点*/ return nodes.get(0); } }
测试,如果生成的Huffman树是正确的,那么前序遍历的结果也是正确的
public static void main(String[] args) { int[] array = {13,7,8,3,29,6,1}; preOrder(createHuffmanTree(array)); } public static void preOrder(Node root){ if (root != null){ root.preOrder(); }else { System.out.println("该树为空,不能遍历"); return; } }
二.Huffman编码
定义: Huffman编码是一种通信的编码,是在电通信领域的基本编码之一
作用: Huffman编码广泛的应用于数据文件的压缩,而且它是前缀编码,可以有效的节省传输的带宽
编码的步骤: 举例 String content = 'i like like like java do you like a java oh oh oh';
1.生成节点
/*定义节点,data用于存放数据,weight用于存放权值*/ class HuffmanNode implements Comparable{ public Byte data; public int weight; public HuffmanNode left; public HuffmanNode right; public HuffmanNode(Byte data, int weight) { this.data = data; this.weight = weight; } public HuffmanNode() { } @Override public int compareTo(HuffmanNode o) { return this.weight - o.weight; } }
2.统计字符串中每一个字符出现的次数
/*统计字符串中每个字符出现的次数,放在List中进行返回*/ /*List存储格式 [Node[date=97 ,weight = 5], Node[date=32,weight = 9]......]*/ public static ListgetNodes(byte[] bytes){ if (bytes.length == 0 || bytes == null){ System.out.println("字符串为空,无法进行编码"); return null; } List nodes = new ArrayList<>(); Map counts = new HashMap<>(); /*遍历bytes ,统计每一个byte出现的次数*/ for (byte item : bytes) { Integer count = counts.get(item); if (count == null){ // Map中没有这个字符,说明是第一次 counts.put(item,1); }else { counts.put(item,count+1); } } /*遍历Map,将键值对转换为Node对象进行存放到List中*/ for (Map.Entry node:counts.entrySet()){ nodes.add(new HuffmanNode(node.getKey(),node.getValue())); } return nodes; }
3.根据List集合,创建Huffm树
public static HuffmanNode createHuffmanTree(Listnodes){ if (nodes.size() == 0 || nodes == null){ System.out.println("生成的List为空,不能生成霍夫曼树"); return null; } while (nodes.size() > 1){ Collections.sort(nodes); HuffmanNode leftNode = nodes.get(0); HuffmanNode rightNode = nodes.get(1); HuffmanNode parent = new HuffmanNode(null,leftNode.weight+rightNode.weight); parent.left = leftNode; parent.right = rightNode; nodes.remove(leftNode); nodes.remove(rightNode); nodes.add(parent); } return nodes.get(0); }
4.将传入的Huffman树进行Huffman编码
/*将传入所有节点的Node节点的Huffman编码得到*/ /*node 传入的节点*/ /*code 路径,向左为0,向右为1*/ /*StringBuild 用于拼接路径,生成编码*/ public static void getCode(HuffmanNode node,String code,StringBuilder stringBuilder){ StringBuilder stringBuilder2 = new StringBuilder(stringBuilder); /*将code加入到stringBuilder2 中*/ stringBuilder2.append(code); if (node!=null){ // 如果node是null,则不进行处理 if (node.data == null){ // 是非叶子节点 //向左递归 getCode(node.left,"0",stringBuilder2); //向右递归 getCode(node.right,"1",stringBuilder2); }else { /*此时表明是叶子结点,说明找到了一条路径的最后*/ huffmanCode.put(node.data,stringBuilder2.toString()); } } } /*方便调用,重载此方法*/ public static MapgetCode(HuffmanNode root){ if (root == null){ System.out.println("没有生成霍夫曼树"); return null; }else { /*处理root左子树*/ getCode(root.left,"0",stringBuilder); /*处理root右子树*/ getCode(root.right,"1",stringBuilder); } return huffmanCode; }
5.使用Huffman编码进行压缩
/*将字符串对应的byte数组,通过生成的Huffman编码表,返回一个Huffman编码压缩后的byte数组*/ /*bytes 原始字符串对应的字节数组*/ /*huffmanCode 生成的Huffman编码表*/ /* 返回Huffman编码处理后的字节数组*/ public static byte[] zip(byte[] bytes,MaphuffmanCode){ if (bytes.length == 0 || bytes == null){ System.out.println("字符串为空,无法进行编码"); return null; } /*1.根据HuffmanCode获取原始的字节数组的二进制的字符串*/ StringBuilder stb = new StringBuilder(); for (byte b : bytes) { stb.append(huffmanCode.get(b)); } /*2.创建存储压缩后的字节数组*/ int index = 0; //记录第几个byte int len = 0; // 确定霍夫曼编码的长度 if (stb.length() % 8 == 0){ len = stb.length() / 8; }else { len = stb.length() / 8 + 1; } byte[] huffmanCodeBytes = new byte[len]; /*每8位对应一个byte,所以步长+8*/ for (int i = 0; i < stb.length();i+=8){ String strByte = null; if (i+8 > stb.length()){ // 不够8位,直接从当前截取到末尾 strByte = stb.substring(i); }else { strByte = stb.substring(i,i+8); //否则按照每8位进行拼接 } /*将strByte 转换成一个个byte,放在要返回的字节数组中,进行返回*/ huffmanCodeBytes[index++] = (byte)Integer.parseInt(strByte,2); } return huffmanCodeBytes; }
查看编码的结果: 压缩率 (49-21) / 49 = 57.14%
压缩之后的字节数组是:
将上述Huffman编码的步骤封装
public static byte[] getZip(byte[] bytes){ Listnodes = getNodes(bytes); // 根据nodes创建赫夫曼树 HuffmanNode root = createHuffmanTree(nodes); // 根据root节点生成霍夫曼编码 huffmanCode = getCode(root); // 根据霍夫曼编码,对数据进行压缩,得到字节数组 byte[] huffmanCodeBytes = zip(bytes,huffmanCode); // System.out.println(Arrays.toString(huffmanCodeBytes)); return huffmanCodeBytes; }
三.使用Huffman进行解码
1.将一个二进制的byte,装换为二进制的字符串
/** * 将一个byte转换成二进制的字符串 * @param flag 表示是否要进行补高位,如果是true则需要补高位,false则不需要补位,如果是最后一个字节不需要补高位 * @param b * @return 是该byte对应的二进制字符串(补码返回) */ public static String byteToBitString(boolean flag,byte b){ int temp = b; /* 使用临时变量,将byte转换为int*/ if (flag){ /*如果是一个正数,需要进行补位操作*/ temp |= 256; /*按位与操作*/ } String str = Integer.toBinaryString(temp); /*返回temp对应的二进制补码*/ if (flag){ // 如果有8位,则按照8位来返回,否则直接返回字符串 return str.substring(str.length()-8); }else { return str; } }
2.解码操作
/** * * @param huffmanCode 对应霍夫曼编码表 * @param huffmanBytes 霍夫曼编码得到的字节数组 * @return 原先字符串对应的字节数组 */ public static byte[] decode(MaphuffmanCode,byte[] huffmanBytes){ /*1.先得到HuffmanBytes对应的二进制的字符串*/ StringBuilder sbt = new StringBuilder(); //将byte字节转换为二进制字符串 for (int i = 0; i < huffmanBytes.length; i++) { byte b = huffmanBytes[i]; // 判断是否是最后一个字节 boolean flag = (i == huffmanBytes.length-1); sbt.append(byteToBitString(!flag,b)); } /*2.把字符串按照指定的方式进行霍夫曼解码*/ /*把Huffman码表进行调换,因为是反向查询*/ Map map = new HashMap<>(); for (Map.Entry entry:huffmanCode.entrySet()){ map.put(entry.getValue(),entry.getKey()); } /*3.创建集合,存放解码后的byte*/ List byteList = new ArrayList<>(); /*使用索引不停的扫描stb*/ for (int k = 0; k < sbt.length();){ int count = 1; /*小的计数器,用于判断是否字符串是否在Huffman的码标中*/ Byte b = null; /*用于存放编码后的字节*/ boolean loop = true; while (loop){ /*k不动,让count进行移动,指定匹配到一个字符*/ String key = sbt.substring(k,k+count); b = map.get(key); if (b == null){ //没有匹配到 count++; }else { //匹配到就退出循环 loop = false; } } byteList.add(b); k += count; //k直接移动到count在进行下一次遍历 } /*4.当for循环结束后,将list中存放的数据放入到byte数组中返回即可*/ byte[] decodeByteCodes = new byte[byteList.size()]; for (int j = 0; j < decodeByteCodes.length; j++) { decodeByteCodes[j] = byteList.get(j); } return decodeByteCodes; }
查看解码后的结果:
完整代码
package data.structer.tree; import java.util.*; public class HuffmanCodeDemo { static MaphuffmanCode = new HashMap<>(); static StringBuilder stringBuilder = new StringBuilder(); public static void main(String[] args) { String content = "i like like like java do you like a java oh oh oh"; //System.out.println("原始的长度是:"+content.length()); byte[] bytes = getZip(content.getBytes()); // System.out.println("Huffman编码后的字符串长度是:"+bytes.length); System.out.println("解码后的字符串是:"+new String(decode(huffmanCode,bytes))); } // 解码 /** * * @param huffmanCode 对应霍夫曼编码表 * @param huffmanBytes 霍夫曼编码得到的字节数组 * @return */ public static byte[] decode(Map huffmanCode,byte[] huffmanBytes){ StringBuilder sbt = new StringBuilder(); //将byte字节转换为二进制字符串 for (int i = 0; i < huffmanBytes.length; i++) { byte b = huffmanBytes[i]; // 判断是否是最后一个字节 boolean flag = (i == huffmanBytes.length-1); sbt.append(byteToBitString(!flag,b)); } //把字符串按照指定的方式进行霍夫曼解码 Map map = new HashMap<>(); for (Map.Entry entry:huffmanCode.entrySet()){ map.put(entry.getValue(),entry.getKey()); } // 创建集合,存放byte List byteList = new ArrayList<>(); for (int k = 0; k < sbt.length();){ int count = 1; Byte b = null; boolean loop = true; while (loop){ String key = sbt.substring(k,k+count); b = map.get(key); if (b == null){ //没有匹配到 count++; }else { loop = false; } } byteList.add(b); k += count; } byte[] decodeByteCodes = new byte[byteList.size()]; for (int j = 0; j < decodeByteCodes.length; j++) { decodeByteCodes[j] = byteList.get(j); } return decodeByteCodes; } /** * 将一个byte转换成二进制的字符串 * @param flag 表示是否要进行补高位,如果是true则需要补高位,false则不需要补位,如果是最后一个字节不需要补高位 * @param b * @return 是该byte对应的二进制字符串(补码返回) */ public static String byteToBitString(boolean flag,byte b){ int temp = b; if (flag){ temp |= 256; } String str = Integer.toBinaryString(temp); if (flag){ return str.substring(str.length()-8); }else { return str; } } public static byte[] getZip(byte[] bytes){ List nodes = getNodes(bytes); // 根据nodes创建赫夫曼树 HuffmanNode root = createHuffmanTree(nodes); // 根据root节点生成霍夫曼编码 huffmanCode = getCode(root); // 根据霍夫曼编码,对数据进行压缩,得到字节数组 byte[] huffmanCodeBytes = zip(bytes,huffmanCode); // System.out.println(Arrays.toString(huffmanCodeBytes)); return huffmanCodeBytes; } /** * 统计字符串中每个字符出现的次数,添加到List中进行返回 * @param bytes * @return */ public static List getNodes(byte[] bytes){ if (bytes.length == 0 || bytes == null){ System.out.println("字符串为空,无法进行编码"); return null; } List nodes = new ArrayList<>(); Map counts = new HashMap<>(); for (byte item : bytes) { Integer count = counts.get(item); if (count == null){ // 说明是第一次 counts.put(item,1); }else { counts.put(item,count+1); } } for (Map.Entry node:counts.entrySet()){ nodes.add(new HuffmanNode(node.getKey(),node.getValue())); } return nodes; } /** * 使用霍夫曼编码进行压缩 * @param bytes * @param huffmanCode * @return */ public static byte[] zip(byte[] bytes,Map huffmanCode){ if (bytes.length == 0 || bytes == null){ System.out.println("字符串为空,无法进行编码"); return null; } StringBuilder stb = new StringBuilder(); for (byte b : bytes) { stb.append(huffmanCode.get(b)); } int index = 0; int len = 0; // 确定霍夫曼编码的长度 if (stb.length() % 8 == 0){ len = stb.length() / 8; }else { len = stb.length() / 8 + 1; } byte[] huffmanCodeBytes = new byte[len]; for (int i = 0; i < stb.length();i+=8){ String strByte = null; if (i+8 > stb.length()){ // 不够8位 strByte = stb.substring(i); }else { strByte = stb.substring(i,i+8); } huffmanCodeBytes[index] = (byte)Integer.parseInt(strByte,2); index++; } return huffmanCodeBytes; } public static void getCode(HuffmanNode node,String code,StringBuilder stringBuilder){ StringBuilder stringBuilder2 = new StringBuilder(stringBuilder); stringBuilder2.append(code); if (node!=null){ if (node.data == null){ // 是非叶子节点 //向左递归 getCode(node.left,"0",stringBuilder2); //向右递归 getCode(node.right,"1",stringBuilder2); }else { huffmanCode.put(node.data,stringBuilder2.toString()); } } } public static Map getCode(HuffmanNode root){ if (root == null){ System.out.println("没有生成霍夫曼树"); return null; }else { getCode(root.left,"0",stringBuilder); getCode(root.right,"1",stringBuilder); } return huffmanCode; } /** * 生成霍夫曼树 * @param nodes * @return */ public static HuffmanNode createHuffmanTree(List nodes){ if (nodes.size() == 0 || nodes == null){ System.out.println("生成的List为空,不能生成霍夫曼树"); return null; } while (nodes.size() > 1){ Collections.sort(nodes); HuffmanNode leftNode = nodes.get(0); HuffmanNode rightNode = nodes.get(1); HuffmanNode parent = new HuffmanNode(null,leftNode.weight+rightNode.weight); parent.left = leftNode; parent.right = rightNode; nodes.remove(leftNode); nodes.remove(rightNode); nodes.add(parent); } return nodes.get(0); } } class HuffmanNode implements Comparable { public Byte data; public int weight; public HuffmanNode left; public HuffmanNode right; public HuffmanNode(Byte data, int weight) { this.data = data; this.weight = weight; } public HuffmanNode() { } @Override public String toString() { return "HuffmanNode{" + "data=" + data + ", weight=" + weight + '}'; } @Override public int compareTo(HuffmanNode o) { return this.weight - o.weight; } }