哈夫曼编码

1)  夫曼 编码 (Huffman Coding) ,又称霍夫曼编码,是一种编码 方式 , 属于一种程序算法
2) 哈夫曼编码 广 泛地用于数据文件压 缩。 压缩率通常在 20% 90% 之间
3) 哈夫 曼码 是可变 字长 编码 (VLC) 的一种。 Huffman 1952 年提出一种编码方法 ,称 之为最佳编
i like like like java do you like a java       // 40 个字符 ( 包括空格 )
d:1 y:1 u:1 j:2  v:2  o:2  l:4  k:4  e:4 i:5  a:5    :9  // 各个字符对应的个数
按照上面字符出现的次数构建一颗赫夫曼树 , 次数作为权值 .( 图后 )

哈夫曼树根据排序方法不同,也可能不太一样这样对应的哈夫曼编码也不完全一样,但wpl 是一样的,都是最小的。

1:根据赫夫曼编码压缩数据的原理,需要创建 "i like like like java do you like a java" 对应的赫夫曼树.

步骤2:

1)生成赫夫曼树对应的赫夫曼编码  , 如下表:
=01 a=100 d=11000 u=11001 e=1110 v=11011 i=101 y=11010 j=0010 k=1111 l=000 o=0011

2)使用赫夫曼编码来生成赫夫曼编码数据 ,照上面的赫夫曼编码"i like like like java do you like a java"   字符串生成对应的编, 形式如下.
1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100

步骤3:

1)前面我们得到了赫夫曼编码和对应的编码byte[] , :[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]

2)现在要求使用赫夫曼编码, 行解码,又重新得到原来的字符串"i like like likejava do you like a java"

使用赫夫曼编码来解码数据,体要求是

1)前面我们得到了赫夫曼编码和对应的编码byte[] , :[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]

2)现在要求使用赫夫曼编码, 行解码,又重新得到原来的字符串"i like like likejava do you like a java"

package com.zhen;

import java.util.*;

public class Huffmancode {

	public static void main(String[] args) {
		String content= "i like like like java do you like a java";
		//转为byte数组
		byte[] bytes = content.getBytes();
		System.out.println("原字符串"+new String(bytes));
		byte[] huffmanzip = huffmanzip(bytes);
		System.out.println("哈夫曼编码压缩后:"+Arrays.toString(huffmanzip));
		byte[] recover=decode(huffman, huffmanzip);
		System.out.println("还原后的字符串"+new String(recover));
	}
	
	
	//三步对应的调用方法
	private static byte[] huffmanzip(byte[] bytes) {
		//第一步,加入集合并生成哈夫曼树
		List<Node> list=getNode(bytes);
		Node tree=huffman(list);
		//第二步,生成哈夫曼编码
		getcode(tree);
		//第三步,生成压缩后的数组
		byte[] zipb = zip(bytes, huffman);
		return zipb; 
	}
	
	//将哈夫曼表存入map<Byte(数值),String(对应的编码值)>例如:32->01,97->100
	static Map<Byte, String> huffman=new HashMap<>();
	//生成哈夫曼编码表示,需要去拼接路径,定义一个StringBuilder,存储某个叶子节点的路径
	static StringBuffer str=new StringBuffer();
	
	/**
	 * 第一步
	 * 生成哈夫曼树
	 * @param bytes  接收字节数组 
	 * @return  返回List的形式
	 */
	public static List<Node> getNode(byte[] bytes) {
		ArrayList<Node> list = new ArrayList<Node>();
		//遍历bytes统计每一个字符出现的次数->map[字符,次数]
		Map<Byte,Integer> map = new HashMap<Byte,Integer>();
		for(byte b:bytes) {
			Integer count=map.get(b);
			//未出现过
			if (count == null) {
				map.put(b, 1);
			}else {
				map.put(b, count+1);
			}
		}
		//将每一个键值对转成Node对象,加入list
		for(Map.Entry<Byte, Integer> entry : map.entrySet()) {
			list.add(new Node(entry.getKey(), entry.getValue()));
		}
		return list;
	}
	
	public static Node huffman(List<Node> list) {
		while(list.size() > 1) {
			//排序
			Collections.sort(list);
			//取出值最小的两颗二叉树
			Node left=list.get(0);
			Node right = list.get(1);
			//构建一颗新的二叉树
			Node parent = new Node(null,left.weight+right.weight);
			parent.left=left;
			parent.right = right;
			//删除使用过的节点
			list.remove(left);
			list.remove(right);
			//将新产生的节点加入list
			list.add(parent);
		}
		return list.get(0);
	}
	
	
	
	/**
	 * 第二步
	 * 生成哈夫曼树对应的编码
	 */
	
