参照维基百科
本篇将详细讨论红黑树节点的删除。
如果需要删除的节点有两个儿子,那么问题可以被转化成删除另一个只有一个儿子的节点的问题(为了表述方便,这里所指的儿子,为非叶子节点的儿子)。对于二叉查找树,在删除带有两个非叶子儿子的节点的时候,我们找到要么在它的左子树中的最大元素、要么在它的右子树中的最小元素,并把它的值转移到要删除的节点中。
我们接着删除我们从中复制出值的那个节点(前趋节点或者后继结点),它必定有少于两个非叶子的儿子。因为只是复制了一个值而不违反任何属性,这就把问题简化为如何删除最多有一个儿子的节点的问题。它不关心这个节点是最初要删除的节点还是我们从中复制出值的那个节点。 在本文余下的部分中,我们只需要讨论删除只有一个儿子的节点(如果它两个儿子都为空,即均为叶子,我们任意将其中一个看作它的儿子)。
如果我们删除一个红色节点(此时该节点的儿子将都为叶子节点,因为满足性质5),它的父亲和儿子一定是黑色的。所以我们可以简单的用它的黑色儿子替换它,并不会破坏属性3和4。通过被删除节点的所有路径只是少了一个红色节点,这样可以继续保证属性5。
另一种简单情况是在被删除节点是黑色而它的儿子是红色的时候。如果只是去除这个黑色节点,用它的红色儿子顶替上来的话,会破坏属性5,但是如果我们重绘它的儿子为黑色,则曾经通过它的所有路径将通过它的黑色儿子,这样可以继续保持属性5。
需要进一步讨论的是在要删除的节点 (delNode)和它的儿子 (N)二者都是黑色的时候,这是一种复杂的情况。我们首先把要删除的节点替换为它的儿子。出于方便,称呼这个儿子为N(在新的位置上), 称呼它的兄弟(它父亲的另一个儿子)为S。在下面的示意图中,我们还是使用P称呼N的父亲,SL称呼S的左儿子,SR称呼S的右儿子。
// 删除节点
void RBTree::deleteRBNode(Node *deNode)
{
Node *lc = deNode->lchild, *rc = deNode->rchild, *repDelNode = NULL, *N = NULL;
if (rc) // 需要删除的节点有右孩子
{
// 用后继节点的值覆盖将要删除的节点的值,后继节点没有左孩子
Node *suc = treeSuccessor(deNode);
deNode->data = suc->data;
repDelNode = suc;
N = repDelNode->rchild;
if (!N) // N 为空节点
{
N = new Node(-1, 'b');
N->parent = repDelNode;
repDelNode->rchild = N;
}
}
else
{
if (lc) //if (lc && !rc) 需要删除的节点有左孩子,无右孩子
{
Node *pre = treePredecessor(deNode);
deNode->data = pre->data;
repDelNode = pre;
N = repDelNode->lchild;
if (!N) // N 为空节点
{
N = new Node(-1, 'b');
N->parent = repDelNode;
repDelNode->lchild = N;
}
}
else // if (!rc && !lc) 没有孩纸
{
repDelNode = deNode;
N = new Node(-1, 'b');
N->parent = repDelNode;
repDelNode->lchild = N;
}
}
// 以上得到要删除的节点 repNode 以及 待删除节点的 唯一孩子 N
// 不管待删除节点为黑或红统一用它唯一的儿子 N 顶替
transplant(repDelNode, N);
// 待删除节点为黑色时要做额外的处理
Node *p = N->parent;
if (repDelNode->color == 'b')
{
if (N->color == 'r')
{
N->color = 'b';// 待删除节点为黑色,他有儿子,且儿子为红色
}
else
{
deleteCase1(N);
}
}
// 如果 N 是后来添加的叶子节点,则删除节点 delNode 后再将 N 简单删掉
if (N->data == -1)
{
Node *pN = N->parent;
if (pN)
{
if (N == pN->lchild)
{
pN->lchild = NULL;
}
else
{
pN->rchild = NULL;
}
}
else //N即为叶节点, 又为根节点,
{
this->root = NULL;
}
delete N;
}
}
注意:
1. 本文下面的步骤思路都假设叶子节点是实际的节点,和普通排序二叉树的叶子节点样,具有其他非叶子节点所有的属性的,它们的孩子为空节点NULL。
2. 如果删除的节点 (repdelNode)没有孩子节点,即N 为 NULL, 这时候需要新生成一个节点,这个新节点只有颜色,数值无效。他将作为 delNode 唯一的儿子,这个儿子赋给 N。 删除步骤完成之后再将这个临时产生的节点简单删除,即还原成NULL。
代码如下:
if (!N) // N 为空节点
{
N = new Node(-1, 'b');
N->parent = repDelNode;
repDelNode->lchild = N; //左右孩子都可以
}
有以下几种情况:
case1:(最简单的情况)被删除节点delNode 为根节点,则用 N 顶替之后, N 变为根节点,即 N的 兄弟节点不存在。
void RBTree::deleteCase1(Node * N)
{
if (N->parent)
{
deleteCase2(N);
}
}
以下都假设 N 为新的父亲p 的左孩子。
case2:S 是红色。在这种情况下我们在N的父亲上做左旋转,把红色兄弟转换成N的祖父,我们接着对调 N 的父亲和祖父的颜色。完成这两个操作后,尽管所有路径上黑色节点的数目没有改变,但现在 N 有了一个黑色的兄弟和一个红色的父亲,(它的新兄弟是黑色因为它是红色S的一个儿子,)所以我们可以接下去按情况 4、5或6来处理,**即将其新兄弟变为黑色,新父亲变为红色。
void RBTree::deleteCase2(Node * N)
{
Node* p = N->parent;
Node* s = N->brother();
if (s && s->color == 'r')
{
p->color = 'r';
s->color = 'b';
if (N == p->lchild)
singRotateRight(p); // 右右情况的旋转
else
singRotateLeft(p); // 左左情况的旋转
}
deleteCase3(N); // 将问题转化为 N 的兄弟为黑色
}
case3 : N 的父亲、S 和 S 的儿子都是黑色的。在这种情况下,我们简单的重绘 S 为红色。结果是通过S的所有路径,它们就是以前不通过 N 的那些路径,都少了一个黑色节点。因为删除 N 的初始的父亲使通过 N 的所有路径少了一个黑色节点,这使事情都平衡了起来。但是,通过 P 的所有路径现在比不通过 P 的路径少了一个黑色节点,所以仍然违反属性4。要修正这个问题,我们要从情况 1 开始,在 P 上做重新平衡处理。
void RBTree::deleteCase3(Node * N)
{
Node* s = N->brother();
Node* p = N->parent;
if (p->color == 'b'&& s->color == 'b' && (!s->rchild || s->rchild->color == 'b') && (!s->lchild || s->lchild->color == 'b'))
{
s->color = 'r';
deleteCase1(p);
}
else
{
deleteCase4(N);
}
}
case 4 : S 和 S 的儿子都是黑色,但是 N 的父亲是红色。在这种情况下,我们简单的交换 N 的兄弟和父亲的颜色。这不影响不通过 N 的路径的黑色节点的数目,但是它在通过 N的路径上对黑色节点数目增加了一,添补了在这些路径上删除的黑色节点。
void RBTree::deleteCase4(Node * N)
{
Node* s = N->brother();
Node* p = N->parent;
if (p->color == 'r'&& s->color == 'b' && (!s->rchild || s->rchild->color == 'b') && (!s->lchild || s->lchild->color == 'b'))
{
s->color = 'r';
p->color = 'b';
}
else
{
deleteCase5(N);
}
}
case5 : S 是黑色,S 的左儿子是红色,S 的右儿子是黑色,而 N 是它父亲的左儿子。在这种情况下我们在 S 上做右旋转,这样 S 的左儿子成为 S 的父亲和 N 的新兄弟。我们接着交换 S 和它的新父亲的颜色。所有路径仍有同样数目的黑色节点,但是现在 N 有了一个右儿子是红色的黑色兄弟,所以我们进入了情况 6。N 和它的父亲都不受这个变换的影响。
void RBTree::deleteCase5(Node * N)
{
Node* s = N->brother();
Node* p = N->parent;
if (N == p->lchild && s && (!s->rchild || s->rchild->color == 'b') && s->lchild && s->lchild->color == 'r')
{
s->color = 'r';
s->lchild->color == 'b';
singRotateLeft(s);
}
else
{
if (N == p->rchild && s && (!s->lchild || s->lchild->color == 'b') && s->rchild && s->rchild->color == 'r')
{
s->color = 'r';
s->rchild->color = 'b';
singRotateRight(s);
}
}
deleteCase6(N);
}
case6 : S 是黑色,S 的右儿子是红色,而 N 是它父亲的左儿子。在这种情况下我们在 N 的父亲上做左旋转,这样 S 成为 N 的父亲和 S 的右儿子的父亲。我们接着交换 N 的父亲和 S 的颜色,并使 S 的右儿子为黑色。子树在它的根上的仍是同样的颜色,所以属性 3 没有被违反。但是,N 现在增加了一个黑色祖先: 要么 N 的父亲变成黑色,要么它是黑色而 S 被增加为一个黑色祖父。所以,通过N 的路径都增加了一个黑色节点。此时,如果一个路径不通过 N,则有两种可能性:它通过 N 的新兄弟。那么它以前和现在都必定通过 S 和N 的父亲,而它们只是交换了颜色。所以路径保持了同样数目的黑色节点。它通过 N 的新叔父,S 的右儿子。那么它以前通过 S、S 的父亲和 S 的右儿子,但是现在只通过 S,它被假定为它以前的父亲的颜色,和 S 的右儿子,它被从红色改变为黑色。合成效果是这个路径通过了同样数目的黑色节点。在任何情况下,在这些路径上的黑色节点数目都没有改变。所以我们恢复了属性 4。在示意图中的白色节点可以是红色或黑色,但是在变换前后都必须指定相同的颜色。
void RBTree::deleteCase6(Node * N)
{
Node* s = N->brother();
Node* p = N->parent;
if (s)
{
s->color = p->color;
p->color = 'b';
if (N == p->lchild)
{
s->rchild->color = 'b';
singRotateRight(p);
}
else
{
s->lchild->color = 'b';
singRotateLeft(p);
}
}
}
几个返回某节点 叔叔,兄弟,祖父的成员函数
其他关于节点和红黑树完整代码可以参见代码
http://download.youkuaiyun.com/detail/lmx2014001/9598650
// 返回叔叔
Node * Node::getUncle()
{
Node* GP = getGrandparent();
if (parent == GP->lchild)
{
return GP->rchild;
}
else
{
return GP->lchild;
}
}
// 返回祖父
Node * Node::getGrandparent()
{
return parent->parent;
}
// 返回兄弟
Node *Node::brother()
{
Node *p = this->parent;
if (this == p->parent)
{
return p->rchild;
}
else
{
return p->lchild;
}
}