ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

一文了解赫夫曼树的构建与赫夫曼编码

2021-11-14 21:04:23  阅读:125  来源: 互联网

标签:Node 编码 一文 byte nodes 节点 夫曼


文章目录


一、赫夫曼树

基本介绍

  1. 给定n个权值作为n个叶子结点,构造一颗二叉树。若该树的带权路径长度(wpl)达到最小,这样的二叉树为最优二叉树,也称为哈夫曼树(HuffmanTree)还有的书翻译为霍夫曼树
  2. 赫夫曼树是带权路径长度最短的树,权值较大的节点离根较近

赫夫曼树几个重要概念和举例说明

  1. 路径和路径长度:在一棵树中,从一个节点往下可以达到的孩子或孙子节点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根节点的层数为1,则从根节点到第L层节点的路径长度为L-1

  2. 节点的权及带权路径长度:若将树中节点赋给一个有着某种含义的数值,则这个数值称为该节点的权,节点的带权路径长度为:从根节点到该节点之间的路径长度与该节点的权的乘积

  3. 例如下图,从根节点到13这个节点的路径长度为3-1=2,13这个节点的带权路径长度为2*13=26
    在这里插入图片描述

  4. 树的带权路径长度:树的带权路径长度规定所有叶子结点的带权路径长度之和,记为WPL(weighted path length)权值越大的节点离根节点越近的二叉树才是最优二叉树

  5. WPL最小的就是赫夫曼树

在这里插入图片描述
在这里插入图片描述

赫夫曼树创建步骤图解

假如有下面一组数据{13,7,8,3,29,6,1},我们来构建赫夫曼树

  1. 从小到大进行排序,每个数据都是一个节点,每个节点可以看成是一颗最简单的二叉树
    1. 排序{1,3,6,7,8,13,29}
  2. 取出根节点权值最小的两个二叉树,组成一颗新的二叉树,该树的二叉树的根节点的权值是前面两棵二叉树根节点权值的和,比如1,3组成二叉树,二叉树节点权值为1+3=4
  3. 再将这颗新的二叉树,以根节点的权值大小再次排序,不断重复1-2-3-4的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树
  4. 在这里插入图片描述

代码构建赫夫曼树

package org.wql.huffmantree;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**哈夫曼树
 * Description
 * User:
 * Date:
 * Time:
 */
public class HuffmanTree {
    public static void main(String[] args) {
        int arr[] = {13,7,8,3,29,6,1};
        Node root = huffman(arr);
        preOrder(root);
    }

    //创建赫夫曼树的方法
    public static Node huffman(int[] arr){
        //遍历arr数组
        //1.遍历arr数组
        //2.将arr的每个元素构成一个Node
        //3.将Node放入到ArrayList中

        List<Node> nodes = new ArrayList<Node>();
        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 parent = new Node(leftNode.value+rightNode.value);
            parent.left=leftNode;
            parent.right=rightNode;

            //将原先的两个最小的节点移出集合
            nodes.remove(leftNode);
            nodes.remove(rightNode);

            //将新节点添加入集合
            nodes.add(parent);
        }

        //将赫夫曼树的头节点返回
        return nodes.get(0);
    }

    public static void preOrder(Node root){
        if(root==null){
            System.out.println("树空,无法遍历");
        }else {
            root.preOrder();
        }
    }


}

//为了让Node对象支持排序
//让Node 实现Comparable<Node>
class Node implements Comparable<Node>{
    int value;
    Node left;
    Node right;

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

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

    @Override
    public int compareTo(Node o) {
        //表示从小到大排
        return this.value-o.value;
    }

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

    }
}

二、赫夫曼编码

1基本介绍

  1. 赫夫曼编码也翻译为哈夫曼编码(Huffman Coding)又称霍夫曼编码,是一种编码方式,属于一种程序算法
  2. 赫夫曼编码是赫夫曼树在电讯通信中的经典的应用之一
  3. 赫夫曼编码广泛的用于数据文件压缩,其压缩率通常在20%~90%之间
  4. 赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,称之为最佳编码

通信领域中的信息的处理方式1-定长编码

如果按照二进制来传递信息,其中长度为359,其中包括空格,可以看到长度非常的长
在这里插入图片描述

通信领域中的信息的处理方式2-变长编码

我们通过统计每个字符出现的次数,作为发送依据完成信息传递,长度大大缩减,但是信息读取的精准度却大大下降
在这里插入图片描述

通信领域中信息的处理方式3-赫夫曼编码

赫夫曼编码的原理剖析
如果我们要传递这样的字符串:“i like like like java do you like a java”

  1. 首先统计各个字符出现的个数

    1. d:1

      y:1

      u:1

      j:2

      v:2

      o:2

      l:4

      k:4

      3:4

      i:5

      a:5

      :9

  2. 按照上面字符出现的个数构建一颗赫夫曼树,将次数作为权值在这里插入图片描述

  3. 根据赫夫曼树,给每个字符规定编码(前缀编码),向左的路径为0,向右的路径为1

    1. o:1000
    2. u:10010
    3. d:100110
    4. y:100111
    5. i:101
    6. a:110
    7. k:1110
    8. e:1111
    9. j:0000
    10. v:0001
    11. l:001
    12. 空格:01
      按照上方的赫夫曼编码,我们得到字符串对应的编码

