12.红黑树

一. 红黑树与2-3树

  • 《算法导论》中的红黑树

1. 每个节点或者是红色的, 或者是黑色的
2. 根节点是黑色的
3. 每一个叶子节点(最后的空节点) 是黑色的
4. 如果一个节点是红色的, 那么他的孩子节点都是黑色的
5. 从任意节点到叶子节点,进过的黑色节点是一样的

 

  • 红黑树与2-3树的等价性

理解了2-3树和红黑树之间的关系之后, 红黑树并不难
学习2-3树, 不仅对于理解红黑树有帮助
对于理解B类树(通常用于磁盘存储、文件系统、数据库),也是有巨大帮助的!

 

  • 2-3树

1. 满足二分搜索树的基本性质

2. 节点可以存放一个元素或两个元素
 a                 b c
/ \               / | \
二节点             三节点


2-3树是一棵绝对平衡的树:
    从根节点到任意一个叶子节点,所经过的节点一定是相同的!

 

二.2-3树的绝对平衡性

2-3树如何保持绝对平衡?

保持平衡的关键是:  永远不会在空的地方添加节点

举例: 2-3树依次 加入 37, 42, 12, 18, 6, 11, 5

1.           37

------------------------------------------------  

2.           37,42

------------------------------------------------  

3.        12,37,42   


             37
            /  \
           12  42
------------------------------------------------  

4.           37
            /  \
         12,18  42
 
------------------------------------------------  

5.           37
            /  \
       6,12,18  42
   
             37
            /  \
         12     42
       /    \
      6     18
  
            12,37
           /  |  \
          6   18  42
 
------------------------------------------------  

6.          12,37
           /  |  \
        6,11  18  42      
  
------------------------------------------------  

7.          12,37
           /  |  \
      5,6,11  18  42      
     
            12,37
           /  |  \
          6   18  42   
         / \
        5   11

            6,12,37
          /  | |  \
         5  11 18  42      
       
              12
             /  \
            6    37 
           / \   / \
          5  11 18 42      
 
------------------------------------------------  


三. 红黑树与2-3树的等价性

红黑树和2-3树本质上是一样的, 
但2-3树的节点最多可以存2个元素, 不利于编写代码,
而红黑树每个节点只能存一个元素。

 

代码的简单编写

在以前二分搜索树BST的基础上修改, 作为红黑树代码的雏形 //****表示修改代码的地方
RBT.java

import java.util.ArrayList;

public class RBTree<K extends Comparable<K>, V> {

    private static final boolean RED = true;  //****静态不可修改的变量
    private static final boolean BLACK = false;

    private class Node{
        public K key;
        public V value;
        public Node left, right;
        public boolean color;

        public Node(K key, V value){
            this.key = key;
            this.value = value;
            left = null;
            right = null;
            color = RED;//****默认新创建的节点为红色, 原因后续会讲解
        }
    }

    private Node root;
    private int size;

    public RBTree(){//*****
        root = null;
        size = 0;
    }

