3树

1树定义

  • 树是n(n>=0)个节点的有限集。当n=0时,称为空树。在任意一个非空树中有如下特点:
  • 1有且仅有一个特定的称为根的节点
  • 2当n>1时,其余节点可分为m(m>0)个互不相交的有限集,每一个集合本身又是一个数,并称为根的子树
  • 概念:根节点,叶子结点,父节点,孩子节点,兄弟节点

2二叉树

  • 二叉树是树的一种特殊形式,这种树的每个节点最多有2个孩子节点(最多有2个,可能只有1个,或者没有孩子节点)
  • 二叉树节点的两个孩子节点,一个称为左孩子,一个称为右孩子
public class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int x){
            val=x;
        }
}

3满二叉树

  • 一个二叉树额所有非叶子节点都存在左右孩子,并且所有叶子节点都在同一层级上,那么这个树就是满二叉树

4完全二叉树

  • 对一个有n个节点的二叉树,按层级顺序编号,则所有节点的编号为从1到n。如果这个树所有节点和同样深度的满二叉树的编号为从1到n的节点位置相同,则这个二叉树为完全二叉树

5二叉树存储

  • 链式存储(最直观)。每一个节点包含3部分:1存储数据的data变量;2指向左孩子的left指针;3指向右孩子的right指针
  • 数组存储。按照层级顺序把二叉树的节点放到数组中对应的位置上,如果某一个节点的左孩子或右孩子空缺,则数组的相应位置也会空出来。

6二叉树应用之查找(二叉查找树)和维持相对顺序(二叉平衡树)

  • 二叉查找树:如果左子树不为空,则左子树上所有节点的值均小于根节点的值;如果右子树不为空,则右子树上所有节点的值均大于节点的值;左右子树也都是二叉查找树

  • 对于一个节点分布相对均衡的二叉查找树来说,如果节点总数是n,那么搜索节点的时间复杂度是O(logn),和树的深度是一样的

  • 二叉平衡树:用于树的自平衡,主要用红黑树,AVL树,树堆等,还有二叉堆,之后会写

7二叉树的遍历

  • 前序遍历:根,左,右
  • 中序遍历:左,根,右
  • 后序遍历:左,右,根
  • 层次遍历

​ 7.1递归实现前序,中序,后序遍历

  //构建二叉树
    public static TreeNode createBinaryTree(LinkedList<Integer> inputList){
        TreeNode node=null;
        if (inputList==null||inputList.isEmpty()){
            return null;
        }
        Integer data=inputList.removeFirst();
        if (data!=null){
            node=new TreeNode(data);
            node.left=createBinaryTree(inputList);
            node.right=createBinaryTree(inputList);
        }
        return node;
    }

    //二叉树前序遍历
    public static void preOrderTraveral(TreeNode node){
        if (node==null){
            return;
        }
        System.out.println(node.val);
        preOrderTraveral(node.left);
        preOrderTraveral(node.right);
    }

    //二叉树中序遍历
    public static void inOrderTraveral(TreeNode node){
        if (node==null){
            return;
        }
        inOrderTraveral(node.left);
        System.out.println(node.val);
        inOrderTraveral(node.right);
    }

    //二叉树后序遍历
    public static void postOrderTraveral(TreeNode node){
        if (node==null){
            return;
        }
        postOrderTraveral(node.left);
        postOrderTraveral(node.right);
        System.out.println(node.val);
    }

    public static void main(String[] args) {
        LinkedList<Integer> inputList = new LinkedList<>(Arrays.asList(new Integer[]{3, 2, 9, null, null, 10, null, null, 8, null, 4}));
        TreeNode treeNode=createBinaryTree(inputList);
        System.out.println("前序遍历: ");
        preOrderTraveral(treeNode);
        System.out.println("中序遍历: ");
        inOrderTraveral(treeNode);
        System.out.println("后序遍历: ");
        postOrderTraveral(treeNode);
    }

