树....

1. 二叉树

1.1 为什么需要树这种数据结构

1.1.1 数组存储方式的分析

  1. 优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
  2. 缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低

1.1.2 链式存储方式的分析

  1. 优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可, 删除效率也很好。
  2. 缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历)

1.1.3 树存储方式的分析

  1. 能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。

1.2 二叉树的示意图

请添加图片描述

1.3 二叉树的概念

  1. 树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
  2. 二叉树的子节点分为左节点和右节点。

请添加图片描述

  1. 如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n 为层数,则我们称为满二叉树。
  2. 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树。

请添加图片描述

1.4 前、中、后序遍历

  1. 前序遍历: 先输出父节点,再遍历左子树和右子树
  2. 中序遍历: 先遍历左子树,再输出父节点,再遍历右子树
  3. 后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点
  4. 小结: 看输出父节点的顺序,就确定是前序,中序还是后序

1.4.1 创建一个二叉树

在这里插入图片描述

1.4.2 思路

1.4.2.1 前序遍历
  1. 先输出当前节点(初始为root节点)
  2. 如果左子节点不为空,就递归继续进行前序遍历
  3. 如果右子节点不为空,就递归继续进行前序遍历
1.4.2.2 中序遍历
  1. 如果当前节点的左子节点不为空,则递归中序遍历
  2. 输出当前节点
  3. 如果当前节点的右子节点不为空,则递归中序遍历
1.4.2.3 后序遍历
  1. 如果当前节点的左子节点不为空,则递归后序遍历
  2. 如果当前节点的右子节点不为空,则递归后序遍历
  3. 输出当前节点

1.4.3 代码实现

1.4.3.1 先编写HeroNode节点类(属性,构造器,get,set方法,toString方法)
class HeroNode {
    private int no;
    private String name;
    private HeroNode left;
    private HeroNode right;

    public HeroNode(){
        
    }
    
    public HeroNode(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public HeroNode getLeft() {
        return left;
    }

    public void setLeft(HeroNode left) {
        this.left = left;
    }

    public HeroNode getRight() {
        return right;
    }

    public void setRight(HeroNode right) {
        this.right = right;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }
}
1.4.3.2 前、中、后序遍历方法编写(节点类中编写)
/***
     * @description: 前序遍历
     * @param:
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 12:29
     */
    public void preOrder() {
        // 先输出当前节点
        System.out.println(this);
        // 判断左子节点是否为空
        if (this.left != null) {
            this.left.preOrder();
        }
        if (this.right != null) {
            this.right.preOrder();
        }
    }

    /***
     * @description: 中序遍历
     * @param:
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 12:30
     */
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.infixOrder();
        }
    }

    /*** 
     * @description: 后序遍历
     * @param: 
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 12:31
     */
    public void postOrder() {
        if (this.left != null) {
            this.left.postOrder();
        }
        if (this.right != null) {
            this.right.postOrder();
        }
        System.out.println(this);
    }
1.4.3.3 编写二叉树类
class BinaryTree {
    private HeroNode root;

    public HeroNode getRoot() {
        return root;
    }

    public void setRoot(HeroNode root) {
        this.root = root;
    }

    /*** 
     * @description: 前序遍历
     * @param: 
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 12:37
     */
    public void preOrder() {
        if (this.root != null) {
            this.root.preOrder();
        } else {
            System.out.println("二叉树为空");
        }
    }

    /*** 
     * @description: 中序遍历
     * @param: 
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 12:37
     */
    public void infixOrder() {
        if (this.root != null) {
            this.root.infixOrder();
        } else {
            System.out.println("二叉树为空");
        }
    }

    /*** 
     * @description: 后序遍历
     * @param: 
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 12:37
     */
    public void postOrder() {
        if (this.root != null) {
            this.root.postOrder();
        } else {
            System.out.println("二叉树为空");
        }
    }

}

二叉树的前、中、后序遍历其实就是调用了节点的前、中、后序遍历

1.4.3.4 编写测试用例
public class BinaryTreeDemo {
    public static void main(String[] args) {
        // 创建树
        BinaryTree binaryTree = new BinaryTree();
        // 创建节点
        HeroNode node1 = new HeroNode(1, "宋江");
        HeroNode node2 = new HeroNode(2, "吴用");
        HeroNode node3 = new HeroNode(3, "卢俊义");
        HeroNode node4 = new HeroNode(4, "林冲");
        // 为节点创建关系
        binaryTree.setRoot(node1);
        node1.setLeft(node2);
        node1.setRight(node3);
        node3.setRight(node4);
        // 测试
        System.out.println("前序遍历");
        binaryTree.preOrder();
        System.out.println("中序遍历");
        binaryTree.infixOrder();
        System.out.println("后序遍历");
        binaryTree.postOrder();
    }
}

请添加图片描述

1.5 查找指定节点

  1. 请编写前序查找,中序查找和后序查找的方法。
  2. 并分别使用三种查找方式,查找 heroNO = 5 的节点
  3. 并分析各种查找方式,分别比较了多少次

在这里插入图片描述

1.5.1 思路分析

1.5.1.1 前序查找思路
  1. 先判断当前节点的no是否等于要查找的,如果相等则返回当前节点
  2. 当前节点的左子节点是否为空,如果不为空,则递归前序查找
  3. 当前节点的右子节点是否为空,如果不为空,则递归前序查找
1.5.1.2 中序查找思路
  1. 先判断当前节点的左子节点是否为空,如果不为空,则递归中序查找
  2. 先判断当前节点的no是否等于要查找的,如果相等则返回当前节点
  3. 判断当前节点的右子节点是否为空,如果不为空,则递归中序查找
1.5.1.3 后序查找思路
  1. 先判断当前节点的左子节点是否为空,如果不为空,则递归后序查找
  2. 判断当前节点的右子节点是否为空,如果不为空,则递归后序查找
  3. 先判断当前节点的no是否等于要查找的,如果相等则返回当前节点

1.5.2 代码实现(节点)

/***
     * @description: 前序查找
     * @param: no
     * @return: com.zjh.树.二叉树.HeroNode
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:05
     */
    public HeroNode preOrderSearch(int no) {
        if (this.no == no) {
            return this;
        }
        HeroNode res = null;
        if (this.left != null) {
            res = this.left.preOrderSearch(no);
        }
        if (res != null) {
            return res;
        }
        if (this.right != null) {
            res = this.right.preOrderSearch(no);
        }
        return res;
    }

    /***
     * @description: 中序遍历查找
     * @param: no
     * @return: com.zjh.树.二叉树.HeroNode
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:11
     */
    public HeroNode infixOrderSearch(int no) {
        HeroNode res = null;
        if (this.left != null) {
            res = this.left.infixOrderSearch(no);
        }
        if (res != null) {
            return res;
        }

        if (this.no == no) {
            return this;
        }

        if (this.right != null) {
            res = this.right.infixOrderSearch(no);
        }
        return res;
    }


    /***
     * @description: 后序查找
     * @param: no
     * @return: com.zjh.树.二叉树.HeroNode
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:14
     */
    public HeroNode postOrderSearch(int no) {
        HeroNode res = null;
        if (this.left != null) {
            res = this.left.postOrderSearch(no);
        }
        if (res != null) {
            return res;
        }
        if (this.right != null) {
            res = this.right.postOrderSearch(no);
        }

        if (res != null) {
            return res;
        }

        if (this.no == no) {
            return this;
        }
        return res;
    }

1.5.3 代码实现(二叉树)

