简单哈弗曼树(Java)

哈夫曼树的实现


  1.编码思想

    哈夫曼编码是一种变长的编码方案,字符的编码根据使用频率的的不同而长短不一, 使用频率高的字符其编码较短,使用频率低的字符编码较长,从而使所有的编码总长度为最短.

  1. 统计原始数据中个新号符号的频率,安频率高低的次序排列
  2. 将两个频率最小的相加,作为原本两个节点的父节点,次父节点的频率为子节点之和
  3. 重复上述两部,直到和为只剩下一个元素,那么这个元素就是根

       2.解码思想

           利用Haffman树进行解码,已知一个二进制位串S,

   从串S的第一位出发,逐位的去匹配二叉树边上标记的0和1

   从haffman树的根节点出发,遇到0时,向左,遇到1时向右,若干连续的0和1确定一条从根节点到某个叶子节点的路径.一旦到达一个叶子节点,便译出一个字符,

   接着从S的下一个位开始继续寻找,然后在重新将指针指向根.


编码和解码操作

主要有三部分

1.对编码进行计数,然后通过键值对构造haffman树,这里主要使用了优先队列,也就是最小堆,需要内部节点类实现Comparable接口

2.通过树,返回叶节点的编码,这里我利用Map<Character, String>这样将字符和编码绑定到了一起,方便后面操作

3.根据输入的字符串,遍历,返回编码后的字符串,如下:

        //toCode()返回一个Map键值对
        Map<Character, String> codes = haff.toCode() ;

        StringBuilder stringcode = new StringBuilder() ;
        //遍历字符串,求其编码
        for(char ch : text.toCharArray()) {
            stringcode.append(codes.get(ch)) ;
        }

        return stringcode.toString() ;

