目录
红黑树的基本编写
红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的
红黑树的性质
- 每个节点不是红色就是黑色
- 根节点是黑色的
- 没有连续的红色节点,但是可以有连续的黑色节点
- 每条路径黑色节点数量相等
- 每个叶子节点都是黑色的(此处的叶子节点值得是空节点--NIL节点,不是以前真正意义上理解的叶子节点)。
假设每条路径的黑节点数量是N,由此可以得知:任意路径长度>=N && 任意路径长度<=2N,这就说明虽短路径是全黑的,最长路径是一黑一红,这也证明最长路径不超过最短路径的2倍。
所以AVL树严格平衡,效率是logN(以2为底),红黑树是近似平衡,效率是2*logN。虽然红黑树效率没有AVL树高,但是对于现阶段的计算机而言,这点效率已经可以忽略不记。并且AVL树在达到平衡的目的下,需要进行旋转,而红黑树没有到最短路径2倍是不用旋转的,AVL树的旋转次数多,使效率降低,所以更推荐用红黑树。
红黑树节点的定义
红黑树的颜色我们用枚举定义,节点的定义用一个三叉链,构造节点,随便给一个颜色,
enum Color
{
RED,
BLACK
};
template<class K,class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Color _col;
pair<K, V> _kv;
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED)
, _kv(kv)
{}
};
红黑树的插入操作
红黑树也是一个二叉搜索树,所以插入值的时候,如果值存在返回false,如果不存在,比当前节点大就向右边插入,如果比当前节点值小,向左边插入。
红黑树右两种颜色,红和黑。对于插入操作,我们因该插入哪个颜色的节点呢?如果插入黑色,那么这条路径的黑色节点数量就会改变,使得每条路径都要增加黑色节点,代价太大了,所以我们选择插入红色节点。
【插入红色节点分以下几种情况】:
约定:cur为当前节点,g为祖父节点,u为叔叔节点
情况1.1
:cur是新增为红,parent为红,grandfather为黑,uncle叔叔节点存在且为红。前三条是固定条件,唯一要看的就是uncle的情况。
解决方式:将p,u改为黑,g改为红,然后把g当cur,继续向上调整
因为新增了红,且有两个连续红节点,所以要向上更新parent节点的颜色为黑,uncle节点也要变成黑色。因为grandparent的节点不一定是根节点,如果不是根节点,需要将grandparent的节点颜色变成红色,来保证每条路径的黑色节点数量相同。当grandparent节点变成红色后,可能又会出现两个连续的红色节点,那么就衍生出第二种情况。
情况1.2:cur不是新增红色节点,parent为红,grandfather为黑,uncle叔叔节点存在且为红。
这种情况和情况1类似,只是cur不是新增节点,而是本就存在的节点。它最开始是有图1变换而来的,最后还是通过情况1的解决方法,也就是将情况1的grandparent当成cur,将现在的parent和uncle节点变黑,grandparent节点变红。如果g节点是根节点,就再次变黑。只要uncle存在且parent和uncle都为红,就会一直这样处理。
情况2
:uncle不存在 || uncle存在且为黑
解决方案:parent为grandfather的左孩子,cur为p的左孩子,则进行右单旋;相反,如果parent为grandfather的右孩子,cur为parent的右孩子,则进行左单旋。parent和grandfather变色:parent变黑,grandfater变红。
uncle不存在的情况下,如上图,如果没有uncle,那么只有一个黑节点,并且增加一个新的节点cur后,左树的路径长度超过了右树路径长度的2倍(从grandfather节点算起)。那么此时不单纯是改变节点颜色的问题,在AVL树中,左右子树高度差大于2,会进行旋转操作。
红黑树中,uncle节点不存在或者为黑的情况,会进行