数据结构和算法学习笔记五_树结构的实际应用(1)

数据结构和算法学习笔记五_树结构的实际应用(1)

学习视频:尚硅谷韩老师Java讲解数据结构与算法

一、 堆排序

1.1、 堆排序基本介绍

  1. 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复

杂度均为 O(nlogn),它也是不稳定排序。

  1. 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆, 注意 : 没有

要求结点的左孩子的值和右孩子的值的大小关系。

  1. 每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆

  2. 大顶堆举例说明:

在这里插入图片描述

  1. 小顶堆举例说明 :

在这里插入图片描述

  1. 一般升序采用大顶堆,降序采用小顶堆

1.2、堆排序基本思想

  1. 将待排序序列构造成一个大顶堆

  2. 此时,整个序列的最大值就是堆顶的根节点。

  3. 将其与末尾元素进行交换,此时末尾就为最大值。

  4. 然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n 个元素的次小值。如此反复执行,便能得到一个有序

序列了。

1.3、代码:

package com.lxf.Tree;

import java.util.Arrays;
import java.util.Random;

public class HeapSort {
    
    
    public static void main(String[] args) {
    
    
        //获取一个数字字符串
        StringBuilder stringBuilder = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < 100000; i++) {
    
    
            stringBuilder.append(random.nextInt(500)+",");
        }
        //去掉最后一个,
        stringBuilder.setLength(stringBuilder.length()-1);
        //获取整数数组
        String[] splits= stringBuilder.toString().split(",");
        int[] nums = Arrays.stream(splits).mapToInt(Integer::valueOf).toArray();
        //获取开始时间
        long  start = System.currentTimeMillis();
        System.out.println("start = " + start);
        //堆排序排序算法
        heapSort(nums);
        //获取执行完的时间
        long end = System.currentTimeMillis();
        System.out.println("end = " + end);
        //计算执行时间
        long sub=end-start;
        System.out.println("花费时间="+sub);

        //三次测试:27.218秒 24.579秒 27.254秒
    }

    /**
     * 堆排序
     *
     * @param arr
     */
    public static void heapSort(int arr[]) {
    
    
        for (int i = arr.length - 1; i > 0; i--) {
    
    
            max_heapify(arr, i);
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;
        }
    }

    /**
     * 构造大根堆
     *
     * @param arr 数组
     * @param n   当前构造大根堆数组的长度
     *            注意:这个树必须是完全二叉树
     */
    private static void max_heapify(int arr[], int n) {
    
    
        for (int j = (n - 1) / 2; j >= 0; j--) {
    
    
            int children = 2 * j + 1;
            if (children != n && arr[children] < arr[children + 1]) {
    
    
                children++;
            }
            if (arr[j] < arr[children]) {
    
    
                int temp = arr[j];
                arr[j] = arr[children];
                arr[children] = temp;
            }
        }
    }
}

二、赫夫曼树(哈夫曼树)

2.1、基本介绍

  1. 给定 n 个权值作为 n 个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为

最优二叉树,也称为哈夫曼树(Huffman Tree), 还有的书翻译为霍夫曼树。

  1. 赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近

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

  1. 路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通

中分支的数目称为路径长度。若规定根结点的层数为 1,则从根结点到第 L 层结点的路径长度为 L-1

  1. 结点的权及带权路径长度:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。

点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积

  1. 树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为 WPL(weighted path

length) ,权值越大的结点离根结点越近的二叉树才是最优二叉树。

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

在这里插入图片描述

2.3、赫夫曼树创建思路图解

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

构成赫夫曼树的步骤

  1. 从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树

  2. 取出根节点权值最小的两颗二叉树

  3. 组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和

  4. 再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数

据都被处理,就得到一颗赫夫曼树

  1. 图解:

在这里插入图片描述

package com.lxf.huffmanTree;

import java.util.PriorityQueue;

