数据结构中的各种树及实现

树的概念

  • 树是一种非线性数据结构
    在这里插入图片描述

  • 树的定义:树是 n 个结点的有限集合,有且仅有一个根结点,其余结点可分为 m 个根结点的子树

  • 树的相关概念

    • 结点的度:一个结点拥有子树的个数称为度。比如A的度为3,C的度为2,H的度为0.度为0的结点称为叶子结点(D,F,G,H)。树的度是树中所有结点的度的最大值,此树的度为3
    • 树中结点的最大层次称为树的深度或高度,此树的深度为4
    • 父节点A的子节点是B,C,D;B,C,D 是兄弟结点
    • 树的集合称为森林,树和森林之间有着密切的关系,删除一个树的根结点,其所有原来的子树都是树,构成森林用。用一个结点连接到森林的所有树的根结点就构成树
  • 特点
    i. 每个结点有0 个或多个子结点
    ii.没有父结点的结点称为根结点
    iii.每一个非根结点有且只有一个父结点
    iiii.除了根结点外,每个子结点可以分为多个不相交的子树

二叉树

在这里插入图片描述
二叉树的特点

  • 每个结点最多有两颗子树,结点的度最大为2
  • 左子树和右子树是有序的,次序不能颠倒
  • 即使某结点只有一个子树,也要区分左右子树

二叉树是一种比较有用的折中方案,它添加、删除元素都很快,并且在查找方面也有很多的算法优化,所以二叉树既有链表的好处,也有数组的好处,是两者的优化方案,在处理大批量的动态数据方面非常有用

扩展
二叉树有很多扩展的数据结构,包括平衡二叉树、红黑树、B+树等,这些数据结构在二叉树的基础上衍生了很多的功能,在实际应用中广泛用到。例如mysql的数据库索引结构用到的就是 B+树,还有 HashMap的底层源码中用到了红黑树

代码实现(包含3种遍历方式及相关常用方法)

