数据结构——二叉搜索树、平衡二叉树、红黑树

news2024/11/15 3:36:52

数据结构——二叉搜索树

  • 一、二叉搜索树
    • 1.二叉搜索树的特性
    • 2.二叉搜索树的查找、插入和删除
  • 二、平衡二叉树
    • 1.基本介绍
    • 2.AVL树的自平衡
      • 1)自平衡的调整操作
      • 2)自平衡调整的局面
    • 3.AVL树的代码实现
    • 4.AVL树的特点
  • 三、红黑树
    • 1.基本介绍
    • 2.红黑树的自平衡
      • 1)是否破坏平衡
      • 2)自平衡的调整操作
    • 3.红黑树的插入
      • 1)局面1
      • 2)局面2
      • 3)局面3
      • 4)局面4
      • 5)局面5
    • 4.红黑树的删除
      • 1)step1
      • 2)step2
      • 3)step3
    • 5.红黑树的实现
    • 6.AVL树和红黑树的对比选用

一、二叉搜索树

1.二叉搜索树的特性

在二叉搜索树中,每个结点的数值比左子树上的每个结点都大,比所有右子树上的结点都小。是一个有数值的有序树。
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树

二叉搜索树可用于查找,对于一个节点分布相对平衡的二叉搜索树,如果节点总数是n,查找节点的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn),和树的深度成正比。

但如果二叉搜索树出现节点分布极端不均的情况,如绝大多数节点都集中在左子树上,查找节点的时间复杂度就会退化成 O ( n ) O(n) O(n)

二叉搜索树还可维持节点的有序性,进行插入、删除操作后始终保持有序。中序遍历二叉搜索树会得到一个有序的序列

2.二叉搜索树的查找、插入和删除

二、平衡二叉树

1.基本介绍

平衡二叉树是一种特殊的二叉搜索树,其中每一个节点的左子树和右子树的高度差至多等于1。也被称为AVL树。

平衡二叉树可以在每次插入、删除节点后进行自平衡调整,重新达到平衡状态。

二叉树上节点的左子树高度和右子树高度的差值称为平衡因子BF(Balance Factor),平衡二叉树上所有节点的平衡因子只可能是-1、0、1,才是保持高度平衡,否则就不平衡。

2.AVL树的自平衡

1)自平衡的调整操作

AVL树提供了两种特殊的操作来进行自平衡:

1、左旋
逆时针旋转AVL树的两个节点,使父节点被自己的右孩子取代,而原父节点成为自己原来位置节点的左孩子。
(空心三角形代表着节点下面的子树。)
在这里插入图片描述
2、右旋
逆时针旋转AVL树的两个节点,使父节点被自己的左孩子取代,而原父节点成为自己原来位置节点的右孩子。
在这里插入图片描述

2)自平衡调整的局面

AVL树的调整分成4种局面:

1、左左局面(LL)
在这里插入图片描述
祖父节点A有一个左孩子节点B,B又有一个左孩子节点C

在这种局面下,以A为轴,进行右旋操作
在这里插入图片描述2、右右局面(RR)
在这里插入图片描述
祖父节点A有一个右孩子节点B,B又有一个右孩子节点C

在这种局面下,以A为轴,进行左旋操作
在这里插入图片描述
3、左右局面(LR)
在这里插入图片描述
祖父节点A有一个左孩子节点B,B又有一个右孩子节点C

在这种局面下,先以B为轴,进行左旋操作
在这里插入图片描述
这样就转化为了左左局面,继续以A为轴,进行右旋操作
在这里插入图片描述
3、右左局面(RL)
在这里插入图片描述
祖父节点A有一个右孩子节点B,B又有一个左孩子节点C

在这种局面下,先以B为轴,进行右旋操作
在这里插入图片描述
这样就转化为了右右局面,继续以A为轴,进行左旋操作
在这里插入图片描述
在平衡二叉树中插入或删除节点时,可能会打破平衡,使子树出现如上4种局面。这时就需要进行相应的操作调整。