	//为了调用方便重载getcode,实现传入根节点就返回对应的哈夫曼编码
	private static Map<Byte, String> getcode(Node root) {
		if (root==null) {
			return null;
		}
		//处理root的左子树
		getcode(root.left, "0", str);
		//处理root的右左子树
		getcode(root.right, "1", str);
		return huffman;
	}
	/**
	 * 将传入的Node节点的所有叶子节点的哈夫曼编码得到,并存入集合
	 * @param node  传入节点
	 * @param code  路径:左子节点为0,右为1
	 * @param stringBuf  用于拼接路径
	 */
	private static void getcode(Node node,String code,StringBuffer stringBuf) {
		StringBuffer str2 = new StringBuffer(stringBuf);
		//将code加入str2
		str2.append(code);
		//如果为null不处理
		if (node!=null) {
			//判断是否为叶子节点
			//不是
			if (node.data==null) {
				//递归处理
				//向左递归
				getcode(node.left, "0", str2);
				//向右递归
				getcode(node.right, "1", str2);
			}else {
				//是叶子节点表示找到了最后
				huffman.put(node.data, str2.toString());
			}
		}
	}
	
	
	
	/**
	 * 第三步:将字符串对应的byte[]数组,通过生成的哈夫曼编码表,返回一个压缩后的byte[]
	 * @param bytes  原始字符串对应的byte[]
	 * @param huffmancode  生成的哈夫曼编码表
	 * @return  返回压缩后的byte[]
	 */
	private static byte[] zip(byte[] bytes,Map<Byte, String> huffmancode) {
		//利用huffmancode将bytes转成哈夫曼编码对应的字符串
		StringBuffer strc = new StringBuffer();
		//遍历加入
		for(byte b:bytes) {
			strc.append(huffmancode.get(b));
		}
		
		//统计返回byte[]huffmancode的长度
		int len;
		if (strc.length()%8==0) {
			len=strc.length()/8;
		}else {
			len=strc.length()/8+1;
		}
		
		//创建存储压缩后的byte数组
		byte[] zipb=new byte[len];
		int index=0;
		//每8位对应一个byte
		for(int i=0;i<strc.length();i+=8) {
			String strbyte;
			if (i+8>strc.length()) {
				//不够8位,有多少取多少
				strbyte=strc.substring(i);
			}else {
				//够8位,每次取出8位
				strbyte=strc.substring(i,i+8);
			}
			//存入byte数组
			zipb[index]=(byte)Integer.parseInt(strbyte,2);
			index++;
		}
		return zipb;
	}
	
	
	//将压缩后的byte数组进行解压得到原来的字符串
	
	/**
	 * 第一步:取出byte[]中的每一个byte转成二进制字符串
	 * @param flag  true表示为正数则需要补高位,false表示不补
	 * @param b  byte[]中取出的每一个byte
	 * @return  b对应的二进制字符串(补码)
	 */
	private static String bytetos(boolean flag,byte b) {
		//因为int类型有转二进制的方法可以直接用,所以首先将byte存为int
		int temp=b;
		if (flag) {
			//与运算补高位,例如:1 0000 0000|0000 0001->1 0000 0001
			temp|=256;
		}
		//返回对应的二进制补码
		String str=Integer.toBinaryString(temp);
		if (flag) {
			//原数是32位的,我们只需要后8位
			return str.substring(str.length()-8);
		}else {
			//不需要补齐则直接返回
			return str;
		}
	}
	/**
	 * 第二步:得到原字符串
	 * @param hcode  哈夫曼编码表
	 * @param huffmanb  哈夫曼编码得到的字节数组(17位)
	 * @return  原来字符串对应的数组
	 */
	private static byte[] decode(Map<Byte, String> hcode,byte[] huffmanb) {
		//先得到对应的二进制字符串
		StringBuilder str = new StringBuilder();
		//将byte数组转成二进制的字符串
		for(int i=0;i<huffmanb.length; i++) {
			byte b = huffmanb[i];
			//判断是否为最后一个字节
			boolean flag = (i==huffmanb.length-1);
			str.append(bytetos(!flag, b));
		}
		//将字符串按照原来的编码表进行解压
		//将编码表进行反转key->value,value->key,因为要进行反向查询
		Map<String, Byte> map=new HashMap<>();
		for(Map.Entry<Byte, String> entry : hcode.entrySet()) {
			map.put(entry.getValue(), entry.getKey());
		}
		//存放byte
		List<Byte> list = new ArrayList<>();
		for (int i = 0; i < str.length();) {
			int count=1;
			boolean flag=true;
			Byte b=null;
			while(flag) {
				//i不动,count移动去匹配每一个字符
				String key = str.substring(i,i+count);
				b=map.get(key);
				if (b == null) {
					count++;
				}else {
					//匹配到就退出
					flag = false;
				}
			}
			list.add(b);
			//i直接移动到count
			i+=count;
		}
		//for循环后list中就存入了原先的字符,将list中的数据存入byte并返回
		byte[] b=new byte[list.size()];
		for (int i = 0; i < b.length; i++) {
			b[i]= list.get(i);
		}
		return b;
	}
}
class Node implements Comparable<Node> {
	//存放数据(字符)本身,比如"a"->97
	Byte data;
	//字符出现的次数(权值)
	int weight;
	
	Node left;
	Node right;
	
	public Node(Byte data, int weight) {
		super();
		this.data = data;
		this.weight = weight;
	}

	@Override
	public int compareTo(Node o) {
		// 从小到大排序
		return this.weight-o.weight;
	}

	@Override
	public String toString() {
		return "Node [data=" + data + ", weight=" + weight + "]";
	}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1while(true){learn}

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值