/**
* 实现comparable从而让后面放入优先队列
* 可以实现从小到大排序
*/
public class HfmNode implements Comparable<HfmNode>{
public Character data;
public Integer val;
public HfmNode left;
public HfmNode right;
public HfmNode parent;
public HfmNode(Character data,Integer val){
this.data = data;
this.val = val;
}
@Override
public int compareTo(HfmNode o) {
return this.val-o.val;
}
}
/**
* 哈夫曼树
* 直白点讲就是事先已经确定了叶子结点,且节点里面有权值
* 我们根据权值进行升序排序的容器中
* 每次取前两个最小的作为左右节点,并将加起来的权值作为父节点的权值
* 再将父节点放入之前的容器中进行排序
* 再次取前两个最小的以此类推,最后构造出来的二叉树就叫哈夫曼树
* 我们把左路径的一条边当做0,右路径 一条边当做1
* 那么当我们到每一个叶子节点所经过的路径都可以得到一个01序列
* 此序列就可以用来当做该叶子节点的值得替代编码
* 我们可以通过这里面编解码操作来实现解压缩之类的功能
*/
public class HfmTree {
HfmNode root;
List<HfmNode> leafs = null;
Map<Character,Integer> valMap = null;
Map<Character,String> codeMap = null;
public HfmTree(Map<Character,Integer> valMap){
this.valMap = valMap;
this.leafs = new ArrayList<>();
this.codeMap = new HashMap<>();
}
/**
* 为每一个叶子节点构造出对应的01序列编码
* 从叶子节点逆向遍历,每次的节点是父节点的左节点就在构造的序列码的前面加0
* 是右节点就加1,直到遍历到根节点没有父节点了就得到了当时的叶子节点的01序列编码了
* @return
*/
public Map<Character,String> list(){
for(int i=0;i<leafs.size();i++){
HfmNode node= this.leafs.get(i);
String code = "";
Character data = node.data;
do{
if(node.parent.left == node){
//左孩子
code = "0"+code;
}else{
code ="1" +code;
}
node = node.parent;
}while (node.parent!=null);
System.out.println(data+":"+code);
this.codeMap.put(data,code);
}
return this.codeMap;
}
/**
* 解码
* 从01序列编码转成明文
* 这里的逻辑我通过直接遍历01序列
* 每次是0 ,我就取左子树
* 是1,我就取右子树
* 直到遍历到叶子节点,就可以取到对应的明文字符了
* 然后将当前节点再重置回根节点,继续遍历,
* 直到遍历完01序列,就得到了明文了
* @param code
* @return
*/
public String decode(String code){
char[] codes = code.toCharArray();
StringBuilder res = new StringBuilder();
HfmNode cur = this.root;
for(int i=0;i<codes.length;i++){
char c = codes[i];
if(c=='1'){
cur = cur.right;
}else {
cur= cur.left;
}
if(cur.left==null && cur.right == null){
res.append(cur.data);
cur = this.root;
}
}
return res.toString();
}
/**
* 编码
* 从明文转换成01序列
* 逻辑比较简单,我们通过之前已经存储好的每个明文对应的01序列的map
* 直接将明文转成字符数组,然后每个字符从map中取到对应的01序列
* 拼接起来就是编码后的结果
* @param str
* @return
*/
public String encode(String str){
char[] chars = str.toCharArray();
StringBuilder sb=new StringBuilder();
for(int i=0;i<chars.length;i++){
Character c = chars[i];
sb.append(this.codeMap.get(c));
}
return sb.toString();
}
/**
* 构建哈夫曼树
* 通过优先队列来实现排序功能
* 每次poll出两个,也就是最小的两个节点进行构造
* 再将两个节点的权值的和构造出的父节点再放入优先队列中进行排序
* 再拿出前两个,依次类推
* 最后只剩一个了,就是根节点
*/
public void createTree(){
PriorityQueue<HfmNode> priorityQueue=new PriorityQueue<>();
for(Map.Entry<Character,Integer> entry:this.valMap.entrySet()){
HfmNode node= new HfmNode(entry.getKey(),entry.getValue());
this.leafs.add(node);
priorityQueue.add(node);
}
int len = this.leafs.size();
for(int i =0;i<len-1;i++){
HfmNode node1 = priorityQueue.poll();
HfmNode node2 = priorityQueue.poll();
Character data = null;
int val = node1.val +node2.val;
HfmNode parent =new HfmNode(data,val);
parent.left = node1;
parent.right = node2;
node1.parent = parent;
node2.parent = parent;
priorityQueue.add(parent);
}
this.root = priorityQueue.poll();
}
public static void main(String[] args) {
Map<Character,Integer> weights = new HashMap<>();
weights.put('a',3);
weights.put('b',24);
weights.put('c',6);
weights.put('d',20);
weights.put('e',34);
weights.put('f',4);
weights.put('g',12);
HfmTree tree= new HfmTree(weights);
tree.createTree();
tree.list();
System.out.println(tree.encode("abcd"));
System.out.println(tree.decode("1011001101000"));
}
}