/***
     * @description: 前序遍历
     * @param: no
     * @return: com.zjh.树.二叉树.HeroNode
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:18
     */
    public HeroNode preOrderSearch(int no) {
        if (this.root != null) {
            return this.root.preOrderSearch(no);
        } else {
            System.out.println("二叉树为空");
            return null;
        }
    }

    /***
     * @description: 中序遍历
     * @param: no
     * @return: com.zjh.树.二叉树.HeroNode
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:18
     */
    public HeroNode infixOrderSearch(int no) {
        if (this.root != null) {
            return this.root.infixOrderSearch(no);
        } else {
            System.out.println("二叉树为空");
            return null;
        }
    }

    /**
     * @description: 后序遍历
     * @param: no
     * @return: com.zjh.树.二叉树.HeroNode
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:18
     */
    public HeroNode postOrderSearch(int no) {
        if (this.root != null) {
            return this.root.postOrderSearch(no);
        } else {
            System.out.println("二叉树为空");
            return null;
        }
    }

在使用不同的方法进行查找的时候,查找的次数不一定相同
可以在节点类的方法在比较当前节点和no进行比较的上面,this.no == no 上面,执行的时候查看有多少句就等于查找了多少次

例如

public HeroNode preOrderSearch(int no) {
        System.out.println("前序查找");
        if (this.no == no) {
            return this;
        }
        HeroNode res = null;
        if (this.left != null) {
            res = this.left.preOrderSearch(no);
        }
        if (res != null) {
            return res;
        }
        if (this.right != null) {
            res = this.right.preOrderSearch(no);
        }
        return res;
    }

测试查找编号为3的节点,前、中、后序查找需要查找多少次

		public class BinaryTreeDemo {
    public static void main(String[] args) {
        // 创建树
        BinaryTree binaryTree = new BinaryTree();
        // 创建节点
        HeroNode node1 = new HeroNode(1, "宋江");
        HeroNode node2 = new HeroNode(2, "吴用");
        HeroNode node3 = new HeroNode(3, "卢俊义");
        HeroNode node4 = new HeroNode(4, "林冲");
        // 为节点创建关系
        binaryTree.setRoot(node1);
        node1.setLeft(node2);
        node1.setRight(node3);
        node3.setRight(node4);
//        // 测试
//        System.out.println("前序遍历");
//        binaryTree.preOrder();
//        System.out.println("中序遍历");
//        binaryTree.infixOrder();
//        System.out.println("后序遍历");
//        binaryTree.postOrder();
        HeroNode heroNode;
        System.out.println("开始前序查找哟");
        heroNode = binaryTree.preOrderSearch(1);
        System.out.println(heroNode);
        System.out.println("===============");
        System.out.println("开始中序查找哟");
        heroNode = binaryTree.infixOrderSearch(1);
        System.out.println(heroNode);
        System.out.println("===============");
        System.out.println("开始后序查找哟");
        heroNode = binaryTree.postOrderSearch(1);
        System.out.println(heroNode);

    }
}

请添加图片描述

前序1次、中序查找用了2次,后序查找用了4次

1.6 删除节点

  1. 如果删除的节点是叶子节点,则删除该节点
  2. 如果删除的节点是非叶子节点,则删除该子树.

1.6.1 思路分析

  1. 因为我们的二叉树是单向的,所以,我们判断当前节点的子节点是否需要删除,而不能去判断当前节点是否该删除
  2. 如果当前节点的左子树不为空,并且左子节点就是要删除的节点,则将this.left = null.
  3. 如果当前节点的右子树不为空,并且右子节点就是要删除的节点,则将this.right= null.

1.6.2 代码实现(节点)

/***
     * @description: 删除节点
     * @param: no
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:47
     */
    public void delNode(int no) {
        if (this.left != null && this.left.no == no) {
            this.left = null;
            return;
        }
        if (this.right != null && this.right.no == no) {
            this.right = null;
            return;
        }
        if (this.left != null) {
            this.left.delNode(no);
        }
        if (this.right != null) {
            this.right.delNode(no);
        }
    }

1.6.3 代码实现(二叉树)

/***
     * @description: 删除节点
     * @param: no
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:50
     */
    public void delNode(int no){
        if (root!=null){
            if (root.getNo()==no){
                root=null;
            }else {
                root.delNode(no);
            }
        }
    }

1.6.4 测试

public class BinaryTreeDemo {
    public static void main(String[] args) {
        // 创建树
        BinaryTree binaryTree = new BinaryTree();
        // 创建节点
        HeroNode node1 = new HeroNode(1, "宋江");
        HeroNode node2 = new HeroNode(2, "吴用");
        HeroNode node3 = new HeroNode(3, "卢俊义");
        HeroNode node4 = new HeroNode(4, "林冲");
        // 为节点创建关系
        binaryTree.setRoot(node1);
        node1.setLeft(node2);
        node1.setRight(node3);
        node3.setRight(node4);
		// 测试
        binaryTree.delNode(4);
        binaryTree.preOrder();

    }
}

请添加图片描述

1.6.5 思考题

  1. 如果要删除的节点是非叶子节点,现在我们不希望将该非叶子节点为根节点的子树删除,需要指定规则, 假如规定如下:
  2. 如果该非叶子节点A只有一个子节点B,则子节点B替代节点A
  3. 如果该非叶子节点A有左子节点B和右子节点C,则让左子节点B替代节点A。

2. 顺序二叉树

2.1 基本说明

从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组
在这里插入图片描述

2.2 要求:

  1. 上图的二叉树的结点,要求以数组的方式来存放 arr : [1, 2, 3, 4, 5, 6, 6]
  2. 要求在遍历数组 arr时,仍然可以以前序遍历,中序遍历和后序遍历的方式完成结点的遍历

2.3 顺序存储二叉树的特点

在这里插入图片描述

  1. 顺序二叉树通常只考虑完全二叉树
  2. 第n个元素的左子节点为 2 * n + 1
  3. 第n个元素的右子节点为 2 * n + 2
  4. 第n个元素的父节点为 (n-1) / 2
  5. n : 表示二叉树中的第几个元素(按0开始编号 如图所示)

2.4 顺序存储二叉树遍历

需求: 给你一个数组 {1,2,3,4,5,6,7},要求以二叉树前序遍历的方式进行遍历。 前序遍历的结果应当为 1,2,4,5,3,6,7

2.5 代码实现

class ArrBinary {
    private int[] arr;

    public int[] getArr() {
        return arr;
    }

    public void setArr(int[] arr) {
        this.arr = arr;
    }

    /***
     * @description: 前序遍历
     * @param: index 数组的下标
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 14:46
     */
    public void preOrder(int index) {
        if (arr == null || arr.length == 0) {
            System.out.println("数组为空");
            return;
        }
        System.out.println(arr[index]);
        if (index * 2 + 1 < arr.length) {
            preOrder(index * 2 + 1);
        }
        if (index * 2 + 2 < arr.length) {
            preOrder(index * 2 + 2);
        }
    }
}

2.6 测试

public class ArrBinaryTreeDemo {
    public static void main(String[] args) {
        ArrBinary arrBinary = new ArrBinary();
        arrBinary.setArr(new int []{1,2,3,4,5,6,7});
        arrBinary.preOrder(0);
    }
}

请添加图片描述

2.7 拓展

完成对数组以二叉树中序,后序遍历方式的代码.