​ 7.2非递归实现前序,中序,后序

	/**
     * 前序遍历
     * 非递归
     */
    public void preOrder1(BinaryNode<AnyType> Node)
    {
        Stack<BinaryNode> stack = new Stack<>();
        while(Node != null || !stack.empty())
        {
            while(Node != null)
            {
                System.out.print(Node.element + "   ");
                stack.push(Node);
                Node = Node.left;
            }
            if(!stack.empty())
            {
                Node = stack.pop();
                Node = Node.right;
            }
        }
    }

    /**
     * 中序遍历
     * 非递归
     */
    public void midOrder1(BinaryNode<AnyType> Node)
    {
        Stack<BinaryNode> stack = new Stack<>();
        while(Node != null || !stack.empty())
        {
            while (Node != null)
            {
                stack.push(Node);
                Node = Node.left;
            }
            if(!stack.empty())
            {
                Node = stack.pop();
                System.out.print(Node.element + "   ");
                Node = Node.right;
            }
        }
    }

    /**
     * 后序遍历
     * 非递归
     */
    public void posOrder1(BinaryNode<AnyType> Node)
    {
        Stack<BinaryNode> stack1 = new Stack<>();
        Stack<Integer> stack2 = new Stack<>();
        int i = 1;
        while(Node != null || !stack1.empty())
        {
            while (Node != null)
            {
                stack1.push(Node);
                stack2.push(0);
                Node = Node.left;
            }

            while(!stack1.empty() && stack2.peek() == i)
            {
                stack2.pop();
                System.out.print(stack1.pop().element + "   ");
            }

            if(!stack1.empty())
            {
                stack2.pop();
                stack2.push(1);
                Node = stack1.peek();
                Node = Node.right;
            }
        }
    }

​ 7.3层序遍历

 public static void levelOrderTraversal(TreeNode root){
        //LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用
        Queue<TreeNode> queue=new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()){
            TreeNode node=queue.poll();
            System.out.println(node.val);
            if (node.left!=null){
                queue.offer(node.left);
            }
            if (node.right!=null){
                queue.offer(node.right);
            }
        }
    }

8二叉堆

​ 8.1二叉堆概念

  • 二叉堆本质上是一种完全二叉树,它分为两个类型
  • 最大堆:任何一个父节点的值都大于或等于它左,右孩子节点的值
  • 最小堆:任何一个父节点的值都小于或等于它左,右孩子节点的值

​ 8.2二叉堆的自我调整

  • 插入节点:插在最后一个位置,不断和父节点比较进行上浮

  • 删除节点:删除的是处于堆顶的节点,将最后一个节点临时补到原来堆顶的位置,接下来不断比较子节点然后下沉就行了

  • 构建二叉堆:就是把一个无序的完全二叉树调整为二叉堆,本质就是让所有非叶子节点依次下沉,从最后一个非叶子节点开始

  • 对于插入节点和删除节点时间复杂度都是O(logn),对于构建二叉堆的时间复杂度就是O(n)

​ 8.3二叉堆的代码实现(以最小堆为例)

  • 需要明确:二叉堆虽然是一个完全二叉树,但它的存储方式并不是链式存储,而是顺序存储,就是二叉堆的所有节点都存储在数组中
  • 假设父节点的下标是parent,那么它的左孩子下标就是2 * parent+1;右孩子下标就是2 * parent+2
 //上浮调整
    public static void upAdjust(int[] array){
        int childIndex=array.length-1;
        int parentIndex=(childIndex-1)/2;
        //temp保存插入的叶子节点值,用于最后的赋值
        int temp=array[childIndex];
        while (childIndex>0 && temp<array[parentIndex]){
            //无须真正交换,单向赋值就行
            array[childIndex]=array[parentIndex];
            childIndex=parentIndex;
            parentIndex=(parentIndex-1)/2;
        }
        array[childIndex]=temp;
    }

    //下沉调整
    //array:要调整的堆 parentIndex:要下沉的父节点 length:堆的有效大小
    public static void downAdjust(int[] array,int parentIndex,int length){
        //temp保存父节点的值,用于最后的赋值
        int temp=array[parentIndex];
        int childIndex=2*parentIndex+1;
        while (childIndex<length){
            //如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
            if (childIndex+1<length && array[childIndex+1]<array[childIndex]){
                childIndex++;
            }
            //如果父节点小于任何一个孩子的值,则直接跳出
            if (temp<=array[childIndex]){
                break;
            }
            array[parentIndex]=array[childIndex];
            parentIndex=childIndex;
            childIndex=2*childIndex+1;
        }
        array[parentIndex]=temp;
    }

    //构建堆
    public static void buildHeap(int[] array){
        //从最后一个非叶子节点开始,以此做下沉调整
        for (int i=(array.length-2)/2;i>=0;i--){
            downAdjust(array,i,array.length);
        }
    }


    public static void main(String[] args) {
        int[] array = {1, 3, 2, 6, 5, 7, 8, 9, 10, 0};
        upAdjust(array);
        System.out.println(Arrays.toString(array));
    }

