11.树结构实际应用

堆排序

基本介绍

  1. 堆排序是利用这种数据结构而设计的一种排序算法,堆排序是一种**选择排序,**它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。

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

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

  4. 大顶堆
    在这里插入图片描述
    在这里插入图片描述

    大顶堆特点: arr[i]>=arr[2 * i+1] && arr[i] >= arr[2 * i+2] // i 对应几个节点,i从0开始编号

  5. 小顶堆
    在这里插入图片描述

小顶堆特点: arr[i] <= arr[2 * i+1] && arr[i] <= arr[2 * i+2] // i 对应几个节点,i从0开始编号

  1. 一般升序采用大顶堆,降序采用小顶堆
  • 堆排序的基本思想是:

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

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

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

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

思路分析

步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

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

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

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

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

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

  1. .将堆顶元素9和末尾元素4进行交换
    在这里插入图片描述
  2. .重新调整结构,使其继续满足堆定义
    在这里插入图片描述
  3. .再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
    在这里插入图片描述
  4. 后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
    在这里插入图片描述
    再简单总结下堆排序的基本思路:

1).将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

2).将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

3).重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

代码展示

package demo06;

import java.util.Arrays;

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

        heapSort(arr);
    }

    //编写一个堆排序的方法
    public static void heapSort(int[] arr) {
   
        int temp=0;
        System.out.println("堆排序!!!");

//        分步完成
//        adjustHeap(arr,1,arr.length);
//        System.out.println("第一次"+ Arrays.toString(arr));
//
//        adjustHeap(arr,0,arr.length);
//        System.out.println("第二次"+ Arrays.toString(arr));

        //完成最终代码
        //1.将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆

        for (int i = arr.length / 2 - 1; i >= 0; i--) {
   
            adjustHeap(arr, i, arr.length);
        }

        //2.将堆顶元素与末尾交换,将最大元素 “沉” 到数组末端
        //3.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行 2和3 两个步骤,直到整个序列有序
        for (int j = arr.length - 1; j > 0; j--) {
   
            //交换
            temp=arr[j];
            arr[j]=arr[0];
            arr[0]=temp;
            adjustHeap(arr,0,j);
        }
        System.out.println("数组=" + Arrays.toString(arr));
    }

    //将一个数组(二叉树),调整成一个大顶堆

    /**
     * 功能:完成 将以 i 指向的 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 length 表示对多少个元素进行调整,length 在逐渐的减小
     */
    public static void adjustHeap(int[] arr, int i, int length) {
   
        int temp = arr[i];//先取出当前元素的值,保存在临时变量
        //开始调整
        //说明
        //1. k=i*2+1  k是i的左子节点
        for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
   
            if (k + 1 < length && 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值放到调整后的位置
    }
}

赫夫曼树

基本介绍

  • 给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree), 还有的书翻译为霍夫曼树

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

几个概念

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

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

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

  • WPL最小的就是赫夫曼树在这里插入图片描述

思路分析

构成赫夫曼树的步骤:

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

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

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

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

代码分析

package demo01;

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

public class HuffmanTree {
   
    public static void main(String[] args) {
   
        int[] arr = {
   13, 7, 8, 3, 29, 6, 1};
        Node root = createHuffmanTree(arr);
        //测试一把
        preOrder(root);
    }

    public static void preOrder(Node root){
   
        if (root!=null){
   
            root.preOrder();
        }else {
   
            System.out.println("该树为空树");
        }
    }

    //创建赫夫曼树的方法
    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);
            System.out.println(nodes);

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

            //(3) 构建一颗新的二叉树
            Node parent = new Node(leftNode.value + rightNode.value);
            parent.left = leftNode;
            parent.right = rightNode;

            //(4) 从ArrayList中删除处理过的二叉树
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //(5) 将parent 加入到nodes中
            nodes.add(parent);
            Collections.sort(nodes);
        }
        return nodes.get(0);
    }
}

//创建节点
//为了让Node 对象持续排序Collections集合排序
//让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 +
                '}';
    }

    @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();
        }
    }
}

赫夫曼编码

基本介绍

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

原理剖析

1、定长编码
  • i like like like java do you like a java // 共40个字符(包括空格) 《转换成ACSII码》

  • 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 (包括空格)

  • 在线转码 工具 :https://www.mokuge.com/tool/asciito16/

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、赫夫曼编码
  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 的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Kw0Q38p-1631090031989)(image-20210624160236316.png)]

  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

  2. 按照上面的赫夫曼编码,我们的"i like like like java do you like a java" 字符串对应的编码为 (注意这里我们使用的无损压缩)

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

  3. 长度为 : 133

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

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

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

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

赫夫曼树的应用

实践操作

数据压缩(生成赫夫曼树)

将给出的一段文本,比如 "i like like like java do you like a java" , 根据前面的讲的赫夫曼编码原理,对其进行数据压缩处理 ,形式如 "1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110
" 
步骤1:根据赫夫曼编码压缩数据的原理,需要创建 "i like like like java do you like a java" 对应的赫夫曼树.

数据压缩(生成赫夫曼编码和赫夫曼编码后的数据)

我们已经生成了 赫夫曼树, 下面我们继续完成任务
  1.生成赫夫曼树对应的赫夫曼编码  , 如下表:=01 a=100 d=11000 u=11001 e=1110 v=11011 i=101 y=11010 j=0010 k=1111 l=000 o=0011
  2.使用赫夫曼编码来生成赫夫曼编码数据 ,即按照上面的赫夫曼编码,将"i like like like java do you like a java"   字符串生成对应的编码数据, 形式下.
  1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100

数据解压(使用赫夫曼编码解码)

使用赫夫曼编码来解码数据,具体要求是
  1.前面我们得到了赫夫曼编码和对应的编码
byte[] , 即:[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]
  2.现在要求使用赫夫曼编码, 进行解码,又重新得到原来的字符串"i like like like java do you like a java"

文件压缩

读取文件-> 得到赫夫曼编码表 -> 完成压缩

文件解压

读取压缩文件(数据和赫夫曼编码表)-> 完成解压(文件恢复)

注意事项

  1.如果文件本身就是经过压缩处理的,那么使用赫夫曼编码再压缩效率不会有明显变化, 比如视频,ppt 等等文件 
  2.赫夫曼编码是按字节来处理的,因此可以处理所有的文件(二进制文件、文本文件) 
  3.如果一个文件中的内容,重复的数据不多,压缩效果也不会很明显. 

代码实现

package demo01;

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

public class HuffmanCode {
   
    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
        //编码
        byte[] huffmanZipBytes = huffmanZip(contentBytes);
        System.out.println(Arrays.toString(huffmanZipBytes));

        //如何将数据进行解压(解码)
        byte[] sourceBytes = decode(huffmanCodes, huffmanZipBytes);
        System.out.println("原来的字符串=" + new String(sourceBytes));
         */

        /*
        //测试压缩文件
        String srcFile="F:\\alphonse.jpg";
        String dstFile="F:\\alphonse.zip";
        zipFile(srcFile,dstFile);
        System.out.println("压缩成功~~");
         */

        //测试解压文件
        String zipFile="F:\\alphonse.zip";
        String dstFile="
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值