3.AVL树的代码实现

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) + 
        //返回旋转后的父结点
        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)
        //返回旋转后的父结点
        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());
    }
}

4.AVL树的特点

AVL树在插入或删除节点时,每一层的相关节点都要修改自身的高度值,这使得AVL树的插入删除操作的平均时间复杂度成了 O ( n log ⁡ n ) O(n\log n) O(nlogn)

AVL树维护的是绝对的平衡,这样查找效率较高,但维护成本也较高。

三、红黑树

1.基本介绍

红黑树和AVL树类似,也是为了维护二叉搜索树的平衡。

红黑树判断树平衡的方式要复杂一些,通过红色和黑色两种节点以及若干规则来判断平衡。

红黑树规则

  1. 节点是红色或黑色
  2. 根节点是黑色
  3. 每个叶子节点都是黑色的空节点(NIL节点)
  4. 每个红色节点的子节点都是黑色(即不存在两个连续的红色节点)
  5. 从任意节点到其每个叶子节点的所有路径都包含相同数目的黑色节点

在这里插入图片描述
这些限制保证了红黑树的平衡性,红黑树从根节点到叶子节点的最长路径不会超过最短路径的2倍。

2.红黑树的自平衡

1)是否破坏平衡

当插入或删除节点的时候,红黑树的规则有可能被打破。这时候就需要做出一些调整,从而继续维持原有的规则。

1、未破坏平衡性:

向原红黑树插入值为14的新节点
在这里插入图片描述
**红黑树新插入的节点,默认是红色的。**由于父节点15是黑色节点,
因此这种情况并不会破坏红黑树的规则,无须做任何调整。

2、破坏平衡性:

向原红黑树插入值为21的新节点
在这里插入图片描述
由于父节点22是红色节点,因此这种情况打破了红黑树的规则4(每个红色节点的两个子节点都是黑色),必须进行调整,使之重新符合红黑树的规则。

2)自平衡的调整操作

1、旋转

旋转操作和AVL树基本相同。

2、变色

为了重新符合红黑树的规则,尝试把红色节点变为黑色,或者把黑色节点变为红色。

下图所表示的是红黑树的一部分(子树),新插入的节点Y是红色节点,它的父亲节点X也是红色的,不符合规则4,因此可以把节点X从红色变成黑色:
在这里插入图片描述
但是,仅仅把一个节点由红变黑,会导致相关路径凭空多出一个黑色节点,这样就打破了规则5。因此,我们还需要对其他节点做进一步的调整。

3.红黑树的插入

在红黑树中插入新节点的时候,可以分成5种不同的局面,每一种局面有不同的调整方法。

1)局面1

新节点A位于树根,没有父节点
在这里插入图片描述
这种局面,直接让新节点变色为黑色,规则2得到满足。同时,黑色的根节点使得每条路径上的黑色节点数目都增加了1,并没有打破规则5。
在这里插入图片描述

2)局面2

新节点B的父节点是黑色
在这里插入图片描述
这种局面,新插入的红色节点B并没有打破红黑树的规则,所以不需要做任何调整。

3)局面3

新节点D的父节点和叔叔节点都是红色
在这里插入图片描述
这种局面,两个红色节点B和D连续,违反了规则4。因此我们做如下调整:

  1. 让节点B变为黑色。
  2. 让节点A变为红色。
  3. 让节点C变为黑色。
    在这里插入图片描述

4)局面4

新节点(D)的父节点是红色,叔叔节点是黑色或者没有叔叔,且新节点是父节点的右孩子,父节点(B)是祖父节点的左孩子。
在这里插入图片描述
以节点B为轴,做一次左旋转,使得新节点D成为父节点,原来的父节点B成为D的左孩子:
在这里插入图片描述
这样一来,进入了局面5。

如果局面4中的父节点B是右孩子,节点D是B的左孩子,则成为了局面4的镜像,原本的左旋操作改为右旋操作。
在这里插入图片描述

5)局面5