9优先队列

​ 9.1概念

  • 最大优先队列:无论入队顺序如何,都是当前最大的元素优先出队
  • 最小优先队列:无论入队顺序如何,都是当前最小的元素优先出队

10二叉查找树

​ 10.1概念

  • 如果左子树不为空,则左子树上所有节点的值均小于根节点的值
  • 如果右子树不为空,则右子树上所有节点的值均大于根节点的值
  • 左右子树也都是二叉查找树
  • 对于一个节点分布相对平衡的二叉查找树,如果节点总数是n,那么查找节点的时间复杂度就是O(logn),和树的深度成正比
  • 对于二叉查找树进行中序遍历就能输出完全升序的排列
  • 正是因为上面这个原因,二叉查找树也被称为二叉排序树

​ 10.2二叉查找树的插入和删除

  • 插入就直接插入
  • 删除要分为三种情况
  • 情况1:待删除的节点没有叶子节点:直接删除
  • 情况2:待删除的节点有一个孩子:让那个孩子节点取代被删除的节点
  • 情况3:待删除的节点有两个孩子:选择与带删除节点最接近的节点来取代它
 private Node root;
    
    //中序遍历
    public static void inOrderTraversal(Node node){
        if(node == null){
            return;
        }
        inOrderTraversal(node.left);
        System.out.print(node.data + " ");
        inOrderTraversal(node.right);
    }
  
    //查找结点
    public Node search(int data) {
        Node targetNode = root;
        while (targetNode!=null && targetNode.data != data) {
            if (data > targetNode.data) {
                targetNode = targetNode.right;
            } else {
                targetNode = targetNode.left;
            }
        }
        if(targetNode == null){
            System.out.println("未找到结点:" + data);
        } else {
            System.out.println("已找到结点:" + data);
        }
        return targetNode;
    }
    

    //插入结点
    public boolean insert(int data) {
        Node node = new Node(data);
        if(root == null){
            root = node;
            return true;
        }
        Node targetNode  = root;
        while (targetNode != null) {
            if( data == targetNode.data){
                System.out.println("二叉查找树中已有重复的结点:" + data);
                return false;
            }
            else if (data > targetNode.data) {
                if(targetNode.right == null){
                    targetNode.right = node;
                    return true;
                }
                targetNode = targetNode.right;
            }
            else {
                if(targetNode.left == null){
                    targetNode.left = node;
                    return true;
                }
                targetNode = targetNode.left;
            }
        }
        return true;
    }

    //删除结点
    public boolean delete(int data) {
        Node targetNode = root;
        Node parentNode = new Node(data);
        //判断待删除结点是否存在
        while (targetNode.data != data) {
            parentNode = targetNode;
            if (data > targetNode.data) {
                targetNode = targetNode.right;
            } else {
                targetNode = targetNode.left;
            }
            if (targetNode == null) {
                // 没有找到待删除结点
                return false;
            }
        }
        // 待删除结点没有子节点
        if (targetNode.right==null && targetNode.left==null) {
            if (targetNode == root) {
                //待删除结点是根结点
                root = null;
            } else {
                if (parentNode.right == targetNode) {
                    parentNode.right = null;
                } else {
                    parentNode.left = null;
                }
            }
        }
        //待删除结点有一个子结点(右)
        else if(targetNode.left == null) {
            if(targetNode == root) {
                root = targetNode.right;
            } else if(parentNode.right == targetNode) {
                parentNode.right = targetNode.right;
            } else {
                parentNode.left = targetNode.right;
            }
        }
        //待删除结点有一个子结点(左)
        else if(targetNode.right == null) {
            if(targetNode == root) {
                root = targetNode.left;
            } else if(parentNode.right == targetNode) {
                parentNode.right = targetNode.left;
            } else {
                parentNode.left = targetNode.left;
            }
        }
        //待删除结点有两个子结点
        else {
            //待删除结点的后继结点的父结点
            Node successParentNode = targetNode;
            //待删除结点的后继结点
            Node successNode = targetNode.right;
            while(successNode.left != null)
            {
                successParentNode = successNode;
                successNode = successNode.left;
            }
            //把后继结点复制到待删除结点位置
            targetNode.data = successNode.data;
            //删除后继结点
            if(successParentNode.right == successNode) {
                successParentNode.right = successNode.right;
            } else {
                successParentNode.left = successNode.right;
            }
        }
        return true;
    }

    // 结点类
    private class Node {
        int data;
        Node right;
        Node left;

        Node(int data){
            this.data = data;
        }
    }

