zoukankan      html  css  js  c++  java
  • 简单哈弗曼树(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 }

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


    学无止境

  • 相关阅读:
    比较对象的相对性
    深拷贝与浅拷贝(TBD)
    创建UI的线程才能访问UI,那么怎样才算访问UI呢
    多层级的数据绑定效果
    众所周知,static修饰的成员只实例化一次,而string类型每次赋值都会重新创建一个实例,那么用static修饰string呢?
    常量、只读字段
    使用dos打开相关软件
    查看电脑硬件信息dos命令
    Windows常用快捷键
    使用外部编辑器出现乱码
  • 原文地址:https://www.cnblogs.com/jffx/p/9997868.html
Copyright © 2011-2022 走看看