——在這個特殊的日子裏,向烈士們致敬!!!
目錄
一、簡介
給定N個權值作爲N個葉子節點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹爲最優二叉樹,也稱爲哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
在計算機數據處理中,哈夫曼編碼使用變長編碼表對源符號(如文件中的一個字母)進行編碼,其中變長編碼表是通過一種評估來源符號出現機率的方法得到的,出現機率高的字母使用較短的編碼,反之出現機率低的則使用較長的編碼,這便使編碼之後的字符串的平均長度、期望值降低,從而達到無損壓縮數據的目的。
哈夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中所有的葉結點的權值乘上其到根結點的路徑長度(若根結點爲0層,葉結點到根結點的路徑長度爲葉結點的層數)。樹的路徑長度是從樹根到每一結點的路徑長度之和,記爲WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N個權值Wi(i=1,2,...n)構成一棵有N個葉結點的二叉樹,相應的葉結點的路徑長度爲Li(i=1,2,...n)。可以證明哈夫曼樹的WPL是最小的。
——百度百科
二、實現思路
2.1 路徑
2.2 節點的權及帶權路徑長度
2.3 樹的帶權路徑長度
2.4 霍夫曼樹的定義
每次挑選值最小的樹節點,將之合成爲一個節點,值爲兩個之和,取代原來兩個,直到只有一個樹節點——也就是根。
對比下圖哪個爲霍夫曼樹:
圖1的WPL爲:7*1+5*2+4*3+1*3 = 32
圖2的WPL爲:7*2+5*2+4*2+1*2 = 34
故:圖1爲霍夫曼樹
2.5 構造霍夫曼樹
現有以下編碼:AABCCADDEFFFCAABBCGAGGCCYYY
統計每個編碼出現的次數
編碼 | 出現次數 |
A | 6 |
B | 3 |
C | 6 |
D | 2 |
E | 1 |
F | 3 |
G | 3 |
Y | 3 |
將編碼根據出現次數進行排序,並且從排序後的數組中取出兩個最小的數進行將之合爲一個節點,取代原來兩個,直到只有一個節點
E | D | B | F | G | Y | A | C |
1 | 2 | 3 | 3 | 3 | 3 | 6 | 6 |
(E,D) | B | F | G | Y | A | C |
3 | 3 | 3 | 3 | 3 | 6 | 6 |
F | G | Y | A | C | (E,D,B) |
3 | 3 | 3 | 6 | 6 | 6 |
注:這是新的霍夫曼樹
Y | A | C | (E,D,B) | (F,G) |
3 | 6 | 6 | 6 | 6 |
注:這是新的霍夫曼樹
C | (E,D,B) | (F,G) | (Y,A) |
6 | 6 | 6 | 9 |
(F,G) | (Y,A) | (E,D,B,C) |
6 | 9 | 12 |
(E,D,B,C) | (F,G,Y,A) |
12 | 15 |
(E,D,B,C,F,G,Y,A) |
27 |
編碼規則:從根節點出發,向左標記爲0,向右標記爲1。
根據編碼規則,進行編碼
節點 | 編碼 |
A | 111 |
B | 010 |
C | 00 |
D | 0111 |
E | 0110 |
F | 100 |
G | 101 |
Y | 1110 |
故AABCCADDEFFFCAABBCGAGGCCYYY對應編碼爲:
111 111 010 00 00 111 0111 0111 0110 100 100 100 00 111 111 010 010 00 101 101 00 00 1110 1110 1110
數組的排序可能和代碼排序不一致,故編碼可能和代碼結果不同
三、代碼實現
@Data
public class HuffmanNode implements Comparable<HuffmanNode>{
private char letter;
private int count;//字符letter出現的頻率,即權重
private HuffmanNode left;
private HuffmanNode right;
public HuffmanNode(char letter, int count) {
this.letter = letter;
this.left = null;
this.right = null;
this.count = count;
}
public HuffmanNode(HuffmanNode left, HuffmanNode right) {
this.letter = '?';
this.left = left;
this.right = right;
this.count = left.count + right.count;
}
//爲了進行排序
@Override
public int compareTo(HuffmanNode o) {
return count - o.count;
}
}
package com.zcxy.tree.HT;
import com.zcxy.bean.HuffmanNode;
import java.util.*;
/**
* @Author: Eating melons the masses
* @Date: 2020/3/22 16:06
*/
public class HuffmanTree {
public static Map<Character,String> map = new HashMap<>();
public static void buildHuffman(String code){
if (code != null){
// 將編碼拆分並統計
char[] chars = code.toCharArray();
Map<Character,List<Character>> codeNumber = codeNumber(chars);
codeNumber.forEach((k,v) -> {
System.out.println(k+" : "+v.size());
});
// 根據編碼統計的結果構建節點
// HuffmanNode huffmanNode = generateTree(codeNumber);
List<HuffmanNode> list = new ArrayList<>();
codeNumber.forEach((k,v) -> {
list.add(new HuffmanNode(k,v.size()));
});
HuffmanNode huffmanNode1 = generateTree(list);
// 根據霍夫曼樹的性質-【左0,右1】進行編碼
code = "";
generateCodes(huffmanNode1, code);
StringBuilder stringBuilder = buildCode(map, chars);
System.out.println("編碼:"+stringBuilder.toString());
StringBuilder deCode = deCode(stringBuilder, map);
System.out.println("解碼:"+deCode.toString());
}
}
// 解碼
public static StringBuilder deCode(StringBuilder sb,Map<Character,String> map){
StringBuilder sbr = new StringBuilder();
String[] split = sb.toString().split(" ");
for (String code : split) {
map.forEach((k,v) -> {
if (code.equals(v)){
sbr.append(k);
}
});
}
return sbr;
}
// 編碼
public static StringBuilder buildCode(Map<Character,String> map,char[] chars){
StringBuilder sb = new StringBuilder();
if (!map.isEmpty()){
for (char aChar : chars) {
map.forEach((k,v) -> {
if (aChar == k){
sb.append(v).append(" ");
}
});
}
}
return sb;
}
//生成編碼,存放在HashMap中,key對應letter,value對應letter的二進制編碼
public static void generateCodes(HuffmanNode root, String code) {
if (root.getLeft() == null && root.getRight() == null) {
map.put(root.getLetter(), code);
} else {
generateCodes(root.getLeft(), code + "0");
generateCodes(root.getRight(), code + "1");
}
}
//構造霍夫曼樹(非遞歸)
private static HuffmanNode generateTree(Map<Character,List<Character>> codeNumber) {
List<HuffmanNode> list = new ArrayList<>();
codeNumber.forEach((k,v) -> {
list.add(new HuffmanNode(k,v.size()));
});
// 將當前節點根據權值進行排序 並構建最小霍夫曼樹
while (true){
Collections.sort(list);
HuffmanNode a = list.remove(0);
if (list.isEmpty()){
return a;
}
HuffmanNode b = list.remove(0);
list.add(new HuffmanNode(a,b));
}
}
// 將當前節點根據權值進行排序 並構建最小霍夫曼樹 (遞歸)
private static HuffmanNode generateTree(List<HuffmanNode> list) {
Collections.sort(list);
HuffmanNode a = list.remove(0);
if (!list.isEmpty()){
HuffmanNode b = list.remove(0);
list.add(new HuffmanNode(a,b));
return generateTree(list);
}
return a;
}
private static Map<Character,List<Character>> codeNumber(char[] chars) {
if (chars.length > 0){
Map<Character,List<Character>> codeNumber = new HashMap<>();
Set<Character> sets = new HashSet<>();
for (char aChar : chars) {
sets.add(aChar);
}
sets.forEach(s -> {
List<Character> codeList = new ArrayList<>();
for (char aChar : chars) {
if (s.equals(aChar)){
codeList.add(s);
}
}
codeNumber.put(s,codeList);
});
return codeNumber;
}
return null;
}
public static void main(String[] args) {
buildHuffman("AABCCADDEFFFCAABBCGAGGCCYYY");
}
}
{\__/} {\__/}
( ·-·) (·-· )
/ >------------------------------------------------< \
| ☆ |
| ☆ |
| ★ |
| ☆ |
| ☆ |
| |
-------------------------------------