数据结构与算法(11)—— 树结构实际应用(一)

1.堆排序(难)

1.1 基本介绍

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

对数组升序排列采用大顶堆,对数组降序排列采用小顶堆。

1.2 堆排序的思想

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

1.3 代码实现

public class HeapSort {
    public static void main(String[] args) {
        //将数组升序排列
        int[] arr = {4,6,8,5,9,13,17,21,25};
        heapSort(arr);
    }

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

        //将无序序列构建成一个堆,根据升序降序选择大顶堆或小顶堆
        //从下往上遍历使之成为大顶堆
        for (int i = arr.length/2 - 1;i >= 0;i--){
            adjustHeap(arr,i,arr.length); 
        }
        System.out.println(Arrays.toString(arr));
        //将堆顶元素其与末尾元素进行交换,此时末尾就为最大值
        //重新调整结构使其满足堆定义,然后继续交换堆顶元素与末尾元素
        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(非叶子节点)为根节点的树调整为大顶堆
     * 举例:int arr[] = {4,6,8,5,9}      i=1 =====>>>>>得到{4,9,8,5,6}
     * @param arr 待调整的数组
     * @param i 表示非叶子节点在数组中的索引
     * @param length 对多少个元素进行调整,length是逐渐减少的
     */
    public static void adjustHeap(int[] arr,int i ,int length){
        int temp = arr[i];//取出当前元素的值保存在临时变量
        //开始调整
        //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节点
                i = k;//!!! i指向k继续比较
            }else{
                break;//!!!
            }
        }
        //当for循环结束后我们已经将以i为父节点的树的最大值放在了最顶上(局部调整成大顶堆)
        arr[i] = temp;//将temp放置到调整之后的位置
    }
}

2.赫夫曼树

2.1 基本介绍

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

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

2.2 构建赫夫曼树的步骤

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

例如:将数组{1,3,6,7,8,13,29}转化为如下赫夫曼树
在这里插入图片描述

2.3 代码实现

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){
        //遍历arr数组,将arr的每个元素构建成一个Node,将Node放入ArrayList中
        ArrayList<Node> nodes = new ArrayList<>();
        for (int value:arr){
            nodes.add(new Node(value));
        }

        while (nodes.size() > 1) {

            //先将ArrayList排序
            Collections.sort(nodes);

            //取出根节点全职最小的两颗二叉树
            //(1)取出权值最小的结点
            Node leftNode = nodes.get(0);
            //(2)取出权值第二小的结点
            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);
        }

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

//创建节点类
//为了让Node对象支持排序,让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();
        }
    }
}

3.赫夫曼编码(Huffman Coding)

3.1 赫夫曼编码原理

1)传输的字符串 ;
2)统计各个字符对应的个数;
3)将上面字符出现的次数构建一颗赫夫曼树,次数作为权值;
注意:这个赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是wpl一样,都是最小的。
4)根据赫夫曼树,给各个字符规定编码(前缀编码),向左的路径为0,向右的路径为1;

在这里插入图片描述

如图:e对应的赫夫曼编码就是1111。

3.2 实践–数据压缩

功能:根据赫夫曼编码压缩数据的原理,需要创建字符串“i like like like java do you like a java”对应的赫夫曼树。

思路:
(1) Node {data(存放数据),weight(权值),left,right}

//创建Node,带数据和权值
class Node implements Comparable<Node>{
    Byte data; //存放数据(字符)本身
    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 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();
        }
    }
}

(2)得到字符串对应的byte[]数组

 		String content = "i like like like java do you like a java";
        byte[] contentBytes = content.getBytes(); //40
        List<Node> nodes = getNodes(contentBytes);

(3)编写方法,将准备构建赫夫曼树的Node放入List中

//获取各个树节点组成的集合
    /**
     *
     * @param bytes 接受字节数组
     * @return 返回的就是List形式
     */
    private static List<Node> getNodes(byte[] bytes){

        //创建ArrayList
        ArrayList<Node> nodes = new ArrayList<>();

        //遍历 统计每个byte出现的次数---->>>>map
        HashMap<Byte,Integer> counts = new HashMap();
        for (byte b:bytes){
            Integer count = counts.get(b);
            if (count == null){//map中还没有这个字符
                counts.put(b,1);
            }else {
                counts.put(b,count+1);
            }
        }

        //把每个键值对转成Node对象,并加入nodes集合
        for(Map.Entry<Byte,Integer> entry:counts.entrySet()){
            nodes.add(new Node(entry.getKey(),entry.getValue()));
        }
        return nodes;
    }

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

//通过List创建对应的赫夫曼树
    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删除
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //将新的二叉树加入到nodes
            nodes.add(parent);
        }

        //返回nodes中最后一个节点
        return nodes.get(0);
    }

(5)使用赫夫曼树来生成对应的赫夫曼编码

//生成赫夫曼树对应的赫夫曼编码表
    //将赫夫曼编码表存放在Map<Byte,String>形式
    static Map<Byte,String> huffmanCodes = new HashMap<Byte,String>();
    //在生成赫夫曼编码表时,需要拼接路径,定义一个Stringbuilder存储某个叶子节点的路径
    static StringBuilder stringBuilder = new StringBuilder();

    /**
     * 功能:将传入的node结点的所有叶子结点的赫夫曼编码得到,并放入到huffmanCode集合中
     * @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等于空不处理
            //判断当前节点是叶子节点还是非叶子节点
            if(node.data == null){//非叶子节点
                //递归处理
                //向左
                getCodes(node.left,"0",stringBuilder2);
                //向右递归
                getCodes(node.right,"1",stringBuilder2);
            }else{//说明是叶子节点
                //表示找到了某个叶子节点的最后
                huffmanCodes.put(node.data,stringBuilder2.toString());
            }
        }
    }

(6)将赫夫曼编码以字符数组的形式输出

 //将一个字符串对应的byte[]数组,通过生成的赫夫曼编码表返回一个赫夫曼编码压缩后的byte[]
    /**
     *
     * @param bytes 原始字符串对应的byte[]
     * @param huffmanCodes 生成的赫夫曼编码
     * @return
     */
    private static byte[] zip(byte[] bytes, Map<Byte,String> huffmanCodes){

        //利用huffmanCodes 将 bytes转成赫夫曼对应的字符串
        StringBuilder stringBuilder = new StringBuilder();
        //遍历bytes数组
        for (byte b : bytes){
            stringBuilder.append(huffmanCodes.get(b));
        }

        //将对应的字符串转为byte[]
        //统计返回的赫夫曼编码的长度
        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=i+8){//每8位对应一个byte
            String strByte;
            if (i+8 > stringBuilder.length()){//最后不够8位
                strByte = stringBuilder.substring(i);
            }else{
                strByte = stringBuilder.substring(i,i+8);
            }

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

        return huffmanCodeBytes;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值