树结构的实际应用

1、堆排序

1.1 堆排序的基本介绍

  1. 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
  2. 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆, 注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系(也就是说左右结点大小不能确定)。
  3. 每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
  4. 大顶堆举例说明

**在这里插入图片描述
5. 小顶堆举例说明
在这里插入图片描述

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

1.2堆排序的思路图解与实现

1.2.1 堆排序基本思想

  1. 将待排序序列构造成一个大顶堆
  2. 此时,整个序列的最大值就是堆顶的根节点。
  3. 将其与末尾元素进行交换,此时末尾就为最大值。
  4. 然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

.
可以看到在构建大顶堆的过程中,元素的个数逐渐减少,最后就得到一个有序序列了.

要求:给你一个数组 {4,6,8,5,9} , 要求使用堆排序法,将数组升序排序。

1.2.2 堆排序步骤图解说明:

步骤一:构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。原始的数组[4,6,8,5,9]

假设给定无序序列结构如下 :
在这里插入图片描述

此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
在这里插入图片描述

找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
在这里插入图片描述

这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
在这里插入图片描述
此时,就将一个无序序列构造成了一个大顶堆。

步骤二:将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

将堆顶元素9和末尾元素4进行交换 。
在这里插入图片描述
重新调整结构,使其继续满足堆定义。
在这里插入图片描述
再将堆顶元素 8 与末尾元素 5 进行交换,得到第二大元素 8。
在这里插入图片描述
后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序。
在这里插入图片描述

对上述思路进行总结:

  1. 将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
  2. 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
  3. 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤, 直到整个序列有序。

代码如下

public class HeapSort {
    public static void main(String[] args) {
        //要求将数组进行升序排序
        int[] arr = {4,6,8,5,9};
        heapSort(arr);
    }

    public static void heapSort(int arr[]){
        /*adjustHeap(arr,1,arr.length);
        System.out.println("第一次"+ Arrays.toString(arr));//4,9,8,5,6
        adjustHeap(arr,0,arr.length);
        System.out.println("第二次"+ Arrays.toString(arr));//9,6,8,5,4*/

        int temp = 0;
        //1.将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小丁堆
        for (int i = arr.length/2 - 1; i >= 0; i--) {
            adjustHeap(arr,i,arr.length);//9,6,8,5,4
        }
        //2.将堆顶元素与末尾元素交换,将最大的元素“沉”到数组末端
        //3.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行
        //调整+交换步骤,直到整个序列有序。
        for (int j = arr.length-1; j > 0 ; j--) {
            temp = arr[j];
            arr[j] = arr[0]; //arr[0]永远是最大(小)的
            arr[0] = temp;
            //调整过后,不再是大顶堆 ,再次进行排序
            adjustHeap(arr,0,j);
        }
        System.out.println(""+ Arrays.toString(arr));
    }

    /**
     * 将一个数组(二叉树),调整成一个大顶堆
     *
     * 功能:完成将以i对应的非叶子节点的树调整成大顶堆
     * 举例 int[] arr = {4,6,8,5,9}; => i=1 =>adjustHeap =>得到{4,9,8,5,6}
     * 如果我们再次调用adjustHeap 传入的是i=0 => 得到{4,9,8,5,6} =》 {9,6,8,5,4}
     * @param arr 待排序的数组
     * @param i     非叶子节点在数组中的索引
     * @param len   表示多少个元素继续调整,len会逐渐减少
     */
    public static void adjustHeap(int arr[],int i,int len){
        int temp = arr[i];//先取出当前元素的值,保存在临时变量里

        //1. k =i*2+1   k是i结点的左子结点
        for (int k =i*2+1 ; k < len; k = k*2+1) {
            if(k+1 < len && arr[k] < arr[k+1]){  //说欧美左子节点的值小于右子结点的值
                k++; //k指向右子节点
            }

            if(arr[k] > temp){ //如果子节点大于父节点
                arr[i] = arr[k];  //把较大的值赋给当前结点
                i = k;  //i指向k,继续循环比较
            }else{
                break;
            }
            //当for循环结束后 我们已经将i为父节点的树的最大值,放在了最顶(局部)
            arr[i] = temp;//将temp值放在调整后的位置
        }
    }
}

2、赫夫曼树

2.1 赫夫曼树的基本介绍

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

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

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

在这里插入图片描述

2.2 赫夫曼树创建步骤图解与实现

