Huffman编码和解码
一.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<Node> {
// 实现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中*/
List<Node> nodes = 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<HuffmanNode>{
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 List<HuffmanNode> getNodes(byte[] bytes){
if (bytes.length == 0 || bytes == null){
System.out.println("字符串为空,无法进行编码");
return null;
}
List<HuffmanNode> nodes = new ArrayList<>();
Map<Byte,Integer> 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<Byte,Integer> node:counts.entrySet()){
nodes.add(new HuffmanNode(node.getKey(),node.getValue()));
}
return nodes;
}
3.根据List集合,创建Huffm树
public static HuffmanNode createHuffmanTree(List<HuffmanNode> 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);
}
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 Map<Byte,String> getCode(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,Map<Byte,String> huffmanCode){
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){
List<HuffmanNode> 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;
}
三.使用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(Map<Byte,String> huffmanCode,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<String,Byte> map = new HashMap<>();
for (Map.Entry<Byte,String> entry:huffmanCode.entrySet()){
map.put(entry.getValue(),entry.getKey());
} /*3.创建集合,存放解码后的byte*/
List<Byte> 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 Map<Byte,String> huffmanCode = 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<Byte,String> 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<String,Byte> map = new HashMap<>();
for (Map.Entry<Byte,String> entry:huffmanCode.entrySet()){
map.put(entry.getValue(),entry.getKey());
} // 创建集合,存放byte
List<Byte> 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<HuffmanNode> 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<HuffmanNode> getNodes(byte[] bytes){
if (bytes.length == 0 || bytes == null){
System.out.println("字符串为空,无法进行编码");
return null;
}
List<HuffmanNode> nodes = new ArrayList<>();
Map<Byte,Integer> 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<Byte,Integer> 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<Byte,String> 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<Byte,String> 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<HuffmanNode> 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<HuffmanNode>{
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;
} }
Huffman编码和解码的更多相关文章
- DS二叉树--Huffman编码与解码
题目描述 1.问题描述 给定n个字符及其对应的权值,构造Huffman树,并进行huffman编码和译(解)码. 构造Huffman树时,要求左子树根的权值小于.等于右子树根的权值. 进行Huffma ...
- 用C++实现Huffman文件编码和解码(2 总结)
这个是代码是昨天写完的,一开始的时候还出了点小bug,这个bug在晚上去吃饭的路上想明白的,回来更改之后运行立刻完成最后一步,大获成功. 简单说下huffman编码和文件压缩主要的技术. Huffma ...
- Huffman 编码压缩算法
前两天发布那个rsync算法后,想看看数据压缩的算法,知道一个经典的压缩算法Huffman算法.相信大家应该听说过 David Huffman 和他的压缩算法—— Huffman Code,一种通过字 ...
- [转载]Huffman编码压缩算法
转自http://coolshell.cn/articles/7459.html 前两天发布那个rsync算法后,想看看数据压缩的算法,知道一个经典的压缩算法Huffman算法.相信大家应该听说过 D ...
- [老文章搬家] 关于 Huffman 编码
按:去年接手一个项目,涉及到一个一个叫做Mxpeg的非主流视频编码格式,编解码器是厂商以源代码形式提供的,但是可能代码写的不算健壮,以至于我们tcp直连设备很正常,但是经过一个UDP数据分发服务器之后 ...
- Jcompress: 一款基于huffman编码和最小堆的压缩、解压缩小程序
前言 最近基于huffman编码和最小堆排序算法实现了一个压缩.解压缩的小程序.其源代码已经上传到github上面: Jcompress下载地址 .在本人的github上面有一个叫Utility的re ...
- Huffman编码实现文件的压缩与解压缩。
以前没事的时候写的,c++写的,原理很简单,代码如下: #include <cstdio> #include <cstdlib> #include <iostream&g ...
- Huffman编码实现压缩解压缩
这是我们的课程中布置的作业.找一些资料将作业完毕,顺便将其写到博客,以后看起来也方便. 原理介绍 什么是Huffman压缩 Huffman( 哈夫曼 ) 算法在上世纪五十年代初提出来了,它是一种无损压 ...
- java编码原理,java编码和解码问题
java的编码方式原理 java的JVM的缺省编码方式由系统的“本地语言环境”设置确定,和操作系统的类型无关 . 在JAVA源文件-->JAVAC-->Class-->Java--& ...
随机推荐
- mumu模拟器手Q自动化配置
{ "platformName": "Android", "platformVersion": "6.0.1", &qu ...
- zabbix安装和使用
前言:zabbix是一款很好用的监控工具,相比nagios(也是监控工具的一种)而言,zabbix提供了强大的视图界面,操作简单,功能强大,只需在页面配置即可,让你用的开心,回家放心. zabbix监 ...
- web三要素
1.带动web的三驾马车 html5,css,javascript(js)就是带动web的三架马车,html是web的结构,css是web的样式,而js则是web的行为(即与用户的交互) 如果把htm ...
- APICloud开发者进阶之路 | UIPickerView 模块示例demo
本文出自APICloud官方论坛 rongCloud2 3.2.8 版本更新后添加了发送小视频接口,发送文件接口. rongCloud2 概述 融云是国内首家专业的即时通讯云服务提供商,专注为互联 ...
- 状态压缩 hdu #10
You are playing CSGO. There are n Main Weapons and m Secondary Weapons in CSGO. You can only choose ...
- bfs + 路径输出
You are given two pots, having the volume of A and B liters respectively. The following operations c ...
- document.visibilityState 监听浏览器
document.hidden:表示页面是否隐藏的布尔值.页面隐藏包括 页面在后台标签页中 或者 浏览器最小化 (注意,页面被其他软件遮盖并不算隐藏,比如打开的 sublime 遮住了浏览器). do ...
- python 进程管道
数据不安全,不常用 import time from multiprocessing import Pipe, Process def producer(prod, cons, name, food) ...
- Qt Installer Framework翻译(2)
开始 Qt IFW作为Qt项目的一部分进行开发.该框架自身也使用Qt.然而,它能被用于安装所有类型的应用程序,包括(但不限于)使用Qt编译的. 支持的平台 已在下列平台中进行了测试: > Mic ...
- lisp学习总结(二)-----lisp应该探索发展的方向
现在流行一种语言叫做Clojure,他是lisp直接嫁接到java的结果,但是我就感觉这却成为lisp的失败. 因为lisp最强大最有优势的能力是构造抽象,构造设计思想,而不是运行期以确定的方式运行, ...