/*** 
     * @description: 中序遍历
     * @param: index
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 14:54
     */
    public void infixOrder(int index){
        if (arr == null || arr.length == 0) {
            System.out.println("数组为空");
            return;
        }
        if (index * 2 + 1 < arr.length) {
            infixOrder(index * 2 + 1);
        }
        System.out.println(arr[index]);
        if (index * 2 + 2 < arr.length) {
            infixOrder(index * 2 + 2);
        }
    }
    
    /*** 
     * @description: 后序遍历
     * @param: index
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 14:55
     */
    public void postOrder(int index){
        if (arr == null || arr.length == 0) {
            System.out.println("数组为空");
            return;
        }
        if (index * 2 + 1 < arr.length) {
            postOrder(index * 2 + 1);
        }
        if (index * 2 + 2 < arr.length) {
            postOrder(index * 2 + 2);
        }
        System.out.println(arr[index]);
    }

顺序存储二叉树应用实例
八大排序算法中的堆排序,就会使用到顺序存储二叉树

3. 线索化二叉树

3.1 先看一个问题

将数列 {1, 3, 6, 8, 10, 14 } 构建成一颗二叉树. n+1=7请添加图片描述

3.2 问题分析:

  1. 当我们对上面的二叉树进行中序遍历时,数列为 {8, 3, 10, 1, 6, 14 }
  2. 但是 6, 8, 10, 14 这几个节点的 左右指针,并没有完全的利用上.
  3. 如果我们希望充分的利用 各个节点的左右指针, 让各个节点可以指向自己的前后节点,怎么办?
  4. 解决方案-线索二叉树

3.3 线索二叉树基本介绍

  1. n个结点的二叉链表中含有n+1 【公式 2n-(n-1)=n+1】 个空指针域利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索") 注意:某次遍历次序
  2. 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
  3. 一个结点的前一个结点,称为前驱结点
  4. 一个结点的后一个结点,称为后继结点

3.4 线索二叉树应用案例

应用案例说明:将下面的二叉树,进行中序线索二叉树。中序遍历的数列为 {8, 3, 10, 1, 14, 6}

请添加图片描述

3.4.1 思路分析

中序遍历的结果:{8, 3, 10, 1, 14, 6}

请添加图片描述
说明: 当线索化二叉树后,Node节点的 属性 left 和 right ,有如下情况:

  1. left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树, 而 ⑩ 节点的 left 指向的就是前驱节点.
  2. right指向的是右子树,也可能是指向后继节点,比如 ① 节点right 指向的是右子树,而⑩ 节点的right 指向的是后继节点.

3.4.2 代码实现

3.4.2.1 HeroNode节点(复制上面的,并且需要进行改造)
  1. 我们在HeroNode类中添加leftType和rightType
  2. 如果lefttype=0,则说明指向的是左子树,如果是1,则表示指向前驱节点
  3. 如果rightType=0,则说明指向的是右子树,如果是1,则表示指向后继节点
class HeroNode {
    private int no;
    private String name;
    private HeroNode left;
    private HeroNode right;

	// 如果lefttype=0,则说明指向的是左子树,如果是1,则表示指向前驱节点
    private int leftType;

    // 如果rightType=0,则说明指向的是右子树,如果是1,则表示指向后继节点
    private int rightType;

    public int getLeftType() {
        return leftType;
    }

    public void setLeftType(int leftType) {
        this.leftType = leftType;
    }

    public int getRightType() {
        return rightType;
    }

    public void setRightType(int rightType) {
        this.rightType = rightType;
    }
    
    public HeroNode() {

    }

    public HeroNode(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public HeroNode getLeft() {
        return left;
    }

    public void setLeft(HeroNode left) {
        this.left = left;
    }

    public HeroNode getRight() {
        return right;
    }

    public void setRight(HeroNode right) {
        this.right = right;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }

    /***
     * @description: 前序遍历
     * @param:
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 12:29
     */
    public void preOrder() {
        // 先输出当前节点
        System.out.println(this);
        // 判断左子节点是否为空
        if (this.left != null) {
            this.left.preOrder();
        }
        if (this.right != null) {
            this.right.preOrder();
        }
    }

    /***
     * @description: 中序遍历
     * @param:
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 12:30
     */
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.infixOrder();
        }
    }

    /***
     * @description: 后序遍历
     * @param:
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 12:31
     */
    public void postOrder() {
        if (this.left != null) {
            this.left.postOrder();
        }
        if (this.right != null) {
            this.right.postOrder();
        }
        System.out.println(this);
    }

    /***
     * @description: 前序查找
     * @param: no
     * @return: HeroNode
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:05
     */
    public HeroNode preOrderSearch(int no) {
        System.out.println("前序查找");
        if (this.no == no) {
            return this;
        }
        HeroNode res = null;
        if (this.left != null) {
            res = this.left.preOrderSearch(no);
        }
        if (res != null) {
            return res;
        }
        if (this.right != null) {
            res = this.right.preOrderSearch(no);
        }
        return res;
    }

    /***
     * @description: 中序遍历查找
     * @param: no
     * @return: HeroNode
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:11
     */
    public HeroNode infixOrderSearch(int no) {

        HeroNode res = null;
        if (this.left != null) {
            res = this.left.infixOrderSearch(no);
        }
        if (res != null) {
            return res;
        }
        System.out.println("中序遍历查找");
        if (this.no == no) {
            return this;
        }

        if (this.right != null) {
            res = this.right.infixOrderSearch(no);
        }
        return res;
    }


    /***
     * @description: 后序查找
     * @param: no
     * @return: HeroNode
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:14
     */
    public HeroNode postOrderSearch(int no) {

        HeroNode res = null;
        if (this.left != null) {
            res = this.left.postOrderSearch(no);
        }
        if (res != null) {
            return res;
        }
        if (this.right != null) {
            res = this.right.postOrderSearch(no);
        }

        if (res != null) {
            return res;
        }
        System.out.println("后序查找");
        if (this.no == no) {
            return this;
        }
        return res;
    }

    /***
     * @description: 删除节点
     * @param: no
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:47
     */
    public void delNode(int no) {
        if (this.left != null && this.left.no == no) {
            this.left = null;
            return;
        }
        if (this.right != null && this.right.no == no) {
            this.right = null;
            return;
        }
        if (this.left != null) {
            this.left.delNode(no);
        }
        if (this.right != null) {
            this.right.delNode(no);
        }
    }
}
3.4.2.2 编写线索化二叉树类(基础代码从上面拷贝)
class BinaryTree {
    private HeroNode root;

    public HeroNode getRoot() {
        return root;
    }

    public void setRoot(HeroNode root) {
        this.root = root;
    }

    /***
     * @description: 前序遍历
     * @param:
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 12:37
     */
    public void preOrder() {
        if (this.root != null) {
            this.root.preOrder();
        } else {
            System.out.println("二叉树为空");
        }
    }

    /***
     * @description: 中序遍历
     * @param:
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 12:37
     */
    public void infixOrder() {
        if (this.root != null) {
            this.root.infixOrder();
        } else {
            System.out.println("二叉树为空");
        }
    }

    /***
     * @description: 后序遍历
     * @param:
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 12:37
     */
    public void postOrder() {
        if (this.root != null) {
            this.root.postOrder();
        } else {
            System.out.println("二叉树为空");
        }
    }

    /***
     * @description: 前序遍历
     * @param: no
     * @return: HeroNode
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:18
     */
    public HeroNode preOrderSearch(int no) {
        if (this.root != null) {
            return this.root.preOrderSearch(no);
        } else {
            System.out.println("二叉树为空");
            return null;
        }
    }

    /***
     * @description: 中序遍历
     * @param: no
     * @return: HeroNode
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:18
     */
    public HeroNode infixOrderSearch(int no) {
        if (this.root != null) {
            return this.root.infixOrderSearch(no);
        } else {
            System.out.println("二叉树为空");
            return null;
        }
    }