在这里插入图片描述
可以看到在提高精准度的同时,数据长度也大大减少

注意:

赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是wpl是一样的,都是最小的

将字符串通过赫夫曼进行压缩

package org.wql.huffmancode;

import java.util.*;

/**
 * Description
 * User:
 * Date:
 * Time:
 */
public class HuffmanCode {

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

    public static void main(String[] args) {
        String content = "i like like like java do you like a java";
        byte[] contentBytes = content.getBytes();
        System.out.println("未压缩之前的长度:"+contentBytes.length);
        byte[] bytes = huffmanZip(contentBytes);
        System.out.println("压缩后的结果是:"+Arrays.toString(bytes));
        System.out.println("压缩率为:"+(double)(contentBytes.length-bytes.length)/contentBytes.length);
    }

    public static byte[] huffmanZip(byte[] contentBytes){
        List<Node> nodes = getNodes(contentBytes);
        System.out.println(nodes);
        //生成赫夫曼树
        Node root = huffman(nodes);
        preOrder(root);
        //利用生成的赫夫曼树,完成赫夫曼码表
        Map<Byte, String> codes = root.getCodes(stringBuilder, huffmanCodes);
        codes.forEach((v,i)->{
            System.out.println(v+":"+i);
        });
        //通过生成的赫夫曼编码表,测试是否生成了对应的赫夫曼编码
        byte[] zip = zip(contentBytes, codes);
        return zip;
    }




    //构建赫夫曼树
    private static Node huffman(List<Node> nodes) {


        while (nodes.size()>1){
            Collections.sort(nodes);

            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);

            Node parent = new Node(null, leftNode.weight + rightNode.weight);

            parent.left=leftNode;
            parent.right=rightNode;

            nodes.remove(leftNode);
            nodes.remove(rightNode);

            nodes.add(parent);
        }
        return nodes.get(0);
    }

    public static List<Node> getNodes(byte[] bytes){

        ArrayList<Node> nodes = new ArrayList<>();
        Map<Byte,Integer> map = new HashMap<>();
        for (byte b : bytes) {
            //count是否为零代表是否已经出现过该字符
            Integer count = map.get(b);
            if(count!=null){
                map.put(b,count+1);
            }else{
                map.put(b,1);
            }
        }
        map.forEach((v,i)->{
            nodes.add(new Node(v,i));
        });
        return nodes;
    }

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

    /**
     *
     * @param bytes 原始字符串对应的byte
     * @param huffmanCodes huffmanCodes 生成的赫夫曼编码
     * @return 返回赫夫曼编码处理后的byte[]
     * 例如返回字符串100101010101010101010001111
     *
     *huffmanCodeBytes[0]=10010101(补码)
     *10010101因为是补码,所以我们现将其转为反码再减1
     * 10010101-1 = 10010100
     */
    private static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCodes){

        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(huffmanCodes.get(b));
        }
        System.out.println(sb.length());
        //将字符串转成byte数组
        //统计返回byte[] huffmanCodeBytes的长度
        int len = (sb.length()+7)/8;
        byte[] huffmanCodeBytes = new byte[len];
        int index = 0;
        for (int i=0;i<sb.length();i+=8){
            //每8位对应一个byte,所以步长+8
            String strByte;
            if(i+8>sb.length()){
                strByte = sb.substring(i);
            }else {
                strByte = sb.substring(i,i+8);
            }
            //将strByte转成byte,放进huffmanCodeBytes
            huffmanCodeBytes[index] = (byte)Integer.parseInt(strByte,2);
            index++;
        }
        return huffmanCodeBytes;
    }
}

class Node implements Comparable<Node> {
    Byte data;
    int weight;
    Node left;
    Node right;

    public Node(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

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

    @Override
    public int compareTo(Node o) {
        //从小到大排序
        return this.weight-o.weight;
    }

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

    public Map<Byte,String> getCodes(StringBuilder stringBuilder,Map<Byte,String> huffmanCodes){
        StringBuilder builder = new StringBuilder(stringBuilder);
        if(this.data!=null){
            huffmanCodes.put(this.data,builder.toString());
            builder=new StringBuilder("");
            return huffmanCodes;
        }
        if(this.left!=null){
            builder.append("0");
            this.left.getCodes(builder,huffmanCodes);
        }
        if(this.right!=null){
            builder.append("1");
            this.right.getCodes(builder,huffmanCodes);
        }
        return huffmanCodes;
    }
}

标签:Node,编码,一文,byte,nodes,节点,夫曼
来源: https://blog.csdn.net/wenwenaier/article/details/121323120

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有