public class HuffmanTreeDemo{
    
    
    public static void main(String[] args) {
    
    
        int[] arr={
    
    13,7,8,3,29,6,1};
        //创建哈夫曼树
        Node root = createHuffmanTree(arr);
        //前序遍历打印哈夫曼树
        //预期:67、29、38、15、7、8、23、10、4、1、3、6、13
        HuffmanTree huffmanTree = new HuffmanTree(root);
        huffmanTree.preOrder();
        //结果:67 29 38 15 7 8 23 10 4 1 3 6 13 
    }

    /**
     * 创建赫夫曼树的方法
     * @param arr
     */
    public static Node createHuffmanTree(int[] arr){
    
    
        //为了操作方便
        //1.遍历arr数组
        //2.将arr的每个元素构成一个Node
        //3.将Node放入PriorityQueue中(已实现Comparable接口,所以nodes是有序的)
        PriorityQueue<Node> nodes = new PriorityQueue<Node>();
        for (int value : arr) {
    
    
            nodes.offer(new Node(value));
        }
        //从nodes中反复取出最小的两个节点拼接成一个子树(子树的节点值为最小的两个节点和)
        while(nodes.size()!=1){
    
    
            //获取第一个节点
            Node left = nodes.poll();
            //获取第二个节点
            Node right = nodes.poll();
            //拼接节点
            Node parent=new Node(left.value+right.value);
            parent.left=left;
            parent.right=right;
            //再将节点加入
            nodes.add(parent);
        }
       return nodes.peek();
    }
}
/**
 * 定义BinaryTree二叉树
 */
class HuffmanTree {
    
    
    private Node root;

    public HuffmanTree() {
    
    
    }

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

}
/**
 * 创建结点类
 * 为了让Node对象持续排序Collection集合排序
 * 让Node实现Comparable接口
 */
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 +
                '}';
    }

    /**
     * 前序遍历的方法
     */
    public void preOrder(){
    
    
        System.out.print(this.value+" ");//先输出父节点的值
        //递归向左子树前序遍历
        if(this.left!=null){
    
    
            this.left.preOrder();
        }
        //递归向右子树前序遍历
        if(this.right!=null){
    
    
            this.right.preOrder();
        }
    }

    @Override
    public int compareTo(Node o) {
    
    
        return Integer.compare(this.value,o.value);
    }
}

三、赫夫曼编码

3.1 基本介绍

  1. 赫夫曼编码也翻译为

哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式, 属于一种程序算法

  1. 赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。

  2. 赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在 **20%~90%**之间

  3. 赫夫曼码是可变字长编码(VLC)的一种。Huffman 于 1952 年提出一种编码方法,称之为最佳编码

3.2 原理剖析

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

在这里插入图片描述

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

在这里插入图片描述

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

传输的 字符串

  1. i like like like java do you like a java

  2. d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 (空格):9 // 各个字符对应的个数

  3. 按照上面字符出现的次数构建一颗赫夫曼树, 次数作为权值

  • 构成赫夫曼树的步骤:
  1. 从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树

  2. 取出根节点权值最小的两颗二叉树

  3. 组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和

  4. 再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数据都被处理,

就得到一颗赫夫曼树
在这里插入图片描述

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

如下:

o: 1000 u: 10010 d: 100110 y: 100111 i: 101 a : 110

k: 1110 e: 1111 j: 0000 v: 0001 l: 001 (空格): 01

  1. 按照上面的赫夫曼编码,我们的"i like like like java do you like a java"

字符串对应的编码为 (注意这里我们使用的无损压缩)

10101001101111011110100110111101111010011011110111101000011000011100110011110000110

01111000100100100110111101111011100100001100001110 通过赫夫曼编码处理 长度为 133

6) 长度为 : 133

说明:

原来长度是 359 , 压缩了 (359-133) / 359 = 62.9%

此编码满足前缀编码, 即字符的编码都不能是其他字符编码的前缀。不会造成匹配的多义性