    /**
     * @description: 后序遍历
     * @param: no
     * @return: HeroNode
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:18
     */
    public HeroNode postOrderSearch(int no) {
        if (this.root != null) {
            return this.root.postOrderSearch(no);
        } else {
            System.out.println("二叉树为空");
            return null;
        }
    }

    /***
     * @description: 删除节点
     * @param: no
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/10 13:50
     */
    public void delNode(int no){
        if (root!=null){
            if (root.getNo()==no){
                root=null;
            }else {
                root.delNode(no);
            }
        }
    }

}

接下来进行功能改造

  1. 为了实现线索化,HeroNode类需要创建一个指向当前节点的前驱节点的一个引用 private HeroNode pre; pre总是保留着前一个节点。因为树是单向的,所以需要保留前驱节点来进行线索化
  2. 先线索化左子树
  3. 线索化当前节点
  4. 线索化右子树

代码实现

 /***
     * @description: 编写对二叉树进行中序线索化的方法
     * @param: heroNode 就是当前需要进行线索化的节点
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/11 11:53
     */
    public void threadNodes(HeroNode heroNode) {
        // 如果heronode为空,则不能进行线索化
        if (heroNode == null) {
            return;
        }
        // 1. 先线索化左子树
        threadNodes(heroNode.getLeft());
        // 2. 线索化当前节点
        // 2.1 先处理当前节点的前驱节点
        if (heroNode.getLeft() == null) {
            // 让当前节点的左指针指向前驱节点
            heroNode.setLeft(pre);
            // 当前节点的左指针是指向前驱节点
            heroNode.setLeftType(1);

        }
        // 2.2 处理后继节点
        if (pre != null && pre.getRight() == null) {
            // 让前驱节点的右指针指向当前节点
            pre.setRight(heroNode);
            // 修改前驱节点的右指针类型
            pre.setRightType(1);
        }
        // 每处理一个节点后,让当前节点是下一个节点的前驱节点
        pre = heroNode;

        // 3. 线索化右子树
        threadNodes(heroNode.getRight());
    }

测试

请添加图片描述

public class ThreadedBinaryTreeDemo {
    public static void main(String[] args) {
        HeroNode root = new HeroNode(1, "tom");
        HeroNode node2 = new HeroNode(3, "jack");
        HeroNode node3 = new HeroNode(6, "smith");
        HeroNode node4 = new HeroNode(8, "marry");
        HeroNode node5 = new HeroNode(10, "king");
        HeroNode node6 = new HeroNode(14, "dim");
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);

        // 测试线索化
        BinaryTree binaryTree = new BinaryTree();
        binaryTree.setRoot(root);
        binaryTree.threadNodes(root);
        // 测试,以10号节点为测试
        HeroNode left = node5.getLeft();
        System.out.println(left);


    }
}

请添加图片描述

3.5 遍历线索化二叉树

因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。 遍历的次序应当和中序遍历保持一致。

/***
     * @description: 线索化中序遍历
     * @param:
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/11 12:40
     */
    public void threadList() {
        // 定义一个变量来存储当前遍历的节点,从root开始
        HeroNode node = root;
        while (node !=null) {
            // 循环找到lefttype==1的节点,第一个找到的就是8这个节点
            // 后面随着遍历而变化,因为当lefttype==1时,说明该节点是按照线索化处理的有效节点
            while(node.getLeftType()==0){
                node = node.getLeft();
            }
            System.out.println(node);
            // 如果当前节点的右指针指向的是后继节点,就一直输出
            while(node.getRightType()==1){
                // 获取到当前节点的后继节点
                node = node.getRight();
                System.out.println(node);
            }
            // 替换这个遍历的节点
            node = node.getRight();
        }
    }

测试

public static void main(String[] args) {
        HeroNode root = new HeroNode(1, "tom");
        HeroNode node2 = new HeroNode(3, "jack");
        HeroNode node3 = new HeroNode(6, "smith");
        HeroNode node4 = new HeroNode(8, "marry");
        HeroNode node5 = new HeroNode(10, "king");
        HeroNode node6 = new HeroNode(14, "dim");
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);

        // 测试线索化
        BinaryTree binaryTree = new BinaryTree();
        binaryTree.setRoot(root);
        binaryTree.threadNodes(root);
       
        System.out.println("使用线索化的方式遍历线索化二叉树");
        binaryTree.threadList();

    }

结果

使用线索化的方式遍历线索化二叉树
HeroNode{no=8, name='marry'}
HeroNode{no=3, name='jack'}
HeroNode{no=10, name='king'}
HeroNode{no=1, name='tom'}
HeroNode{no=14, name='dim'}
HeroNode{no=6, name='smith'}

线索化二叉树的遍历有难度,建议按照上图配合代码进行学习

4. 赫夫曼树(哈夫曼树)

4.1 基本介绍

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

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

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

请添加图片描述

4.3 赫夫曼树创建思路图解

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

4.3.1 思路

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

4.3.2 图解

请添加图片描述

4.4 代码实现

节点类

/*** 
 * @description: 节点类
 * @param: null
 * @return: 
 * @author ZhangJiaHao
 * @date: 2022/1/11 17:43
 */
class Node implements Comparable<Node>{
    private int value; // 节点的权值
    private Node left; // 左子节点
    private Node right; // 右子节点

	/*** 
     * @description: 前驱遍历
     * @param: 
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/11 18:01
     */
    public void preOrder() {
        System.out.println(this);
        if (this.left != null) {
            this.left.preOrder();
        }
        if (this.right != null) {
            this.right.preOrder();
        }
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

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

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

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

创建哈夫曼树类

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

    }
    /***
     * @description: 前驱遍历
     * @param: root
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/11 18:03
     */
    public static void preOrder(Node root){
        if (root!=null){
            root.preOrder();
        }else {
            System.out.println("节点为空,无法进行前驱遍历");
        }
    }

    /***
     * @description: 创建哈夫曼树
     * @param: arr
     * @return: com.zjh.树.哈夫曼树.Node
     * @author ZhangJiaHao
     * @date: 2022/1/11 18:03
     */
    public static Node creatHaffmanTree(int arr[]) {
        // 1. 先将数组里的数字一一取出,赋值给Node,再将node存入list集合中
        List<Node> nodes = new ArrayList<>();
        for (int i : arr) {
            nodes.add(new Node(i));
        }

        while (nodes.size() > 1) {
            // 排序,从小到大进行排序,排序是根据Node的重写方法
            Collections.sort(nodes);
            // 取出根节点权值最小的两颗二叉树节点
            Node left = nodes.get(0);
            Node right = nodes.get(1);
            // 构建一颗新的二叉树
            Node parent = new Node(left.getValue() + right.getValue());
            parent.setLeft(left);
            parent.setRight(right);
            // 删除掉list集合里取出的两个node
            nodes.remove(left);
            nodes.remove(right);
            // 将parent加入list集合
            nodes.add(parent);
        }
        return nodes.get(0);
    }
}

结果请添加图片描述

5. 赫夫曼编码(哈夫曼编码)

5.1 基本介绍

  1. 赫夫曼编码也翻译为 哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式, 属于一种程序算法

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

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

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

5.2 原理剖析

