数据结构(61):带你手撕红黑树的插入与删除(图文详解)

目录

1、红黑树的定义和性质

2、插入

2.1、红黑树为空树

2.2、插入节点的key在红黑树中已存在。

2.3、插入节点的父节点是黑色

2.4、插入节点的父节点是红色,叔叔节点存在并且为红色

2.5、父节点是红色,叔叔节点不存在或者为黑色,当前节点和父亲节点均为左子节点

2.6、父节点是红色且为左子节点,叔叔节点不存在或者为黑色,当前节点为右子节点

2.7、父节点是红色,叔叔节点不存在或者为黑色,当前节点和父节点均为右子节点

2.8、父节点是红色且为右子节点,叔叔节点不存在或者为黑色,当前节点为左子节点

3、删除 

3.1、替换节点是红色

3.2、替换节点是黑色、左子节点,兄弟节点是黑色,兄弟节点的右子节点为红色,左子节点任意颜色

3.3、替换节点是黑色、左子节点,兄弟节点是黑色,兄弟节点的右子节点为黑色,左子节点为红色

3.4、替换节点是黑色、左子节点,兄弟节点是黑色,兄弟节点的左右子节点均为黑色

3.5、替换节点是黑色、左子节点,兄弟节点是红色

3.6、 替换节点是黑色、右子节点,兄弟节点是黑色,兄弟节点的左子节点为红色,右子节点任意颜色

3.7、替换节点是黑色、右子节点,兄弟节点是黑色,兄弟节点的左子节点为黑色,右子节点为红色

3.8、 替换节点是黑色、右子节点,兄弟节点是黑色,兄弟节点的左右子节点均为黑色

3.9、替换节点是黑色、右子节点,兄弟节点是红色


1、红黑树的定义和性质

红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树的一般要求以外,对于任何有效的红黑树增加了如下的额外要求:

  1. 节点是红色或黑色。
  2. 根是黑色。
  3. 所有叶子都是黑色(叶子是NIL节点)。
  4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
  5. 从任一节点到每个叶子的所有简单路径都包含相同数目的黑色节点。

这些约束确保了红黑树的关键特性:从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

要知道为什么这些性质确保了这个结果,注意到性质4导致了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据性质5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。

在很多树数据结构的表示中,一个节点有可能只有一个子节点,而叶子节点包含数据。用这种范例表示红黑树是可能的,但是这会改变一些性质并使算法复杂。为此,本文中我们使用"nil叶子"或"空(null)叶子",如上图所示,它不包含数据而只充当树在此结束的指示。这些节点在绘图中经常被省略,导致了这些树好像同上述原则相矛盾,而实际上不是这样。与此有关的结论是所有节点都有两个子节点,尽管其中的一个或两个可能是空叶子。

因为每一个红黑树也是一个特化的二叉查找树,因此红黑树上的只读操作与普通二叉查找树上的只读操作相同。然而,在红黑树上进行插入操作和删除操作会导致不再符合红黑树的性质。恢复红黑树的性质需要少量的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。虽然插入和删除很复杂,但操作时间仍可以保持为次。

2、插入

我们首先以二叉查找树的方法增加节点并标记它为红色。(如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑结点,这个是很难调整的。但是设为红色节点后,可能导致出现两个连续红色节点的冲突,那么可以通过颜色变换和树旋转来调整)下面要进行什么操作取决于其他邻近节点的颜色。

同人类家族树一样,我们将使用叔父节点来指代一个结点的父节点的兄弟结点。注意:

  • 性质1和性质3总是保持着。
  • 性质4只在增加红色节点、重绘黑色节点为红色,或做旋转时受到威胁。
  • 性质5只在增加黑色节点、重绘红色节点为黑色,或做旋转时受到威胁。

在下面的示例中,将要插入的节点标记为N,N的父节点标记为P,N的祖父节点标记为G,N的叔父节点标记为U。在图中展示的任何颜色要么是由它所处情形这些所作的假定,要么是假定所暗含的。

通过下列函数,可以找到一个节点的叔父和祖父节点。

node* grandparent(node* n)
{
    return n->parent->parent;
}

node* uncle(node *n)
{
    if(n->parent == grandparent(n)->left)   //如果父节点是左节点
        return grandparent(n)->right;
    else 
        return grandparent(n)->left;
}

2.1、红黑树为空树

方法:把当前节点绘制为黑色,并作为根节点插入,

2.2、插入节点的key在红黑树中已存在。

方法:把U设置为当前节点的颜色,然后更新当前节点的值为插入节点的值。

2.3、插入节点的父节点是黑色

 方法:因为插入节点是红色的,所以直接插入即可。

2.4、插入节点的父节点是红色,叔叔节点存在并且为红色

2.5、父节点是红色,叔叔节点不存在或者为黑色,当前节点和父亲节点均为左子节点

2.6、父节点是红色且为左子节点,叔叔节点不存在或者为黑色,当前节点为右子节点

2.7、父节点是红色,叔叔节点不存在或者为黑色,当前节点和父节点均为右子节点

2.8、节点是红色且为右子节点,叔叔节点不存在或者为黑色,当前节点为左子节点

3、删除 

我们将要删除的节点称为目标节点,我们删除了这个节点就需要有节点补到被删除节点的位置(叶子节点除外)。

我们默认使用目标节点的右子树中的最小节点(替代节点)去覆盖目标节点(当然也可以使用左子树最大节点),而我们只需要在覆盖完成后删除替代节点即可。现在问题简化为删除叶子节点(因为子树的最小节点一定是叶子节点)。

在删除之前,我们还是来做一个默认名称的规定

下面具体介绍删除操作的集中情形:

3.1、替换节点是红色

3.2、替换节点是黑色、左子节点,兄弟节点是黑色,兄弟节点的右子节点为红色,左子节点任意颜色

3.3、替换节点是黑色、左子节点,兄弟节点是黑色,兄弟节点的右子节点为黑色,左子节点为红色

3.4、替换节点是黑色、左子节点,兄弟节点是黑色,兄弟节点的左右子节点均为黑色

3.5、替换节点是黑色、左子节点,兄弟节点是红色

3.6、 替换节点是黑色、右子节点,兄弟节点是黑色,兄弟节点的左子节点为红色,右子节点任意颜色

3.7、替换节点是黑色、右子节点,兄弟节点是黑色,兄弟节点的左子节点为黑色,右子节点为红色

3.8、 替换节点是黑色、右子节点,兄弟节点是黑色,兄弟节点的左右子节点均为黑色

3.9、替换节点是黑色、右子节点,兄弟节点是红色

红黑树的删除看完这所有的情况有没有发现一个共同的规律,就是“借”,“借兄弟的” > “借兄弟孩子的” > “借祖先的”,“借”红色的节点以达到删除后的平衡。 

猜你喜欢

转载自blog.csdn.net/weixin_40179091/article/details/110912990