数据结构与算法入门——二叉树之赫夫曼树

赫夫曼树概述

  • 赫夫曼树,也叫哈夫曼树、最优二叉树,他是n个带权叶子结点构成的二叉树中,带权路径长度最小的二叉树。
    • 叶结点的带权路径:即从根结点到目标叶子结点的路径长度乘以目标结点的权
    • 树的带权路径长度WPL:树中所有的叶子结点的带权路径长度之和
      • 权值越大的结点离根结点越近的二叉树才是最优二叉树

赫夫曼树代码的实现

步骤:

  • 取除根节点权值最小的两棵二叉树
  • 组成一颗新的二叉树(一个根节点),新二叉树的权值等于前两颗二叉树的权值之和
  • 没一个根节点的权值都是前两取出的二叉树的权值之和
    在这里插入图片描述
    在这里插入图片描述
public class TestHuffmanTree {
    public static void main(String[] args) {
        int[] arr = new int[]{3,7,8,29,23,5,11,14};
        HufumanTreeNode node = huffmanTree(arr);
        //System.out.println(node.rightNode);
        middelShow(node);
    }
    //中序遍历
    public static void middelShow(HufumanTreeNode node){
        if (node.leftNode != null){
            middelShow(node.leftNode);
        }
        System.out.println(node.value);
        if (node.rightNode != null){
            middelShow(node.rightNode);
        }
    }
    //创建赫夫曼树
    public static HufumanTreeNode huffmanTree(int[] arr){
        //创建一个数组存储二叉树
        List<HufumanTreeNode> nodes = new ArrayList<>();
        //遍历数组使每一个元素创建一个二叉树(只有一个结点)
        for (int value:arr) {
            nodes.add(new HufumanTreeNode(value));
        }
        //循环处理
        while (nodes.size()>1){
            //首先排序
            nodes.sort(new Comparator<HufumanTreeNode>() {
                @Override
                public int compare(HufumanTreeNode o1, HufumanTreeNode o2) {
                    //降序排列
                    return o2.value-o1.value;
                }
            });

            //取出权值最小的两棵二叉树
            HufumanTreeNode left = nodes.get(nodes.size()-1);
            HufumanTreeNode right = nodes.get(nodes.size()-2);
            //创建一颗新二叉树
            HufumanTreeNode parent = new HufumanTreeNode(left.value+right.value);
            //将左右子树加上
            parent.leftNode = left;
            parent.rightNode = right;
            //把取出来的两个二叉树移除
            nodes.remove(left);
            nodes.remove(right);
            //将新的二叉树放回到数组中
            nodes.add(parent);
        }
        //当列表中仅有一个元素时返回当前值
        return nodes.get(0);
    }
}

public class HufumanTreeNode {
    int value;
    HufumanTreeNode leftNode;
    HufumanTreeNode rightNode;

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

    public void setLeftNode(HufumanTreeNode leftNode) {
        this.leftNode = leftNode;
    }

    public void setRightNode(HufumanTreeNode rightNode) {
        this.rightNode = rightNode;
    }


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

赫夫曼编码的实现

  • 原理分析:在传输信息时,由于计算机只能识别二进制,所以传输的信息最终都要转换成二进制,这就造成了资源消耗大的问题。
    在这里插入图片描述
  • 而如果使用赫夫曼树来解决这一问题,效率就很提高很多。统计每一个字符出现的次数,次数便是权值,然后将这些字符以及他们的权值建成一棵哈夫曼树。说白了就是数据压缩
    在这里插入图片描述

赫夫曼编码与解码代码实现(数据压缩与解压)

  • 统计字符数并排序
  • 创建赫夫曼树
  • 创建赫夫曼编码表
  • 编码
    简单地说就是把要传输的消息一个一个存到旧byte数组中,出现的每一个字符统计一下出现次数与该字符作为键值对存入到Map表中,然后建成赫夫曼树,该树的每一个叶子结点就是出现的字符,权值就是该字符出现的次数,该字符代表的二进制就是从根节点到该叶子结点的路径。最后将二进制路径通过补码运算算出十进制,最后再存储到新的byte数组
//节点类
public class HuffmanTreeCodeNode {
    //字符
    Byte data;
    //权值
    int weight;
    HuffmanTreeCodeNode leftNode;
    HuffmanTreeCodeNode rightNode;


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

