在数据结构的学习中,你肯定会遇到红黑树。不管是c++的STL中关联容器(map和set)的底层实现,还是linux中i/o复用函数epoll的底层实现,都采用了红黑树这个数据结构。为什么会采用它呢???效率高?嗯,我看是。。。
红黑树首先是一棵二叉排序树,二叉排序树的特点还记得吗
是一棵空树或者是满足下列特点:
如果存在左子树,则左子树上所有节点的值都小于根节点的值,如果存在右子树,则右子树上所有节点的值都大于根节点的值。任意一棵左右子树都是一棵二叉排序树,当然也没有值相同的节点。
百度百科上说它是一棵自平衡二叉搜索树,那它为什么会拥有自平衡的特异功能呢,来看看它下面的性质你就知道了
性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个叶节点(NIL节点,空节点)是黑色的。
性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
性质4和性质5保证了其的平衡性,所以在最坏的情况下它的查找和删除等操作是优于普通二叉排序树的。
红黑树中重要的旋转:
先说说为什么要旋转,原因是在对红黑树进行插入、删除的操作时,为保证它在插入一个新的节点和删除一个节点后它的那些性质不改变(主要是第四第五条性质),所以需要对其进行一定的旋转节点操作和变换节点颜色的操作。
以下是红黑树的节点数据结构:
enum NodeColor{ RED, BLACK }; //节点颜色类型
typedef int Type; //关键字类型
typedef struct RBTreeNode
{
NodeColor color; //节点颜色
Type key; //关键字
RBTreeNode *left; //左孩子
RBTreeNode *right; //右孩子
RBTreeNode *parent; //父节点
}Node;
左旋转
还是觉得上图比较靠谱
左旋转代码:
void RBTreeLeftRotate(Node **root, Node *b)
{
if (root == NULL || b == NULL) return;
//第一步,将e的左孩子变为b的右孩子
Node *e = b->right;
b->right = e->left;
if (e->left != NULL) e->left->parent = b;
//第二步,更新e的双亲
e->parent = b->parent;
if(b->parent == NULL) *root = e;
else
{
if(b->parent->left == b)
{
b->parent->left = e;
}
else
{
b->parent->right = e;
}
}
//第三步,更新b的双亲
b->parent = e;
e->left = b;
}
右旋转
static void RBTreeRightRotate(Node **root, Node *c)
{
if(root == NULL || c == NULL) return;
//第一步,将d的右孩子变为c的左孩子
Node *d = c->left;
c->left = d->right;
if(d->right != NULL) d->right->parent = c;
//第二步,更新d的双亲
d->parent = c->parent;
if(c->parent == NULL)
{
*root = d;
}
else
{
if(c->parent->left == c)
{
c->parent->left = d;
}
else
{
c->parent->right = d;
}
}
//第三步,更新c的双亲
c->parent = d;
d->right = c;
}
旋转看完就该看插入了,红黑树的插入和排序二叉树的插入一样先得找到插入的合适位置,然后进行插入,但要保证它的性质不变,还需要对其进行修复工作。我们在每次插入的时候都假定插入的元素初始状态都是红色的,如果插入到一个黑色节点的下面,就不需要进行修复。(因为没有破坏它的特点),但如果插入到一个红色的节点下面就需要进行修复工作了。
红黑树的插入修复(迭代修复)主要分以下几种情况:
1. 父节点为祖父节点的左节点
1.1叔节点为红色,直接变换父节点和叔节点为红色、祖节点为黑色,不需要旋转,从祖节点继续迭代往上遍历。
1.2叔节点为黑色
1.2.1新节点为父节点的左节点,交换父节点和祖节点颜色,对祖节点进行右单旋转。
1.2.2新节点为父节点的右节点,先对父节点进行左单旋转,再做1.2.1的操作。
2. 父节点为祖父节点的右节点
2.1叔节点为红色,直接变换父节点和叔节点为红色、祖节点为黑色,不需要旋转,从祖节点继续迭代往上遍历。 (图就不画了,和1.1类似)
2.2叔节点为黑色
2.2.1新节点为父节点的右节点,交换父节点和祖节点的颜色,并对祖节点进行左单旋转。
2.2.2新节点为父节点的左节点,先对父节点进行右单旋转,然后再做2.2.1操作。
以上是红黑树的插入,,来看红黑树的删除:
删除红黑树的节点基本删除思想也是和删除排序二叉树的思想是一样的,删除的节点情况不外乎有以下三种:
删除叶子节点;
删除单支节点;
删除双分支节点;
其中删除双分支节点可以转变为删除单分支节点或者是叶子节点。主要是通过找到它左孩子树中值最大的节点替换它,然后删除左孩子中最大值得节点;或者是找其右孩子树中最小值得节点去替换它,然后删除右孩子树中最小值得节点。所以双分支的删除可以归结到单分支的删除和叶子节点的删除。
根据红黑树的性质可以知道:
1、 删除操作中真正被删除的必定是只有一个红色孩子或没有孩子的点。
2、 如果真正的删除点是一个红色结点,那么它必定是一个叶子结点。
下图中只有(a)符合要求
删除分类情况:
如果删除的为红色节点,则直接删除。(必定为叶子节点)
如果删除的为黑色节点且它没有兄弟节点,则把它的孩子节点的值赋给它,然后删除它的孩子节点。
如果删除的节点有兄弟节点,那就得分以下几种情况:
假设删除节点为N,它的兄弟节点为S,它的父节点为P。
N的兄弟节点S为红
S为黑色,S的两个儿子都是黑色,且N的父节点P是黑色的
S为黑色,S的两个儿子都是黑色,且N的父节点P是红色的
S为黑色,S的左儿子是红色,S的右儿子是黑色,而N是它父亲的左儿子
S为黑色,S的右儿子是红色,而N是它父亲的左儿子
最后再说一下红黑树的时间复杂度,由于它是基于排序二叉树的,所以它的查找和排序二叉树相同,都为O(log(n))。
所以开始前说的红黑树它是一棵自平衡的搜索二叉树,现在是不是明白了。
哪里说的不对,请指出,谢谢~~
参考博主”cvan的小窝”和博主”好好学习”。
//http://blog.youkuaiyun.com/chenhuajie123/article/details/11951777
//http://blog.youkuaiyun.com/very_2/article/details/5722682