算法之红黑树:从插入到删除的深度解析与实践
写作初衷
在编程学习的漫漫长路上,算法犹如一座高峰,吸引着我们不断攀登。我在探索算法的过程中,遇到过不少难题,也积累了许多宝贵的经验。深知独自面对算法挑战时的艰辛,所以希望通过这篇博客,能与大家一同深入学习算法知识,在交流和分享中共同进步,让更多人领略算法的魅力,提升编程能力。
红黑树的删除操作解析
2-3-4树的插入算法(自顶向下)
2-3-4树是理解红黑树删除操作的重要基础。在2-3-4树中,允许存在4-结点。其插入算法沿查找路径向下进行变换,目的是保证当前结点不是4-结点,这样树底才有空间插入新键;沿查找路径向上进行变换,是为了将之前创建的4-结点配平 。
具体来说,向下变换时,如果根结点是4-结点,就将它分解成三个2-结点,树高加1;如果遇到一个父结点为2-结点的4-结点,将4-结点分解为两个2-结点并将中间键传递给父结点,使父结点变为3-结点;如果遇到父结点为3-结点的4-结点,同样将4-结点分解为两个2-结点并将中间键传递给父结点,使父结点变为4-结点 。
用红黑树实现该算法时,需要将4-结点表示为由三个2-结点组成的一棵平衡子树,根结点和两个子结点都用红链接相连;在向下过程中分解所有4-结点并进行颜色转换;在向上过程中用旋转将4-结点配平 。令人惊讶的是,只需要移动算法3.4中put()
方法的一行代码(将颜色转换语句及其if
语句移动到递归调用之前)就能实现2-3-4树的插入操作 。
删除最小键操作
在2-3树中删除最小键时,从树底部的3-结点中删除键很简单,但从2-结点中删除键会破坏树的完美平衡性。所以,为了保证不删除2-结点,我们沿着左链接向下进行变换,确保当前结点不是2-结点(可能是3-结点,也可能是临时的4-结点) 。
根结点可能有两种情况:如果根是2-结点且它的两个子结点都是2-结点,我们可以直接将这三个结点变成一个4-结点;否则,需要保证根结点的左子结点不是2-结点,如有必要可以从它右侧的兄弟结点“借”一个键来 。
在沿着左链接向下的过程中,要保证以下情况之一成立:
- 如果当前结点的左子结点不是2-结点,完成;
- 如果当前结点的左子结点是2-结点而它的亲兄弟结点不是2-结点,将左子结点的兄弟结点中的一个键移动到左子结点中;
- 如果当前结点的左子结点和它的亲兄弟结点都是2-结点,将左子结点、父结点中的最小键和左子结点最近的兄弟结点合并为一个4-结点,使父结点由3-结点变为2-结点或者由4-结点变为3-结点 。
遍历结束后,能得到一个含有最小键的3-结点或者4-结点,然后可以直接从中删除最小键,将3-结点变为2-结点,或者将4-结点变为3-结点。最后再回头向上分解所有临时的4-结点 。
删除操作
在红黑树中删除操作和删除最小键类似,在查找路径上进行相同的变换,保证在查找过程中任意当前结点均不是2-结点 。
如果被查找的键在树的底部,可以直接删除它。如果不在,需要将它和它的后继结点交换,就像在二叉查找树中一样。因为当前结点必然不是2-结点,问题就转化为在一棵根结点不是2-结点的子树中删除最小的键,然后使用删除最小键的算法进行处理。删除之后,还需要向上回溯并分解余下的4-结点 。
为了更直观地理解删除操作,我们通过表格总结如下:
操作 | 具体情况 | 操作步骤 |
---|---|---|
删除最小键 | 根是2-结点且两个子结点都是2-结点 | 将三个结点变成一个4-结点 |
删除最小键 | 根的左子结点是2-结点且亲兄弟结点不是2-结点 | 将兄弟结点中的一个键移动到左子结点中 |
删除最小键 | 根的左子结点和亲兄弟结点都是2-结点 | 将左子结点、父结点中的最小键和左子结点最近的兄弟结点合并为一个4-结点,调整父结点 |
删除操作 | 被查找的键在树底 | 直接删除 |
删除操作 | 被查找的键不在树底 | 和后继结点交换,转化为删除最小键操作,处理后向上回溯分解4-结点 |
红黑树的性质与性能分析
红黑树的性质
红黑树通过红黑链接来模拟2-3树的结构,满足一些关键性质:红链接均为左链接;没有任何一个结点同时和两条红链接相连;树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同 。
性能分析
无论键的插入顺序如何,红黑树都几乎是完美平衡的。一棵大小为N的红黑树的高度不会超过2lgN 。这是因为红黑树的最坏情况是它所对应的2-3树中构成最左边的路径结点全部都是3-结点而其余均为2-结点,这种情况下最左边的路径长度是只包含2-结点的路径长度(约lgN)的两倍 。
红黑树的所有操作(范围查找除外,它所需的额外时间和返回的键的数量成正比)的运行时间都为对数级别。这使得红黑树在实际应用中,如数据库索引、缓存系统等场景,表现出高效的查找、插入和删除性能 。
代码示例
下面是一个简化的红黑树删除操作的Java代码示例,重点展示删除最小键的逻辑:
public class RedBlackTree<Key extends Comparable<Key>, Value> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private Node root;
private