5.2.1 通信领域中信息的处理方式1-定长编码

  1. i like like like java do you like a java // 共40个字符(包括空格)
  2. 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码
  3. 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 //对应的二进制
  4. 按照二进制来传递信息,总的长度是 359 (包括空格)

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

  1. i like like like java do you like a java // 共40个字符(包括空格)
  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. 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 ,其它依次类推.
  4. 按照上面给各个字符规定的编码,则我们在传输 “i like like like java do you like a java” 数据时,编码就是 10010110100…
  5. 字符的编码都不能是其他字符编码的前缀,符合此要求的编码叫做前缀编码, 即不能匹配到重复的编码(即再解码的时候,遇到第一个1,就有可能会识别为a,就会造成匹配的多义性,是有问题的,后面的赫夫曼编码是真正的前缀编码

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

  1. i like like like java do you like a java // 共40个字符(包括空格)
  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. 根据赫夫曼树,给各个字符规定编码 , 向左的路径为0,向右的路径为1 , 编码如下:
    请添加图片描述
  2. 按照上面的赫夫曼编码,我们的"i like like like java do you like a java" 字符串对应的编码为 (注意这里我们使用的无损压缩)
  3. 1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110
  4. 长度为 : 133
    说明:
    原来长度是 359 , 压缩了 (359-133) / 359 = 62.9%
    此编码满足前缀编码, 即字符的编码都不能是其他字符编码的前缀。不会造成匹配的多义性
  5. 注意, 这个赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是wpl 是一样的,都是最小的,比如: 如果我们让每次生成的新的二叉树总是排在权值相同的二叉树的最后一个,则生成的二叉树为:
    在这里插入图片描述

5.3 数据压缩(赫夫曼编码代码实现)

5.3.1 题目

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

5.3.2 思路

  1. 创建Node节点{data:存放数据,weight:存放权值,left,right}
  2. 得到 i like like like java do you like a java 对应的byte[]数组
  3. 编写一个方法,将准备构建赫夫曼树的Node节点存到list’中,来体现 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来构造一个哈夫曼树00
5.3.2.1 构建节点类
class Node implements Comparable<Node> {
    private Byte data; // 存放数据本身,比如 'a' =》 97
    private int weight; // 权值
    private Node left;
    private Node right;

    public Byte getData() {
        return data;
    }

    public void setData(Byte data) {
        this.data = data;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

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

    public Node() {

    }


    @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();
        }
    }
}
5.3.2.2 将字符串转为的bytes数组一一转为Node节点并存放到List集合中
/*** 
     * @description: 将一个bytes数组转为list集合,里面存放Node节点
     * @param: bytes
     * @return: java.util.List<com.zjh.树.哈夫曼编码.Node>
     * @author ZhangJiaHao
     * @date: 2022/1/12 14:09
     */
    private static List<Node> getNodes(byte[] bytes) {
        // 创建一个ArrayList
        ArrayList<Node> nodes = new ArrayList<>();
        // 统计每个byte出现的次数
        Map<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集合
        for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }
        
        return nodes;
    }
5.3.2.3 创建哈夫曼树代码
/*** 
     * @description: 创建哈夫曼树
     * @param: list
     * @return: com.zjh.树.哈夫曼编码.Node
     * @author ZhangJiaHao
     * @date: 2022/1/12 14:17
     */
    public static Node createHuffmanTree(List<Node> list){
        while(list.size()>1){
            // 排序
            Collections.sort(list);
            Node leftNode = list.get(0);
            Node rightNode = list.get(1);
            Node parent = new Node(null,leftNode.getWeight()+rightNode.getWeight());
            parent.setLeft(leftNode);
            parent.setRight(rightNode);
            list.remove(leftNode);
            list.remove(rightNode);
            list.add(parent);
        }
        return list.get(0);
    }
5.3.2.4 生成赫夫曼编码

规定编码,向左的路径为0,向右的路径为1.
思路

  1. 将赫夫曼编码表存放在Map中,Map<Byte,String> Byte存放值,String存放对应值的编码
  2. 在生成赫夫曼编码时,需要去拼接路径,定义一个StringBuilder,存储某个叶子节点的路径
/***
     * @description: 将传入的node节点的所有的叶子节点的赫夫曼编码得到,并放入到map集合中
     * @param: node 节点
     * @param: code 路径:左子节点是0,右子节点是1
     * @param: stringBuilder 拼接路径的
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/12 16:55
     */
    private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        stringBuilder2.append(code);
        if (node!=null){
            // 判断当前node是叶子节点还是非叶子节点
            if (node.getData()==null){
                // 说明是一个非叶子节点,就递归处理
                // 向左递归
                getCodes(node.getLeft(),"0",stringBuilder2);
                // 向右递归
                getCodes(node.getRight(),"1",stringBuilder2);
            }else {
                // 说明是叶子节点,就表示找到了某个叶子节点的最后
                huffmanCodes.put(node.getData(),stringBuilder2.toString());
            }
        }
    }
5.3.2.5 生成赫夫曼编码字节数组
/***
     * @description: 返回赫夫曼编码数组
     * @param: bytes 原始的字符串对应的byte[]数组,
     * @param: huffmanCodes 赫夫曼编码数组
     * @return: java.lang.Byte[]
     * @author ZhangJiaHao
     * @date: 2022/1/12 17:11
     */
    private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
        // 先利用huffmanCode 将btyes数组转为赫夫曼编码对应的字符串
        StringBuilder stringBuilder = new StringBuilder();
        // 遍历bytes数组
        for (byte b : bytes) {
            stringBuilder.append(huffmanCodes.get(b));
        }
        //System.out.println(stringBuilder.toString());  //1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100
        // 将上述字符串转为byte数组 byte数组是八位为一个。
        int len;
        if (stringBuilder.length() % 8 == 0) {
            len = stringBuilder.length() / 8;
        } else {
            len = stringBuilder.length() / 8 + 1;
        }

        byte[] by = new byte[len];
        int index = 0;
        for (int i = 0; i < stringBuilder.length(); i += 8) { // 因为每8位对应一个byte,所以步长+8
            String strByte;
            if (i + 8 > stringBuilder.length()) {
                strByte = stringBuilder.substring(i);
            } else {
                strByte = stringBuilder.substring(i, i + 8);
            }
            // 将strByte转为一个byte,放到数组中
            by[index] = (byte) Integer.parseInt(strByte, 2);
            index++;
        }
        return by;
    }
5.3.2.6 测试
public static void main(String[] args) {
        String s = "i like like like java do you like a java";
        // 获取字节数组
        byte[] contentBytes = s.getBytes();
        // 获取list
        List<Node> nodes = getNodes(contentBytes);
        // 获取哈夫曼树
        Node root = createHuffmanTree(nodes);
        // 获取哈夫曼编码
        getCodes(root, "", stringBuilder);
        // 获取哈夫曼编码数组
        byte[] zip = zip(contentBytes, huffmanCodes);
        System.out.println(Arrays.toString(zip));

    }

请添加图片描述

5.3.2.7 方法组合
/*** 
     * @description:  将上述方法全部组合在一起 string-> huffman编码数组
     * @param: s
     * @return: byte[]
     * @author ZhangJiaHao
     * @date: 2022/1/12 18:20
     */
    private static byte[] stringToHuffman(String s){
        // 获取字节数组
        byte[] contentBytes = s.getBytes();
        // 获取list
        List<Node> nodes = getNodes(contentBytes);
        // 获取哈夫曼树
        Node root = createHuffmanTree(nodes);

         Map<Byte, String> huffmanCodes = new HashMap<Byte, String>();

         StringBuilder stringBuilder = new StringBuilder();
        // 获取哈夫曼编码
        getCodes(root, "", stringBuilder);
        // 获取哈夫曼编码数组
        byte[] zip = zip(contentBytes, huffmanCodes);
        
        return zip;
    }

