1.为何须要红黑树?html
对于二叉搜索树,若是插入的数据是随机的,那么它就是接近平衡的二叉树,平衡的二叉树,它的操做效率(查询,插入,删除)效率较高,时间复杂度是O(logN)。可是可能会出现一种极端的状况,那就是插入的数据是有序的(递增或者递减),那么全部的节点都会在根节点的右侧或左侧,此时,二叉搜索树就变为了一个链表,它的操做效率就下降了,时间复杂度为O(N),因此能够认为二叉搜索树的时间复杂度介于O(logN)和O(N)之间,视状况而定。那么为了应对这种极端状况,红黑树就出现了,它是具有了某些特性的二叉搜索树,能解决非平衡树问题,红黑树是一种接近平衡的二叉树。node
2.红黑树的特性有哪些?spa
首先,红黑树是一个二叉搜索树,它同时知足如下特性:3d
(1) 每一个节点要么是黑色,要么是红色code
(2) 根节点是黑色htm
(3) 若是节点是红色的,那么它的子节点必须是黑色的(反之,不必定须要成立)blog
(4) 从根节点到叶节点或空子节点的每条路径,都包含相同数目的黑色节ip
经过看图来理解以上四个特性get
3.红黑树的效率io
红黑树的查找,插入和删除操做,时间复杂度都是O(logN)。查找操做时,它和普通的相对平衡的二叉搜索树的效率相同,都是经过相同的方式来查找的,没有用到红黑树特有的特性。但,若是插入的时候是有序数据,那么红黑树的查询效率就比二叉搜索树要高了,由于此时二叉搜索树不是平衡树,它的时间复杂度O(N)。插入和删除操做时,因为红黑树的每次操做平均要旋转一次和变换颜色,因此它比普通的二叉搜索树效率要低一点,不过期间复杂度仍然是O(logN)。总之,红黑树的优势就是对有序数据的查询操做不会慢到O(logN)的时间复杂度。
4.对旋转的理解
在红黑树中,插入或者删除数据时,为了保持红黑树的那五个特性,须要进行旋转和变换颜色的操做。旋转必需要一次性作两件事情:
* 使一些节点上升,一些节点降低,帮助树平衡
* 保证不破坏二叉搜索树的特征
旋转分为左旋转和右旋转,那么咱们就看下左旋转和右旋转是怎么回事。
4.1 左旋转
此处,以50为支点进行逆时针旋转,而后75成为了顶点,50成为了75的左子节点,65成为了50的右子节点,这个操做就是左旋转。
4.2 右旋转
此处,以75为支点顺时针旋转,而后50成为了顶点,75成为了50的右子节点,65成为了75的左子节点,这就是右旋转操做。
5.插入操做
在介绍插入操做前,先约定一下各个节点的名称。看图:
在红黑树中,插入一个节点,都执行了哪些操做呢?
首先,查找要插入节点的位置,而后给予颜色(红色或者黑色),可是为了保证红黑树的特性,须要进行旋转或者更改颜色,须要注意的是,在旋转前和旋转后,红黑树一直都是一个二叉搜索树,二叉搜索树的特征从未改变过。
这里,对于新插入的节点,咱们给予的颜色是红色,是由于为了和红黑树的第(4)条特性不冲突: 从根节点到叶节点或空子节点的每条路径,都包含相同数目的黑色节点。这样就少了不少操做。而后看其它几个特性
* 对于(1)特性:每一个节点要么是黑色,要么是红色,不冲突。
* 对于(2)特性:根节点是黑色,若是插入的节点不是根节点,也不冲突。若是是根节点,那么直接给予颜色黑色
那么,惟一须要知足的特性就是(3):若是节点是红色的,那么它的子节点必须是黑色的。这里,当咱们给予新插入的节点颜色是红色时,须要根据父节点的不一样状况作不一样的处理,以知足这个特性。有如下几种状况:
根据代码来理解这几种状况:
/* 红黑树修正程序 */ void insert_repair_tree(struct node* n) {
//状况一:节点N的父节点为null,说明该节点是根节点 if (parent(n) == NULL) { insert_case1(n); } else if (parent(n)->color == BLACK) {
//状况二:父节点是黑色 insert_case2(n); } else if (uncle(n)->color == RED) {
//状况三: 父节点和叔叔节点都是红色 insert_case3(n); } else {
//状况四:父节点是红色,叔叔节点是黑色 insert_case4(n); } }
5.1:若是新插入节点是根节点,那么给予该节点颜色是黑色
看代码:
/* 状况一:插入的节点是根节点 */ void insert_case1(struct node* n) { if (parent(n) == NULL) n->color = BLACK; }
5.2:若是新插入节点的父节点是黑色,那么给予该节点是红色不会对该红黑树有任何影响,因此不作任何处理。
看代码:
/* 状况二:父节点是黑色 */ void insert_case2(struct node* n) { return; /* Do nothing since tree is still valid */ }
5.3:若是新插入节点的父节点是红色,同时叔叔节点也是红色
须要将父亲节点和叔叔节点从新绘制为黑色,祖父节点绘制为红色。可是,若是此处祖父节点为根节点,那么须要调用红黑树修正程序,将祖父节点绘制为黑色。
看代码:发现看代码比看图要更容易理解,而且逻辑也更严谨,对于父亲节点和叔叔节点都是红色时,发现其余文章都没有考虑祖父节点为根节点的状况,这里图就省了。
/* 状况三:父节点和叔叔节点都是红色 */ void insert_case3(struct node* n) { parent(n)->color = BLACK; uncle(n)->color = BLACK; grandparent(n)->color = RED;
//调用红黑树修正程序 insert_repair_tree(grandparent(n)); }
5.4:若是新插入节点的父节点是红色,同时叔叔节点是黑色
看代码:
/* 状况四:第一步 */ void insert_case4(struct node* n) { struct node* p = parent(n); struct node* g = grandparent(n); if (n == g->left->right) { /* 若是父节点是祖父节点的左子节点,新插入节点是父节点的右子节点,那么以父节点为支点左旋 */ rotate_left(p); n = n->left; } else if (n == g->right->left) { /* 若是父节点是祖父节点的右子节点,新插入节点是父节点的左子节点,那么以父节点为支点右旋 */ rotate_right(p); n = n->right; } insert_case4step2(n); }
/* 状况四:第二步 */
/* 在第二步时:当前节点n确定处于 祖父节点左子节点的左侧,或者祖父节点右子节点的右侧 */ void insert_case4step2(struct node* n) {
struct node* p = parent(n); struct node* g = grandparent(n); if (n == p->left)
/* 以祖父节点为支点进行右旋 */ rotate_right(g); else rotate_left(g); p->color = BLACK; g->color = RED; }
在知足父节点是红色,叔叔节点是黑色的条件下,咱们能够分如下几种状况:
状况(a):父节点p是祖父节点g的左子节点,新插入节点n是父节点p的右子节点
处理:以父节点为支点左旋,而后以祖父节点为支点进行右旋,最后给父节点(第一次旋转后的父节点)绘制为黑色,给祖父节(第一次旋转后的祖父节点)点绘制为红色
看图:
状况(b):父节点p是祖父节点g的右子节点,新插入节点n是父节点p的左子节点
处理:以父节点为支点进行右旋,而后以祖父为支点进行左旋,最后给父节点(第一次旋转后的父节点)绘制为黑色,给祖父节(第一次旋转后的祖父节点)点绘制为红色
看图:
状况(c):父节点p是祖父节点g的左子节点,新插入节点n是父节点p的左子节点
处理:以祖父节点为支点进行右旋,最后给父节点绘制为黑色,给祖父节点绘制为红色,也就是直接调用第二步的方法 insert_case4step2
状况(d):父节点p是祖父节点g的右子节点,新插入节点n是父节点p的右子节点
处理:以祖父为支点进行左旋,最后给父节点绘制为黑色,给祖父节点绘制为红色,也就是直接调用第二步的方法 insert_case4step2
6 删除操做
对于删除操做,处理的逻辑是:根据二叉搜索树的特色找到被删除的节点,将其删除掉,而后再经过改变颜色和旋转操做保持其红黑树的特性。此处咱们统一规定被删除节点是D,真正被删除节点为RD,真正被删除节点的兄弟节点是B,真正被删除节点的父节点是P,B的两个子节点是BL和BR。删除操做有三种状况:
6.1 被删除节点为叶子节点,分为两种状况:
状况(a):该叶子节点是红色
处理:直接删除
状况(b):该叶子节点是黑色,
处理:删除节点,而后旋转和变换颜色。
6.2 被删除节点只有一个子节点,那么使被删除节点的父节点指向被删除节点的子节点,而后经过改变节点颜色和旋转来保持红黑树特性
6.3 被删除节点D有两个子节点,那么能够将被删除节点D的后继节点(D的右子树中的最小元素RD)的值赋值给被删除节点D,可是被删除节点D的颜色不作改变,此时删除操做就转化为了删除后继节点RD的状况了。那么删除RD的状况又分为五种:
状况(a): RD节点是红色的
处理:若是RD节点是红色的,那么它的父节点和右子节点应该是黑色的,当删除RD节点时,直接把RD节点的父节点指向RD节点的右子节点便可。
下面四种状况RD节点都是黑色的
状况(b):RD的兄弟节点B为红色
处理:若是B节点为红色,那么P节点和B的两个子节点都是黑色。交换B节点和P节点的颜色,而后以P节点为支点进行左旋转。这样处理后,就将状况(b)转为(c),(d),(e)中的一种。
看图:
状况(c):
后续更新。。。
参考资料:
模拟红黑树:https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
维基百科红黑树:https://en.wikipedia.org/wiki/Red%E2%80%93black_tree#Insertion