/**
 * TODO
 *  二叉树
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class BianryTree {

    TreeNode root;

    //设置根节点
    public void setRoot(TreeNode root){
        this.root = root;
    }

    //获取根节点
    public TreeNode getRoot(){
        return root;
    }

    //前序遍历
    public void frontShow(){
        if(root != null){
            root.frontShow();
        }
    }

    public void midShow(){
        if(root != null){
            root.midShow();
        }
    }

    public void afterShow(){
        if(root != null){
            root.afterShow();
        }
    }

    //查找节点信息
    public TreeNode frontSearch(int i){
        return root.frontSearch(i);
    }

    //删除结点
    public void delete(int i){
        root.delete(i);
    }
}

/**
 * TODO
 *  二叉树
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class TreeNode {
    //节点权值
    int value;
    //左子节点
    TreeNode leftNode;
    //右子节点
    TreeNode rightNode;

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

    //设置左儿子
    public void setLeftNode(TreeNode leftNode){
        this.leftNode = leftNode;
    }

    //设置右儿子
    public void setRightNode(TreeNode rightNode){
        this.rightNode = rightNode;
    }

    //各种遍历方法的实现
    public void frontShow(){
        //先遍历当前节点的内容
        System.out.println(value);
        //左节点
        if(leftNode != null){
            leftNode.frontShow();
        }
        //右节点
        if(rightNode != null){
            rightNode.frontShow();
        }
    }

    public void midShow(){
        if(leftNode != null){
            leftNode.midShow();
        }
        System.out.println(value);
        if(rightNode != null){
            rightNode.midShow();
        }
    }

    public void afterShow(){
        if(leftNode != null){
            leftNode.afterShow();
        }
        if(rightNode != null){
            rightNode.afterShow();
        }
        System.out.println(value);
    }

    //前序遍历查找节点
    public TreeNode frontSearch(int i){
        TreeNode target = null;
        //对比当前节点的值
        if(this.value == i){
            return this;
        }else {
            //查找左儿子
            if(leftNode != null){
                //有可能可以找到,也可能找不到
                //找不到的话 target 还是 null
                target = leftNode.frontSearch(i);
            }
            //如果不为空,说明在左儿子中已经找到
            if(target != null){
                return target;
            }
            //查找右儿子
            if(rightNode != null){
                target = rightNode.frontSearch(i);
            }
        }
        return target;
    }

    //删除节点
    public void delete(int i){
        TreeNode parent = this;
        //判断左儿子
        if(parent.leftNode!=null
                && parent.leftNode.value == i){
            parent.leftNode = null;
            return;
        }
        //判断右儿子
        if(parent.rightNode != null
        && parent.rightNode.value == i){
            parent.rightNode = null;
            return;
        }

        //递归检查并删除左儿子
        parent = leftNode;
        if(parent != null){
            parent.delete(i);
        }

        //递归检查并删除右儿子
        parent = rightNode;
        if(parent != null){
            parent.delete(i);
        }
    }

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

/**
 * TODO
 *  二叉树
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class TestBianryTree {

    public static void main(String[] args) {
        //创建一颗树
        BianryTree bianryTree = new BianryTree();
        //创建一个根节点
        TreeNode root = new TreeNode(1);
        //把根节点赋给树
        bianryTree.setRoot(root);
        //创建一个左节点
        TreeNode rootL = new TreeNode(2);
        //把新创建的结点设置为根节点的子节点
        root.setLeftNode(rootL);
        //创建一个右节点
        TreeNode rootR = new TreeNode(3);
        //把新创建的节点设置为根节点的子节点
        root.setRightNode(rootR);

        //下面为根节点的子节点再添加子节点,方便遍历
        TreeNode rootLL = new TreeNode(4);
        rootL.setLeftNode(rootLL);
        TreeNode rootLR = new TreeNode(5);
        rootL.setRightNode(rootLR);

        TreeNode rootRL = new TreeNode(6);
        rootR.setLeftNode(rootRL);
        TreeNode rootRR = new TreeNode(7);
        rootR.setRightNode(rootRR);

        //前序遍历树
        bianryTree.frontShow();
        System.out.println("================");
        TreeNode result = bianryTree.frontSearch(6);
        System.out.println("找到节点信息为:"+result);
        System.out.println("下面删除该结点");
        bianryTree.delete(6);
        bianryTree.frontShow();
    }
}

满二叉树

高度为 h ,由 2^h - 1个结点构成的二叉树称为满二叉树

完全二叉树

完全二叉树是由满二叉树而引出来的,若设二叉树的深度为 h,除第 h 层外,其他各层(1到h-1)的结点数都达到最大个数(即1到h-1层为一个满二叉树),第h层所有的结点都连续集中在最左边,这既是完全二叉树。(所有叶子结点都在最后一层或倒数第二层,且最后一层的叶子结点在左边连续,倒数第二层的叶子结点在右边连续
堆一般都是用完全二叉树来实现。
在这里插入图片描述
二叉树的应用中,常要求在树中查找具有某种特征的节点,或对树中全部节点进行某种处理,此时涉及二叉树的遍历。

3个基本单元:根节点,左子树,右子树

先序遍历:先访问根节点,再先序遍历左子树,最后遍历右子树。(根-左-右)
中序遍历:先中序遍历左子树,再访问根节点,最后中序遍历右子树。(左-根-右)
后序遍历:先后序遍历左子树,在后续遍历右子树,最后访问根节点。(左-右-根)
层序遍历:所有深度为d的节点要在深度d+1的节点之前执行;用到队列、属于广度优先。
在这里插入图片描述

  • 树与二叉树的区别

二叉树每个节点最多有2个节点,树则无限制,二叉树的平均深度为O(sqrt(N));
二叉树中子树分为左子树和右子树,二叉树是有序的(即使某节点只有一棵子树也要指明是左/右);
树决不能为空,至少有一个节点,但二叉树可以为空。

顺序二叉树

顺序二叉树存储的二叉树在用数组实现的时候只考虑 完全二叉树/满二叉树(因为当某个结点的左/右子结点为空时,用数组来存储可能会出现某个下标对应的数值为空的情况造成错误)

  • 相关性质:
    第 n 个结点的左/右子结点是 :2n + 1/2n + 2
    第 n 个结点的父结点是: (n-1)/2
    在这里插入图片描述

代码实现

/**
 * TODO
 *  顺序二叉树
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class ArrayBinaryTree {

    int[] data;

    public ArrayBinaryTree(int[] data){
        this.data = data;
    }

    //无参 遍历顺序二叉树-前序遍历
    public void frontShow(){
        frontShow(0);
    }

    //前序遍历
    public void frontShow(int index){
        if(data == null || data.length == 0){
            return;
        }
        //先遍历当前节点的内容
        System.out.println(data[index]);
        //2*index+1:处理左子树
        if(2*index+1<data.length){
            frontShow(2*index+1);
        }
        //2*index+2:处理右子树
        if(2*index+2<data.length){
            frontShow(2*index+2);
        }
    }

}
/**
 * TODO
 *  测试顺序二叉树的遍历
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class TestArrayBinaryTree {

    public static void main(String[] args) {
        int[] data = new int[]{1,2,3,4,5,6,7};
        ArrayBinaryTree tree = new ArrayBinaryTree(data);
        //前序遍历:1245367
        tree.frontShow();
    }
}

线索二叉树

为了解决“指针”的浪费——类似双向链表,以达到可以获取结点的前驱后继
如下图,5的右节点应该指向1,即需要额外标志来区别前后指向是子节点还是排序的顺序

在这里插入图片描述

代码实现

/**
 * TODO
 *  线索二叉树
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class ThreadedBinaryTree {

    ThreadedNode root;
    //用于临时存储前驱节点
    ThreadedNode pre = null;

    //设置根节点
    public void setRoot(ThreadedNode root){
        this.root = root;
    }

    //遍历线索二叉树
    public void threadIterate(){
        //用于临时存储当前遍历节点
        ThreadedNode node = root;
        while(node != null){
            //循环找到最开始的节点
            while(node.leftType==0){
                node = node.leftNode;
            }
            //打印当前节点的值
            System.out.println(node.value);
            //如果当前节点的右指针指向的是后继节点
            //可能后继节点还有后继节点
            while (node.rightType == 1){
                node = node.rightNode;
                System.out.println(node.value);
            }
            //替换遍历的节点
            node = node.rightNode;
        }
    }

    //中序遍历
    public void midShow(){
        if(root != null){
            root.midShow();
        }
    }

    //中序线索化二叉树-无参方法
    public void threadNodes(){
        threadNodes(root);
    }

    public void threadNodes(ThreadedNode node){
        //当前节点如果为 null,直接返回
        if(node == null){
            return;
        }
        //处理左子树
        threadNodes(node.leftNode);
        //处理前驱节点
        if(node.leftNode == null){
            //让当前节点的左指针指向前驱节点
            node.leftNode=pre;
            //改变当前节点左指针的类型
            node.leftType=1;
        }
        //处理前驱的右指针,如果前驱接地那的右指针是null
        if(pre!=null&&pre.rightNode==null){
            //让前驱节点的右指针指向当前节点
            pre.rightNode = node;
            //改变前驱节点的右指针类型
            pre.rightType = 1;
        }

        //每处理一个节点,当前节点就是下一个节点的前驱节点
        pre = node;
        //处理右子树
        threadNodes(node.rightNode);

    }

}

/**
 * TODO
 *  线索二叉树
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class ThreadedNode {
    //节点的权
    int value;
    //左右儿子
    ThreadedNode leftNode;
    ThreadedNode rightNode;
    //标识指针类型:0-子树,1-前驱后继
    int leftType;
    int rightType;

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

    public void midShow(){
        if(leftNode != null){
            leftNode.midShow();
        }
        System.out.println(value);
        if(rightNode != null){
            rightNode.midShow();
        }
    }

    public int getValue() {
        return value;
    }

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

    public ThreadedNode getLeftNode() {
        return leftNode;
    }

    public void setLeftNode(ThreadedNode leftNode) {
        this.leftNode = leftNode;
    }

    public ThreadedNode getRightNode() {
        return rightNode;
    }

    public void setRightNode(ThreadedNode rightNode) {
        this.rightNode = rightNode;
    }

    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;
    }
}

/**
 * TODO
 *  线索二叉树测试
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class TestThreadedBinaryTree {
    public static void main(String[] args) {
        //创建一颗树
        ThreadedBinaryTree binTree = new ThreadedBinaryTree();
        //创建一个根节点
        ThreadedNode root = new ThreadedNode(1);
        //把根节点赋给树
        binTree.setRoot(root);
        //创建一个左节点
        ThreadedNode rootL = new ThreadedNode(2);
        //把新创建的结点设置为根节点的子节点
        root.setLeftNode(rootL);
        //创建一个右节点
        ThreadedNode rootR = new ThreadedNode(3);
        //把新创建的节点设置为根节点的子节点
        root.setRightNode(rootR);

        //为第二层的左节点创建两个子节点
        rootL.setLeftNode(new ThreadedNode(4));
        ThreadedNode fiveNode = new ThreadedNode(5);
        rootL.setRightNode(fiveNode);
        //为第二层的右节点创建两个子节点
        rootR.setLeftNode(new ThreadedNode(6));
        rootR.setRightNode(new ThreadedNode(7));
        //中序遍历树
        binTree.midShow();
        System.out.println("====线索化中====");
        //中序线索化二叉树
        binTree.threadNodes();
        ThreadedNode afterNode = fiveNode.rightNode;
        System.out.println(afterNode.value);
        System.out.println("开始遍历线索二叉树");
        binTree.threadIterate();
    }
}

线索二叉树的遍历不需要递归

赫夫曼树

  • 最优二叉树。它是n个带权叶子结点构成的所有二叉树中,带权路径长度最小的二叉树。

  • 叶节点的带权路径:从根结点到叶子结点经过的结点数 * 叶子结点的权值

  • 树的带权路径长度 WPL:树中所有叶子结点的带权路径长度之和
    在这里插入图片描述
    WPL(a)=9x2+4x2+5x2+2x2=40
    WPL(b)=9x1+5x2+4x3+2x3=37
    WPL©=4x1+2x2+5x3+9x3=50

  • 权值越大的结点离根结点越近的二叉树才是最优二叉树

代码实现

/**
 * TODO
 *  赫夫曼树节点类
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class Node implements Comparable<Node>{
    int value;
    Node left;
    Node right;

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

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

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

/**
 * TODO
 *  创建赫夫曼树测试类
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class TestHuffmanTree {
    public static void main(String[] args) {
        int[] arr = new int[]{3,5,7,8,11,14,23,29};
        Node node = createHuffmanTree(arr);
        System.out.println(node);
    }
    //创建赫夫曼树
    public static Node createHuffmanTree(int[] arr){
        //先使用数组中所有的元素创建若干个二叉树(只有一个节点)
        List<Node> nodes = new ArrayList<>();
        for(int value:arr){
            nodes.add(new Node(value));
        }
        //循环处理
        while(nodes.size() > 1){
            // 排序
            Collections.sort(nodes);
            //取出权值最小的两个二叉树
            //取出权值最小的二叉树,因为是降序排序故在最后面
            Node left = nodes.get(nodes.size()-1);
            //取出权值第二小的二叉树
            Node right = nodes.get(nodes.size()-2);
            //创建一颗新的二叉树
            Node parent = new Node(left.value + right.value);
            //把取出来的两颗二叉树移除
            nodes.remove(left);
            nodes.remove(right);
            // 放入原来的二叉树集合
            nodes.add(parent);
        }
        //System.out.println(nodes.get(0).value);
        return nodes.get(0);
    }
}

二叉查找树/二叉排序树/二叉搜索树

定义

  • 若左子树不为空,则左子树上所有结点的值均小于他的根结点的值

  • 若右子树不为空,则右子树上所有结点的值均大于他的根结点的值

  • 左右子树也分别为二叉排序树

  • 没有键值相等的结点

  • 性质
    二叉查找树的平均深度为O(log(N))

  • 二叉查找树的构建
    在这里插入图片描述

  • 性能分析
    当给定值相同但顺序不同时,所构建的二叉查找树形态是不同的
    在这里插入图片描述

  • 不同形态二叉查找树的ASL(平均查找长度)不同

    i.含有n个结点的二叉查找树的平均查找长度和树的形态有关

    ii.最坏的情况:当先后插入的关键字有序时,构成的二叉查找树蜕变成单支树,树的深度为n,其平均查找长度为(n+1)/2:

    iii.最好的情况:二叉查找树的形态和折半查找的判定树相同,其 ASL和 log2n成正比

    iiii.平均情况:二叉查找树的 ASL 和 logn 等数量级。

为获得更好的性能,二叉查找树构建过程需要“平衡化处理”,使查找树的高度为 O(log(n))

二叉查找树删除结点分析
首先需要定位包含该元素的结点,以及他的父结点:

若无子结点,即叶子结点可以直接删除;

若有子节点,则考虑两种情况:

  • 需要删除的结点下有一个子结点(左/右),那么只需要将 父结点 和 当前节点 的子节点相连(让当前节点的子节点成为父结点的新右子结点——保证根结点的值永远小于右边的值)
  • 需要删除的结点下有两个子结点。首先需要的要删除的结点的右子树
    i. 找到一个最小的值(因为右子树中的结点值一定大于根结点)
    ii. 然后用找到的“最小值”与要需要删除的结点的值替
    iii. 最后将“最小值”的原结点进行删除
    (简单来说就是保证 结点 永远比左边大,比右边小)

代码实现

/**
 * TODO
 *  二叉排序树
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class BinarySoreTree {
    Node root;

    /**
     * 删除节点
     * 三种情况:
     * 1.删除叶子节点
     * 2.删除有一个子节点的节点
     * 3.删除有两个子节点的节点
     */
    public void delete(int value){
        if(root == null){
            return;
        }else{
            //找到该节点
            Node target = search(value);
            //若找不到该节点
            if(target == null){
                return;
            }
            //找到该节点的父节点
            Node parent = searchParent(value);
            //1.要删除的结点是叶子节点
            if(target.left==null && target.right==null){
                //要删除的结点是左子节点
                if(parent.left.value==value){
                    parent.left = null;
                }else {
                    parent.right = null;
                }
                //删除有两个子节点的节点
            }else if(target.left!=null && target.right!=null){
                //删除右子树中值最小的节点,获取到该节点的值
                int min = deleteMin(target.right);
                //替换目标结点的值
                target.value = min;

            //删除有一个子节点的节点
            }else{
                //有左子节点
                if(target.left!=null){
                    //要删除的节点是父节点的左子节点
                    if(parent.left.value == value){
                        //将目标节点的子节点赋给目标节点父节点作为新节点
                        parent.left = target.left;
                    //要删除的节点是父节点的右子节点
                    }else {
                        parent.right = target.left;
                    }
                 //有右子节点
                }else {
                    //要删除的节点是父节点的左子节点
                    if(parent.left.value == value){
                        parent.left = target.right;
                    //要删除的节点是父节点的右子节点
                    }else {
                        parent.right = target.right;
                    }
                }
            }
        }
    }

    /**
     * 删除一棵树中最小节点
     * @param node  实际要删除的目标结点的右子节点
     * @return  返回 右子树 中的最小值
     */
    private int deleteMin(Node node){
        Node target = node;
        //递归向左找
        while(target.left != null){
            target = target.left;
        }
        //删除最小的这个节点
        delete(target.value);
        return target.value;
    }

    /**
     * 寻找父节点
     */
    public Node searchParent(int value){
        if(root == null){
            return null;
        }else{
            return root.searchParent(value);
        }
    }

    /**
     * 向二叉排序树中添加节点
     */
    public void add(Node node){
        //如果是空树
        if(root == null){
            root=node;
        }else {
            root.add(node);
        }
    }

    /**
     * 查找节点
     */
    public Node search(int value){
        if(root == null){
            return null;
        }else{
            return root.search(value);
        }
    }

    /**
     * 遍历树
     */
    public void midShow(){
        if(root != null){
            root.midShow(root);
        }
    }
}

