树结构应用学习

树结构应用

赫夫曼树

基本介绍

给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

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

  • 路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1
  • 结点的权及带权路径长度:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积
  • 树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL(weighted path length) ,权值越大的结点离根结点越近的二叉树才是最优二叉树。
  • WPL最小的就是赫夫曼树
    在这里插入图片描述

赫夫曼树创建思路图解

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

  1. 从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树
  2. 取出根节点权值最小的两颗二叉树
  3. 组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
  4. 再将这颗新的二叉树,以根节点的权值大小再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树

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

赫夫曼编码

基本介绍

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

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

  • i like like like java do you like a java // 共40个字符(包括空格)
  • 105 32 108 105 107 101 32 108 105 107 101 32 108 105 107 101 32 106 97 118 97 32 100 111 32 121 111 117 32 108 105 107 101 32 97 32 106 97 118 97 //对应Ascii码
  • 01101001 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101010 01100001 01110110 01100001 00100000 01100100 01101111 00100000 01111001 01101111 01110101 00100000 01101100 01101001 01101011 01100101 00100000 01100001 00100000 01101010 01100001 01110110 01100001 //对应的二进制
  • 按照二进制来传递信息,总的长度是 359 (包括空格)

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

  • i like like like java do you like a java // 共40个字符(包括空格)
  • d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9 // 各个字符对应的个数
    0= , 1=a, 10=i, 11=e, 100=k, 101=l, 110=o, 111=v, 1000=j, 1001=u, 1010=y, 1011=d 说明:按照各个字符出现的次数进行编码,原则是出现次数越多的,则编码越小,比如 空格出现了9 次, 编码为0 ,其它依次类推.
  • 按照上面给各个字符规定的编码,则在传输 “i like like like java do you like a java” 数据时,编码就是 10010110100…
  • 字符的编码都不能是其他字符编码的前缀,符合此要求的编码叫做前缀编码, 即不能匹配到重复的编码

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

  • i like like like java do you like a java // 共40个字符(包括空格)
  • d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9 // 各个字符对应的个数
  • 按照上面字符出现的次数构建一颗赫夫曼树, 次数作为权值.(图后)

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

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

实现压缩解压功能:数据压缩/解压,文件压缩/解压

代码实现

package com.atguigu.huffmancode;

import java.io.*;
import java.util.*;

/**
 * @author ysxstart
 * @create 2022-04-15 23:25
 */
public class HuffmanCode3 {
   
    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); // 40

        List<Node> nodeList = getNodes(contentBytes);
        Node root = createHuffmanTree(nodeList);
//      preOrder(root);

        Map<Byte, String> huffmanCodes = getCodes(root);
        System.out.println("赫夫曼编码表:" + huffmanCodes); // {32=01, 97=100, 100=11000, 117=11001, 101=1110, 118=11011, 105=101, 121=11010, 106=0010, 107=1111, 108=000, 111=0011}

//      byte[] zip = zip(contentBytes, huffmanCodes);
//      System.out.println(Arrays.toString(zip));
        byte[] huffmanZip = huffmanZip(contentBytes);
        System.out.println("赫夫曼编码压缩处理之后:"+Arrays.toString(huffmanZip)); // [-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]

//      byteToBitString(true, (byte) -10);
        byte[] sourceBytes = decode(huffmanCodes, huffmanZip);
        System.out.println("解压,原来的字符串:" + new String(sourceBytes)); // i like like like java do you like a java
*/
        //测试压缩文件