新节点(D)的父节点是红色,叔叔节点是黑色或者没有叔叔,且新节点是父节点的左孩子,父节点(B)是祖父节点的左孩子。
在这里插入图片描述
对于这种局面,做如下调整:

  1. 以节点A为轴,做一次右旋转,使得节点B成为祖父节点,节点A
    成为节点B的右孩子。
  2. 让节点B变为黑色,节点A变为红色。

在这里插入图片描述
如果局面5中的父节点B是右孩子,节点D是B的右孩子,则成为了局面5的镜像,原本的右旋操作改为左旋操作。
在这里插入图片描述

4.红黑树的删除

1)step1

如果待删除节点有两个非空的孩子节点,转化成待删除节点只有一个孩子(或没有孩子)的情况。
在这里插入图片描述
假设节点8是待删除节点。由于节点8有两个孩子,我们选择仅大于8的节点10复制到8的位置,节点颜色变成待删除节点的颜色:
在这里插入图片描述
接下来我们需要删除原本的节点10(节点11的左子树)。

节点10能成为仅大于8的节点,必定没有左孩子节点,所以问题转换成了待删除节点只有一个右孩子(或没有孩子)的情况。接下来我们进入第2步。

2)step2

根据待删除节点和其唯一子节点的颜色,分不同局面来处理

局面1:自身是红色,子节点是黑色
这种情况最简单,按照二叉查找树的删除操作,删除节点1即可。
在这里插入图片描述

局面2:自身是黑色,子节点是红色

做如下调整:

  1. 删除节点1。
  2. 把节点2变成黑色节点(因为路径凭空减少了一个黑色节点)。

在这里插入图片描述
局面3:自身是黑色,子节点也是黑色,或者子节点是空叶子节点

这种情况最复杂,涉及很多变化。首先我们删除节点1:
在这里插入图片描述
显然,这条路径上减少了一个黑色节点,而且节点2再怎么变色也解决不了问题。这时候我们进入第3步,专门解决父子双黑的情况。

3)step3

针对局面3这样的双黑节点情形,在删除父节点之后将会衍生出6种子局面,我们根据不同子局面做不同调整。

子局面1:节点2是红黑树的根节点
此时所有路径都减少了一个黑色节点,并未打破规则,不需要调整。

子局面2:节点2的父亲、兄弟、侄子节点都是黑色
此时,我们直接把节点2的兄弟节点B改为红色:
在这里插入图片描述
这样一来,原本节点2所在的路径少了一个黑色节点,现在节点B所在的路径也少了一个黑色节点,两边“扯平”了。可是,节点A以下的每一条路径都减少了一个黑色节点,与节点A之外的其他路径又造成了新的不平衡。对此,我们可以让节点A扮演原先节点2的角色,进行递归操作,重新判断各种情况。

子局面3:节点2的兄弟节点是红色

做如下调整:

  1. 以节点2的父节点A为轴,进行左旋。
  2. 节点A变成红色、节点B变成黑色。

在这里插入图片描述
这样的变化有可能转换成子局面4、5、6中的任意一种

子局面4:节点2的父节点是红色,兄弟和侄子节点是黑色

对于这种局面,我们直接让节点2的父节点A变成黑色,兄弟节点B变成红色:
在这里插入图片描述
这样一来,节点2的路径补充了黑色节点,而节点B的路径并没有减
少黑色节点,重新符合了红黑树的规则。

子局面5:节点2的父节点随意,兄弟节点B是黑色右孩子,左侄子节点是红色,右侄子节点是黑色

做如下调整:

  1. 以节点2的兄弟节点B为轴进行右旋。
  2. 节点B变为红色,节点C变为黑色。

在这里插入图片描述
这样的变化转换成了子局面6。

子局面6:节点2的父节点随意,兄弟节点B是黑色右孩子,右侄子节点是红色

做如下调整:

  1. 以节点2的父节点A为轴左旋。
  2. 让节点A和节点B的颜色交换,并且节点D变为黑色。

