Huffman树和编解码

Huffman树的建立

基本介绍
  1. 给定n个权值作为n 个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)
  2. 赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近
赫夫曼树几个重要概念
  1. 路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1
  2. 结点的权及带权路径长度:若将树中结点赋给一个有着某种含义的数值.则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积
  3. 树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL(weighted path length) ,权值越大的结点离根结点越近的二叉树才是最优二叉树
  4. WPL最小的就是赫夫曼树
赫夫曼树创建思路

给你一个数列{13,7,8,3,29,6,1},要求转成一颗赫夫曼树.构成赫夫曼树的步骤:

  1. 从小到大进行排序,将每一个数据,每个数据都是一个节点,每个节点可以看成是一颗最简单的二叉树
  2. 取出根节点权值最小的两颗二叉树
  3. 组成一颗新的二叉树,该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
  4. 再将这颗新的二叉树,以根节点的权值大小再次排序.不断重复1-2-3-4 的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树
public class HuffmanTree {
    public static void main(String[] args) {
        int[] arr={13,7,8,3,29,6,1};
        Node huffmanTree=createHuffmanTree(arr);
        preOrder(huffmanTree);
    }

    //前序遍历
    public static void preOrder(Node root){
        if(root!=null)
            root.preOrder();
        else
            System.out.println("空树,不能遍历");
    }

    public static Node createHuffmanTree(int[] arr){
        List<Node> nodes=new ArrayList<>();
        for(int value:arr)
            nodes.add(new Node(value));
        while(nodes.size()>1)
        {
            Collections.sort(nodes);
            Node leftNode=nodes.get(0);
            Node rightNode=nodes.get(1);
            Node parentNode=new Node(leftNode.value+rightNode.value);
            parentNode.left=leftNode;
            parentNode.right=rightNode;
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            nodes.add(parentNode);
        }
        return nodes.get(0);
    }

}
class Node implements Comparable<Node>{
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }

    //前序遍历
    public void preOrder(){
        System.out.println(this);
        if(this.left!=null)
            this.left.preOrder();
        if(this.right!=null)
            this.right.preOrder();
    }

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                '}';
    }

    @Override
    public int compareTo(Node node) {
        return this.value-node.value;
    }
}

赫夫曼编解码

public class HuffmanCode {
    static Map<Byte,String> huffmanCodes=new HashMap<>();
    static StringBuilder stringBuilder=new StringBuilder();

    public static void main(String[] args) {
        String content="asdds astkk nhb sgacsw aevsbd";
        byte[] contentBytes=content.getBytes();
        byte[] huffmanCodeBytes=huffmanZip(contentBytes);
        System.out.println(Arrays.toString(huffmanCodeBytes));
        byte[] sourceBytes=decode(huffmanCodes,huffmanCodeBytes);
        System.out.println(new String(sourceBytes));

    }

    /**
     * 将一个byte转化为一个二进制的字符串
     * @param flag 表示是否需要补高位,如果是true表示需要补高位,如果是false表示不补,如果是最后一个字节无需补高位
     * @param b
     * @return
     */
    public static String byteToBitString(boolean flag,byte b){
        int temp=b;
        if(flag)
            temp|=256;
        String str=Integer.toBinaryString(temp);
        if(flag)
            return str.substring(str.length()-8);
        else
            return str;
    }