赫夫曼编码是无损处理方案

注意事项:

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

一样的,都是最小的, 最后生成的赫夫曼编码的长度是一样,比如: 如果我们让每次生成的新的二叉树总是排在权

值相同的二叉树的最后一个,则生成的二叉树为:

在这里插入图片描述

3.3、最佳实践-数据压缩与解压(创建赫夫曼树)

将给出的一段文本,比如 “i like like like java do you like a java” , 根据前面的讲的赫夫曼编码原理,对其进行数

据 压 缩 处 理 , 形 式 如:

“1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100 110111101111011100100001100001110”

步骤 1:根据赫夫曼编码压缩数据的原理,需要创建 “i like like like java do you like a java” 对应的赫夫曼树.

思路:

(1)、Node(data(存放数据)、weight(权值)、left和right)

(2)、得到"i like like like java do you like a java"对应的byte[]数组

(3)、编写一个方法,将准备构建赫夫曼树的Node结点放到List,形式[Node[data=97,weight=5],Node[data=32,weight=9]…],体现:d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 (空格):9

(4)、可以通过List创建对应的赫夫曼树

(5)、生成赫夫曼树对应的赫夫曼编码,如下表:

o: 1000 u: 10010 d: 100110 y: 100111 i: 101 a : 110

k: 1110 e: 1111 j: 0000 v: 0001 l: 001 (空格): 01

(6)、使用赫夫曼编码来生成赫夫曼编码数据,即将按照上面的赫夫曼编码,将"i like like like java do you like a java"字符串生成对应的编码数据,形式如下.

1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100

(7)、按原路返回就是解压了

(8)、拓展:压缩与解压文件

(9)、注意:老师在解压过程中忽略了最后不满足8位会丢失0的情况:

  • 比如最后几位剩01000,1前面的0就会丢失,造成解压失败(最后截取的时候就会超出范围)
  • 比如最后几位剩1000,就不会丢失
  • 总结:最后剩余1开头就不会发生丢失情况,剩余0开头就会发生丢失情况
  • 所以在测试解压文件时是有概率失败的
  • 解决方法:记录缺失的0的数量,在decode的时候补上去就好了

代码实现:

package com.lxf.huffmanTree;

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