在这里插入图片描述
经过节点2的路径由(随意+黑)变成了(随意+黑+黑),补充了一个黑色节点;经过节点D的路径由(随意+黑+红)变成了(随意+黑),黑色节点并没有减少。所以,这时候重新符合了红黑树的规则。

5.红黑树的实现

import java.util.LinkedList;
import java.util.Queue;

public class RedBlackTree {
    TreeNode root;
    final static boolean RED = true;
    final static boolean BLACK = false;

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

    //插入结点
    public boolean insert(int data) {
        TreeNode node = new TreeNode(data);
        //局面1:新结点位于树根,没有父结点。
        if (root == null) {
            root = node;
            node.color = BLACK;
            return true;
        }
        TreeNode 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;
                    node.parent = targetNode;
                    insertAdjust(node);
                    return true;
                }
                targetNode = targetNode.right;
            } else {
                if(targetNode.left == null){
                    targetNode.left = node;
                    node.parent = targetNode;
                    insertAdjust(node);
                    return true;
                }
                targetNode = targetNode.left;
            }
        }
        return true;
    }

    //插入后自我调整
    private void insertAdjust(TreeNode node) {
        //创建父结点和祖父结点指针
        TreeNode parent, grandParent;
        //局面3的调整有可能引发后续的一系列调整,所以使用while循环。
        while (node.parent != null && node.parent.color == RED) {
            parent = node.parent;
            grandParent = parent.parent;
            if (grandParent.left == parent) {
                TreeNode uncle = grandParent.right;
                //局面3:新结点的父结点和叔叔结点都是红色。
                if (uncle != null && uncle.color == RED) {
                    parent.color = BLACK;
                    uncle.color = BLACK;
                    grandParent.color = RED;
                    node = grandParent;
                    continue;
                }
                //局面4:新结点的父结点是红色,叔叔结点是黑色或者没有叔叔,且新结点是父结点的右孩子,父结点是祖父结点的左孩子。
                if (node == parent.right) {
                    leftRotate(parent);
                    TreeNode tmp = node;
                    node = parent;
                    parent = tmp;
                }
                //局面5:新结点的父结点是红色,叔叔结点是黑色或者没有叔叔,且新结点是父结点的左孩子,父结点是祖父结点的左孩子。
                parent.color = BLACK;
                grandParent.color = RED;
                rightRotate(grandParent);
            } else {
                TreeNode uncle = grandParent.left;
                //局面3(镜像):新结点的父结点和叔叔结点都是红色。
                if (uncle != null && uncle.color == RED) {
                    parent.color = BLACK;
                    uncle.color = BLACK;
                    grandParent.color = RED;
                    node = grandParent;
                    continue;
                }
                //局面4(镜像):新结点的父结点是红色,叔叔结点是黑色或者没有叔叔,且新结点是父结点的左孩子,父结点是祖父结点的右孩子。
                if (node == parent.left) {
                    rightRotate(parent);
                    TreeNode tmp = node;
                    node = parent;
                    parent = tmp;
                }
                //局面5(镜像):新结点的父结点是红色,叔叔结点是黑色或者没有叔叔,且新结点是父结点的右孩子,父结点是祖父结点的右孩子。
                parent.color = BLACK;
                grandParent.color = RED;
                leftRotate(grandParent);
            }
        }
        //经过局面3的调整,有可能把根结点变为红色,此时再变回黑色即可。
        if(root.color == RED){
            root.color = BLACK;
        }
    }

    //删除节点
    public void remove(int key) {
        remove(search(key));
    }

    //删除节点详细逻辑
    private void remove(TreeNode node) {
        TreeNode targetNode = node;
        if (node == null)
            return;
        //第一步:如果待删除结点有两个非空的孩子结点,转化成待删除结点只有一个孩子(或没有孩子)的情况。
        if (node.left != null && node.right != null) {
            //待删除结点的后继结点
            TreeNode successNode = targetNode.right;
            while(successNode.left != null) {
                successNode = successNode.left;
            }
            if(targetNode == root) {
                root = successNode;
            }
            //把后继结点复制到待删除结点位置
            targetNode.data = successNode.data;
            remove(successNode);
            return;
        }
        //第二步:根据待删除结点和其唯一子结点的颜色,分情况处理。
        TreeNode successNode = node.right; //node只可能拥有右孩子或没有孩子
        TreeNode parent = node.parent;
        if (parent == null) {
            //子情况1,被删除结点是红黑树的根结点:
            root = successNode;
            if (successNode != null)
                successNode.parent = null;
        } else {
            //无论何种情况,都需要先删除node结点
            if (successNode != null)
                successNode.parent = parent;
            if (parent.left == node)
                parent.left = successNode;
            else {
                parent.right = successNode;
            }
        }
        //此时情况1已处理完毕,如果父结点是黑色结点,则增加额外处理
        if (node.color == BLACK)
            if(successNode!=null && successNode.color == RED ){
                //情况2:父结点是黑,子节点是红
                successNode.color = BLACK;
            }else {
                //情况3:遇到双黑结点。此时进入第三步,在子结点顶替父结点之后,分成6种子情况处理。
                removeAdjust(parent, successNode);
            }
    }

    //删除结点后的自我调整
    private void removeAdjust(TreeNode parent, TreeNode node) {
        while ((node == null || node.color == BLACK) && node != root) {
            if (parent.left == node) {
                //node的兄弟节点
                TreeNode sibling = parent.right;
                //子情况3,node的兄弟结点是红色:
                if (sibling != null && sibling.color == RED) {
                    parent.color = RED;
                    sibling.color = BLACK;
                    leftRotate(parent);
                    sibling = parent.right;
                }
                if (sibling == null || ((sibling.left == null || sibling.left.color == BLACK) && (sibling.right == null || sibling.right.color == BLACK))) {
                    //子情况2(镜像),node的父结点是黑色,兄弟和侄子结点是黑色:
                    if(parent.color == BLACK){
                        sibling.color = RED;
                        node = parent;
                        parent = node.parent;
                        continue;
                    }
                    //子情况4(镜像),node的父结点是红色,兄弟和侄子结点是黑色:
                    else {
                        sibling.color = RED;
                        break;
                    }
                }
                //子情况5,node的父结点随意,兄弟结点是黑色右孩子,左侄子结点是红色:
                if (sibling.color == BLACK && sibling.left!=null && sibling.left.color == RED) {
                    rightRotate(sibling);
                    sibling.parent.color = BLACK;
                    sibling.color = RED;
                    sibling = sibling.parent;
                }
                //子情况6,node的父结点随意,兄弟结点是黑色右孩子,右侄子结点是红色:
                if (sibling.color == BLACK && sibling.right!=null && sibling.right.color == RED) {
                    leftRotate(sibling.parent);
                    sibling.color = sibling.left.color;
                    sibling.left.color = BLACK;
                    sibling.right.color = BLACK;
                }
                node = root; //跳出循环
            } else {
                //node的兄弟节点
                TreeNode sibling = parent.left;
                //子情况3(镜像),node的兄弟结点是红色:
                if (sibling != null && sibling.color == RED) {
                    parent.color = RED;
                    sibling.color = BLACK;
                    rightRotate(parent);
                    sibling = parent.left;
                }
                if (sibling == null || ((sibling.left == null || sibling.left.color == BLACK) && (sibling.right == null || sibling.right.color == BLACK))) {
                    //子情况2(镜像),node的父结点是黑色,兄弟和侄子结点是黑色:
                    if(parent.color == BLACK){
                        sibling.color = RED;
                        node = parent;
                        parent = node.parent;
                        continue;
                    }
                    //子情况4(镜像),node的父结点是红色,兄弟和侄子结点是黑色:
                    else {
                        sibling.color = RED;
                        break;
                    }
                }
                //子情况5(镜像),node的父结点随意,兄弟结点是黑色左孩子,右侄子结点是红色:
                if (sibling.color == BLACK && sibling.right!=null && sibling.right.color == RED) {
                    leftRotate(sibling);
                    sibling.parent.color = BLACK;
                    sibling.color = RED;
                    sibling = sibling.parent;
                }
                //子情况6(镜像),node的父结点随意,兄弟结点是黑色左孩子,左侄子结点是红色:
                if (sibling.color == BLACK && sibling.left!=null && sibling.left.color == RED) {
                    rightRotate(sibling.parent);
                    sibling.color = sibling.right.color;
                    sibling.right.color = BLACK;
                    sibling.left.color = BLACK;
                }
                node = root; //跳出循环
            }
        }
        if (node != null) {
            node.color = BLACK;
        }
    }

    //左旋转
    private void leftRotate(TreeNode node) {
        TreeNode right = node.right;
        TreeNode parent = node.parent;
        if (parent == null) {
            root = right;
            right.parent = null;
        } else {
            if (parent.left != null && parent.left == node) {
                parent.left = right;
            } else {
                parent.right = right;
            }
            right.parent = parent;
        }
        node.parent = right;
        node.right = right.left;
        if (right.left != null) {
            right.left.parent = node;
        }
        right.left = node;
    }

    //右旋转
    private void rightRotate(TreeNode node) {
        TreeNode left = node.left;
        TreeNode parent = node.parent;
        if (parent == null) {
            root = left;
            left.parent = null;
        } else {
            if (parent.left != null && parent.left == node) {
                parent.left = left;
            } else {
                parent.right = left;
            }
            left.parent = parent;
        }
        node.parent = left;
        node.left = left.right;
        if (left.right != null) {
            left.right.parent = node;
        }
        left.right = node;
    }

    //中序遍历
    public static void inOrderTraversal(TreeNode node) {
        if(node != null) {
            inOrderTraversal(node.left);
            System.out.print(node);
            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);
            if(node.left != null){
                queue.offer(node.left);
            }
            if(node.right != null){
                queue.offer(node.right);
            }
        }
    }

    class TreeNode {
        int data;
        boolean color;
        TreeNode left;
        TreeNode right;
        TreeNode parent;

        public TreeNode(int data) {
            this.data = data;
            this.color = RED;
        }

        @Override
        public String toString() {
            return  data + (color?"(red)":"(black)") + "  " ;
        }
    }

    public static void main(String[] args) {
        //case 1:
        RedBlackTree rbTree = new RedBlackTree();
        int input[]= {13,8,17,1,11,15,25,6,22,27};
        for(int i=0; i<10; i++) {
            rbTree.insert(input[i]);
        }
        rbTree.remove(25);
        System.out.println("中序遍历: ");
        inOrderTraversal(rbTree.root);
        System.out.println();
        System.out.println("层序遍历: ");
        levelOrderTraversal(rbTree.root);
        System.out.println();

        //case 2:
        rbTree = new RedBlackTree();
        for(int i=0; i<1000; i++) {
            rbTree.insert(i);
        }
        for(int i=100; i<1000; i+=100) {
            rbTree.remove(i);
        }
        System.out.println("中序遍历: ");
        inOrderTraversal(rbTree.root);
        System.out.println();
        System.out.println("层序遍历: ");
        levelOrderTraversal(rbTree.root);
        System.out.println();

    }
}

