哈夫曼编码
基本介绍
赫夫曼编码也翻译为 哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式, 属于一种程序算法
赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。
赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间
赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,称之为最佳编码
i like like like java do you like a java
// 共40个字符(包括空格)
105 32 108 105 107 101 32 108 105 107 101 32 108 105 107 101 32 106 97 118 97 32 100 111 32 121 111 117 32 108 105 107 101 32 97 32 106 97 118 97
//对应Ascii码
01101001 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101010 01100001 01110110 01100001 00100000 01100100 01101111 00100000 01111001 01101111 01110101 00100000 01101100 01101001 01101011 01100101 00100000 01100001 00100000 01101010 01100001 01110110 01100001
//对应的二进制
按照二进制来传递信息,总的长度是 359 (包括空格)
传输的 字符串
1 i like like like java do you like a java
2 d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9 // 各个字符对应的个数
3 按照上面字符出现的次数构建一颗赫夫曼树, 次数作为权值
步骤:
构成赫夫曼树的步骤:
- 从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树
- 取出根节点权值最小的两颗二叉树
- 组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
- 再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树
4 根据赫夫曼树,给各个字符,规定编码 (前缀编码), 向左的路径为0 向右的路径为1 , 编码如下:
o: 1000 u: 10010 d: 100110 y: 100111 i: 101
a : 110 k: 1110 e: 1111 j: 0000 v: 0001
l: 001 : 01
5 按照上面的赫夫曼编码,我们的"i like like like java do you like a java" 字符串对应的编码为 (注意这里我们使用的无损压缩)
1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110 通过赫夫曼编码处理 长度为 133
6 长度为 : 133
说明:
原来长度是 359 , 压缩了 (359-133) / 359 = 62.9%
此编码满足前缀编码, 即字符的编码都不能是其他字符编码的前缀。不会造成匹配的多义性
赫夫曼编码是无损处理方案
思路
(1) Node { data (存放数据), weight (权值), left 和 right }
(2) 得到 “i like like like java do you like a java” 对应的 byte[] 数组
(3) 编写一个方法,将准备构建赫夫曼树的Node 节点放到 List , 形式 [Node[date=97 ,weight = 5], Node[]date=32,weight = 9]…], 体现 d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9
(4) 可以通过List 创建对应的赫夫曼树
输入: i like like like java do you like a java
首先统计各个字符的数目,创建节点类和节点类对应的List集合
节点
创建HuffmanNode节点类继承Comparable,重写compareTo方法, 便于根据属性排序,重写tostring方法,便于直接输出。
- data:字符
- b: 该字符对应的字节
- frequency: 该字符出现的频次
- code :该字符对应的哈夫曼编码
- left
- right
class HuffmanNode implements Comparable<HuffmanNode>{
public HuffmanNode(char data, Integer frequency, HuffmanNode left, HuffmanNode right) {
this.data = data;
this.frequency = frequency;
this.left = left;
this.right = right;
}
public HuffmanNode(byte b,char data, Integer frequency) {
this.b=b;
this.data = data;
this.frequency = frequency;
}
byte b;
char data;
Integer frequency;
String code;
HuffmanNode left;
HuffmanNode right;
@Override
public int compareTo(HuffmanNode o) {
if(this.frequency>o.frequency) return 1;
else if (this.frequency<o.frequency) return -1;
else return 0;
}
@Override
public String toString() {
return "HuffmanNode{" +
"b=" + b +
", data=" + data +
", frequency=" + frequency +
", code=" + code +"}";
}
}
创建方法统计各个字符数目和直接排序返回list
// 获得排序有序的节点list
public static ArrayList<HuffmanNode> getList(byte[] bytes){
ArrayList<HuffmanNode> list = new ArrayList<>();
// 字符串进行扫描
for (int i = 0; i < bytes.length; i++) {
Boolean flag = true;
// 查看列表里是否有该节点
for (int j = 0; j < list.size(); j++) {
if (bytes[i]==list.get(j).b){
list.get(j).frequency++;
flag = false;
break;
}
}
// 没有添加该节点
if(flag) list.add(new HuffmanNode(bytes[i],(char) bytes[i], 1));
}
// 对节点频数进行排序 从小到大
Collections.sort(list);
System.out.println();
return list;
}
根据List中的节点创建哈夫曼树并返回根节点
构建哈夫曼树, 每次选两个最小的节点,构成一个新的节点,放入list,排序,如此循环直到list只剩一个节点,也就是说只有一棵树了。
// 构建哈夫曼树,返回根节点
public static HuffmanNode constructHuffmanTree(ArrayList<HuffmanNode> list, Integer length) {
while (length>1){
HuffmanNode leftNode = list.get(0);
HuffmanNode rightNode = list.get(1);
list.add(new HuffmanNode('-', leftNode.frequency+rightNode.frequency,leftNode,rightNode ));
// 踩坑 删除两次第一个 而不是 0 1
list.remove(0);
list.remove(0);
Collections.sort(list);
length--;
}
return list.get(0);
}
根据构建好的哈夫曼树,创建哈夫曼编码表,即获得每个字符对应的哈夫曼编码
// 获得每个字符对应的哈夫曼表
public static HashMap<Byte,String> HaffumanCodeTable = new HashMap<>();
public static void constructHuffmanCodeTable(HuffmanNode root){
if(root.left==null&&root.right==null){
root.code= code;
HaffumanCodeTable.put(root.b,root.code);
}
if(root.left!=null){
code=code+"0";
constructHuffmanCodeTable(root.left);
code=code.substring(0,code.length()-1);
}
if(root.right!=null){
code=code+"1";
constructHuffmanCodeTable(root.right);
code=code.substring(0,code.length()-1);
}
}
对字节数组根据哈夫曼编码进行压缩
/*
* 功能:压缩
* 输入: 源字符串转化为的bytes数组 byte[] 长度为40 需要40个字节来存储
* 输出: 利用哈夫曼编码压缩后的byte[] 这里长度17
*
* 举例:
* 字符串:i like like like java do you like a java
* 对应的编码数据:10101000101111111100100010111111110010001011111111001001010011011100011100000110111010001111001010 00101111111100110001001010011011100
* 此时该字符串长度133
* => byte[] haffumanCodeBytes => 每八位存进一个字节byte[i]
* 对于负数而言
* haffumanCodeBytes[0] = 10101000 (补码) => 10101000- 1 = 10100111 (反码) 取反=> 11011000(原码)=-88
* */
private static byte[] zip(byte[] bytes) {
// 1 利用哈夫曼编码表 将字节数组转化
String code="";
for (int i = 0; i < bytes.length; i++) {
code=code+HaffumanCodeTable.get(bytes[i]);
}
int len;
if(code.length()%8==0) len=code.length()/8;
else len=code.length()/8+1;
// 2 创建一个压缩后的byte数组
byte[] zip=new byte[len];
for (int i = 0; i < len; i++) {
if(i*8+7>len){
zip[i] = (byte) Integer.parseInt(code.substring(i*8,len),2);
break;
}
zip[i] = (byte) Integer.parseInt(code.substring(i*8,i*8+7),2);
}
return zip;
}
完整代码
package Tree;
import org.junit.Test;
import java.security.Principal;
import java.util.*;
import static java.util.Collections.*;
/**
* @author yyq
* @create 2022-01-07 19:57
*
* 哈夫曼编码
* 问题: I love you.
* 以最简单的方式对 I,l,o,v,e,y,u,.,空格 进行编码传输
* 例如 I:01, l:11 ....
*
* 解决办法:
* 1. 统计各个字符出现的次数
* 2. 构建哈夫曼树
* 3. 按照左 0 ,右 1 输出各个字符的编码
*
* 哈夫曼树节点 data,frequency,left,right
*
*/
public class HuffmanCode {
@Test
public void testByte(){
String strByte = "10101000";
System.out.println((byte)Integer.parseInt(strByte, 2));
}
public static String code="";
public static HashMap<Byte,String> HaffumanCodeTable = new HashMap<>();
public static void main(String[] args) {
String input = "i like like like java do you like a java";
byte[] bytes = haffumanZip(input);
System.out.println(Arrays.toString(bytes));
String s = haffumanUnZip(bytes);
}
/*
* 功能:对压缩后的字节数组还原成字符串
* */
public static String haffumanUnZip(byte[] zip){
String code="";
for (int i = 0; i < zip.length; i++) {
code=code+zip.toString();
}
return null;
}
/*
* 功能: 输入字符串进行压缩,返回压缩后的字节数组
* */
public static byte[] haffumanZip(String input){
byte[] bytes = input.getBytes();
System.out.println("压缩前字节数:"+bytes.length);
ArrayList<HuffmanNode> list = new ArrayList<>();
list = getList(bytes);
// 构建哈夫曼树 每次挑选两个最小的节点 组成一个新的节点 在放入list 重复 直达节点长度为1
Integer length = list.size();
HuffmanNode root = constructHuffmanTree(list,length);
// 生成哈夫曼树编码表
constructHuffmanCodeTable(root);
System.out.println("遍历哈夫曼树:");
preOrder(root);
System.out.println("哈夫曼表:");
for(Byte key:HaffumanCodeTable.keySet()){
System.out.println("key:"+key+" "+"Value:"+HaffumanCodeTable.get(key));
}
// 将字符串对应的byte数组通过哈夫曼编码表进行压缩,输出哈夫曼编码表压缩后的byte数组
byte[] zip=zip(bytes);
System.out.println("压缩后的字节数:"+zip.length);
return zip;
}
private static void Action(byte[] bytes) {
ArrayList<HuffmanNode> list = new ArrayList<>();
list = getList(bytes);
// 构建哈夫曼树 每次挑选两个最小的节点 组成一个新的节点 在放入list 重复 直达节点长度为1
Integer length = list.size();
HuffmanNode root = constructHuffmanTree(list,length);
// 生成哈夫曼树编码表
constructHuffmanCodeTable(root);
System.out.println("遍历哈夫曼树:");
preOrder(root);
System.out.println("哈夫曼表:");
for(Byte key:HaffumanCodeTable.keySet()){
System.out.println("key:"+key+" "+"Value:"+HaffumanCodeTable.get(key));
}
// 将字符串对应的byte数组通过哈夫曼编码表进行压缩,输出哈夫曼编码表压缩后的byte数组
byte[] zip=zip(bytes);
System.out.println("压缩后的字节数:"+zip.length);
}
/*
* 功能:压缩
* 输入: 源字符串转化为的bytes数组 byte[] 长度为40 需要40个字节来存储
* 输出: 利用哈夫曼编码压缩后的byte[] 这里长度17
*
* 举例:
* 字符串:i like like like java do you like a java
* 对应的编码数据:10101000101111111100100010111111110010001011111111001001010011011100011100000110111010001111001010 00101111111100110001001010011011100
* 此时该字符串长度133
* => byte[] haffumanCodeBytes => 每八位存进一个字节byte[i]
* 对于负数而言
* haffumanCodeBytes[0] = 10101000 (补码) => 10101000- 1 = 10100111 (反码) 取反=> 11011000(原码)=-88
* */
private static byte[] zip(byte[] bytes) {
// 1 利用哈夫曼编码表 将字节数组转化
String code="";
for (int i = 0; i < bytes.length; i++) {
code=code+HaffumanCodeTable.get(bytes[i]);
}
int len;
if(code.length()%8==0) len=code.length()/8;
else len=code.length()/8+1;
// 2 创建一个压缩后的byte数组
byte[] zip=new byte[len];
for (int i = 0; i < len; i++) {
if(i*8+7>len){
zip[i] = (byte) Integer.parseInt(code.substring(i*8,len),2);
break;
}
zip[i] = (byte) Integer.parseInt(code.substring(i*8,i*8+7),2);
}
return zip;
}
public static void preOrder(HuffmanNode node){
if(node.left==null&&node.right==null)
System.out.println(node);
if(node.left!=null) preOrder(node.left);
if(node.right!=null) preOrder(node.right);
}
// 获得每个字符对应的哈夫曼表
public static void constructHuffmanCodeTable(HuffmanNode root){
if(root.left==null&&root.right==null){
root.code= code;
HaffumanCodeTable.put(root.b,root.code);
}
if(root.left!=null){
code=code+"0";
constructHuffmanCodeTable(root.left);
code=code.substring(0,code.length()-1);
}
if(root.right!=null){
code=code+"1";
constructHuffmanCodeTable(root.right);
code=code.substring(0,code.length()-1);
}
}
// 构建哈夫曼树,返回根节点
public static HuffmanNode constructHuffmanTree(ArrayList<HuffmanNode> list, Integer length) {
while (length>1){
HuffmanNode leftNode = list.get(0);
HuffmanNode rightNode = list.get(1);
list.add(new HuffmanNode('-', leftNode.frequency+rightNode.frequency,leftNode,rightNode ));
// 踩坑 删除两次第一个 而不是 0 1
list.remove(0);
list.remove(0);
Collections.sort(list);
length--;
}
return list.get(0);
}
// 获得排序有序的节点list
public static ArrayList<HuffmanNode> getList(byte[] bytes){
ArrayList<HuffmanNode> list = new ArrayList<>();
// 字符串进行扫描
for (int i = 0; i < bytes.length; i++) {
Boolean flag = true;
// 查看列表里是否有该节点
for (int j = 0; j < list.size(); j++) {
if (bytes[i]==list.get(j).b){
list.get(j).frequency++;
flag = false;
break;
}
}
// 没有添加该节点
if(flag) list.add(new HuffmanNode(bytes[i],(char) bytes[i], 1));
}
// 对节点频数进行排序 从小到大
Collections.sort(list);
System.out.println();
return list;
}
}
class HuffmanNode implements Comparable<HuffmanNode>{
public HuffmanNode(char data, Integer frequency, HuffmanNode left, HuffmanNode right) {
this.data = data;
this.frequency = frequency;
this.left = left;
this.right = right;
}
public HuffmanNode(byte b,char data, Integer frequency) {
this.b=b;
this.data = data;
this.frequency = frequency;
}
byte b;
char data;
Integer frequency;
String code;
HuffmanNode left;
HuffmanNode right;
@Override
public int compareTo(HuffmanNode o) {
if(this.frequency>o.frequency) return 1;
else if (this.frequency<o.frequency) return -1;
else return 0;
}
@Override
public String toString() {
return "HuffmanNode{" +
"b=" + b +
", data=" + data +
", frequency=" + frequency +
", code=" + code +"}";
}
}