要求:给你一个数列 {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};
        System.out.println(System.currentTimeMillis());//验证处理生成赫夫曼树的时间
        Node tree = createHuffmanTree(arr);
        System.out.println(System.currentTimeMillis());

        preOrder(tree);

    }

    /**
     * 创建赫夫曼树的方法
     * @param arr  需要创建赫夫曼树的数组
     * @return      返回创建好后的root结点
     */
    public static Node createHuffmanTree(int[] arr){
        //第一步为了操作方便
        //1 遍历arr数组
        //2 将arr的每个元素构成一个node
        //3 将node 放入ArrayList 方便排序
        List<Node> nodes = new ArrayList<>();
        for (int value : arr){
            nodes.add(new Node(value));
        }

        // 处理的过程是循环的过程
        while (nodes.size() > 1) {

            // 排序:从小到大排序
            Collections.sort(nodes);

            // 取出根节点权值最小的两颗二叉树
            //1 取出权值最小的结点(二叉树)
            Node leftNode = nodes.get(0);
            //2 取出权值另一个最小的结点(二叉树)
            Node rightNode = nodes.get(1);

            //3 构建一个新的二叉树
            Node parents = new Node(leftNode.val + rightNode.val);
            parents.left = leftNode;
            parents.right = rightNode;

            //4 从ArrayList删除处理过的二叉树
            nodes.remove(leftNode);
            nodes.remove(rightNode);

            //5 将parent加入到nodes
            nodes.add(parents);
        }

        // 返回赫夫曼树的root结点
        return nodes.get(0);

    }

    // 前序遍历方法
    public static void preOrder(Node root) {
        if (root != null) {
            root.preOrder();
        } else {
            System.out.println("这是一个空树。无法遍历");
        }
    }

}

// 创建结点类
// 为了让Node 对象持续排序Collections集合排序
// 让Node 实现Comparable接口
class Node implements Comparable<Node>{
    int val;
    Node left;
    Node right;

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

    //前序遍历
    public void preOrder(){
        /*if(this == null){
            return;
        }*/
        System.out.println(this);
        if(this.left != null){
            this.left.preOrder();
        }
        if(this.right != null){
            this.right.preOrder();
        }
    }
    @Override
    public String toString() {
        return "Node{" +"val=" + val +'}';
    }

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

3、赫夫曼编码

3.1 赫夫曼编码基本介绍

基本介绍:

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

3.2 赫夫曼编码的原理图解

变长编码的举例说明

  1. 通信领域中信息的处理方式1-定长编码
    在这里插入图片描述

  2. 通信领域中信息的处理方式2-变长编码
    在这里插入图片描述

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

要传输的字符串

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

构成赫夫曼树的步骤

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

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

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

  4. 再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树
    > 1 2 3 4 5 在这里插入图片描述

  5. 根据赫夫曼树,给各个字符,规定编码 (前缀编码), 向左的路径为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

  6. 按照上面的赫夫曼编码,我们的"i like like like java do you like a java"
    字符串对应的编码为 (注意这里我们使用的无损压缩)
    101010011011110111101001101111011110100110111101111010000110000111001100
    1111000011001111000100100100110111101111011100100001100001110
    通过赫夫曼编码处理 长度为 133

  1. 长度为 : 133 说明: 原来长度是 359 , 压缩了 (359-133) / 359 = 62.9% 此编码满足前缀编码,
    即字符的编码都不能是其他字符编码的前缀。不会造成匹配的多义性 赫夫曼编码是无损处理方案

注意事项

注意, 这个赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是wpl 是一样的,都是最小的, 比如: 如果我们让每次生成的新的二叉树总是排在权值相同的二叉树的最后一个,则生成的二叉树为:
在这里插入图片描述

3.3 数据压缩-创建赫夫曼树和赫夫曼编码表思路及实现

将给出的一段文本,比如 “i like like like java do you like a java”,根据前面叙述的赫夫曼编码原理,对其进行数据压缩处理 ,形式如"1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110 "

步骤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[date=97 ,weight = 5], Node[date=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. 通过赫夫曼树对应的生成赫夫曼编码, 如下表:
    (_=01 a=100 d=11000 u=11001 e=1110 v=11011 i=101 y=11010 j=0010 k=1111 l=000 o=0011)

代码实现

package tree.Huffman;

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

       
        /*//得到list<Code>集合
        List<Code> nodes = getNodes(bytes);
        //System.out.println(nodes);

        //创建赫夫曼树
        Code huffmanTree = createHuffmanTree(nodes);
        //preOrder(huffmanTree);

        //生成赫夫曼编码
//        getCodes(huffmanTree,"",stringBuilder);
        Map<Byte, String> huffmanCodes = getCodes(huffmanTree);
        //System.out.println("赫夫曼编码表:"+huffmanCodes);

        //生成赫夫曼编码后的byte数组
        byte[] huffmanCodeBytes = zip(bytes, huffmanCodes);
        System.out.println("huffmanCodeBytes"+Arrays.toString(huffmanCodeBytes));
    */
		 byte[] huffmanZip = huffmanZip(bytes);
        System.out.println("huffmanZip"+Arrays.toString(huffmanZip));
    }