/**
 * TODO
 *  二叉排序树
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class Node {
    int value;
    Node left;
    Node right;

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

    public void midShow(Node node){
        if(node == null){
            return;
        }
        midShow(node.left);
        System.out.println(node.value);
        midShow(node.right);
    }

    /**
     * 查找节点
     */
    public Node search(int value){
        if(this.value == value){
            return this;
        }else if(value < this.value){
            if(left == null){
                return null;
            }
            return left.search(value);
        }else {
            if(right == null){
                return null;
            }
            return right.search(value);
        }
    }

    /**
     * 寻找节点的父节点
     * @param value 要查找的节点
     * @return 返回节点父节点
     */
    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;
        }
    }

    /**
     * 往子树中添加节点
     * @param node
     */
    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 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;
    }
}

/**
 * TODO
 *  二叉排序树测试
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class test {
    public static void main(String[] args) {
        int[] arr = new int[]{7,3,10,12,5,1,9};
        //创建一颗二叉排序树
        BinarySoreTree bst = new BinarySoreTree();
        //循环添加
        for(int i:arr){
            bst.add(new Node(i));
        }
        //中序遍历查看树
        bst.midShow();
//        System.out.println("查找中");
//        Node t = bst.search(10);
//        System.out.println(t.value);
//        Node s = bst.search(15);
//        System.out.println(s);
//        System.out.println("======查找父节点=====");
//        Node p = bst.searchParent(10);
//        System.out.println(p);
//        System.out.println("==========删除叶子节点==========");
//        bst.delete(12);
//        bst.midShow();
//        System.out.println("-----");
//        bst.delete(10);
//        bst.midShow();
        System.out.println("-=-=-=-=-=-=-");
        bst.delete(7);
        bst.midShow();

    }
}

平衡二叉树/AVL(二叉排序树进化版)

平衡二叉树又称 AVL 树,他或者是一颗空树,或者是具有下列性质的二叉树:

  • 左子树和右子树都是平衡二叉树
  • 且左子树和右子树的深度之差的绝对值不超过1
    在这里插入图片描述
    AVL 树是最先发明的自平衡二叉查找树算法。在 AVL 中任何结点的两个儿子子树的高度之差为1,所以他也被称为高度平衡树。

特点:

  • n 个结点的 AVL 树最大深度约 1.44log2 n
  • 在高度为 h 的 AVL 树中,最少结点数 S(h) 由 S(h-1) + S(h-2)+1 给出
    增加和删除可能需要通过一次或多次树旋转来重新平衡这个树

单旋转:

  • 左子树高度比右子树高度高:右旋转
  • 右子树高度比左子树高度高:左旋转
  • 向矮的一方旋转

以右旋转举例(即左子树高度比右子树高度>=2)

在这里插入图片描述

  • 创建一个新结点,其值等于当前节点的值

在这里插入图片描述

  • 将当前结点的右子结点设置成新结点的右子结点
    在这里插入图片描述

  • 将当前节点的左子节点的右子结点设置为新结点的左子结点(如果左子结点无右子结点跳过)
    在这里插入图片描述

  • 将当前节点的值,设置成左子结点的值
    在这里插入图片描述

  • 将当前节点的左子节点设置(替换成)成左子节点的左子节点
    在这里插入图片描述

原树剩下
在这里插入图片描述

  • 将当前节点的右子结点设置成新结点
    在这里插入图片描述
    总的来说就是要保证每个结点的“排序性”

双旋转

  • 较复杂。单旋转是左子树或者右子树整体高度比另一个子树高度>=2
  • 而当 左右子树高度相差 >= 2 的同时内部 子树的左右子树也发生了高度差 >= 2 ,且与上一层子树相反(如上一层的子树中是左子树比右子树高度>=2,而在内层中的子树是右子树比左子树高度>=2
  • 此时旋转一次并不能解决问题,需要两次旋转
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    即:旋转的顺序要看最底层的左右子树的情况,根据高度来判断先进行哪种旋转。

代码实现

/**
 * TODO
 *  二叉平衡树
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class BinarySoreTree {
    Node root;

    /**
     * 删除节点
     * 三种情况:
     * 1.删除叶子节点
     * 2.删除有一个子节点的节点
     * 3.删除有两个子节点的节点
     */
    public void delete(int value){
        if(root == null){
            return;
        }else{
            //找到该节点
            Node target = search(value);
            //若找不到该节点
            if(target == null){
                return;
            }
            //找到该节点的父节点
            Node parent = searchParent(value);
            //1.要删除的结点是叶子节点
            if(target.left==null && target.right==null){
                //要删除的结点是左子节点
                if(parent.left.value==value){
                    parent.left = null;
                }else {
                    parent.right = null;
                }
                //删除有两个子节点的节点
            }else if(target.left!=null && target.right!=null){
                //删除右子树中值最小的节点,获取到该节点的值
                int min = deleteMin(target.right);
                //替换目标结点的值
                target.value = min;

            //删除有一个子节点的节点
            }else{
                //有左子节点
                if(target.left!=null){
                    //要删除的节点是父节点的左子节点
                    if(parent.left.value == value){
                        //将目标节点的子节点赋给目标节点父节点作为新节点
                        parent.left = target.left;
                    //要删除的节点是父节点的右子节点
                    }else {
                        parent.right = target.left;
                    }
                 //有右子节点
                }else {
                    //要删除的节点是父节点的左子节点
                    if(parent.left.value == value){
                        parent.left = target.right;
                    //要删除的节点是父节点的右子节点
                    }else {
                        parent.right = target.right;
                    }
                }
            }
        }
    }

    /**
     * 删除一棵树中最小节点
     * @param node  实际要删除的目标结点的右子节点
     * @return  返回 右子树 中的最小值
     */
    private int deleteMin(Node node){
        Node target = node;
        //递归向左找
        while(target.left != null){
            target = target.left;
        }
        //删除最小的这个节点
        delete(target.value);
        return target.value;
    }

    /**
     * 寻找父节点
     */
    public Node searchParent(int value){
        if(root == null){
            return null;
        }else{
            return root.searchParent(value);
        }
    }

    /**
     * 向二叉排序树中添加节点
     */
    public void add(Node node){
        //如果是空树
        if(root == null){
            root=node;
        }else {
            root.add(node);
        }
    }

    /**
     * 查找节点
     */
    public Node search(int value){
        if(root == null){
            return null;
        }else{
            return root.search(value);
        }
    }

    /**
     * 遍历树
     */
    public void midShow(){
        if(root != null){
            root.midShow(root);
        }
    }
}
/**
 * TODO
 *  二叉平衡树
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class Node {
    int value;
    Node left;
    Node right;

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

    public void midShow(Node node){
        if(node == null){
            return;
        }
        midShow(node.left);
        System.out.println(node.value);
        midShow(node.right);
    }

    /**
     * 查找节点
     */
    public Node search(int value){
        if(this.value == value){
            return this;
        }else if(value < this.value){
            if(left == null){
                return null;
            }
            return left.search(value);
        }else {
            if(right == null){
                return null;
            }
            return right.search(value);
        }
    }

    /**
     * 寻找节点的父节点
     * @param value 要查找的节点
     * @return 返回节点父节点
     */
    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;
        }
    }

    /**
     * 往子树中添加节点
     * @param node
     */
    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(leftHeight() - rightHeight() >= 2){
            //判断内层子树的高度情况
            // 内部是否需要双旋转
            // 即在整体左子树高度>右子树高度时,内部出现“左”<“右”
            if(left!=null && left.leftHeight()<left.rightHeight()){
                //子树先旋转
                left.leftRotate();
                //子树完成后再整体右旋转
                rightRotate();
            }else{
                //默认单旋转-左长右短,右旋转
                rightRotate();
            }
        }
        //左短右长,左旋转
        if(leftHeight() - rightHeight() <= -2){
            //判断内层子树的高度情况
            // 内部是否需要双旋转
            // 即在整体左子树高度<右子树高度时,内部出现“左”>“右”
            if(right!=null && right.rightHeight()<right.leftHeight()){
                //子树先旋转
                right.rightRotate();
                //子树完成后再整体左旋转
                leftRotate();
            }else{
                //默认单旋转-左短右长,左旋转
                leftRotate();
            }
        }
    }

    /**
     * 左旋转
     */
    public void leftRotate(){
        Node newLeft = new Node(value);
        newLeft.left = left;
        newLeft.right = right.left;
        value = right.value;
        right = right.right;
        left = newLeft;
    }

    /**
     * 右旋转
     */
    private void rightRotate(){
        //创建一个新的节点,值等于当前节点的值
        Node newRight = new Node(value);
        //把新节点的右子树设置成当前节点的右子树
        newRight.right = right;
        //把新节点的左子树设置成当前节点的左子树,的右子树
        newRight.left = left.right;
        //把当前节点的值设置成左子节点的值
        value = left.value;
        //把当前节点的左子树,设置成左子树的左子树(谋权篡位集体升级)
        left = left.left;
        //把当前节点的右子树设置为新节点
        right = newRight;
    }

    /**
     * 获取左子树的高度
     * @return 左子树高度
     */
    public int leftHeight(){
        if(left == null){
            return 0;
        }
        return left.height();
    }

    /**
     * 获取右子树的高度
     * @return 右子树高度
     */
    public int rightHeight(){
        if(right == null){
            return 0;
        }
        return right.height();
    }

    /**
     * 返回当前节点的高度
     * @return 高度
     */
    public int height(){
        //取左右子树的高度较大的,加上本身 1 ,就是该节点所在子树的高度
        return Math.max(left==null?0:left.height(),right==null?0:right.height())+1;
    }

    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;
    }
}