6.AVL树和红黑树的对比选用

AVL树是高度平衡的二叉查找树,要求每个节点的左右子树高度差不超过1;而红黑树则要宽松一些,要求任何一条路径的长度不超过其他路径长度的2倍。

正因为这个差别,AVL树的查找效率更高,但维持平衡的成本也更高在需要频繁查找时,选用AVL树更合适在需要频繁插入、删除时,选用红黑树更合适

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/440345.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

秒杀系统如何设计

思路&#xff1a;对于秒杀系统&#xff0c;两个架构优化思路&#xff1a; 1&#xff09;尽量将请求拦截在系统上游 2&#xff09;读多写少的常用多使用缓存 1、限制用户在x秒之内只能提交一次请求 2、同一个uid&#xff0c;或同一类查询&#xff08;例如车次&#xff09;。限制…

配电网光伏/储能双层优化配置模型(选址定容)

目录 1 主要内容 上层目标函数考虑光伏和储能的投资成本。 程序采用模块化编程&#xff0c;并有每个模块功能介绍&#xff0c;方便学习。 2 部分代码 3 程序结果 4 程序结果 1 主要内容 该程序主要方法复现《含高比例可再生能源配电网灵活资源双层优化配置》运行-规划联合…

【Maven 入门】第二章、Maven核心程序解压与配置

