数据结构:第三章(二)

视频链接:https://www.bilibili.com/video/BV1HQ4y1d7th

视频范围P121 - P167

1.哈夫曼树

  1. 给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)
  2. 哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
  3. 树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL
  4. 权值越大的结点距离根结点越近的二叉树才是最优二叉树
  5. WPL最小的就是哈夫曼树

结点类

package huffman;

public class Node implements Comparable<Node>{
    public int value;
    public Node left;
    public Node right;

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


    //this和node之间比较
    @Override
    public int compareTo(Node node) {
        return this.value - node.value;
    }

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

        if (this.right != null){
            this.right.preSelect();
        }
    }

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                '}';
    }
}

哈夫曼树

package huffman;

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

public class HuffmanTree {
    public static void main(String[] args) {
        int[] array = new int[]{13,7,8,29,6,1};

        Node root = createHuffmanTree(array);

        preSelect(root);
    }

    //把数列转换为哈夫曼树
    public static Node createHuffmanTree(int[] array){
        //1.遍历数组
        //2.将每一个元素构成一个结点Node
        //3.将Node放入到一个集合中并且已经是排序的,从小到大
        List<Node> nodes = new ArrayList<Node>();

        //将元素转为Node对象
        for(int i : array){
            nodes.add(new Node(i));
        }

        //排序从小到大
        //取出最小的两个数 从集合  index:0, index:1
        while (nodes.size() > 1){
            //排序 从小到大
            Collections.sort(nodes);

            System.out.println("集合中的元素:" + nodes);

            Node leftnode = nodes.get(0);
            Node rightnode = nodes.get(1);

            //创建新的结点
            Node root = new Node(leftnode.value + rightnode.value);
            root.left = leftnode;
            root.right = rightnode;

            //删除结点
            nodes.remove(leftnode);
            nodes.remove(rightnode);

            //添加新结点
            nodes.add(root);
        }

        return nodes.get(0);
    }

    //前序遍历
    public static void preSelect(Node root){
        if (root != null){
            root.preSelect();
        }else{
            System.out.println("空树,无数据...");
        }
    }
}

哈夫曼树

在这里插入图片描述

测试结果

在这里插入图片描述

2.哈夫曼编码和编码解压

哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)

结点类

package huffman;

public class NodeCode implements Comparable<NodeCode>{
    //用来描述文字(字母)十进制数
    public Byte data;

    public int weight;

    public NodeCode left;
    public NodeCode right;

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

    public NodeCode(int weight) {
        this.weight = weight;
    }

    @Override
    public int compareTo(NodeCode o) {
        return this.weight - o.weight;
    }

    @Override
    public String toString() {
        return "NodeCode{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }

    //前序遍历
    public void preSelect(){
        System.out.println(this);

        if (this.left != null){
            this.left.preSelect();
        }

        if (this.right != null){
            this.right.preSelect();
        }
    }
}

哈夫曼代码

package huffman;


import java.util.*;

public class HuffmanCodes {

    //1.需要统计字母所出现的次数
    //2.创建哈夫曼树
    //3.获取哈夫曼编码
    //4.数据压缩

    //存储每一个叶子结点的路径
    private static Map<Byte,String> huffmanCode = new HashMap<>();

    //存储拼接哈夫曼编码的
    private static StringBuilder stringBuilder = new StringBuilder();

    public static void main(String[] args) {

        String string = "Im liujie laizi sichuangguangyuanwhereareyou";

        //原始数据 获取ASCII码值
        byte[] bytes = string.getBytes();
        List<NodeCode> nodeWeight = getNodeWeight(bytes);

        //System.out.println(nodeWeight);

        NodeCode root = createHuffmanTree(nodeWeight);
        Map<Byte, String> huffmanCodes = createHuffmanCodes(root);

        System.out.println("生成的哈夫曼编码:" + huffmanCodes);

        //压缩后的数据
        byte[] zip = zip(bytes, huffmanCodes);
        System.out.println("压缩后的数组:" + Arrays.toString(zip));

        float p = ((float) bytes.length - (float)zip.length) / (float)bytes.length;
        System.out.println(p);

        String string1 = byteToString(false, (byte) -1);
        System.out.println(string1);

        System.out.println("=========================");

        byte[] decode = decode(huffmanCode,zip);
        System.out.println(new String(decode));
    }

