一.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编码和解码的更多相关文章

  1. DS二叉树--Huffman编码与解码

    题目描述 1.问题描述 给定n个字符及其对应的权值,构造Huffman树,并进行huffman编码和译(解)码. 构造Huffman树时,要求左子树根的权值小于.等于右子树根的权值. 进行Huffma ...

  2. 用C++实现Huffman文件编码和解码(2 总结)

    这个是代码是昨天写完的,一开始的时候还出了点小bug,这个bug在晚上去吃饭的路上想明白的,回来更改之后运行立刻完成最后一步,大获成功. 简单说下huffman编码和文件压缩主要的技术. Huffma ...

  3. Huffman 编码压缩算法

    前两天发布那个rsync算法后,想看看数据压缩的算法,知道一个经典的压缩算法Huffman算法.相信大家应该听说过 David Huffman 和他的压缩算法—— Huffman Code,一种通过字 ...

  4. [转载]Huffman编码压缩算法

    转自http://coolshell.cn/articles/7459.html 前两天发布那个rsync算法后,想看看数据压缩的算法,知道一个经典的压缩算法Huffman算法.相信大家应该听说过 D ...

  5. [老文章搬家] 关于 Huffman 编码

    按:去年接手一个项目,涉及到一个一个叫做Mxpeg的非主流视频编码格式,编解码器是厂商以源代码形式提供的,但是可能代码写的不算健壮,以至于我们tcp直连设备很正常,但是经过一个UDP数据分发服务器之后 ...

  6. Jcompress: 一款基于huffman编码和最小堆的压缩、解压缩小程序

    前言 最近基于huffman编码和最小堆排序算法实现了一个压缩.解压缩的小程序.其源代码已经上传到github上面: Jcompress下载地址 .在本人的github上面有一个叫Utility的re ...

  7. Huffman编码实现文件的压缩与解压缩。

    以前没事的时候写的,c++写的,原理很简单,代码如下: #include <cstdio> #include <cstdlib> #include <iostream&g ...

  8. Huffman编码实现压缩解压缩

    这是我们的课程中布置的作业.找一些资料将作业完毕,顺便将其写到博客,以后看起来也方便. 原理介绍 什么是Huffman压缩 Huffman( 哈夫曼 ) 算法在上世纪五十年代初提出来了,它是一种无损压 ...

  9. java编码原理,java编码和解码问题

    java的编码方式原理 java的JVM的缺省编码方式由系统的“本地语言环境”设置确定,和操作系统的类型无关 . 在JAVA源文件-->JAVAC-->Class-->Java--& ...

随机推荐

  1. mumu模拟器手Q自动化配置

    { "platformName": "Android", "platformVersion": "6.0.1", &qu ...

  2. zabbix安装和使用

    前言:zabbix是一款很好用的监控工具,相比nagios(也是监控工具的一种)而言,zabbix提供了强大的视图界面,操作简单,功能强大,只需在页面配置即可,让你用的开心,回家放心. zabbix监 ...

  3. web三要素

    1.带动web的三驾马车 html5,css,javascript(js)就是带动web的三架马车,html是web的结构,css是web的样式,而js则是web的行为(即与用户的交互) 如果把htm ...

  4. APICloud开发者进阶之路 | UIPickerView 模块示例demo

    本文出自APICloud官方论坛 rongCloud2  3.2.8 版本更新后添加了发送小视频接口,发送文件接口. rongCloud2  概述 融云是国内首家专业的即时通讯云服务提供商,专注为互联 ...

  5. 状态压缩 hdu #10

    You are playing CSGO. There are n Main Weapons and m Secondary Weapons in CSGO. You can only choose ...

  6. bfs + 路径输出

    You are given two pots, having the volume of A and B liters respectively. The following operations c ...

  7. document.visibilityState 监听浏览器

    document.hidden:表示页面是否隐藏的布尔值.页面隐藏包括 页面在后台标签页中 或者 浏览器最小化 (注意,页面被其他软件遮盖并不算隐藏,比如打开的 sublime 遮住了浏览器). do ...

  8. python 进程管道

    数据不安全,不常用 import time from multiprocessing import Pipe, Process def producer(prod, cons, name, food) ...

  9. Qt Installer Framework翻译(2)

    开始 Qt IFW作为Qt项目的一部分进行开发.该框架自身也使用Qt.然而,它能被用于安装所有类型的应用程序,包括(但不限于)使用Qt编译的. 支持的平台 已在下列平台中进行了测试: > Mic ...

  10. lisp学习总结(二)-----lisp应该探索发展的方向

    现在流行一种语言叫做Clojure,他是lisp直接嫁接到java的结果,但是我就感觉这却成为lisp的失败. 因为lisp最强大最有优势的能力是构造抽象,构造设计思想,而不是运行期以确定的方式运行, ...