    @Override
    public String toString() {
        return "HuffmanTreeCodeNode{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }
}
public class TestHuffmanTreeCode {
    public static void main(String[] args) {
        String str = "can you can a can as a can canner can a can.";
        //创建一个byte数组存储单个字符
        byte[] bytes = str.getBytes();
        //将该数组进行赫夫曼编码压缩
        byte[] b = HuffmanCode(bytes);
        //打印压缩前和压缩后的数组长度
        System.out.println(bytes.length+"...."+b.length);
        //使用赫夫曼编码进行解码
        byte[] newBytes = decode(huffCodes,b);
        System.out.println(new String(newBytes));
    }

    /**
     * 使用指定的赫夫曼编码表进行编码
     * @param huffCodes
     * @param bytes
     * @return
     */
    private static byte[] decode(Map<Byte, String> huffCodes, byte[] bytes) {
        //创建一个临时存储的字符串
        StringBuilder sb = new StringBuilder();
        //把byte数组转化为一个二进制的字符表
        for (int i = 0; i < bytes.length; i++) {
            byte b = bytes[i];
            //判断是否是最后一个
            boolean flag = (i == bytes.length-1);
            sb.append(byteToBitStr(!flag,b));
        }
        //把字符串按照指定的赫夫曼编码进行解码
        //把赫夫曼编码的键值对进行调换
        Map<String,Byte> map = new HashMap<>();
        for (Map.Entry<Byte,String> entry:huffCodes.entrySet()){
            map.put(entry.getValue(),entry.getKey());
        }
        //创建一个集合,用于存储byte
        List<Byte> list = new ArrayList<>();
        //处理字符串,首先取字符串的前一位二进制字符到map中查询,如结果为空,则继续取前两位二进制字符,还是空继续取前三位,直到不为空
        for (int i = 0; i < sb.length();) {
            //定义一个自增长变量,决定取的二进制字符前几位
            int count = 1;
            //循环条件
            boolean flag = true;
            //临时存储字符的byte
            Byte b = null;
            while (flag){
                //首先取前一个字符,然后取前两个,循环下去
                String key = sb.substring(i,i+count);
                //在map中获取的结果
                 b = map.get(key);
                if (b == null){
                    //如果结果为空,则继续循环取
                    count++;
                }else {
                    //如果结果不为空,则不再循环
                    flag = false;
                }
            }
            //把结果都放入list集合
            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 String byteToBitStr(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 bytes
     * @return
     */

    public static byte[] HuffmanCode(byte[] bytes){
        //先统计每一个byte出现的次数,并放入一个集合中
        List<HuffmanTreeCodeNode> nodes = getNodes(bytes);
        //创建一棵赫夫曼树
        HuffmanTreeCodeNode tree = creatHuffmanTree(nodes);
        System.out.println(tree);
        //创建一个赫夫曼编码表
        Map<Byte,String> huffCodes = getCodes(tree);
        System.out.println(huffCodes);
        //编码
        byte[] b = zip(bytes,huffCodes);
        return b;
    }

    private static byte[] zip(byte[] bytes, Map<Byte, String> huffCodes) {
        //创建一个临时存储的字符串
        StringBuilder sb = new StringBuilder();
        //把需要压缩的byte数组处理成一个二进制的字符串
        for (byte b:bytes) {
            sb.append(huffCodes.get(b));
        }
        System.out.println(sb);
        //定义长度
        int len;
        //如果字符串长度刚好是8的倍数,数组长度就等于sb.length()/8
        if (sb.length()%8==0){
            len = sb.length()/8;
        }else {
            //如果字符串长度不是8的倍数,数组长度就等于sb.length()/8+1
            len = sb.length()/8+1;
        }
        //用于存储压缩后的byte
        byte[] by = new byte[len];
        //记录by的位置
        int index = 0;
        //遍历字符串
        for (int i = 0; i <sb.length() ; i+=8) {
            String str;
            //如果到了最后不足8位
            if (i+8>sb.length()){
                //将剩下的放到最后一个数组
                str = sb.substring(i);
            }else {
                //其余的每8个分成一组
                str = sb.substring(i,i+8);
            }
            //将二进制转化为十进制补码
            byte byt = (byte)Integer.parseInt(str,2);
            by[index] = byt;
            index++;

        }
        return by;
    }

    //用于临时存储路径
    static StringBuilder sb = new StringBuilder();
    //用于存储赫夫曼编码
    static Map<Byte,String> huffCodes = new HashMap<>();

    /**
     * 根据赫夫曼树获取赫夫曼编码
     * @param tree
     * @return
     */
    private static Map<Byte, String> getCodes(HuffmanTreeCodeNode tree) {
        //如果当前二叉树(单结点)等于空,则说明当前节点不是叶子结点
        if (tree == null){
            return null;
        }
        //然后继续递归其左右结点
        getCodes(tree.leftNode,"0",sb);
        getCodes(tree.rightNode,"1",sb);
        return huffCodes;
    }
    //递归方法
    private static void getCodes(HuffmanTreeCodeNode node, String code, StringBuilder sb) {
        //创建一个新的存储字符串
        StringBuilder sb2 = new StringBuilder(sb);
        //将经过的路径连起来
        sb2.append(code);
        //如果当前结点data为空,则证明不是叶子结点
        if (node.data == null){
            //递归判断其左右结点,知道找到其叶子结点
            getCodes(node.leftNode,"0",sb2);
            getCodes(node.rightNode,"1",sb2);
        }else {
            //找到叶子结点后,将其data值及路径放入map中
            huffCodes.put(node.data,sb2.toString());
        }
    }


    public static HuffmanTreeCodeNode creatHuffmanTree(List<HuffmanTreeCodeNode> node){
        //排序
        while (node.size() > 1){
            node.sort(new Comparator<HuffmanTreeCodeNode>() {
                @Override
                public int compare(HuffmanTreeCodeNode o1, HuffmanTreeCodeNode o2) {
                    return o2.weight-o1.weight;
                }
            });
            //取出权值两个最低的二叉树
            HuffmanTreeCodeNode left = node.get(node.size()-1);
            HuffmanTreeCodeNode right = node.get(node.size()-2);
            //合成一个新二叉树
            HuffmanTreeCodeNode parent = new HuffmanTreeCodeNode(null,left.weight+right.weight);
            //将新二叉树加上左右子树
            parent.leftNode = left;
            parent.rightNode = right;
            //删除获取到的两个子树
            node.remove(left);
            node.remove(right);
            //将新树添加到列表中
            node.add(parent);
        }
        return node.get(0);
    }

    /**
     * 把byte数组转换为node集合
     * @param bytes
     * @return
     */
    private static List<HuffmanTreeCodeNode> getNodes(byte[] bytes) {
        //创建一个列表用于存储二叉树
        List<HuffmanTreeCodeNode> nodes = new ArrayList<>();
        //存储每一个byte出现了多少次
        Map<Byte,Integer> counts = new HashMap<>();
        //统计每一个byte出现的次数
        for (byte b:bytes){
            Integer count = counts.get(b);
            if (count == null){
                counts.put(b,1);
            }else {
                counts.put(b,count+1);
            }
        }
        //把每一个键值对转换为一个对象
        for (Map.Entry<Byte,Integer> entry:
             counts.entrySet()) {
            nodes.add(new HuffmanTreeCodeNode(entry.getKey(),entry.getValue()));
        }
        return nodes;
    }
}

发布了23 篇原创文章 · 获赞 5 · 访问量 1143

猜你喜欢

转载自blog.csdn.net/qq_40181435/article/details/104845532