11平衡二叉树

​ 11.1概念

  • 平衡二叉树也被称为AVL树,它在每次插入,删除节点之后,可以进行“自平衡”,也就是通过一系列调整查询达到平衡状态
  • 对于AVL树的每一个节点,平衡因子是它的左子树和右子树高度的差值,只有当二叉树所有节点的平衡因子都是-1,0,1这三个值的时候,这课二叉树才是一棵合格的AVL树
  • AVL树对平衡因子的限制,保证了任意节点的两棵子树的高度差都不超过1,这种状态被称为高度平衡

​ 11.2AVL树旋转

  • 左左局面:右旋操作
  • 右右局面:左旋操作
  • 左右局面:先左旋,再右旋
  • 左右局面:先右旋,再左旋
public class AVLTree {
    private TreeNode root;
    /*
     * 获取树的高度
     */
    private int height(TreeNode node) {
        if (node != null)
            return node.height;
        return 0;
    }

    public int height() {
        return height(root);
    }

    //查找结点
    public TreeNode search(TreeNode node, int data) {
        while (node!=null) {
            if (data < node.data)
                node = node.left;
            else if (data > node.data)
                node = node.right;
            else
                return node;
        }
        return node;
    }

    //左左局面旋转
    private TreeNode leftLeftRotation(TreeNode node) {
        //leftChildNode 对应示意图中的结点B
        TreeNode leftChildNode = node.left;
        node.left = leftChildNode.right;
        leftChildNode.right = node;
        //刷新结点A和结点B的高度
        node.height = Math.max(height(node.left), height(node.right)) + 1;
        leftChildNode.height = Math.max(height(leftChildNode.left), node.height) + 1;
        //返回旋转后的父结点
        return leftChildNode;
    }

    //右右局面旋转
    private TreeNode rightRightRotation(TreeNode node) {
        //rightChildNode 对应示意图中的结点B
        TreeNode rightChildNode = node.right;
        node.right = rightChildNode.left;
        rightChildNode.left = node;
        //刷新结点A和结点B的高度
        node.height = Math.max(height(node.left), height(node.right)) + 1;
        rightChildNode.height = Math.max(height(rightChildNode.right), node.height) + 1;
        //返回旋转后的父结点
        return rightChildNode;
    }

    //左右局面旋转
    private TreeNode leftRightRotation(TreeNode node) {
        //先做左旋
        node.left = rightRightRotation(node.left);
        //再做右旋
        return leftLeftRotation(node);
    }

    //右左局面旋转
    private TreeNode rightLeftRotation(TreeNode node) {
        //先做右旋
        node.right = leftLeftRotation(node.right);
        //再做左旋
        return rightRightRotation(node);
    }

    //插入结点
    public void insert(int data) {
        root = insert(root, data);
    }

