红黑树底层原理及Linux内核红黑树算法深度研究

1. 红黑树1.1 红黑树概述红黑树和我们以前学过的AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能 。不过自从红黑树出来后,AVL树就被放到了博物馆里,据说是红黑树有更好的效率,更高的统计性能 。这一点在我们了解了红黑树的实现原理后,就会有更加深切的体会 。
红黑树和AVL树的区别在于它使用颜色来标识结点的高度,它所追求的是局部平衡而不是AVL树中的非常严格的平衡 。学过数据结构的人应该都已经领教过AVL树的复杂,但AVL树的复杂比起红黑树来说简直是小巫见大巫,红黑树才是真正的变态级数据结构 。
由于STL中的关联式容器默认的底层实现都是红黑树,因此红黑树对于后续学习STL源码还是很重要的,有必要掌握红黑树的实现原理和源码实现 。
红黑树是AVL树的变种,红黑树通过一些着色法则确保没有一条路径会比其它路径长出两倍,因而达到接近平衡的目的 。所谓红黑树,不仅是一个二叉搜索树,而且必须满足一下规则:

  •  1、每个节点不是红色就是黑色 。
  •  2、根节点为黑色 。
  •  3、如果节点为红色,其子节点必须为黑色 。
  •  4、任意一个节点到到NULL(树尾端)的任何路径,所含之黑色节点数必须相同 。
上面的这些约束保证了这个树大致上是平衡的,这也决定了红黑树的插入、删除、查询等操作是比较快速的 。根据规则4,新增节点必须为红色;根据规则3,新增节点之父节点必须为黑色 。当新增节点根据二叉搜索树的规则到达其插入点时,却未能符合上述条件时,就必须调整颜色并旋转树形,如下图:
红黑树底层原理及Linux内核红黑树算法深度研究

文章插图
 
假设我们为上图分别插入节点3、8、35、75,根据二叉搜索树的规则,插入这四个节点后,我们会发现它们都破坏了红黑树的规则,因此我们必须调整树形,也就是旋转树形并改变节点的颜色 。
1.2 红黑树上结点的插入在讨论红黑树的插入操作之前必须要明白,任何一个即将插入的新结点的初始颜色都为红色 。这一点很容易理解,因为插入黑点会增加某条路径上黑结点的数目,从而导致整棵树黑高度的不平衡 。但如果新结点的父结点为红色时(如下图所示),将会违反红黑树的性质:一条路径上不能出现相邻的两个红色结点 。这时就需要通过一系列操作来使红黑树保持平衡 。
红黑树底层原理及Linux内核红黑树算法深度研究

文章插图
 
为了清楚地表示插入操作以下在结点中使用“新”字表示一个新插入的结点;使用“父”字表示新插入点的父结点;使用“叔”字表示“父”结点的兄弟结点;使用“祖”字表示“父”结点的父结点 。插入操作分为以下几种情况:
1.2.1 黑父如下图所示,如果新节点的父结点为黑色结点,那么插入一个红点将不会影响红黑树的平衡,此时插入操作完成 。红黑树比AVL树优秀的地方之一在于黑父的情况比较常见,从而使红黑树需要旋转的几率相对AVL树来说会少一些 。
红黑树底层原理及Linux内核红黑树算法深度研究

文章插图
 
1.2.2 红父如果新节点的父结点为红色,这时就需要进行一系列操作以保证整棵树红黑性质 。如下图所示,由于父结点为红色,此时可以判定,祖父结点必定为黑色 。这时需要根据叔父结点的颜色来决定做什么样的操作 。青色结点表示颜色未知 。由于有可能需要根结点到新点的路径上进行多次旋转操作,而每次进行不平衡判断的起始点(我们可将其视为新点)都不一样 。所以我们在此使用一个蓝色箭头指向这个起始点,并称之为判定点 。
红黑树底层原理及Linux内核红黑树算法深度研究

文章插图
 
1.2.2.1 红叔当叔父结点为红色时,如下图所示,无需进行旋转操作,只要将父和叔结点变为黑色,将祖父结点变为红色即可 。但由于祖父结点的父结点有可能为红色,从而违反红黑树性质 。此时必须将祖父结点作为新的判定点继续向上(迭代)进行平衡操作 。
红黑树底层原理及Linux内核红黑树算法深度研究

文章插图
 
需要注意的是,无论“父节点”在“叔节点”的左边还是右边,无论“新节点”是“父节点”的左孩子还是右孩子,它们的操作都是完全一样的(其实这种情况包括4种,只需调整颜色,不需要旋转树形) 。


推荐阅读