    //统计字符串所出现的次数
    //返回集合 [NodeCode{data = 32,weight = 4}]
    public static List<NodeCode> getNodeWeight(byte[] bytes){

        List<NodeCode> nodeCodes = new ArrayList();

        //每一个byte出现的次数,使用一个map集合, key = byte,value = weight
        Map<Byte,Integer> counts = new HashMap<>();
        for (byte aByte : bytes){
            Integer count = counts.get(aByte);
            if (count == null){
                counts.put(aByte,1);
            }else {
                counts.put(aByte,count + 1);
            }
        }

        //把每一个map对象转Node对象,存放在集合中
        for (Map.Entry<Byte,Integer> entry : counts.entrySet()){
            nodeCodes.add(new NodeCode(entry.getKey(),entry.getValue()));
        }

        return nodeCodes;
    }

    //创建哈夫曼树
    public static NodeCode createHuffmanTree(List<NodeCode> nodeCodes){
        //循环处理
        while (nodeCodes.size() > 1){
            //排序 从小到大 根据权重值(也就是次数)排序
            Collections.sort(nodeCodes);

            //找到权重值最小的两个元素(结点)
            NodeCode leftNode = nodeCodes.get(0);
            NodeCode rightNode = nodeCodes.get(1);

            //两个最小结点weight相加,生成一个新的结点
            NodeCode root = new NodeCode(leftNode.weight + rightNode.weight);

            //将新的结点作为父节点,构成一个新的二叉树
            root.left = leftNode;
            root.right = rightNode;

            //将两个结点删除
            nodeCodes.remove(leftNode);
            nodeCodes.remove(rightNode);

            //将新的结点添加到集合中
            nodeCodes.add(root);
        }
        return nodeCodes.get(0);
    }

    //获取哈夫曼编码
    //1.往左取0,往右取1  a:101 b:01000
    //完整的哈夫曼编码:10101000
    //StringBuilder codes:用来拼接完整的哈夫曼编码
    //Map 用来存储叶子结点路径 key = 32 value = 101
    public static void getHuffmanCode(NodeCode nodeCode,String code,StringBuilder stringBuilder){
        StringBuilder newString = new StringBuilder(stringBuilder);
        //上面等价于
        //StringBuilder newString = new StringBuilder();
        //newString.append(stringBuilder.toString());

        newString.append(code);

        if (nodeCode != null){
            if (nodeCode.data == null){
                //向左递归
                getHuffmanCode(nodeCode.left,"0",newString);

                //向右递归
                getHuffmanCode(nodeCode.right,"1",newString);
            }else {
                huffmanCode.put(nodeCode.data,newString.toString());
            }
        }
    }

    //设置统一入口
    public static Map<Byte,String> createHuffmanCodes(NodeCode root){
        if (root == null){
            return null;
        }
        //处理左子树
        getHuffmanCode(root.left,"0",stringBuilder);

        //处理右子树
        getHuffmanCode(root.right,"1",stringBuilder);

        return huffmanCode;
    }

    //数据压缩
    public static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCode){
        //用来拼接所有叶子结点路径
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : bytes){
            stringBuilder.append(huffmanCode.get(b));
        }

        int length;
        if (stringBuilder.length() % 8 == 0){
            length = stringBuilder.length() / 8;
        }else {
            length = stringBuilder.length() / 8 + 1;
        }

        //压缩数据的数组
        byte[] huffmanCodeByte = new byte[length];

        int index = 0;

        for (int i = 0; i < stringBuilder.length(); i += 8) {
            String str;
            if (i + 8 > stringBuilder.length()){
                str = stringBuilder.substring(i);
            }else {
                str = stringBuilder.substring(i,i + 8);
            }

            huffmanCodeByte[index] = (byte)Integer.parseInt(str,2);
            index++;
        }
        return huffmanCodeByte;
    }

    //数据解压过程
    //1.首先把压缩数组转成哈夫曼编码,哈夫曼编码可以使用字符串进行接受拼接
    //2.得到哈夫曼编码,转二进制
    public static String byteToString(boolean isRepair,byte b){
        int temp = b;

        if (isRepair){
            //或运算
            temp |= 256;//1000 0000 和 0000 0001 = 1000 0001
        }

        String string = Integer.toBinaryString(temp);//把int类型数据转二进制补码
        if (isRepair){
            return string.substring(string.length() - 8);
        }else {
            return string;
        }
    }
    
    //哈夫曼解码
    public static byte[] decode(Map<Byte,String> huffmanCode,byte[] huffmanBytes){
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < huffmanBytes.length; i++) {
            byte b = huffmanBytes[i];
            boolean flg = false;
            if (i == huffmanBytes.length - 1){
                flg = true;
            }
            stringBuilder.append(byteToString(!flg,b));
        }

        //将原键值反过来存取
        Map<String,Byte> map = new HashMap<>();
        for (Map.Entry<Byte,String> entry : huffmanCode.entrySet()){
            map.put(entry.getValue(),entry.getKey());
        }

        List<Byte> bytes = new ArrayList<>();
        for (int i = 0; i < stringBuilder.length(); ) {
            int count = 1;
            boolean flg = true;
            Byte b = null;
            while (flg){
                String key = stringBuilder.substring(i,i + count);
                b = map.get(key);

                if (b == null){
                    count++;
                }else {
                    flg = false;
                    bytes.add(b);
                }
            }
            i += count;
        }

        byte[] buff = new byte[bytes.size()];
        for (int i = 0; i < buff.length; i++) {
            buff[i] = bytes.get(i);
        }
        return buff;
    }
}