6. 哈夫曼解码

  1. 先将上述得到的huffman数组[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]转为字符串1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100
  2. 将huffman编码得到的字符串对照huffman编码转换为字符串
/*** 
     * @description: 将byte数组转为最初的字符串
     * @param: huffmanCodes haffman编码表
     * @param: huffmanBytes 压缩后的字节数组
     * @return: byte[] 字符串原来对应的byte数组
     * @author ZhangJiaHao
     * @date: 2022/1/13 11:38
     */
    public static String decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
        // 1. 先得到huffmanBytes对应的二进制的字符串
        StringBuilder stringBuilder = new StringBuilder();
        // 2. 将byte数组转为二进制的字符串
        for (int i = 0; i < huffmanBytes.length; i++) {
            // 判断是不是最后一个字节
            boolean flag = (i == huffmanBytes.length - 1);
            String s = byteToBitString(!flag, huffmanBytes[i]);
            stringBuilder.append(s);
        }
        // 3. 吧字符串按照指定的haffman编码进行解码
        Map<String, Byte> map = new HashMap<>();
        for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
            map.put(entry.getValue(), entry.getKey());
        }

        // 4. 创建一个集合来存放byte
        List<Byte> list = new ArrayList<>();
        for (int i = 0; i < stringBuilder.length(); ) {
            int count = 1; // 计数器
            boolean flag = true;
            Byte b = null;
            while (flag) {
                // 递增的取出key
                String key = stringBuilder.substring(i, i + count); // i不动,count移动
                b = map.get(key);
                if (b == null) {
                    count++;// 说明还没有匹配到
                } else {
                    flag = false;
                }
            }
            list.add(b);
            i += count;// i移动到count的位置
        }
        byte[] b = new byte[list.size()];
        for (int i = 0;i<b.length;i++){
            b[i] = list.get(i);
        }
        return new String(b);
    }


    /*** 
     * @description: 将一个byte转换为二进制的字符串(注意:是按照补码返回的,编码的时候也是按照补码的形式编码的)
     * @param: flag 标识是否需要补高位,true标识补高位,false标识不补
     * @param: b
     * @return: java.lang.String
     * @author ZhangJiaHao
     * @date: 2022/1/13 11:31
     */
    private static String byteToBitString(boolean flag, byte b) {
        // 使用变量保存b
        int temp = b;
        // 如果是正数,我们还存在补高位
        if (flag) {
            temp |= 256;
        }
        String str = Integer.toBinaryString(temp);
        if (flag) {
            return str.substring(str.length() - 8);
        } else {
            return str;
        }
    }

7. 二叉排序树

7.1 需求

给你一个数列 (7, 3, 10, 12, 5, 1, 9),要求能够高效的完成对数据的查询和添加。

  1. 使用数组
    1.1 数组未排序, 优点:直接在数组尾添加,速度快。 缺点:查找速度慢.
    1.2 数组排序,优点:可以使用二分查找,查找速度快,缺点:为了保证数组有序,在添加新数据时,找到插入位置后,后面的数据需整体移动,速度慢。

  2. 使用链式存储-链表
    不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动。

  3. 使用二叉排序树

7.2 二叉排序树介绍

二叉排序树:BST: (Binary Sort(Search) Tree), 对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点

比如针对前面的数据 (7, 3, 10, 12, 5, 1, 9) ,对应的二叉排序树为:
请添加图片描述
说明:
3<7 所以3在7左边
10>7 所以10在7右边
12>7,所以12在7右边,12>10 所以12在10右边

7.3 二叉排序树创建和遍历

一个数组创建成对应的二叉排序树,并使用中序遍历二叉排序树,比如: 数组为 Array(7, 3, 10, 12, 5, 1, 9) , 创建成对应的二叉排序树为 :

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

class BinarySortTree{
    private Node root;

    public Node getRoot() {
        return root;
    }

    public void setRoot(Node root) {
        this.root = root;
    }

    public void add(Node node){
        if (root == null){
            System.out.println("error");
            return;
        }else {
            root.add(node);
        }
    }

    public void infixOrder(){
        if (root == null){
            System.out.println("error");
            return;
        }else {
            root.infixOrder();
        }
    }

}

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

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

    public Node() {

    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

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

    /***
     * @description: 二叉排序树添加节点方法
     * @param: node
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/13 13:25
     */
    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);
            }
        }
    }

    /***
     * @description: 中序遍历
     * @param:
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/13 13:27
     */
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.infixOrder();
        }
    }



请添加图片描述

7.4 二叉排序树删除节点

二叉排序树的删除情况比较复杂,有下面三种情况需要考虑

  1. 删除叶子节点 (比如:2, 5, 9, 12)
  2. 删除只有一颗子树的节点 (比如:1)
  3. 删除有两颗子树的节点. (比如:7, 3,10 )

在这里插入图片描述

7.4.1 删除叶子节点

思路

  1. 确定删除的节点是存在的,找到节点 targetNode
  2. 确定删除节点的父节点 parent
  3. 判断targetNode是parent的左子节点还是右子节点
  4. 删除节点

代码
node类

/***
     * @description: 寻找删除的Node
     * @param: value node的值
     * @return: com.zjh.树.二叉排序树.Node
     * @author ZhangJiaHao
     * @date: 2022/1/13 14:46
     */
    public Node search(int value) {
        if (this.value == value) {
            return this;
        } else if (this.value > value) {
            // 向左子树递归查找
            if (this.left != null) {
                return this.left.search(value);
            } else {
                return null;
            }
        } else {
            // 向右子树递归查找
            if (this.right != null) {
                return this.right.search(value);
            } else {
                return null;
            }
        }
    }

    /***
     * @description: 查找待删除节点的父节点
     * @param: value
     * @return: com.zjh.树.二叉排序树.Node
     * @author ZhangJiaHao
     * @date: 2022/1/13 14:51
     */
    public Node searchParent(int value) {
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {
            return this;
        } else {
            if (this.value > value && this.left != null) {
                return this.left.searchParent(value);
            } else if (this.value <= value && this.right != null) {
                return this.right.searchParent(value);
            }
            return null;
        }
    }

二叉排序树类

public Node search(int value) {
        if (root == null) {
            return null;
        } else {
            return root.search(value);
        }
    }

    public Node searchParent(int value) {
        if (root == null) {
            return null;
        } else {
            return root.searchParent(value);
        }
    }

    /***
     * @description: 删除叶子节点
     * @param: value
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/13 15:13
     */
public void delNode(int value) {
        if (root == null) {
            return;
        } else {
            // 找到删除节点
            Node targetNode = search(value);
            if (targetNode == null) {
                return;
            }

            // 找到父节点
            Node parent = searchParent(value);
            if (parent == null) {
                // 说明targetNode是根节点
                root = null;
                return;
            }

            if (targetNode.getLeft() == null && targetNode.getRight() == null) {
                // targetNode是叶子节点
                if (parent.getLeft() == targetNode) {
                    parent.setLeft(null);
                    return;
                } else if (parent.getRight() == targetNode) {
                    parent.setRight(null);
                    return;
                }
            }
        }
    }

7.4.2 删除只有一颗子树的节点(比如:1)

思路

  1. 确定删除的节点是存在的,找到节点 targetNode
  2. 确定删除节点的父节点 parent
  3. 确定target的子节点是左子节点还是右子节点
  4. targetNode是parent的左子节点还是右子节点
  5. 删除节点(注意,只是删除target,target的子节点是要连接到target的父节点上的)即例如,删除1的时候,2要变成3的左子节点