//        String srcFile = "D:\\1066\\audi\\a1.jpg";
//        String desFile = "D:\\1066\\audi\\a5.zip";
//        zipFile(srcFile, desFile);
//        System.out.println("赫夫曼压缩文件OK!....");

        //测试解压文件
        String zipFile = "D:\\1066\\audi\\a5.zip";
        String desFile = "D:\\1066\\audi\\a6.jpg";
        unZipFile(zipFile, desFile);
        System.out.println("赫夫曼解压文件OK!....");

    }
    //补充:解压缩文件(有个小问题,自己无法解决): 对象流ObjectOutputStream不能序列化static成员变量huffmanCodes,但是韩老师用了可以,我用不行,就很奇怪!
    /**
     * 对压缩文件的解压
     *
     * @param zipFile 准备解压的文件
     * @param dstFile 将文件解压到哪个路径
     */
    public static void unZipFile(String zipFile, String dstFile) {
   

        //定义文件输入流
        InputStream is = null;
        //定义一个对象输入流
        ObjectInputStream ois = null;
        //定义文件的输出流
        OutputStream os = null;
        try {
   
            //创建文件输入流
            is = new FileInputStream(zipFile);
            //创建一个和  is关联的对象输入流
            ois = new ObjectInputStream(is);
            //读取byte数组  huffmanBytes
            byte[] huffmanBytes = (byte[]) ois.readObject();
            //读取赫夫曼编码表
            Map<Byte, String> huffmanCodes = (Map<Byte, String>) ois.readObject();

            //解码
            byte[] bytes = decode(huffmanCodes, huffmanBytes);
            //将bytes 数组写入到目标文件
            os = new FileOutputStream(dstFile);
            //写数据到 dstFile 文件
            os.write(bytes);
        } catch (Exception e) {
   
            System.out.println(e.getMessage());
        } finally {
   
            try {
   
                os.close();
                ois.close();
                is.close();
            } catch (Exception e2) {
   
                System.out.println(e2.getMessage());
            }
        }
    }

    /**
     * 将一个文件进行压缩
     *
     * @param srcFile 被压缩文件的全路径
     * @param dstFile 赫夫曼编码压缩处理之后的字节数组放到哪个目录
     */
    public static void zipFile(String srcFile, String dstFile) {
   
        //创建输出流
        OutputStream os = null;
        ObjectOutputStream oos = null;
        //创建文件的输入流
        FileInputStream is = null;
        try {
   
            //创建文件的输入流
            is = new FileInputStream(srcFile);
            //创建一个和源文件大小一样的byte[]
            byte[] b = new byte[is.available()];
            //读取文件
            is.read(b);
            //直接对源文件压缩
            byte[] huffmanBytes = huffmanZip(b);
            //创建文件的输出流, 存放压缩文件
            os = new FileOutputStream(dstFile);
            //创建一个和文件输出流关联的ObjectOutputStream
            oos = new ObjectOutputStream(os);
            //把 赫夫曼编码后的字节数组写入压缩文件
            oos.writeObject(huffmanBytes); //我们是把
            //赫夫曼编码,是为了以后我们恢复源文件时使用
            //注意一定要把赫夫曼编码 写入压缩文件
            oos.writeObject(huffmanCodes);
        } catch (Exception e) {
   
            System.out.println(e.getMessage());
        } finally {
   
            try {
   
                is.close();
                oos.close();
                os.close();
            } catch (Exception e) {
   
                System.out.println(e.getMessage());
            }
        }
    }


    //第五大步:使用赫夫曼编码进行解压
    //思路:
    //1. 将经过赫夫曼编码压缩处理的字节数组重新转换成赫夫曼编码对应的二进制的字符串 "1010100010111..."
    //2. 赫夫曼编码对应的二进制的字符串 "1010100010111..." =》 对照赫夫曼编码  =》 "i like like like java do you like a java"

    /**
     * 完成对压缩数据的解码
     *
     * @param huffmanCodes     赫夫曼编码表 map
     * @param huffmanCodeBytes 赫夫曼编码压缩处理之后的字节数组
     * @return 返回原来的字符串对应的字节数组
     */
    private static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanCodeBytes) {
   
        //1.先得到huffmanBytes对应的二进制的字符串,存入sb中
        StringBuilder sb = new StringBuilder();
        //将huffmanBytes数组转成二进制的字符串
        for (int i = 0; i < huffmanCodeBytes.length; i++) {
   
            byte b = huffmanCodeBytes[i];
            //判断是不是最后一个字节
            boolean flag = (i == huffmanCodeBytes.length - 1);
            sb.append(byteToBitString(!flag, b));
        }
        //2.把赫夫曼编码表key-value进行调换,因为反向查询: a->100  100->a
        Map<String, Byte> map = new HashMap<>();
        for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
   
            map.put(entry.getValue(), entry.getKey());
        }
        //3.根据调换的赫夫曼编码表匹配二进制字符串;创建集合,存放byte
        List<Byte> list = new ArrayList<>();
        for (int i = 0; i < sb.length(); ) {
   
            int count = 1; //遍历二进制字符串的计数器
            boolean flag = true;
            Byte b = null;
            while (flag) {
   
                //1010100010111...
                //递增的取出key: 1,10,101
                String key = sb.substring(i, i + count); // i不动,count移动,指定匹配到一个字符
                b = map.get(key); //key是编码,b是字符数据
                if (b == null) {
    //没有匹配到
                    count++;
                } else {
    //匹配到
                    flag = false;
                }
            }
            list.add(b);
            i += count;  // i直接移动到count
        }
        //4.把list中的数据放入到byte[]并返回;当for循环结束后,list中就存放了所有的字符: "i like like like java do you like a java"
        byte[] b = new byte[list.size()];
        for (int i = 0; i < b.length; i++) {
   
            b[i] = list.get(i);
        }
        return b;
    }

    /**
     * 将一个byte转成一个二进制的字符串
     *
     * @param flag 标志是否需要补高位;如果是true,表示需要补高位;false表示不补, 如果是最后一个字节,无需补高位
     * @param b    传入的byte
     * @return 是该b对应的二进制的字符串,(注意是按补码返回)
     */
    private static String byteToBitString(boolean flag, byte b) 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值