    public int getSize(){
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    //**** 判断节点node的颜色
    private boolean isRed(Node node){
        if(node == null)
            return BLACK;
        return node.color;
    }
    
    
    ...  //其他代码暂时保持BST的编码, 后面会修改
}


四. 红黑树的基本性质和复杂度分析

 

 

回顾算法导论中对红黑树的描述

1. 每个节点或者是红色的, 或者是黑色的
2. 根节点是黑色的
3. 每一个叶子节点(最后的空节点) 是黑色的
4. 如果一个节点是红色的, 那么他的孩子节点都是黑色的
5. 从任意节点到叶子节点,进过的黑色节点是一样多的

解读

1. 红黑树的特点, 不多说
2. 查看第一幅图, 红黑树只有两种节点(二节点和三节点),
   每种节点的根部都为黑色节点, 所以根结点必然为黑色
3. 相当于定义:在红黑树中, 空节点为叶子节点, 并且为黑色。
   对应到第二条性质, 空的红黑树的根节点也是黑色的
4. 依然对应第一幅图,只有两种节点,节点的根部都是黑色的
   对于黑色的节点,右孩子一定是黑色的,左孩子不一定
5. 从2-3树推到红黑树, 2-3树每个节点都可以对应到红黑树的一个黑色节点。

总结

1. 红黑树的保持'黑平衡'的二叉树, 本质是因为2-3树是绝对平衡的
2. 严格意义上不是平衡二叉树
3. 最大高度 2logn   O(logn)
4. 如果存储的数据会频繁的添加和删除,红黑树是很好的选择
5. 如果存储的数据不会频繁的改动,更多的是查询操作的话, 选择AVL树


五. 保持根节点为黑色和左旋转

首先回顾一下2-3树插入节点的过程:

2-3树中添加一个新的元素
要么添加进一个2-节点,形成一个3-节点
要么添加进一个3-节点,形成一个4-节点

红黑树也一样, 只是红黑树永远添加红色节点, 并且保持根节点为黑色节点。

先将add函数中加入保持根结点为黑色的代码
RBTTree.java

...
 // 向红黑树中添加新的元素(key, value)
    public void add(K key, V value){
        root = add(root, key, value);
        root.color = BLACK; // 最终根节点为黑色节点
    }
...

 

红黑树添加新元素

添加的元素在依照二分搜索树那样添加后, 在左子树,无需其他操作。

 

如果添加的元素在右子树, 根据红黑树的性质, 我需要做左旋转

编写左旋转代码


    //   node                     x
    //  /   \     左旋转         /  \
    // T1   x   --------->   node   T3
    //     / \              /   \
    //    T2 T3            T1   T2
    private Node leftRotate(Node node){

        Node x = node.right;

        // 左旋转
        node.right = x.left;
        x.left = node;

        x.color = node.color;
        node.color = RED;

        return x;
    }

 

注意点:
    如果在过程图中原来的37也是红色的, 那么在左旋转后42依然是红色的。
    左旋转只是一个子过程,并没有解决红黑树的添加问题, 后面会完善。

六. 颜色翻转和右旋转

  • 颜色翻转

 

向2-3树的3-节点添加元素,对应到红黑树中,第一种情况 如下图所示

37,42中添加66

 

2-3树中的最后的拆分过程,对应到红黑树中:

    1. 先要把37和66变成黑色
    2. 接着42变为红色, 因为红色表示会和父亲节点去融合。

 

编写颜色翻转代码

 // 颜色翻转
    private void flipColors(Node node){

        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }

 

  • 右旋转

在3节点中添加元素的另一种情况:

向37,42中添加12

 

对应的,我们需要进行右旋转,才能和2-3树的逻辑保持一致 

 

 

我们还需维护一下颜色:
第一步:
    在右旋转之后,本质上依然是4节点, 所以37黑色,12和42变为红色

此时又回到了之前颜色翻转的情况
第二步:
    再将37变为红色, 12和42变为黑色

 

右旋转代码(第二步会在颜色翻转的情况时进行维护, 右旋转中先不写)


    //     node                   x
    //    /   \     右旋转       /  \
    //   x    T2   ------->   y   node
    //  / \                       /  \
    // y  T1                     T1  T2
    private Node rightRotate(Node node){

        Node x = node.left;

        // 右旋转
        node.left = x.right;
        x.right = node;

        x.color = node.color;
        node.color = RED;

        return x;
    }

七. 红黑树中添加新元素

向3-节点添加元素,我们之前讲解了两种情况,分别是

1. 向37,42  添加66    66处于37,42后面
2. 向36,42  添加12    12处于37,42前面

现在还有第三种情况

3. 向37,42  添加40    40处于37,42中间

对于第三中情况的解决方案其实就是,前面所有方案的综合

 

 

解决流程:

这样,我们可以把三种情况放在一套流程中去解决
需要几次判断:
        1.判断添加的元素是否处于中间,是的话按流程走,否则跳到下一个流程点中
        2.判断添加的元素是否处于左边,是的话按流程走,否则跳到下一个流程点中
        3.判断添加的元素是否处于右边(可不判断),最后进行颜色维护

根据上面的逻辑,修改add函数

RBTree.java

...
  // 向红黑树中添加新的元素(key, value)
    public void add(K key, V value){
        root = add(root, key, value);
        root.color = BLACK; // 最终根节点为黑色节点
    }