代码

public void delNode(int value) {
        if (root == null) {
            return;
        } else {
            // 找到删除节点
            Node targetNode = search(value);
            if (targetNode == null) {
                return;
            }

            // 找到父节点
            Node parent = searchParent(value);
            if (parent == null) {
                // 说明targetNode是根节点
                root = null;
                return;
            }

            if ((targetNode.getLeft() == null) && (targetNode.getRight() == null)) {
                // targetNode是叶子节点
                if (parent.getLeft() == targetNode) {
                    parent.setLeft(null);
                    return;
                } else if (parent.getRight() == targetNode) {
                    parent.setRight(null);
                    return;
                }
            } else if (targetNode.getLeft() != null && targetNode.getRight() != null) {
                // targetNode有两颗子树

            } else {
                // targetNode有一颗子树 
                if (targetNode.getLeft() != null) {
                    if (parent.getLeft() == targetNode) {
                        parent.setLeft(targetNode.getLeft());
                    } else {
                        parent.setRight(parent.getLeft());
                    }
                } else {
                    if (parent.getLeft() == targetNode) {
                        parent.setLeft(targetNode.getRight());
                    } else {
                        parent.setRight(parent.getRight());
                    }
                }
            }
        }
    }

7.4.3 删除有两颗子树的节点 (比如:7, 3,10 )

思路1

  1. 确定删除的节点是存在的,找到节点 targetNode
  2. 确定删除节点的父节点 parent
  3. 从targetNode的右子树找到最小的节点
  4. 用临时变量将最小值保存在temp中
  5. 删除右子树找到最小的节点
  6. 将temp的值赋予targetNode
    思路2
  7. 确定删除的节点是存在的,找到节点 targetNode
  8. 确定删除节点的父节点 parent
  9. 从targetNode的左子树找到最大的节点
  10. 用临时变量将最大值保存在temp中
  11. 删除左子树找到最大的节点
  12. 将temp的值赋予targetNode
/***
     * @description: 删除叶子节点
     * @param: value
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/13 15:13
     */
    public void delNode(int value) {
        if (root == null) {
            return;
        } else {
            // 找到删除节点
            Node targetNode = search(value);
            if (targetNode == null) {
                return;
            }

            // 找到父节点
            Node parent = searchParent(value);
            if (parent == null) {
                // 说明targetNode是根节点
                root = null;
                return;
            }

            if ((targetNode.getLeft() == null) && (targetNode.getRight() == null)) {
                // targetNode是叶子节点
                if (parent.getLeft() == targetNode) {
                    parent.setLeft(null);
                    return;
                } else if (parent.getRight() == targetNode) {
                    parent.setRight(null);
                    return;
                }
            } else if (targetNode.getLeft() != null && targetNode.getRight() != null) {
                // targetNode有两颗子树
                // 从targetNode的右子树寻找最小的节点
                int min = delRightTreeMin(targetNode);
                targetNode.setValue(min);


            } else {
                // targetNode有一颗子树
                if (targetNode.getLeft() != null) {
                    if (parent.getLeft() == targetNode) {
                        parent.setLeft(targetNode.getLeft());
                    } else {
                        parent.setRight(parent.getLeft());
                    }
                } else {
                    if (parent.getLeft() == targetNode) {
                        parent.setLeft(targetNode.getRight());
                    } else {
                        parent.setRight(parent.getRight());
                    }
                }
            }
        }
    }

    /***
     * @description: 寻找以node为根节点的右子树中的最小节点得到值,并且删除该最小节点
     * @param: node
     * @return: com.zjh.树.二叉排序树.Node
     * @author ZhangJiaHao
     * @date: 2022/1/13 15:35
     */
    public int delRightTreeMin(Node node){
        Node target = node;
        while(target.getRight()!=null){
            target = target.getRight();
        }
        delNode(target.getValue());
        return target.getValue();
    }
}

8. 平衡二叉树(AVL树)

8.1 案例

给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在.
在这里插入图片描述
BST 存在的问题分析:

  1. 左子树全部为空,从形式上看,更像一个单链表.
  2. 插入速度没有影响
  3. 查询速度明显降低(因为需要依次比较), 不能发挥BST的优势,因为每次还需要比较左子树,其查询速度比单链表还慢
  4. 解决方案-平衡二叉树(AVL)

8.2 基本介绍

  1. 平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树, 可以保证查询效率较高。
  2. 具有以下特点:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL(这是一种算法)、替罪羊树、Treap、伸展树等。

8.3 思路

要求: 给你一个数列,创建出对应的平衡二叉树.数列 {4,3,6,5,7,8}

先构造为二叉排序树,下图左侧

在这里插入图片描述

因为上图左侧是右子树比左子树高,所以进行左旋

8.4 代码实现

因为avl树其实是在二叉排序树的基础上实现左旋和右旋的功能。所以我们可以复用二叉排序树的功能,在二叉排序树上添加左旋和右旋的功能即可

如何判断是否需要左旋或者右旋,其实就是看子树的高度(注意是子树的高度)

8.4.1 求树的高度

/*** 
     * @description: 返回左子树的高度
     * @param:
     * @return: int
     * @author ZhangJiaHao
     * @date: 2022/1/14 11:17
     */
    public int leftHeight() {
        if (left == null) {
            return 0;
        }
        return left.height();
    }


    /*** 
     * @description: 返回右子树的高度
     * @param:
     * @return: int
     * @author ZhangJiaHao
     * @date: 2022/1/14 11:17
     */
    public int rightHeight() {
        if (right == null) {
            return 0;
        }
        return right.height();
    }


    /*** 
     * @description: 返回以当前节点为根节点的树的高度
     * @param:
     * @return: int
     * @author ZhangJiaHao
     * @date: 2022/1/14 11:15
     */
    public int height() {
        return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
    }

8.4.2 左旋

/*** 
     * @description: 左旋
     * @param: 
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/14 11:43
     */
    public void leftRotate(){
        //1. 创建新的根节点,值为当前根节点的值
        Node newNode = new Node(value);
        //2. 把新的节点的左子树设置成当前节点的左子树
        newNode.left = left;
        //3. 把新的节点的右子树设置成当前节点的右子树的左子树
        newNode.right = right.left;
        //4. 把当前节点的值替换成右子节点的值
        value = right.value;
        //5. 把当前节点的右子树设置成当前节点的右子树的右子树
        right = right.right;
        //6. 把当前节点的左子节点设置成新的节点
        left = newNode;
    }

那么左旋应该什么时候用呢?
应该是在二叉排序树在添加节点的时候出现了右子树高度-左子树的高度大于1的情况

注意是右子树高度-左子树的高度

/***
     * @description: 二叉排序树添加节点方法
     * @param: node
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/13 13:25
     */
    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);
            }
        }

        // 当添加完一个节点后,如果  右子树的高度-左子树的高度>1,就左旋
        if (rightHeight()-leftHeight()>1){
            leftRotate();
        }
    }

注意,此时调用左旋的判断条件比较简单化,后面会进行修改

8.4.3 右旋

在这里插入图片描述

/*** 
     * @description: 右旋 
     * @param: 
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/14 11:58
     */
    public void rightRotate(){
        Node newNode = new Node(value);
        newNode.right = right;
        newNode.left = left.right;
        value = left.value;
        left = left.left;
        right = newNode;
    }