一、Maven 官网地址 首页&#xff1a; Maven – Welcome to Apache Maven(opens new window) 下载页面&#xff1a; Maven – Download Apache Maven(opens new window) 本文以maven-3.3.8为例 具体下载地址&#xff1a;https://dlcdn.apache.org/maven/maven-3/3.8.8/bina…

LeetCode刷题集(二)(LeetCode 2037使每位学生都有座位的最少移动次数)

学习目标&#xff1a; 掌握LeetCode2037使每位学生都有座位的最少移动次数 题目内容&#xff1a; 一个房间里有 n 个座位和 n 名学生&#xff0c;房间用一个数轴表示。给你一个长度为 n 的数组 seats &#xff0c;其中 seats[i] 是第 i 个座位的位置。同时给你一个长度为 n 的数…

数据结构-排序3(终章)

前言&#xff1a; 上一章&#xff0c;对交换排序的冒牌和快排做了复盘&#xff0c;这一章对&#xff0c;归并排序以及非比较排序中的计数排序做一个复盘。 目录 2.4归并排序 2.4.1规定递归 2.4.2归并非递归 2.5非比较排序 2.5.1计数排序 2.6排序的稳定性分析 2.6.1冒…

【Transformer系列(2)】注意力机制、自注意力机制、多头注意力机制、通道注意力机制、空间注意力机制超详细讲解