    //插入结点详细过程(递归)
    private TreeNode insert(TreeNode node, int data) {
        if (node == null) {
            node = new TreeNode(data);
        } else {
            if (data < node.data) {
                //新结点小于当前结点,选择当前结点的左子树插入
                node.left = insert(node.left, data);
                // 插入节点后,若AVL树失去平衡,则进行相应的调节。
                if (node.getBalance() == 2) {
                    if (data < node.left.data) {
                        node = leftLeftRotation(node);
                    } else {
                        node = leftRightRotation(node);
                    }
                }
            } else if (data > node.data)  {
                //新结点大于当前结点,选择当前结点的右子树插入
                node.right = insert(node.right, data);
                // 插入节点后,若AVL树失去平衡,则进行相应的调节。
                if (node.getBalance() == -2) {
                    if (data > node.right.data) {
                        node = rightRightRotation(node);
                    } else {
                        node = rightLeftRotation(node);
                    }
                }
            } else {
                System.out.println("AVL树中已有重复的结点!");
            }
        }
        //刷新结点的高度
        node.height = Math.max(height(node.left), height(node.right)) + 1;
        return node;
    }

    //删除结点
    public void remove(int data) {
        TreeNode deletedNode;
        if ((deletedNode = search(root, data)) != null)
            root = remove(root, deletedNode);
    }

    //删除结点详细过程(递归)
    private TreeNode remove(TreeNode node, TreeNode deletedNode) {
        // 根为空 或者 没有要删除的节点,直接返回null。
        if (node==null || deletedNode==null)
            return null;
        if (deletedNode.data < node.data){
            //待删除结点小于当前结点,在当前结点的左子树继续执行
            node.left = remove(node.left, deletedNode);
            // 删除节点后,若AVL树失去平衡,则进行相应的调节。
            if (height(node.right) - height(node.left) == 2) {
                TreeNode r =  node.right;
                if (height(r.left) > height(r.right))
                    node = rightLeftRotation(node);
                else
                    node = rightRightRotation(node);
            }
        } else  if (deletedNode.data > node.data) {
            //待删除结点大于当前结点,在当前结点的右子树继续执行
            node.right = remove(node.right, deletedNode);
            // 删除节点后,若AVL树失去平衡,则进行相应的调节。
            if (height(node.left) - height(node.right) == 2) {
                TreeNode l =  node.left;
                if (height(l.right) > height(l.left))
                    node = leftRightRotation(node);
                else
                    node = leftLeftRotation(node);
            }
        } else {
            // tree的左右孩子都非空
            if ((node.left!=null) && (node.right!=null)) {
                if (height(node.left) > height(node.right)) {
                    // 如果node的左子树比右子树高,找出左子树最大结点赋值给Node,并删除最小结点
                    TreeNode max = maximum(node.left);
                    node.data = max.data;
                    node.left = remove(node.left, max);
                } else {
                    // 如果node的右子树比左子树高,找出右子树最小结点赋值给Node,并删除最小结点
                    TreeNode min = minimum(node.right);
                    node.data = min.data;
                    node.right = remove(node.right, min);
                }
            } else {
                node = (node.left!=null) ? node.left : node.right;
            }
        }
        node.height = Math.max(height(node.left), height(node.right)) + 1;
        return node;
    }

    //找出结点node为根的子树的最大节点
    private TreeNode maximum(TreeNode node) {
        if (node == null)
            return null;
        while(node.right != null)
            node = node.right;
        return node;
    }

    //找出结点node为根的子树的最小节点
    private TreeNode minimum(TreeNode node) {
        if (node == null)
            return null;
        while(node.left != null)
            node = node.left;
        return node;
    }

    //中序遍历
    public static void inOrderTraversal(TreeNode node) {
        if(node != null) {
            inOrderTraversal(node.left);
            System.out.print(node.data+" ");
            inOrderTraversal(node.right);
        }
    }