public class HuffmanCode {
    
    
    public static void main(String[] args) {
    
    
        //String str = "i like like like java do you like a java";
        //byte[] bytes = str.getBytes();
        //System.out.println(bytes.length);//40
        //System.out.println(Arrays.toString(bytes));

        //byte[] huffmanZip = huffmanZip(bytes);
        //System.out.println("压缩后的结果是:"+Arrays.toString(huffmanZip)+",长度="+huffmanZip.length+",压缩率="+(new DecimalFormat("#0.00%").format((double)huffmanZip.length/bytes.length)) );


        //byte[] decode = decode(huffmanCodes, huffmanZip);
        //System.out.println(Arrays.toString(decode));

        //测试压缩文件
        zipFile("C:\\Users\\Administrator\\Desktop\\数据结构和算法笔记\\3.png", "C:\\Users\\Administrator\\Desktop\\数据结构和算法笔记\\3.zip");

        //测试解压文件
        unZipFile("C:\\Users\\Administrator\\Desktop\\数据结构和算法笔记\\3.zip", "C:\\Users\\Administrator\\Desktop\\数据结构和算法笔记\\3copy.png");

    }
    //统计丢失0的个数
    private static Integer countZero;
    /**
     * 将文件压缩的方法
     *
     * @param srcFile srcFile 你传入的希望压缩的文件的全路径
     * @param desFile dstFile 我们压缩后将压缩文件放到哪个目录
     */
    public static void zipFile(String srcFile, String desFile) {
    
    
        //文件输入流
        FileInputStream in = null;
        //文件输出流
        FileOutputStream out = null;
        ObjectOutputStream oos = null;
        try {
    
    
            //创建srcFile文件输入流
            in = new FileInputStream(srcFile);
            //创建一个和源文件大小一样的byte[]
            byte[] b = new byte[in.available()];
            //读取整个文件
            in.read(b);
            //直接对源文件压缩
            byte[] huffmanBytes = huffmanZip(b);
            //创建desFile文件输入流
            oos = new ObjectOutputStream(new FileOutputStream(desFile));
            //把赫夫曼编码后的字节数组写入压缩文件
            oos.writeObject(huffmanBytes);
            //以对象流的方式写入赫夫曼编码(为了以后我们恢复源文件时使用)
            oos.writeObject(huffmanCodes);
            System.out.println("压缩成功!");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                in.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    /**
     * 将文件解压的方法
     *
     * @param zipFile zipFile 你传入压缩的文件的全路径
     * @param desFile dstFile 我们解压后将解压文件放到哪个目录
     */
    public static void unZipFile(String zipFile, String desFile) {
    
    
        //定义文件输入流
        FileInputStream in = null;
        //定义一个对象输入流
        ObjectInputStream ins = null;
        //定义文件的输出流
        FileOutputStream os = null;

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

            //解码
            byte[] bytes = decode(huffmanCodes, huffmanBytes);
            //将bytes数组写入到目标文件
            os=new FileOutputStream(desFile);
            //写数组到desFile目标文件中
            os.write(bytes);
            System.out.println("解压成功!");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                os.close();
                ins.close();
                in.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    /**
     * 完成对压缩数据的解码
     *
     * @param huffmanCodes 赫夫曼编码表
     * @param huffmanBytes 赫夫曼编码得到的字节数组
     * @return 就是原来的字符串对应的数组
     */
    private static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
    
    
        //1.先得到huffmanBytes对应的二进制字符串,形式101010001011...
        StringBuilder builder = new StringBuilder();
        //将byte数组转成二进制的字符串
        for (int i = 0; i < huffmanBytes.length; i++) {
    
    
            //判断是不是最后一个字节
            boolean flag = (i == huffmanBytes.length - 1);
            builder.append(byteToBitString(!flag, huffmanBytes[i]));
        }
        //把字符串安装到指定的赫夫曼编码进行编码
        //把赫夫曼编码编码表进行调换,因为反向查询a->100 100->a
        HashMap<String, Byte> map = new HashMap<>();
        for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
    
    
            map.put(entry.getValue(), entry.getKey());
        }

        //创建一个集合,存放byte
        List<Byte> list = new ArrayList<>();
        //遍历builder字符串,找到map(key->二进制编码,value->数值)
        for (int i = 0; i < builder.length(); ) {
    
    
            int count = 1;
            boolean flag = true;
            Byte b = null;

            while (flag) {
    
    
                //取出一个'1' '0'
                String key = builder.substring(i, i + count);//i不动,让count移动,指定匹配一个字符
                b = map.get(key);
                if (b == null) {
    
    
                    //说明没有匹配到
                    count++;
                } else {
    
    
                    //匹配到
                    flag = false;
                }
            }
            list.add(b);
            i += count;//i直接移动到count
        }
        //当for循环结束后,我们list中就存放了所有的字符"i like like like java do you like a java"
        //把list中的数据放入到byte[]并返回
        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) {
    
    
        //使用变量保存b
        int temp = b;//将b转成int
        //如果是正数我们还存在补高位
        if (flag) {
    
    
            temp |= 256;//按位与256 1 0000 0000 | 0000 0001=>1 0000 0001
        }
        String str = Integer.toBinaryString(temp);//返回的是temp对应的二进制的补码
        if (flag) {
    
    
            return str.substring(str.length() - 8);
        } else {
    
    
            while(countZero>0){
    
    
                str="0"+str;
                countZero--;
            }
            return str;
        }
    }


    /**
     * @param bytes 原始的字符串对应的字节数组
     * @return 经过赫夫曼编码处理后的字节数组
     */
    private static byte[] huffmanZip(byte[] bytes) {
    
    
        //1.统计每个字符对应的数量并获取每个字符对应的结点
        List<Node> nodes = getNodes(bytes);
        //System.out.println(nodes);
        //[Node [data= 32 weight=9], Node [data= 97 weight=5], Node [data= 100 weight=1], Node [data= 101 weight=4], Node [data= 117 weight=1], Node [data= 118 weight=2], Node [data= 105 weight=5], Node [data= 121 weight=1], Node [data= 106 weight=2], Node [data= 107 weight=4], Node [data= 108 weight=4], Node [data= 111 weight=2]]

        //2.创建赫夫曼结点树
        Node huffmanTree = createHuffmanTree(nodes);
        //System.out.println("前序遍历:");
        //preOrder(huffmanTree);
        // Node [data= null weight=40]
        // Node [data= null weight=17]
        // Node [data= null weight=8]
        // Node [data= 108 weight=4]
        // Node [data= null weight=4]
        // Node [data= 106 weight=2]
        // Node [data= 111 weight=2]
        // Node [data= 32 weight=9]
        // Node [data= null weight=23]
        // Node [data= null weight=10]
        // Node [data= 97 weight=5]
        // Node [data= 105 weight=5]
        // Node [data= null weight=13]
        // Node [data= null weight=5]
        // Node [data= null weight=2]
        // Node [data= 100 weight=1]
        // Node [data= 117 weight=1]
        // Node [data= null weight=3]
        // Node [data= 121 weight=1]
        // Node [data= 118 weight=2]
        // Node [data= null weight=8]
        // Node [data= 101 weight=4]
        // Node [data= 107 weight=4]


        //3.生成的赫夫曼编码表
        StringBuilder stringBuilder1 = new StringBuilder();
        getCodes(huffmanTree, "", stringBuilder1);
        // getCodes(huffmanTree);
        //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}

        //4.生成赫夫曼编码对应的字节数组
        StringBuilder stringBuilder2 = new StringBuilder();
        byte[] huffmanCodeBytes = zip(bytes, huffmanCodes, stringBuilder2);
        //System.out.println("huffmanCodeBytes="+Arrays.toString(huffmanCodeBytes));
        //huffmanCodeBytes=[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]

        //压缩完毕,可以发送了~
        return huffmanCodeBytes;
    }


    /**
     * 将字符串对应的byte[] 数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码 压缩后的byte[]
     *
     * @param bytes        这时原始的字符串对应的byte[]
     * @param huffmanCodes 生成的赫夫曼编码map
     * @return 返回赫夫曼编码处理后一个数组
     * <p>
     * 举例:String str="i like like like java do you like a java";
     * byte[] bytes = str.getBytes();
     * 返回:10101000101111111100100010111111110010001011111111001001010011011100011100000110111010001111001010 00101111111100110001001010011011100
     * 对应的byte[] huffmanCodeBytes 即8位对应一个byte,放入到huffmanCodeBytes
     * huffmanCodeBytes[0]=1010100(补码) =>byte [10101000 - 1=>10100111(反码)=>11011000(原码)]
     * huffmanCodeBytes[1]=-88
     */
    private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes, StringBuilder stringBuilder) {
    
    
        //1.利用huffmanCodes将bytes转成赫夫曼编码对应的字符串
        StringBuilder builder = new StringBuilder();
        //遍历byte 数组
        for (byte b : bytes) {
    
    
            stringBuilder.append(huffmanCodes.get(b));
        }
        //System.out.println(stringBuilder.toString());
        //统计返回byte[] huffmanCodeBytes长度
        int len = (stringBuilder.length() + 7) / 8;
        //创建一个压缩后的byte数组
        byte[] huffmanCodeBytes = new byte[len];
        //将stringBuilder每8位截一次
        int index = 0;
        countZero=0;
        for (int i = 0; i < stringBuilder.length(); i += 8) {
    
    
            String strByte;
            if (i + 8 > stringBuilder.length()) {
    
    
                strByte = stringBuilder.substring(i);
                System.out.println(strByte);
                int j=i;
                while(j<stringBuilder.length()-1&&'0'==stringBuilder.charAt(j)){
    
    
                    countZero++;
                    j++;
                }
            } else {
    
    
                strByte = stringBuilder.substring(i, i + 8);
            }
            //将strByte 转成一个byte,放入到
            huffmanCodeBytes[index++] = (byte) Integer.parseInt(strByte, 2);
        }
        return huffmanCodeBytes;
    }