运行结果

在这里插入图片描述
总结:此代码还有BUG,必须将字符串调整到一个合适的情况,才能正确的解码

3.二叉排序树

  1. 二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。是数据结构中的一类
  2. 在一般情况下,查询效率比链表结构要高
  3. 如果有相同的值,可以将该节点放在左子节点和右子节点上
  4. 同一集数据对应的二叉排序树不唯一。但经过中序遍历得到的关键码序列都是一个递增序列。

结点类

package binarysort;

import java.lang.annotation.ElementType;

public class Node {
    public int value;
    public Node left;
    public Node right;

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

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                '}';
    }

    //添加结点
    public void add(Node node){
        if (node == null){
            return;
        }

        //传入的值与当前节点关系比较
        if (node.value < this.value){
            if (this.left == null){
                this.left = node;
            }else {
                this.left.add(node);
            }
        }else {
            if (this.right == null){
                this.right = node;
            }else {
                this.right.add(node);
            }
        }
    }

    //中序遍历,关键码值是递增
    public void infixSelect(){
        if (this.left != null){
            this.left.infixSelect();
        }
        System.out.println(this);

        if (this.right != null){
            this.right.infixSelect();
        }
    }

    //输入关键码值,输出删除的结点
    public Node selectByVal(int val){
        if (this.value == val){
            return this;
        }else if (val < this.value){
            //查左子树
            if (this.left == null){
                return null;
            }
            return this.left.selectByVal(val);
        }else{
            //查询右子树
            if (this.right == null){
                return null;
            }
            return this.right.selectByVal(val);
        }
    }

    /**
     *
     * @param val 删除结点关键码值
     * @return  删除结点的父结点对象
     */
    public Node selectByParent(int val){
        if ((this.left != null && this.left.value == val) ||(this.right != null && this.right.value == val)){
            return this;
        }else{
            if (val < this.value && this.left != null){
                return this.left.selectByParent(val);
            }else if (val >= this.value && this.right != null){
                return this.right.selectByParent(val);
            }else {
                return null;
            }
        }
    }

}

二叉树类

package binarysort;

public class BinarySortTree {
    private Node root;

    public Node getRoot() {
        return root;
    }

    //查询待删除结点
    public Node selectByVal(int val){
        if (root == null){
            return null;
        }else {
           return root.selectByVal(val);
        }

    }

    //查询待删除结点的父结点
    public Node selectByParent(int val){
        if (root == null){
            System.out.println("root is null");
            return null;
        }else {
            return root.selectByParent(val);
        }
    }

    //添加结点
    public void add(Node node){
        if (root == null){
            root = node;
        }else {
            root.add(node);
        }
    }

    //中序遍历
    public void infixSelect(){
        if (root != null){
            root.infixSelect();
        }else {
            System.out.println("空树...");
        }
    }

    //找出右子树中最小值
    public int rightTreeMin(Node node){
        Node target = node;
        while (target.left != null){
            target = target.left;
        }

        delNode(target.value);
        return target.value;
    }



    //删除结点
    //1.如果删除的结点是叶子结点,找到父结点直接删除关联
    //2.如果删除的结点带有左子树或者带有右子树,把左子树或右子树的结点交给删除该结点的父结点
    //3.如果删除的结点带有双亲结点,可以从左子树中找到一个最大结点替换删除结点,也可以找右子树中最小的结点替换要删除的结点