    /**
     * 将所有方法封装起来,调用直接可以生成赫夫曼编码
     * @param bytes  原始数组对应的字节数组
     * @return  经过赫夫曼编码压缩后的字节数组
     */
    private static byte[] huffmanZip(byte[] bytes){
        //1.将原始数组进行处理,得到每个字节出现次数的Code结点
        List<Code> nodes = getNodes(bytes);
        //2.根据list集合生成赫夫曼树
        Code huffmanTreeRoot = createHuffmanTree(nodes);
        //3. 根据赫夫曼树生成赫夫曼编码表
        Map<Byte, String> huffmanCodes = getCodes(huffmanTreeRoot);

        //4.根据 字节数组和赫夫曼编码压缩生成赫夫曼编码字节数组
        byte[] zip = zip(bytes, huffmanCodes);
        return zip;
    }


    /**
     * @Description   编写一个方法,将字符串对应的byte[]数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码压缩后的byte[]
     * @param bytes 原始的字符串对应的byte[]
     * @param huffCodes 生成的赫夫曼编码map
     * @return 返回赫夫曼编码处理后的 byte[]
     *
     * 举例: String content = "i like like like java do you like a java";
     * 	 =》 byte[] contentBytes = content.getBytes();
     *
     * 返回的是字符串 "1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100"
     * 	 => 对应的 byte[] huffmanCodeBytes ,即 8位数字对应一个 byte,放入到 huffmanCodeBytes
     *
     * hfCodeBytes[0] =  10101000(补码)
     * 	 => byte  [推导  10101000=> 10101000 - 1 => 10100111(反码)=> 11011000= -88 ]
     * hfCodeBytes[1] = -88
     */
    private static byte[] zip(byte[] bytes,Map<Byte,String> huffCodes){
        //1.利用 huffCodes 将  bytes 转成赫夫曼编码对应的字符串
        StringBuilder stringBuilder = new StringBuilder();
        //遍历bytes数组
        for(byte b : bytes){
            stringBuilder.append(huffCodes.get(b));
        }

        //将“10101000101111...”转成byte[]
        //统计返回byte[]  huffmanCodeBytes长度
        //一句话ok  int len = (stringBuilder.length() + 7)/8
        int len;
        if(stringBuilder.length() % 8 == 0){
            len = stringBuilder.length() / 8;
        }else{
            len = stringBuilder.length() / 8 + 1;
        }

        //创建存储压缩后的byte数组
        byte[] huffmanCodeBytes = new byte[len];
        int index = 0;//记录是第几个byte
        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);
            }

            //将strByte转成一个byte,放入到huffmanCodeBytes
            huffmanCodeBytes[index] =(byte) Integer.parseInt(strByte,2);
            index++;
        }
        return huffmanCodeBytes;
    }


    //生成赫夫曼树对应的赫夫曼编码
    //思路:
    //1.将赫夫曼编码表存放在 Map<Byte,String> 形式:
    //  生成的赫夫曼编码表{32=01, 97=100, 100=11000, 117=11001, 101=1110, 118=11011, 105=101, 121=11010, 106=0010, 107=1111, 108=000, 111=0011}
    static Map<Byte, String> huffCodes = new HashMap<Byte,String>();
    //2.在生成赫夫曼编码表示,需要去拼接路径,定义一个StringBuilder 存储某个叶子结点的路径
    static StringBuilder stringBuilder = new StringBuilder();

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

    //为了调用方便,进行重载 getCodes
    private static Map<Byte, String> getCodes(Code root) {
        if(root == null) {
            return null;
        }
        //处理root的左子树
        getCodes(root.left, "0", stringBuilder);
        //处理root的右子树
        getCodes(root.right, "1", stringBuilder);
        return huffCodes;
    }


    //创建赫夫曼树 返回他的根结点
    public static Code createHuffmanTree(List<Code> nodes){
        if(nodes == null){
            return null;
        }

        while (nodes.size() > 1){
            Collections.sort(nodes);//从小到大排序

            //取出最小的两个二叉树
            Code leftNode = nodes.get(0);
            Code rightNode = nodes.get(1);

            //创建新的二叉树,他的根节点没有data,只有权值(weight)
            Code parent = new Code(null, leftNode.weight + rightNode.weight);
            parent.left = leftNode;
            parent.right = rightNode;

            //移出已处理好的二叉树
            nodes.remove(leftNode);
            nodes.remove(rightNode);

            //将新的二叉树根结点加入
            nodes.add(parent);

        }
        // 返回赫夫曼树的root结点
        return nodes.get(0);
    }



    /** 根据byte的字符及每个字符出现次数来创建list集合
     * @param bytes 接受字符数组
     * @return 返回的就是List形式  [Node[date=97 ,weight = 5],Node[date=32,weight = 9]......]
     */
    public static List<Code> getNodes(byte[] bytes){
        //1 创建一个ArrayList
        ArrayList<Code> codes = new ArrayList<>();

        //存储每个byte出现的次数
        HashMap<Byte, Integer> map = new HashMap<>();
        for(Byte b : bytes){
            if(!map.containsKey(b)){
                map.put(b,1);
            }else{
                map.put(b,map.get(b)+1);
            }
        }
        /*for(Byte b : bytes){
            Integer count = map.get(b);
            if(count == null){
                map.put(b,1);
            }else{
                map.put(b,count+1);
            }
        }*/
        //把每一个键值对转成Node对象,且加入到list集合中
        Set<Map.Entry<Byte, Integer>> entries = map.entrySet();
        for (Map.Entry<Byte, Integer> entry : entries){
            Byte data = entry.getKey();
            Integer weight = entry.getValue();
            codes.add(new Code(data,weight));
        }

        return codes;
    }


    //前序遍历
    public static void preOrder(Code root){
        if(root != null){
            root.preOrder();
        }else{
            System.out.println("树为空~~~");
        }
    }
}