    //生成赫夫曼树对应的赫夫曼编码
    //思路:
    //1.将赫夫曼编码表存放在Map<Byte,String>形式
    //32->01 97->100 100->11000等等形式
    static Map<Byte, String> huffmanCodes = new HashMap<Byte, String>();
    //2.在生成赫夫曼编码表示,需要去拼凑路径,定义一个StringBuilder存储某个叶子结点的路径
    //static StringBuilder stringBuilder=new StringBuilder();

    /**
     * 功能:将传入的node结点的所有叶子结点的赫夫曼编码的到,并放入huffmanCodes集合
     *
     * @param node          传入结点
     * @param code          路径:左子结点0,右子结点1
     * @param stringBuilder 用于拼接路径
     */
    private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
    
    
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        //将code加入到stringBuilder2
        stringBuilder2.append(code);
        if (node != null) {
    
    //如果node==null不处理
            //判断当前node,是叶子结点还是非叶子结点
            if (node.data == null) {
    
    //非叶子结点
                //递归处理
                //向左递归
                getCodes(node.left, "0", stringBuilder2);
                //向右递归
                getCodes(node.right, "1", stringBuilder2);
            } else {
    
    //说明是一个叶子结点
                //表示找到了某个叶子结点的最后
                huffmanCodes.put(node.data, stringBuilder2.toString());
            }
        }
    }

    /**
     * 重载:为了调用方便
     *
     * @param root
     * @return
     */
    private static Map<Byte, String> getCodes(Node root, StringBuilder stringBuilder) {
    
    
        if (root == null) {
    
    
            return null;
        }
        //处理root的左子树
        getCodes(root.left, "0", stringBuilder);
        //处理root的右子树
        getCodes(root.right, "1", stringBuilder);
        return huffmanCodes;
    }

    /**
     * 前序遍历
     *
     * @param root
     */
    private static void preOrder(Node root) {
    
    
        if (root != null) {
    
    
            root.preOrder();
        } else {
    
    
            System.out.println("赫夫曼树为空!");
        }
    }

    /**
     * 生成赫夫曼树
     *
     * @param nodes 传入的结点数组
     * @return
     */
    private static Node createHuffmanTree(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(0);
            nodes.remove(0);
            nodes.add(parent);
        }
        return nodes.get(0);
    }

    /**
     * @param bytes 接受字节数组
     * @return 返回就是List<Node>
     */
    private static List<Node> getNodes(byte[] bytes) {
    
    
        //1.创建一个ArrayList
        List<Node> nodes = new ArrayList<>();

        //遍历bytes,统计每一个byte出现的次数->map[key,value]
        HashMap<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);
            }
        }
        //把每一个键值对转成一个Node对象,并加入到nodes集合中
        //遍历map
        for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
    
    
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }
        return nodes;
    }

}


class Node implements Comparable<Node> {
    
    
    Byte data;//存放数据本身,比如'a'=>97 ' '=>32
    int weight;//权值,表示字符的数量
    Node left;
    Node right;

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


    @Override
    public int compareTo(Node o) {
    
    
        return Integer.compare(this.weight, o.weight);
    }

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

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

猜你喜欢

转载自blog.csdn.net/Inmaturity_7/article/details/112435941