什么时候调用呢

 /***
     * @description: 二叉排序树添加节点方法
     * @param: node
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/13 13:25
     */
    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);
            }
        }

        // 当添加完一个节点后,如果  右子树的高度-左子树的高度>1,就左旋
        if (rightHeight()-leftHeight()>1){
            leftRotate();
        }
        // 当添加完一个节点后如果 左子树的高度- 右子树的高度 >1 就右旋
        if (leftHeight()-rightHeight()>1){
            rightRotate();
        }
    }

8.4.4 双旋转

大多数数列进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换。比如数列
int[] arr = { 10, 11, 7, 6, 8, 9 }; 运行原来的代码可以看到,并没有转成 AVL树.
int[] arr = {2,1,6,5,7,3}; // 运行原来的代码可以看到,并没有转成 AVL树

测试

public static void main(String[] args) {
        int arr[] = {10, 11, 7, 6, 8, 9};
        // 创建一个avltree对象
        AVLTree avlTree = new AVLTree();
        avlTree.setRoot(new Node(arr[0]));
        // 添加节点
        for (int i = 1;i< arr.length;i++){
            avlTree.add(new Node(arr[i]));
        }

        System.out.println("树的高度"+avlTree.getRoot().height());
        System.out.println("左子树高度"+avlTree.getRoot().leftHeight());
        System.out.println("右子树高度"+avlTree.getRoot().rightHeight());

请添加图片描述

8.4.4.1 问题分析

在这里插入图片描述
上图进行右旋后还是非平衡二叉树

问题分析:

  1. 当符合右旋的条件时,如果他(根节点)的左子树的右子树的高度大于他的左子树的左子树的高度
  2. 先对当前节点(根节点)的左节点进行左旋,
  3. 再对当前节点(根节点)进行右旋
/***
     * @description: 二叉排序树添加节点方法
     * @param: node
     * @return: void
     * @author ZhangJiaHao
     * @date: 2022/1/13 13:25
     */
    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);
            }
        }

        // 当添加完一个节点后,如果  右子树的高度-左子树的高度>1,就左旋
        if (rightHeight() - leftHeight() > 1) {
            // 如果他的右子树的左子树的高度大于他的右子树的右子树的高度
            if (right != null && right.leftHeight() > right.rightHeight()) {
                // 先对当前节点的右节点进行右旋
                right.rightRotate();
                // 在对当前节点进行左旋
                leftRotate();
            } else {
                leftRotate();
            }
            // 这个return必须要,因为如果先进行了此方法,就说明已经平衡了,不要再执行下面的右旋了
            return;
        }
        // 当添加完一个节点后如果 左子树的高度- 右子树的高度 >1 就右旋
        if (leftHeight() - rightHeight() > 1) {
            // 如果他(根节点)的左子树的右子树的高度大于他的左子树的左子树的高度
            if (left != null && left.rightHeight() > left.leftHeight()) {
                // 先对当前节点(根节点)的左节点进行左旋,
                left.leftRotate();
                //再对当前节点(根节点)进行右旋
                rightRotate();
            } else {
                // 直接进行右旋
                rightRotate();
            }

        }
    }

9. 二叉树与B树

二叉树的问题分析

二叉树的操作效率较高,但是也存在问题, 请看下面的二叉树

在这里插入图片描述

二叉树需要加载到内存的,如果二叉树的节点少,没有什么问题,但是如果二叉树的节点很多(比如1亿), 就存在如下问题:

  1. 问题1:在构建二叉树时,需要多次进行i/o操作(海量数据存在数据库或文件中),节点海量,构建二叉树时,速度有影响
  2. 问题2:节点海量,也会造成二叉树的高度很大,会降低操作速度.

节点的度:就是该节点有多少个子节点
树的度:就是该树的所有节点中max(节点的度)

9.1 多叉树

  1. 在二叉树中,每个节点有数据项,最多有两个子节点。如果允许每个节点可以有更多的数据项和更多的子节点,就是多叉树(multiway tree)
  2. 后面我们讲解的2-3树,2-3-4树就是多叉树,多叉树通过重新组织节点,减少树的高度,能对二叉树进行优化。
  3. 举例说明(下面2-3树就是一颗多叉树)
    在这里插入图片描述

9.1.1 B树的基本介绍

B树通过重新组织节点,降低树的高度,并且减少i/o读写次数来提升效率。
在这里插入图片描述

  1. 如图B树通过重新组织节点, 降低了树的高度.
  2. 文件系统及数据库系统的设计者利用了磁盘预读原理,将一个节点的大小设为等于一个页(页得大小通常为4k),这样每个节点只需要一次I/O就可以完全载入
  3. 将树的度M设置为1024,在600亿个元素中最多只需要4次I/O操作就可以读取到想要的元素, B树(B+)广泛应用于文件存储系统以及数据库系统中

9.1.2 2-3树基本介绍

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

  1. 2-3树的所有叶子节点都在同一层.(只要是B树都满足这个条件)

  2. 有两个子节点的节点叫二节点,二节点要么没有子节点,要么有两个子节点.

  3. 有三个子节点的节点叫三节点,三节点要么没有子节点,要么有三个子节点.

  4. 2-3树是由二节点和三节点构成的树

9.1.3 实现2-3树

插入规则:

  1. 2-3树的所有叶子节点都在同一层.(只要是B树都满足这个条件)

  2. 有两个子节点的节点叫二节点,二节点要么没有子节点,要么有两个子节点.

  3. 有三个子节点的节点叫三节点,三节点要么没有子节点,要么有三个子节点

  4. 当按照规则插入一个数到某个节点时,不能满足上面三个要求,就需要拆,先向上拆,如果上层满,则拆本层,拆后仍然需要满足上面3个条件。

  5. 对于三节点的子树的值大小仍然遵守(BST 二叉排序树)的规则

请添加图片描述

9.1.4 其它说明

除了23树,还有234树等,概念和23树类似,也是一种B树。 如图:
在这里插入图片描述
多了四节点

9.1.5 B树的介绍

B-tree树即B树,B即Balanced,平衡的意思。有人把B-tree翻译成B-树,容易让人产生误解。会以为B-树是一种树,而B树又是另一种树。实际上,B-tree就是指的B树。

前面已经介绍了2-3树和2-3-4树,他们就是B树(英语:B-tree 也写成B-树),这里我们再做一个说明,我们在学习Mysql时,经常听到说某种类型的索引是基于B树或者B+树的,如图:
在这里插入图片描述

  1. B树的阶:节点的最多子节点个数。比如2-3树的阶是3,2-3-4树的阶是4

  2. B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点

  3. 关键字集合分布在整颗树中, 即叶子节点和非叶子节点都存放数据.

  4. 搜索有可能在非叶子结点结束

  5. 其搜索性能等价于在关键字全集内做一次二分查找

9.1.6 B+树的介绍

B+树是B树的变体,也是一种多路搜索树。
在这里插入图片描述

  1. B+树的搜索与B树也基本相同,区别是B+树只有达到叶子结点才命中(B树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找
  2. 所有关键字都出现在叶子结点的链表中(即数据只能在叶子节点【也叫稠密索引】),且链表中的关键字(数据)恰好是有序的。
  3. 不可能在非叶子结点命中
  4. 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层
  5. 更适合文件索引系统
  6. B树和B+树各有自己的应用场景,不能说B+树完全比B树好,反之亦然.

9.1.7 B*树的介绍

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

  1. B*树定义了非叶子结点关键字个数至少为(2/3)*M M是度,即块的最低使用率为2/3,而B+树的块的最低使用率为B+树的1/2。

  2. 从第1个特点我们可以看出,B*树分配新结点的概率比B+树要低,空间使用率更高

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值