//创建Node,存放数据和权值(防止和刚刚Node冲突,改为Code)
class Code implements Comparable<Code>{
    Byte date;	//存放数据(字符)本身,比如‘a’ =》 97 ; ' ' =》 32
    int weight;	//权值,表示字符
    Code left;	//指向左子树
    Code right;	//指向右子树

    public Code() {
    }

    public Code(Byte date, int weight) {
        this.date = date;
        this.weight = weight;
    }

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

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

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

4、数据解压-赫夫曼解码

    //完成数据的解压思路
    //1.将huffmanCodes[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]
    //  重写先转成赫夫曼编码对应的二进制的字符串 "1010100010111..."
    //2.赫夫曼编码对应的二进制的字符串 "1010100010111..." =》 对照 赫夫曼编码  =》 "i like like like java do you like a java"


    /**
     * 编写一个方法,完成对压缩数据的解码
     * @Description
     * @param huffmanCodes 赫夫曼编码表 map
     * @param huffmanBytes 赫夫曼编码得到的字节数组
     * @return 就是原来的字符串对应的数组
     */
    private static byte[] decode(Map<Byte,String> huffmanCodes, byte[] huffmanBytes) {
        //1.先得到 huffmanBytes对应的 二进制的字符串 , 形式 1010100010111...
        StringBuilder builder = new StringBuilder();

        //将byte数组转成二进制的字符串
        for(int i = 0; i < huffmanBytes.length; i++) {
            byte b = huffmanBytes[i];
            //判断是不是最后一个字节
            boolean flag = (i == huffmanBytes.length - 1);
            builder.append(byToBitString(!flag, b));
        }
//		System.out.println("二进制字符串="+builder.toString());

        //把字符串安装指定的赫夫曼编码进行解码
        //把赫夫曼编码表进行调换,因为反向查询 a->100 100->a
        Map<String, Byte>  map = new HashMap<String,Byte>();
        for(Map.Entry<Byte, String> entry: huffmanCodes.entrySet()) {
            map.put(entry.getValue(), entry.getKey());
        }

        //创建要给集合,存放byte
        List<Byte> list = new ArrayList<>();
        //i 可以理解成就是索引,扫描 builder
        for(int  i = 0; i < builder.length(); ) {
            int count = 1; 	//小的计数器
            boolean flag = true;
            Byte b = null;

            while(flag) {
                //1010100010111... 递增的取出 key 1
                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 转成一个二进制的字符串, 如果看不懂,可以参考我的笔记:Java基础二进制的原码,反码,补码
     * @Description
     * @author subei
     * @date 2020年6月10日上午8:31:31
     * @param b 传入的 byte
     * @param flag 标志是否需要补高位如果是true ,表示需要补高位,如果是false表示不补, 如果是最后一个字节,无需补高位
     * @return 是该b 对应的二进制的字符串(注意是按补码返回)
     */
    private static String byToBitString(boolean flag,byte 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{
            return str;
        }

    }



代码调试

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


        //赫夫曼编码
        byte[] huffmanZip = huffmanZip(bytes);
        System.out.println("huffmanZip"+Arrays.toString(huffmanZip));//[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]


        //赫夫曼解码
        byte[] decode = decode(huffCodes, huffmanZip);
        System.out.println("原来的字符串=" + new String(decode));//i like like like java do you like a java
    }
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值