前言 注意力机制一直是一个比较热的话题&#xff0c;其实在很早之前就提出了&#xff0c;我们在学习图像分类时在SENet就见到过&#xff08;直通车&#xff1a;经典神经网络论文超详细解读&#xff08;七&#xff09;——SENet&#xff08;注意力机制&#xff09;学习笔记&…

金陵科技学院五年一贯制专转本管理学原理考试大纲

金陵科技学院五年一贯制专转本管理学原理考试大纲 一、考核对象 本课程的考核对象为五年一贯制高职专转本“旅游管理”专业入学考试考生。 二、考核方式 本课程考核采用闭卷笔试的方式。 三、命题依据及原则 1、命题依据 参考书目&#xff1a;《管理学——原理与方法》 …

Docker Swarm集群企业案例实战

1. Docker Swarm集群企业案例实战 Docker Swarm 和 Docker Compose 一样&#xff0c;都是 Docker 官方容器编排项目&#xff0c;但不同的是&#xff0c;Docker Compose 是一个在单个服务器或主机上创建多个容器的工具&#xff0c;而 Docker Swarm 则可以在多个服务器或主机上创…

驼峰式匹配-力扣1023-java

一、题目描述 如果我们可以将小写字母插入模式串 pattern 得到待查询项 query&#xff0c;那么待查询项与给定模式串匹配。&#xff08;我们可以在任何位置插入每个字符&#xff0c;也可以插入 0 个字符。&#xff09; 给定待查询列表 queries&#xff0c;和模式串 pattern&a…

未来技术方向——“乐高式”可组装式开发能力

技术正在改变各行各业的发展&#xff0c;Gartner的主要战略技术趋势一直是行业的技术风向标之一。近3年&#xff0c;Gartner在主要的战略技术趋势中都提到组装式技术&#xff0c;2021年首次提出组装式企业&#xff0c;2022年提出可组装式应用&#xff0c;2023年在2项主要战略技…

ModuleNotFoundError: No module named ‘d2l’