    //层序遍历
    public static void levelOrderTraversal(TreeNode root){
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            System.out.print(node.data+" ");
            if(node.left != null){
                queue.offer(node.left);
            }
            if(node.right != null){
                queue.offer(node.right);
            }
        }
    }

    class TreeNode {
        int data;
        int height;
        TreeNode left;
        TreeNode right;

        public TreeNode(int data) {
            this.data = data;
            this.height = 0;
        }

        //获得结点的平衡因子
        public int getBalance(){
            int left =  (this.left==null ? 0:this.left.height);
            int right = (this.right==null ? 0:this.right.height);
            return left - right;
        }
    }

    public static void main(String[] args) {
        AVLTree tree = new AVLTree();
        int input[]= {5,3,7,2,4,6,9,1};
        for(int i=0; i<input.length; i++) {
            tree.insert(input[i]);
        }
        System.out.println("中序遍历: ");
        inOrderTraversal(tree.root);
        System.out.println();
        System.out.println("层序遍历: ");
        levelOrderTraversal(tree.root);
        System.out.println();
        System.out.printf("高度: %d\n", tree.height());
        int deletedData = 3;

        System.out.printf("删除根节点: %d\n", deletedData);
        tree.remove(deletedData);

        System.out.println("中序遍历: ");
        inOrderTraversal(tree.root);
        System.out.println();
        System.out.println("层序遍历: ");
        levelOrderTraversal(tree.root);
        System.out.println();
        System.out.printf("高度: %d\n", tree.height());
    }
}

12红黑树

​ 12.1概念

  • 红黑树规则如下:
  • 节点是红色或者黑色
  • 根节点是黑色
  • 每个叶子节点都是黑色的空节点(null节点)
  • 每个红色节点的子节点都是黑色的(即不存在两个连续的红色节点)
  • 从任意节点到其每个叶子的所有路径都包含相同数目的黑色节点
  • 正因为这些规则的限制,才保证了红黑树的平衡性,红黑树从根到叶子的最长路径不会超过最短路径的2倍
  • 当向红黑树插入或删除元素时,会破坏红黑树的规则,其实就需要变色和旋转(旋转和AVL树一样)

​ 12.2红黑树的插入

  • 局面一:新节点A位于树根,没有父节点:这种局面直接让新节点变色为黑色
  • 局面二:新节点B的父节点是黑色:这种局面不需要修改
  • 局面三:新节点D的父节点和叔叔节点都是红色的:先让父节点变为黑色,再让叔叔节点变为黑色,最后让叔叔和父节点的共同父节点(祖父节点)变为红色
  • 局面四:新节点D的父节点是红色,叔叔节点是黑色或没有叔叔,且新节点是父节点的右孩子,父节点是祖父节点的左孩子:以父节点为轴做一次旋转,使得新节点D称为父节点,原来的父节点成为D的左孩子,这样一来进入了局面五
  • 局面五:新节点D的父节点是红色,叔叔节点是黑色或者没有叔叔,且新节点是父节点的左孩子,父节点是祖父节点的左孩子:首先以祖父节点A为轴做一次右旋转,使得父节点B成为祖父节点,祖父节点A称为节点B的右孩子,接着让节点B变为黑色,让节点A变为红色
  • 如果局面四和局面五当中的父节点B是祖父节点A的右孩子,我们只需要把原来左旋操作变成右旋操作就行了

​ 12.3红黑树的删除

  • 第1步:如果待删除节点有两个非空的孩子节点,转化成待删除节点只有一个孩子或没有孩子的情况(和二叉查找树一样,找一个最接近这个节点的子节点)
  • 第2步:根据带删除节点和其唯一子节点的颜色,分不同局面来处理
    • 局面1:自身是红色,子节点是黑色:直接删除自身就行
    • 局面2:自身是黑色,子节点是红色:删除自身,将子节点变成黑色(因为路径凭空少了一个黑色节点)
    • 局面3:自身是黑色,子节点也是黑色:先删除自身节点,然后进入6种子局面(假设自身叫节点1,子节点叫节点2)
      • 子局面1:节点2是根节点:不需要调整
      • 子局面2:节点2的父亲,兄弟,侄子都是黑色的:我们直接把节点2的兄弟节点B改为红色
      • 子局面3:节点2的兄弟节点是红色:先以节点2的父节点A为轴进行左旋,然后将节点A变成红色,兄弟节点B变成黑色
      • 子局面4:节点2的父节点是红色,兄弟和侄子节点是黑色:直接让父节点A变成黑色,兄弟节点B变成红色
      • 子局面5:节点2的父节点随意,兄弟节点B是黑色右孩子,左侄子节点C是黑色,右侄子节点D是黑色:先以兄弟节点B为轴进行右转,然后将节点B变为黑色,节点C变为黑色
      • 子局面6:节点2的父节点随意,兄弟节点B是黑色右孩子,右侄子节点D是红色:首先以父节点A为轴左旋,在让节点A和节点B颜色交换,并且节点D变为黑色

