一、红黑树的性质
红黑树是一种特殊的二叉搜索树
- 每个节点只有红色或者黑色。
- 根节点一定是黑色。
- 叶子节点一定是空节点,且是黑色。
- 每个红色节点一定有两个黑色节点,即不能有连续的红色节点。
- 从一个节点到他的所有叶子节点,所经历的黑色节点数目相同。
二、红黑树的优点
- 红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在 O ( l o g n ) O(log_n) O(logn)时间内完成查找、插入和删除,这里的 n n n是树中元素的数目。
三、实现红黑树
3.1 基本结构
3.1.1 节点
树的节点选用三叉链表结构,即缓存了本节点的父节点、左子节点、右子节点。
namespace RBTree
{
public class RBTreeNode
{
public int Data;
public RBTreeNode LeftNode;
public RBTreeNode RightNode;
public RBTreeNode ParentNode;
public bool ColorBlack = true;
public RBTreeNode()
{
}
public RBTreeNode(int d)
{
Data = d;
}
}
}
3.1.2 树
只保存了链表的头节点,既只有一个根节点
namespace RBTree
{
public class RBTree
{
// 根节点
public RBTreeNode Root;
}
}
3.2 基本方法
3.2.1 获取祖父、叔父、兄弟节点方法
这些支持方法在后续的其他方法中经常用到
public class RBTree
{
// 获取祖父节点
public RBTreeNode GrandParentNode(RBTreeNode node)
{
if (node == null)
{
return null;
}
if (node.ParentNode == null)
{
return null;
}
return node.ParentNode.ParentNode;
}
// 获取叔父节点
public RBTreeNode UncleNode(RBTreeNode node)
{
var grandPa = GrandParentNode(node);
if (grandPa == null)
{
return null;
}
if (grandPa.LeftNode == node.ParentNode)
{
return grandPa.RightNode;
}
else
{
return grandPa.LeftNode;
}
}
// 获取兄弟节点
public RBTreeNode BrotherNode(RBTreeNode node)
{
if (node == null)
{
return null;
}
if (node.ParentNode == null)
{
return null;
}
if (node.ParentNode.LeftNode == node)
{
return node.ParentNode.RightNode;
}
else
{
return node.ParentNode.LeftNode;
}
}
}
3.2.2 旋转
旋转是为了维持一棵树的平衡,操作分为左旋
和右旋
。
右旋:
将 Q 的左子树设为 P 的右子树
将 P 的右子树设为Q
左旋:
将P的右子树设为Q的左子树
将Q的左子树设为P
[注意
]一定要看下面方法中node是上图中的什么节点
- 在右旋时,传入的是P节点
- 在左旋时,传入的是Q节点
参看文献【5】的左旋传入的是P节点,右旋传入的是Q节点。传入节点不同,实现方法也不同!
private void RotateLeft(RBTreeNode node)
{
var pNode = node.ParentNode;
if (pNode == null)
{
// 没父节点,旋转个毛线
return;
}
// 获取祖父和左子节点
var grandPaNode = node.ParentNode.ParentNode;
var leftChildNode = node.LeftNode;
// leftChildNode变为pNode的rightChild
pNode.RightNode = leftChildNode;
if (leftChildNode != null)
{
leftChildNode.ParentNode = pNode;
}
// pNode变为node的leftChild
pNode.ParentNode = node;
node.LeftNode = pNode;
// 处理祖父节点
DoGrandPaNodeAndNode(node, pNode, grandPaNode);
}
private void RotateRight(RBTreeNode node)
{
var pNode = node.ParentNode;
if (pNode == null)
{
// 没父节点,旋转个毛线
return;
}
// 获取祖父和右子节点
var grandPaNode = node.ParentNode.ParentNode;
var rightChildNode = node.RightNode;
// 右子节点变为父节点的左子节点
pNode.LeftNode = rightChildNode;
if (rightChildNode != null)
{
rightChildNode.ParentNode = pNode;
}
// pNode置为node的右子节点
pNode.ParentNode = node;
node.RightNode = pNode;
// 处理和祖父节点的关系
DoGrandPaNodeAndNode(node, pNode, grandPaNode);
}
private void DoGrandPaNodeAndNode(RBTreeNode node, RBTreeNode pNode, RBTreeNode grandPaNode)
{
node.ParentNode = grandPaNode;
if (grandPaNode == null)
{
Root = node;
}
else
{
if (grandPaNode.LeftNode == pNode)
{
grandPaNode.LeftNode = node;
}
else
{
grandPaNode.RightNode = node;
}
}
}
3.3 核心操作
3.3.1 插入
3.3.1.1 操作
1、首先以二叉搜索树的方法插入节点N,并标记为红色。将要插入的节点标为N,N的父节点标为P,N的祖父节点标为G,N的叔父节点标为U
2、调整这棵树,保持红黑树的五条性质
情形1:若新节点N位于树的根上,没有父节点。则把N重绘为黑色即可,否则进入情形2。
情形2:若新节点的父节点P是黑色。则红黑树性质全满足,不需要操作,否则进入情形3。
情形3:若父节点P和叔父节点U都是红色。则把父节点P和叔父节点U重绘为黑色,把祖父节点G重绘为红色,并且把祖父节点G当作新插入节点执行情形1。否则进入情形4。
情形4:
4.0 一定会进入情形5,首先设置要进入情形5的节点X为节点N。
4.1 若节点N是父节点P的右子节点且父节点P是祖父节点G的左子节点,则左旋N,设进入情形5的节点X为父节点P。
4.2 若节点N是父节点P的左子节点且父节点P是祖父节点G的右子节点,则右旋N,设进入情形5的节点X为父节点P。
4.3 把节点X传入情形5。
情形5:
5.1 节点X的父节点重绘为黑色,祖父节点重绘为红色。
5.2 若节点X是其父节点的左子节点且其父节点是其祖父节点的左子节点,则右旋其父节点。否则左旋其父节点。
3.3.1.2 C#代码实现
public RBTreeNode Insert(int data)
{
if (Root == null)
{
Root = new RBTreeNode(data);
return Root;
}
// 插入节点
var node = InnerInsert(Root, data);
// node为空表示插入失败,一般是传入了一样的值
if (node != null)
{
// 调整树满足红黑树性质
InsertCase1(node);
}
return node;
}
private RBTreeNode InnerInsert(RBTreeNode parent, int data)
{
if (parent.Data == data)
{
return null;
}
else if (parent.Data > data)
{
if (parent.LeftNode == null)
{
RBTreeNode node = new RBTreeNode(data);
node.ParentNode = parent;
parent.LeftNode = node;
node.ColorBlack = false;
return node;
}
else
{
return InnerInsert(parent.LeftNode, data);
}
}
else
{
if (parent.RightNode == null)
{
RBTreeNode node = new RBTreeNode(data);
node.ParentNode = parent;
parent.RightNode = node;
node.ColorBlack = false;
return node;
}
else
{
return InnerInsert(parent.RightNode, data);
}
}
}
private void InsertCase1(RBTreeNode node)
{
if (node.ParentNode == null)
{
node.ColorBlack = true;
}
else
{
InsertCase2(node);
}
}
private void InsertCase2(RBTreeNode node)
{
if (node.ParentNode.ColorBlack)
{
return;
}
InsertCase3(node);
}
private void InsertCase3(RBTreeNode node)
{
var uncle = UncleNode(node);
if (uncle != null && !uncle.ColorBlack)
{
node.ParentNode.ColorBlack = true;
uncle.ColorBlack = true;
var grandPa = GrandParentNode(node);
grandPa.ColorBlack = false;
InsertCase1(grandPa);
}
else
{
InsertCase4(node);
}
}
private void InsertCase4(RBTreeNode node)
{
RBTreeNode nextNode = node;
if (node == node.ParentNode.RightNode && node.ParentNode == GrandParentNode(node).LeftNode)
{
RotateLeft(node);
nextNode = node.LeftNode;
}
else if (node == node.ParentNode.LeftNode && node.ParentNode == GrandParentNode(node).RightNode)
{
RotateRight(node);
nextNode = node.RightNode;
}
InsertCase5(nextNode);
}
private void InsertCase5(RBTreeNode node)
{
node.ParentNode.ColorBlack = true;
GrandParentNode(node).ColorBlack = false;
if (node == node.ParentNode.LeftNode && node.ParentNode == GrandParentNode(node).LeftNode)
{
RotateRight(node.ParentNode);
}
else
{
RotateLeft(node.ParentNode);
}
}
3.3.1.3 原理
1、以二叉查找树的方法增加节点时为什么标记为红色?因为如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑节点,这个是很难调整的。但是设为红色节点后,可能会导致出现两个连续红色节点的冲突,那么可以通过颜色调换(color flips)和树旋转来调整。
2、当插入一个节点时,红黑树的性质会受到威胁,以下是性质受到威胁的情况列举:
- 性质1和性质3总是保持着
- 性质4只在增加红色节点、重绘黑色节点为红色、或做旋转,时受到威胁。
- 性质5只在增加黑色节点、重绘红色节点为黑色、或做旋转,时受到威胁。
3、各种情形的分析
将要插入的节点标为N,N的父节点标为P,N的祖父节点标为G,N的叔父节点标为U
情形1:新节点N位于树的根上,没有父节点。
- 在这种情形下,我们把它重绘为黑色以满足性质2。因为它在每个路径上对黑节点数目增加一,性质5符合。
情形2:新节点的父节点P是黑色。
- 在这种情形下,性质4没有失效(新节点是红色的)。
- 性质5也未受到威胁,尽管新节点N有两个黑色叶子子节点;但由于新节点N是红色,通过它的每个子节点的路径就都有同通过它所取代的黑色的叶子的路径同样数目的黑色节点,所以依然满足这个性质。树仍是有效的。
情形3:如果父节点P和叔父节点U二者都是红色。
(此时新插入节点N做为P的左子节点或右子节点都属于情形3,这里右图仅显示N做为P左子的情形)
- 我们可以将它们两个重绘为黑色并重绘祖父节点G为红色(用来保持性质5)。
- 现在我们的新节点N有了一个黑色的父节点P。
- 因为通过父节点P或叔父节点U的任何路径都必定通过祖父节点G,在这些路径上的黑节点数目没有改变。但是,红色的祖父节点G可能是根节点,这就违反了性质2,也有可能祖父节点G的父节点是红色的,这就违反了性质4。为了解决这个问题,我们在祖父节点G上递归地进行情形1的整个过程。(把G当成是新加入的节点进行各种情形的检查)
情形4:父节点P是红色而叔父节点U是黑色或缺少,并且新节点N是其父节点P的右子节点而父节点P又是其父节点的左子节点。
- 我们进行一次左旋转调换新节点和其父节点的角色。
- 接着,我们按情形5处理以前的父节点P以解决仍然失效的性质4。
- 注意这个改变会导致某些路径通过它们以前不通过的新节点N(比如图中1号叶子节点)或不通过节点P(比如图中3号叶子节点),但由于这两个节点都是红色的,所以性质5仍有效。
情形5:父节点P是红色而叔父节点U是黑色或缺少,新节点N是其父节点的左子节点,而父节点P又是其父节点G的左子节点。
- 我们进行针对祖父节点G的一次右旋转。
- 在旋转产生的树中,以前的父节点P现在是新节点N和以前的祖父节点G的父节点。我们知道以前的祖父节点G是黑色,否则父节点P就不可能是红色(如果P和G都是红色就违反了性质4,所以G必须是黑色)。我们切换以前的父节点P和祖父节点G的颜色,结果的树满足性质4。性质5也仍然保持满足,因为通过这三个节点中任何一个的所有路径以前都通过祖父节点G,现在它们都通过以前的父节点P。在各自的情形下,这都是三个节点中唯一的黑色节点。
3.3.2 刪除
3.3.2.1 操作
1、按照二叉搜索树的方法找到要删除的节点P。
2、如果该节点P没有右子节点PR,那么对P执行【删除操作】。
3、如果有PR,那么找到PR的树下最小的节点PRMinimum。
4.、交换节点P和PRMinimum的值。
5、对PRMinimum执行【删除操作】。
删除操作:
1、找到要删除节点X的孩子节点C:先设左子节点为C,如果没有左子节点,那么设右子节点为C。
2、如果X为树的根,且没有左右子节点,把根节点置为空,流程结束。
3、如果X为树根,把C置为树根,且染成黑色,流程结束。
4.、设XP为X节点的父节点,链接XP和C,即把C的父节点置为XP,XP的左或者右(根据X是XP的左或者右)节点置为C。
5、如果X节点颜色为红色,什么都不做。如果X节点颜色为黑色那么进行如下操作:C为红色时,C染成黑色。C为黑色,对C进行【平衡操作】
平衡操作:
1、如果C的兄弟节点B为红色,把C的父节点CP染成红色,把B染成黑色。如果C是CP的左子节点,对CP进行左旋。如果C是CP的右子节点,对CP进行右旋。
2、
2.1 如果CP是黑色,B是黑色,B的左子节点BL是黑色,B的右子节点BR也是黑色,那么把C染成红色,对CP进行【平衡操作】。
2.2 如果CP是红色,B是黑色,B的左子节点BL是黑色,B的右子节点BR也是黑色,那么把B染成红色,把CP染成黑色。
2.3
2.3.1 当B是黑色时
2.3.1.1 如果C是CP的左子树且BL是红色且BR是黑色,那么B染色红色,BL染成黑色,右旋BL。
2.3.1.2 如果C是CP的右子树且BL是黑色且BR是红色,那么B染成红色,BR染成黑色,左旋BR。
2.3.2 把B的颜色染成CP的颜色,CP的颜色再染成黑色。
2.3.3
2.3.3.1 如果C是CP的左子节点,那么BR染成黑色,左旋B。
2.3.3.2 如果C是CP的右子节点,那么BL染成黑色,右旋B。
3.3.2.2 C#代码实现
public bool Delete(int data)
{
return PrivateDelete(Root, data);
}
private bool PrivateDelete(RBTreeNode node, int data)
{
if (node == null)
{
return false;
}
if (node.Data > data)
{
return PrivateDelete(node.LeftNode, data);
}
else if (node.Data < data)
{
return PrivateDelete(node.RightNode, data);
}
else
{
RBTreeNode p = node;
RBTreeNode pr = node.RightNode;
if (pr == null)
{
// 只有1个或0个子节点
DeleteAction(p);
}
else
{
// 转化为只有1个或0个子节点的情况
RBTreeNodeprMinimum = FindMinimumByNode(pr);
int d = p.Data;
p.Data = prMinimum.Data;
prMinimum.Data = d;
DeleteAction(prMinimum);
}
return true;
}
}
private void DeleteAction(RBTreeNode node)
{
var CNode = node.LeftNode != null ? node.LeftNode : node.RightNode;
if (node.ParentNode == null)
{
if (CNode == null)
{
Root = null;
}
else
{
CNode.ParentNode = null;
Root = CNode;
CNode.ColorBlack = true;
}
return;
}
else
{
var XPNode = node.ParentNode;
if (node == XPNode.LeftNode)
{
XPNode.LeftNode = CNode;
}
else
{
XPNode.RightNode = CNode;
}
if (CNode != null)
{
CNode.ParentNode = XPNode;
}
if (CNode != null && node.ColorBlack)
{
if (!CNode.ColorBlack)
{
CNode.ColorBlack = true;
}
else
{
DeleteCase(CNode);
}
}
}
}
private void DeleteCase(RBTreeNode CNode)
{
var BNode = BrotherNode(CNode);
/*
* 这里为什么一定有兄弟节点???
* 因为能进来的前提是:node为DeleteAction函数里的CNode,而CNode,node,XPNode都是黑色。
* 如果XPNode的兄弟节点不存在,那么这棵树违反性质5,即从一个节点到它所有的子节点通过的黑色数目不一致
*/
if (!BNode.ColorBlack)
{
var CPNode1 = CNode.ParentNode;
CPNode1.ColorBlack = false;
BNode.ColorBlack = true;
if (CNode == CNode.ParentNode.LeftNode)
{
RotateLeft(CPNode1);
}
else
{
RotateRight(CPNode1);
}
}
/*
* 注意上文进行了左右旋转 BNode已经不是CNode的兄弟节点了!
*/
var CPNode = CNode.ParentNode;
BNode = BrotherNode(CNode);
var BLNode = BNode.LeftNode;
var BRNode = BNode.RightNode;
if (CPNode.ColorBlack && BNode.ColorBlack && BLNode.ColorBlack && BRNode.ColorBlack)
{
CNode.ColorBlack = false;
DeleteCase(CPNode);
}
else if (!CPNode.ColorBlack && BNode.ColorBlack && BLNode.ColorBlack && BRNode.ColorBlack)
{
BNode.ColorBlack = false;
CPNode.ColorBlack = true;
}
else
{
if (BNode.ColorBlack)
{
if (CNode == CPNode.LeftNode && !BLNode.ColorBlack && BRNode.ColorBlack)
{
BNode.ColorBlack = false;
BLNode.ColorBlack = true;
RotateRight(BLNode);
}
else if (CNode == CPNode.RightNode && BLNode.ColorBlack && !BRNode.ColorBlack)
{
BNode.ColorBlack = false;
RotateLeft(BRNode);
}
}
/*
* 注意上文有可能进行了左右旋转,为了保险起见,不能直接用BNode
*/
var BNode3 = BrotherNode(CNode);
var CPNode3 = CNode.ParentNode;
BNode3.ColorBlack = CPNode3.ColorBlack;
CPNode3.ColorBlack = true;
if (CNode == CPNode3.LeftNode)
{
BNode3.RightNode.ColorBlack = true;
RotateLeft(BNode3);
}
else
{
BNode3.LeftNode.ColorBlack = true;
RotateRight(BNode3);
}
}
}
参考文献
[1] 维基百科-红黑树
[2] 红黑树简介及左旋、右旋、变色
[3] 30张图带你彻底理解红黑树
[4] 维基百科-树旋转
[5] Red-Black Tree in C#