/**
 * TODO
 *  二叉平衡树测试
 * @author Redamancy
 * @version 1.0
 * @since jdk 1.8
 */
public class test {
    public static void main(String[] args) {
        //用于构建二叉排序树的数据
//        int[] arr = new int[]{7,3,10,12,5,1,9};
        //用于右旋转测试的数据
//        int[] arr = new int[]{8,9,6,7,5,4};
        //用于左旋转测试的数据
//        int[] arr = new int[]{2,1,4,3,5,6};
        //用于双旋转测试的数据-先左后右
        int[] arr = new int[]{8,9,5,4,6,7};
        //创建一颗二叉排序树
        BinarySoreTree bst = new BinarySoreTree();
        //循环添加
        for(int i:arr){
            //添加元素的时候构建平衡二叉树
            bst.add(new Node(i));
        }
        //中序遍历查看树
        //bst.midShow();
//        System.out.println("查找中");
//        Node t = bst.search(10);
//        System.out.println(t.value);
//        Node s = bst.search(15);
//        System.out.println(s);
//        System.out.println("======查找父节点=====");
//        Node p = bst.searchParent(10);
//        System.out.println(p);
//        System.out.println("==========删除叶子节点==========");
//        bst.delete(12);
//        bst.midShow();
//        System.out.println("-----");
//        bst.delete(10);
//        bst.midShow();
//        System.out.println("-=-=-=-=-=-=-");
//        bst.delete(7);
//
        //旋转测试
        System.out.println(bst.root.height());
        System.out.println(bst.root.value);

    }
}

多路查找树- B树 & B+树

2-3 树

B 树所有叶子结点都在同一层
二结点:要么有两个子结点,要么没有子结点
三结点:要么有三个子结点,要么没有子结点
在这里插入图片描述
在这里插入图片描述

2-3-4 树

以此类推,节点要么有2,3,4个节点,要么没有

B树的阶:

2-3树是 3 阶的B树
2-3-4树是 4 阶的B树

B+ 树

B树的变形(实际例子:MySql索引)

  • 非叶子结点只存储索引信息,不存储数据
  • 叶子结点最右边的指针指向下一个相邻的叶子结点
  • 所有的叶子结点形成了一个有序链表

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值