    public void delNode(int val){
        if (root == null){
            return;
        }else{
            //找到待查询的结点
            Node target = selectByVal(val);
            if (target == null){
                return;
            }

            //在只有一个结点的情况下,直接删除
            if (root.left == null && root.right == null){
                root = null;
                return;
            }

            //找到目标结点的父结点
            Node parentNode = selectByParent(val);

            //1.假设删除的是一个叶子结点
            if (target.left == null && target.right == null){
                //判断删除结点是父结点的左结点,还是右结点
                if (parentNode.left != null && parentNode.left.value == val){
                    //左结点
                    parentNode.left = null;
                }else if(parentNode.right != null && parentNode.right.value == val){
                    //右结点
                    parentNode.right = null;
                }
            }else if (target.left != null && target.right != null){
                //待删除的结点为中间结点【非叶子结点非根结点】

                //找到右边子树的最小值
                int minVal = rightTreeMin(target.right);
                target.value = minVal;
            }else {
                //删除的只有一个子树(可能右子树,可能是左子树)
                if (target.left != null){//左子树
                    if (parentNode != null){
                        //确定是左子结点
                        if (parentNode.left.value == val){
                            parentNode.left = target.left;
                        }else {//右结点
                            parentNode.right = target.left;
                        }
                    }else{
                        root = target.left;
                    }
                }else{//右子树
                    if (parentNode != null){
                        //确定是左子结点
                        if (parentNode.left.value == val){
                            parentNode.left = target.right;
                        }else {//右结点
                            parentNode.right = target.right;
                        }
                    }else{
                        root = target.right;
                    }
                }
            }
        }
    }
}

测试类

package binarysort;

public class Test {
    public static void main(String[] args) {
        int[] array = new int[]{7,3,10,12,5,1,9,2};
        BinarySortTree binarySortTree = new BinarySortTree();
        for (int i = 0; i < array.length; i++) {
            binarySortTree.add(new Node(array[i]));
        }

        binarySortTree.infixSelect();
        System.out.println("======================");

        binarySortTree.delNode(3);
        binarySortTree.infixSelect();
        System.out.println("======================");

    }
}

运行结果:

在这里插入图片描述

4.多路查找树

4.1 二叉树和B树

  1. 二叉树中每个结点有一个数据项,最多有两个子节点,如果允许树的每个节点可以有两个以上的子节点,那么这个树就称为n阶的多叉树,或者称为n叉树
  2. 多叉树通过重新组织节点,减少树的高度,能对二叉树进行优化

在这里插入图片描述

4.2 2-3树

树是最简单的B树结构,具有如下特点

  1. 2-3树的所有叶子节点都在同一层.(只要是B树都满足这个条件)
  2. 有两个子节点的节点叫二节点,二节点要么没有子节点,要么有两个子节点
  3. 有三个子节点的节点叫三节点,三节点要么没有子节点,要么有三个子节点
  4. 2-3树是由二 点和三节点构成的树

在这里插入图片描述

4.3 B树、B+树、B*树

  1. B树通过重新组织节点,降低树的高度,并且减少i/o读写次数来提升效率
  2. B-tree 树即B树,B即 Balanced,平衡的意思。有人把 B-tree 翻译成B-树,容易让人产生误解。会以为B-树是一种树,而B树又是另一种树。实际上,B-tree就是指的B树。
  3. 2-3树和2-3-4树,他们就是B树(英语: B-tree也写成B-树),这里再做一个说明,在学习Mysql 时,经常听到说某种类型的索引是基于B树或者B+树的
  4. B+树是B树的变体,也是一种多路搜索树。
  5. B+树的搜索与B树也基本相同,区别是 B+树只有达到叶子结点才命中(B树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找
  6. 所有关键字都出现在叶子结点的链表中(即数据只能在叶子节点【也叫稠密索引】),且链表中的关键字(数据)恰好是有序的。
  7. 不可能在非叶子结点命中
  8. 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层
  9. 更适合文件索引系统
  10. B树和 B+树各有自己的应用场景,不能说B+树完全比B树好,反之亦然.

在这里插入图片描述

B*树是 B+树的变体,在 B+树的非根和非叶子结点再增指向兄弟的指针
在这里插入图片描述

  1. B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3,而 B+树的块的最低使用率为的1/2。
  2. 从第1个特点可以看出,B*树分配新结点的概率比 B+树要低,空间使用率更高
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值