下面是代码演示:

  1 package com.jffx.util;
  2 
  3 import java.util.*;
  4 
  5 /*
  6 本类负责逻辑处理,
  7 负责串---->码的编码
  8     吗---->串的解码
  9  */
 10 public class HaffmanCode {
 11 
 12     private Node root ;
 13 
 14 
 15     //---------------------------------------- 下面是内部节点类
 16     static class Node implements Comparable<Node> {
 17         Integer weight ;
 18         String code = "" ;                  //取一个字符串域表示每个节点的编码
 19         Character data ;
 20 
 21         Node parent ;
 22         Node left ;
 23         Node right ;
 24         public Node(int w, Node left, Node right, Node parent) {
 25             this.weight = w ;
 26             this.left = left ;
 27             this.parent = parent ;
 28             this.right = right ;
 29         }
 30 
 31         @Override
 32         public int compareTo(Node o) {
 33             return this.weight - o.weight ;
 34         }
 35 
 36         @Override
 37         public String toString() {
 38             return "[" + this.data + ", " + this.weight + "]" ;
 39         }
 40     }
 41 
 42 
 43     /**
 44      * 构造haffman树
 45      * 先将strs这样的键值对转化为节点, 然后加入优先队列
 46      * @param strs 字符--频率这样的键值对
 47      */
 48     public HaffmanCode(Map<Character, Integer> strs) {
 49         Queue<Node> queue = new PriorityQueue<>() ;
 50 
 51         Set<Character> keys = strs.keySet() ;
 52         for(Character key : keys) {
 53             Node newNode = new Node(strs.get(key), null, null, null) ;
 54             newNode.data = key ;
 55             newNode.weight = strs.get(key) ;
 56 
 57             //入堆 -- 覆写CompareTo
 58             queue.add(newNode) ;
 59         }
 60 
 61         while(queue.size() > 1) {
 62             Node n1 = queue.poll() ;
 63             Node n2 = queue.poll() ;
 64 
 65             Node sumNode = new Node(n1.weight + n2.weight , n1, n2, null) ;
 66 
 67             n1.parent = sumNode ;
 68             n2.parent = sumNode ;
 69 
 70             queue.add(sumNode) ;
 71         }
 72 
 73         this.root = queue.poll() ;
 74     }
 75 
 76     /**
 77      * 将树的叶节点的编码返回, 返回的键值是   字符 -- 编码 这样的形式
 78      * @return
 79      */
 80     public Map<Character, String> toCode() {
 81         Map<Character, String> map = new HashMap<>() ;
 82 
 83         preTraverse() ;
 84 
 85         //前序遍历.将叶节点的属性保存到map中
 86         List<Node> stack = new LinkedList<>() ;
 87         Node p = this.root ;
 88         while(p != null || !stack.isEmpty()) {
 89             while(p != null) {
 90                 stack.add(p) ;
 91                 p = p.left ;
 92             }
 93             //如果栈不空,去栈顶元素,然后向右
 94             if(!stack.isEmpty()) {
 95                 p = ((LinkedList<Node>) stack).pop() ;
 96                 if(isLeafNode(p)) {
 97                     map.put(p.data, p.code) ;
 98                 }
 99                 p = p.right ;
100             }
101         }
102         return map ;
103     }
104 
105     /**
106      * 输入一串编码,然后返回解码后的字符串
107      * @return
108      */
109     public String deCode(String codes) {
110         StringBuilder stringBuilder = new StringBuilder() ;
111 
112         Node p = this.root ;        //从根出发
113         for(int i = 0 ; i < codes.length() ; ++i) {
114             if(codes.charAt(i) == '0') {
115                 p = p.left ;
116             } else {
117                 p = p.right ;
118             }
119             if(isLeafNode(p)) { //如果是叶子
120                 stringBuilder.append(p.data) ;
121                 p = this.root ;
122             }
123         }
124         return stringBuilder.toString() ;
125     }
126 
127     private void preTraverse() {
128         preTraverse(this.root) ;
129     }
130     private void preTraverse(Node root) {
131         if(root != null) {
132             if(root != this.root) {     //如果不是根节点
133                 if(root == root.parent.left) {  //左孩子
134                     root.code = root.parent.code + "0" ;
135                 } else {
136                     root.code = root.parent.code + "1" ;
137                 }
138             }
139             //System.out.println(root) ;
140             preTraverse(root.left) ;
141             preTraverse(root.right) ;
142         }
143     }
144 
145     public boolean isLeafNode(Node node) {
146         return node.left == null && node.right == null ;
147     }
148 
149 
150 
151 
152 
153 
154 
155 
156 
157     /**
158      * 测试
159      * @param args
160      */
161     public static void main(String[] args) {
162         String str = "today is Saturday." ;
163 
164         char[] strCh = str.toCharArray() ;
165         Map<Character, Integer> map = new HashMap<>() ;
166 
167         for(int i = 0 ; i < strCh.length ; ++i) {
168             if(map.containsKey(strCh[i])) { //如果存在键
169                 map.put(strCh[i], map.get(strCh[i]) + 1) ;
170             } else {
171                 map.put(strCh[i], 1) ;
172             }
173         }
174 
175         HaffmanCode haff = new HaffmanCode(map) ;
176 
177         String code = getTextCode(haff, str) ;
178         String decode = haff.deCode(code) ;
179         System.out.println(decode) ;
180     }
181 
182     /**
183      * 获取一个字符串的编码
184      * @param text 需要压缩的字符串
185      * @return
186      */
187     public static String getTextCode(HaffmanCode haff, String text) {
188 
189         Map<Character, String> codes = haff.toCode() ;
190         StringBuilder stringcode = new StringBuilder() ;
191         for(char ch : text.toCharArray()) {
192             stringcode.append(codes.get(ch)) ;
193         }
194         return stringcode.toString() ;
195     }
196 }

这里你会发现,你压缩后的数据比你没压缩前还大,额--------


学无止境

原文地址:https://www.cnblogs.com/jffx/p/9997868.html