目录 1. 下载李沐老师分享的源代码 step1&#xff1a;下载李沐老师分享的源代码&#xff1a; step3&#xff1a;Anaconda Prompt中安装d2l(这个l是英文) step4&#xff1a;运行代码&#xff0c;成功&#xff1a; &#xff08;番外&#xff09;ModuleNotFoundError: No mod…

【微服务】5、声明式 HTTP 客户端 —— Feign

目录 一、RestTemplate 不好的地方二、Feign 是什么三、使用四、自定义 Feign 的配置(1) Feign 的几个常见配置(2) 配置 Feign 的日志级别① 通过配置文件② Java 代码配置日志级别 五、Feign 性能优化(1) 性能优化介绍(2) 修改 Feign 底层的 HTTP 请求客户端 六、Feign 的最佳…

C++:std::function模板类(前言):为什么有了函数指针还需要Functional

为什么有了函数指针还有 Functional 1: 函数指针定义2&#xff1a; 函数指针结论3&#xff1a;疑问4&#xff1a; Function来源5&#xff1a;Functional 特点 1: 函数指针定义 在C中可以使用指针指向一段代码&#xff0c;这个指针就叫函数指针&#xff0c;假设有下面一段代码 …

交友项目【首页推荐,今日佳人,佳人信息】

目录 1&#xff1a;首页推荐 1.1&#xff1a;接口地址 1.2&#xff1a;流程分析 1.3&#xff1a;代码实现 2&#xff1a;今日佳人 1.1&#xff1a;接口地址 1.2&#xff1a;流程分析 1.3&#xff1a;代码实现 3&#xff1a;佳人信息 1.1&#xff1a;接口地址 1.2&am…

计算机基础--MySQL--索引

参考文献 [MySQL索引连环18问&#xff01;] https://zhuanlan.zhihu.com/p/364041898[深入理解MySQL索引] https://www.infoq.cn/article/ojkwyykjoyc2ygb0sj2c[聚集索引和非聚集索引的区别] https://juejin.cn/post/7001094401858469918[索引分类] https://blog.csdn.net/dd2…

【消费战略】解读100个食品品牌丨王小卤 4年10亿爆品破局

爆品破局 王小卤的聚焦发展! 王小卤创建于 2016 年&#xff0c;与饮料行业的独角兽元气森林同年。 相较于元气森林的快速增长&#xff0c;王小卤历经 三年坎坷之路&#xff0c;直至 2019 年才踏上高增长的赛道&#xff0c;实现四年十亿的增长。 “所有的消费品都值得重新 做…

RHCSA练习作业(二)

目录 题目一 题目二 题目三 第四题 第五题 题目一 文件查看&#xff1a;查看/opt/passwd文件的第六行&#xff08;使用head和tail指令&#xff09; 代码如下&#xff1a; head -6 /opt/passwd | tail -1 题目二 在/etc及其子目录中&#xff0c;查找host开头的文件&#x…

纯净Python环境的安装以及配置PyCharm编辑器

前言 优质的教程可以让我们少走很多弯路&#xff0c;这一点毋庸置疑。去年二月我接触了Python&#xff0c;并找到了一份优质的配置教程&#xff0c;让我能够快速上手Python的学习。现在&#xff0c;一年多过去了&#xff0c;我已经有了很大的进步和变化&#xff0c;这也让我更…

ARM kernel 内核的移植 - 从三星官方内核开始移植

一、内核移植初体验 1、三星官方移植版内核获取 (1) 从网盘下载源码包。 (2) 这个文件最初是来自于三星的 SMDKV210 开发板附带的光盘资料。 2、构建移植环境 (1) Windows下建立工程。 (2) ubuntu下解压。 3、配置编译下载尝试 (1) 检查 Makefile 中 ARCH 和 CROSS_COMPI…

手把手教你Python爬虫

前言 python爬虫技术在java开发工作中属于工具性的技术属性&#xff0c;所以我这里就只从爬取一个网站的数据为例作为教学内容&#xff0c;具体的基础学习与其它的扩展知识内容&#xff0c;我会以链接的形式给出&#xff0c;若有兴趣可自行点击学习。 python基础知识教学 Pyth…