哈夫曼编码与解码

##哈夫曼编码

  • 哈夫曼编码(Huffman Coding)是一种根据字符出现的概率对字符进行编码的编码方法

  • 哈夫曼编码的算法哈夫曼编码与解码_第1张图片
    哈夫曼编码与解码_第2张图片

  • 用java实现哈夫曼编码
    1.统计文本中各字符的出现次数

/*
	 * 输入一个文本,输出该文本的字符统计信息的
	 * 
	 * @return 用map表示统计信息
	 */
	private static Map<Character, Integer> text2Map(String text) {
		if (text == null || "".equals(text.trim()))
			return null;

		Map<Character, Integer> map = new TreeMap<Character, Integer>();

		for (int i = 0; i < text.length(); i++) {
			if (map.containsKey(text.charAt(i))) {
				map.put(text.charAt(i), map.get(text.charAt(i)) + 1);
			} else {
				map.put(text.charAt(i), 1);
			}
		}
		return map;
	}

2 构造哈夫曼树
哈夫曼编码与解码_第3张图片
。 每次从列表里提取(取出并删除)2个权(对应字符出现的次数)最小的节点:min1和min2,
。 同时创建新的节点n,n的权等于min1的权 + min2的权,
。 设置4个指针:parent,parent,left和right,
。把n加入列表
。当列表中只有2个节点时是最后一次从列表中取节点

节点:

public class CharacterPo implements Comparable {
	private Character character;// 字符
	private Integer number;// 字符个数
	private Boolean needEncode;// 在构造hfm tree时是否可以取出
	private CharacterPo parent;// 父节点
	private CharacterPo left;// 左节点
	private CharacterPo right;// 右节点
	private List<Integer> codeList;// 编码
	private Boolean leaf;// 是否是叶节点(是否是需要编码的节点)
	
	public int compareTo(Object o) {
		if (o instanceof CharacterPo) {
			CharacterPo po = (CharacterPo) o;
			return this.number - po.number;
		}
		return 0;
	}

}

把map形式的字符统计信息转成CharacterPO组成的列表

	/* 把k-v pair装成对象放入list中 */
	private static List<CharacterPo> map2POList(Map<Character, Integer> map) {
		// System.out.println(map);
		ArrayList<CharacterPo> list = new ArrayList<CharacterPo>();
		for (Character c : map.keySet()) {
			CharacterPo characterPo = new CharacterPo(c, map.get(c), true);
			characterPo.setLeaf(true);
			CharacterPo po = characterPo;
			list.add(po);
		}
		return list;
	}

构造哈夫曼树

/* 输入字符统计信息的list,返回haffman tree's root */
	private CharacterPo getHuffmanTree(List<CharacterPo> list) {

		if (list == null || list.isEmpty())
			return null;
		CharacterPo root = null;
		Integer size = StatisticsUtil.getNeedEncodeSize(list);
		while (StatisticsUtil.getNeedEncodeSize(list) > 0) {

			// last time means only 2 nodes’s needEncode is true in the list
			boolean lastTime = false;

			if (StatisticsUtil.getNeedEncodeSize(list) == 2)
				lastTime = true;

			CharacterPo min1 = StatisticsUtil.extractMin(list);
			CharacterPo min2 = StatisticsUtil.extractMin(list);

			// 在这里设置false无效,why:必须在取min2前设置min1
			// 的needEncode=false
			// 所以把设置needEncode放在了extractMin方法中
			// 这样做其实不好
			// min1.setNeedEncode(false);
			// min2.setNeedEncode(false);

			CharacterPo p = new CharacterPo('#', min1.getNumber()
					+ min2.getNumber(), true);
			p.setLeft(min1);
			p.setRight(min2);
			min1.setParent(p);
			min2.setParent(p);

			// 当只有2个字符时-最后一次
			if (!lastTime)
				list.add(p);

			root = p;
		}

		return root;
	}

3 用哈夫曼树进行编码

	/* 根据huffman tree进行编码 */
	private List<CharacterPo> huffmanEncode(List<CharacterPo> list) {
		// left:0 right:1
		for (CharacterPo po : list) {
			if (!po.getLeaf())
				continue;
			CharacterPo tmpPo = po;
			while (tmpPo.getParent() != null) {
				CharacterPo parent = tmpPo.getParent();
				Integer code = parent.getLeft() == tmpPo ? 0 : 1;
				po.getCodeList().add(code);

				tmpPo = tmpPo.getParent();
			}
		}
		for (CharacterPo po : list) {
			if (po.getLeaf()) {
				Collections.reverse(po.getCodeList());
			}
		}

		// 筛选出需要编码的节点
		ArrayList<CharacterPo> resList = new ArrayList<CharacterPo>();
		for (CharacterPo po : list) {
			if (po.getLeaf())
				resList.add(po);
		}
		return resList;
	}

 
 

哈夫曼解码

  • 借鉴“搜索引擎原理”中基于字典的中文分词方法:正向最大匹配算法、逆向最大匹配算法
  • 正向最大匹配算法:
    。字典中最长的key的长度是n
    。从左往右扫描要解码的文本:第一次扫描n个字符,如果字典中有匹配的key则用该key对应的value替换这n个字符,如果无匹配的key则n=n-1,再次扫描

经验证“逆向最大匹配”算法不可以用来进行哈夫曼解码

验证:
字典 110=d,10=b,111=c,0=a
bc编码后:11110
采用逆向最大匹配时最后一个字符会解码成d(110)原串中最后一个字符是c

  • java实现
public String decode(Map<Character, String> dictMap, String text) {
		if (text == null || text.trim().length() == 0)
			return null;
		String decode = "";

		// 调换dictMap的k,v
		Map<String, Character> map = MapUtil.swapKV(dictMap);
		// 获取map中最长的key
		String longestKey = MapUtil.getLongestKey(map);

		int i = 0;
		int len = Math.min(longestKey.length(), text.length());
		while (i < text.length()) {
			String s = text.substring(i, i + len);
			Character character = map.get(s);
			if (character == null)
				len--;
			else {
				decode += character;
				i += len;
				len = Math.min(longestKey.length(), text.length() - i);
			}

		}
		return decode;

	}

需要完整代码请点击

你可能感兴趣的:(数据结构,算法)