    /**
     * 解码
     * @param huffmanCodes
     * @param huffmanBytes
     * @return
     */
    public static byte[] decode(Map<Byte,String> huffmanCodes,byte[] huffmanBytes){
        StringBuilder stringBuilder=new StringBuilder();
        for(int i=0;i<huffmanBytes.length;i++){
            byte b=huffmanBytes[i];
            boolean flag=(i==huffmanBytes.length-1);
            stringBuilder.append(byteToBitString(!flag,b));
        }
        Map<String,Byte> map=new HashMap<>();
        for(Map.Entry<Byte,String>entry:huffmanCodes.entrySet()){
            map.put(entry.getValue(),entry.getKey());
        }

        List<Byte> list=new ArrayList<>();
        for(int i=0;i<stringBuilder.length();){
            int count=1;
            boolean flag=true;
            Byte b=null;
            while(flag){
                String key=stringBuilder.substring(i,i+count);
                b=map.get(key);
                if(b==null)
                    count++;
                else
                    flag=false;
            }
            list.add(b);
            i+=count;
        }
        byte[] b=new byte[list.size()];
        for(int i=0;i<b.length;i++)
            b[i]=list.get(i);
        return b;
    }
    //封装流程
    private static byte[] huffmanZip(byte[] bytes){
        List<Node> nodes=getNodes(bytes);
        Node huffmanTreeRoot=createHuffmanTree(nodes);
        Map<Byte,String> huffmanCodes=getCodes(huffmanTreeRoot);
        byte[] huffmanCodeBytes=zip(bytes,huffmanCodes);

        return huffmanCodeBytes;
    }

    /**
     * 一、将传入的字节数组转为List<Node>集合
     * @param bytes 传入的字节数组
     * @return List<Node>集合
     */
    private static List<Node> getNodes(byte[] bytes){
        ArrayList<Node> nodes=new ArrayList<>();
        Map<Byte,Integer> counts=new HashMap<>();

        for(byte b:bytes) {
            Integer count=counts.get(b);
            if(count==null)
                counts.put(b,1);
            else
                counts.put(b,count+1);
        }

        for(Byte b:counts.keySet()){
            Node node=new Node(b,counts.get(b));
            nodes.add(node);
        }
        return nodes;
    }

    /**
     * 二、构建Huffman树
     * @param nodes Node集合
     * @return 根节点
     */
    public static Node createHuffmanTree(List<Node> nodes){
        while(nodes.size()>1)
        {
            Collections.sort(nodes);
            Node leftNode=nodes.get(0);
            Node rightNode=nodes.get(1);
            Node parentNode=new Node(null,leftNode.weight+rightNode.weight);
            parentNode.left=leftNode;
            parentNode.right=rightNode;

            nodes.remove(leftNode);
            nodes.remove(rightNode);
            nodes.add(parentNode);
        }
        return nodes.get(0);
    }

    //重载
    public static Map<Byte,String> getCodes(Node root){
        if(root==null)
            return null;
        else{
            getCodes(root.left,"0",stringBuilder);
            getCodes(root.right,"1",stringBuilder);
        }
        return huffmanCodes;
    }

    /**
     * 三、获取Huffman编码表
     * @param node 节点
     * @param code 路径:左子节点为0,右子节点为1
     * @param stringBuilder 拼接路径(编码)
     */
    public static void getCodes(Node node,String code,StringBuilder stringBuilder){
        StringBuilder stringBuilder2=new StringBuilder(stringBuilder);
        stringBuilder2.append(code);
        if(node!=null){
            if(node.data==null){
                getCodes(node.left,"0",stringBuilder2);
                getCodes(node.right,"1",stringBuilder2);
            }
            else
                huffmanCodes.put(node.data,stringBuilder2.toString());
        }
    }

    /**
     * 四、将原始数组转化为压缩后的字节数组
     * @param bytes 原始字节数组
     * @param huffmanCodes 编码表
     * @return 压缩后的字节数组
     */
    private static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCodes){
        StringBuilder stringBuilder=new StringBuilder();
        for(byte b:bytes){
            stringBuilder.append(huffmanCodes.get(b));
        }
        int len=(stringBuilder.length()+7)/8;
        byte[] huffmanCodeBytes=new byte[len];
        int index=0;
        for(int i=0;i<stringBuilder.length();i+=8){
            String strByte;
            if(i+8>stringBuilder.length())
                strByte=stringBuilder.substring(i);
            else
                strByte=stringBuilder.substring(i,i+8);
            huffmanCodeBytes[index]=(byte)Integer.parseInt(strByte,2);
            index++;
        }
        return huffmanCodeBytes;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容