    // 向以node为根的红黑树中插入元素(key, value),递归算法
    // 返回插入新节点后红黑树的根
    private Node add(Node node, K key, V value){

        if(node == null){
            size ++;
            return new Node(key, value); // 默认插入红色节点
        }

        if(key.compareTo(node.key) < 0)
            node.left = add(node.left, key, value);
        else if(key.compareTo(node.key) > 0)
            node.right = add(node.right, key, value);
        else // key.compareTo(node.key) == 0
            node.value = value;
        
        //****流程逻辑
        if (isRed(node.right) && !isRed(node.left))
            node = leftRotate(node);

        if (isRed(node.left) && isRed(node.left.left))
            node = rightRotate(node);

        if (isRed(node.left) && isRed(node.right))
            flipColors(node);

        return node;
    }
...

完成add之后,剩下的函数不需要修改于BST一致。

关于几种树结构的总结

1. 二分搜索树
对于完全随机的数据, 普通的二分搜索树很好用!
缺点: 极端情况下退化为链表(或高度不平衡)

2. AVL树
对于查询较多的使用情况,AVL树很好用!

3. 红黑树
牺牲了平衡型(2logn的高度)
统计性能更优(综合增删改查所有操作)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 答案:Python语言中有一个内置的二叉搜索树模块,叫做 BST(Binary Search Tree),它可以用来实现红黑树。可以参考如下代码:from BST import BST# Create a new Binary Search Tree bst = BST()# Insert some values into the tree bst.insert(10) bst.insert(5) bst.insert(15) bst.insert(2) bst.insert(7) bst.insert(12) bst.insert(20)# Print the values in the tree, in ascending order bst.inorder() ### 回答2: 红黑树是一种自平衡的二叉查找树,通过在每个节点中维护额外的信息来保持平衡。下面给出一个用Python实现红黑树的基本思路: 1. 创建一个节点类,包含节点值、颜色、左子节点、右子节点和父节点属性。 2. 创建一个红黑树类,包含根节点、插入节点、删除节点、查找节点和旋转等基本操作。 3. 红黑树的特性: - 每个节点要么是红色,要么是黑色。 - 根节点是黑色。 - 所有叶节点(NIL节点)是黑色。 - 如果一个节点是红色的,则它的两个子节点都是黑色的。 - 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。 4. 插入节点: - 将新插入的节点设为红色。 - 从根节点开始,按照二叉查找树的规则找到插入位置,并将新节点插入。 - 根据插入节点的父节点和叔父节点的颜色进行不同的旋转和变色操作来保持红黑树的平衡。 5. 删除节点: - 找到待删除的节点。 - 如果待删除的节点有两个子节点,则找到其后继节点(右子树的最小节点),将后继节点的值赋给待删除节点,再删除后继节点。 - 如果待删除的节点只有一个子节点或没有子节点,直接删除即可。 - 根据删除节点的兄弟节点和兄弟节点的子节点(侄子节点)的颜色进行不同的旋转和变色操作来保持红黑树的平衡。 6. 查找节点: - 从根节点开始,按照二叉查找树的规则递归搜索。 - 如果找到节点,返回该节点,否则返回None。 7. 旋转操作: - 左旋:以某个节点为轴,将其右子节点旋转上来,其右子节点的左子节点成为该节点的右子节点。 - 右旋:以某个节点为轴,将其左子节点旋转上来,其左子节点的右子节点成为该节点的左子节点。 需要注意的是,上述代码只是红黑树基本操作的实现示例,还可以根据具体需求进行扩展和优化。实际应用中,可以使用自定义的测试用例对代码进行验证和调试。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值