​ 12.3使用场景

  • AVL树是高度平衡二叉查找树,要求每个节点的左右子树高度差不超过1;而红黑树则要宽松一点,要求任何一条路径的长度不超过其他路径长度的2倍
  • 正因为这个差别,AVL树的查找效率更高,但维持平衡的成本也更高。在需要频繁查找时,选用AVL树更合适,在需要频繁插入,删除时,选用红黑树更合适

13B树

​ 13.1概念

  • 数据库的索引并没有采用二叉树,虽然平衡二叉树查找操作上的比较次数确实是最少的,但是在现实应用中,我们不得不考虑磁盘和内存读写速度的差距。我们只以索引树的节点为基本单元,每次把单一节点从磁盘读取到内存中,进行后续操作
  • 由于磁盘I/O次数等于索引树的高度,所以为了减少磁盘I/O。我们需要把原来瘦高的树结构变成矮胖的,让每一个节点承载更多的元素,拥有更多的孩子,因此有了B树和B+树

​ 13.2定义

  • B树单一节点拥有的最多子节点数量,称为B树的阶,一个m阶的B树,具有如下几个特征:
  • 根节点至少有两个子节点
  • 每个中间节点都包含k-1个元素(也称为关键字)和k个孩子,其中m/2<=k<=m
  • 每一个叶子节点都包含k-1个元素,其中m/2<=k<=m
  • 所有叶子节点都位于同一层
  • 每个节点中的元素从小到大排序,节点当中k-1个元素正好是k个孩子包含的元素的值域划分

​ 13.3插入

  • B树的插入操作是在叶子节点进行的, 可以分为两种情况:
  • 情况1:插入元素未改变规则,即叶子节点元素数量不超过m-1:不需要任何改变
  • 情况2:插入元素使叶子节点元素过多,超过m-1个元素:需要进行节点分裂,把节点m/2位置的元素上升到父节点,剩余的左半边元素和右半边元素分成两个独立的叶子节点

​ 13.4删除

  • 情况1:删除元素在叶子节点,未改变规则:不用做任何调整
  • 情况2:删除元素在叶子节点,剩余元素不足,相邻兄弟节点有多余元素:向兄弟节点借一个元素
  • 情况3:删除元素在叶子节点,剩余元素不足,相邻兄弟节点没有多余远古三:可以将父节点中的元素和兄弟节点元素进行合并
  • 情况4:删除元素在中间节点:选择该元素的前驱或后驱元素来顶替它的位置

​ 13.5弊端

  • 数据库的查询不止涉及到单一结果查询,也会涉及到一个区间内结果的查询(比如查询成绩在60-80分的所有学生)
  • 对于前者B树很容易实现,但对于后者B树实现起来会非常困难,需要进行中序遍历,在父节点和子节点之间不断切换
  • 因此需要B+树

14B+树

​ 14.1概念

  • 一个m阶B+数具有如下几个特征
  • 有K个子树的中间节点包含k个元素(B树中是k-1个元素),每个元素不保存数据,所有数据都保存在叶子节点
  • 所有的叶子节点包含了全部元素,依照元素的大小升降排列,叶子节点之间用双向指针相连接
  • 所有中间节点的元素都同时存在于子节点,在子节点元素中是最大(或最小)元素
posted @ 2021-10-16 18:36  fao99  阅读(83)  评论(0)    收藏  举报