红黑树

简介

一直想写的一种数据结构,非常厉害的思想,插入,删除,查找,修改,都是\(log_2 n\)的时间复杂度。

比AVL更强大的是,插入删除综合效率比AVL要优秀一点。

性质

一颗红黑树是满足红黑性质的二叉搜索树:

  1. 每个节点是红色或者黑色的。
  2. 根节点是黑色的。
  3. 每个叶节点(NIL)是黑色的
  4. 如果一个节点是红色的,那么它的两个子节点都是黑色的。
  5. 对于每个节点,从当前节点到其所有后代叶节点的简单路径上,黑节点的个数是相同的。

插入

插入肯定是插入到某叶节点的位置,颜色设为红色。所以可能违反性质4,按一定的规则修复。

插入修复可能需要多次变色,但旋转最多2次。

共分为3种情况(是父节点的左右孩子,对称共8种情况,以本节点是父节点的左孩子为例):

  1. 叔节点为红色。
  2. 叔节点为黑色,且本节点为父节点的右孩子。
  3. 叔节点为黑色,且本节点为父节点的左孩子。

2和3情况需要旋转。情况1可能变为1,2,3。但2只能变为3,3完成修复,所以最多2次旋转。

删除

删除一定是非叶节点,在二叉搜索树的删除方法上做了一定改动,并且按一定规则修复。

删除修复可能需要多次变色,但旋转最多3次。

共分为4种情况(是父节点的左右孩子,对称共8种情况,以本节点是父节点的左孩子为例):

  1. 兄弟节点为红色,且左右孩子节点都为黑色。
  2. 兄弟节点为黑色,且左右孩子节点都为黑色。
  3. 兄弟节点为黑色,且左孩子节点为红色,右孩子节点为黑色。
  4. 兄弟节点为黑色,且左孩子节点为黑色,右孩子节点为红色。

实现

# include <cstdio>
# include <iostream>
using namespace std; /**************************/ /*
红黑树的定义:
1.每个结点要么是红色,要么是黑色。
2.根结点是黑色的。
3.每个叶结点(NIL)是黑色的。
4.如果一个结点是红色的,那么它的两个子结点是黑色的。
5.每个结点到后代的叶结点的简单路径上的黑色结点个数相同。
*/ // 规定颜色
const int RED = 0;
const int BLACK = 1; struct RBTreeNode {
int key;
int color; // 颜色
RBTreeNode * p; // 父节点
RBTreeNode * left; // 左孩子
RBTreeNode * right; // 右孩子
} * NIL, * root; /// 初始化
void init() {
NIL = new RBTreeNode;
NIL->color = BLACK;
NIL->p = NULL;
NIL->left = NULL;
NIL->right = NULL;
root = NIL;
} /// 新建节点
RBTreeNode * create_node(int key) {
RBTreeNode * p = new RBTreeNode;
p->key = key;
p->p = NIL;
p->left = NIL;
p->right = NIL;
p->color = RED;
return p;
} /// 根据键查询
RBTreeNode * search_node(int key) {
RBTreeNode * x = root;
while(x!=NIL && x->key != key) {
if (key < x->key) x = x->left;
else x = x->right;
}
return x;
} /// 查找某子树最小结点
RBTreeNode * search_minimum(RBTreeNode * p) {
if (p == NIL) return NIL;
while(p->left != NIL) p = p->left;
return p;
} /// 查找某子树最大结点
RBTreeNode * search_maximum(RBTreeNode * p) {
if (p == NIL) return NIL;
while (p->right != NIL) p = p->right;
return p;
} /// 查询结点前驱结点(结点)
RBTreeNode * search_predecessor(RBTreeNode * p) {
if (p == NIL) return NIL;
if (p->left != NIL) {
return search_maximum(p->left); // 拥有左子树,后继一定是左子树的最大节点
} else {
RBTreeNode * y = p->p;
while(y!=NIL && y->left==p) { // 找到高层节点中以p所在的树为右子树的树的根节点,即是前驱节点
p = y;
y = y->p;
}
return y;
}
} /// 查找结点后继节点(结点)
RBTreeNode * search_successor(RBTreeNode * p) {
if (p == NIL) return NIL;
if (p->right != NIL) {
return search_minimum(p->right); // 拥有右子树,后继一定是右子树的最小节点
} else {
RBTreeNode * y = p->p;
while(y!=NIL && y->right==p) { // 找到高层节点中以p所在的树为左子树的树的根节点,即是后继节点
p = y;
y = y->p;
}
return y;
}
} /// 替换子树, u被v替换
void transplant_node(RBTreeNode * u, RBTreeNode * v) {
if (u->p == NIL) {
root = v;
} else if (u->p->left == u) {
u->p->left = v;
} else u->p->right = v;
if (v != NIL) {
v->p = u->p;
}
} /// 结点左旋(x, y不为NIL)
void left_rotate(RBTreeNode * x) {
RBTreeNode * y = x->right;
transplant_node(x, y);
RBTreeNode * z = y->left;
x->p = y;
y->left = x;
if (z != NIL) z->p = x;
x->right = z;
} /// 结点右旋(x, y不为NIL)
void right_rotate(RBTreeNode * x) {
RBTreeNode * y = x->left;
transplant_node(x, y);
RBTreeNode * z = y->right;
x->p = y;
y->right = x;
if (z != NIL) z->p = x;
x->left = z;
} /// 插入结点调整
void insert_node_fixup(RBTreeNode * x) {
while (x->p->color == RED) {
RBTreeNode * y = x->p;
if (y->p->left == y) { // 位于爷结点的左子树
RBTreeNode * z = y->p->right;
if (z->color == RED) { // case1: 叔结点是红色
z->color = BLACK;
y->color = BLACK;
y->p->color = RED;
x = y->p;
continue;
}
if (y->right == x) { // case2: 叔结点是黑色,是父结点的右孩子
x = x->p;
left_rotate(y);
}
x->p->color = BLACK; // case3: 叔结点是黑色,是父结点的左孩子
x->p->p->color = RED;
right_rotate(x->p->p);
} else { // 位于爷结点的右子树
RBTreeNode * z = y->p->left;
if (z->color == RED) {
z->color = BLACK;
y->color = BLACK;
y->p->color = RED;
x = y->p;
continue;
}
if (y->left == x) {
x = x->p;
right_rotate(y);
}
x->p->color = BLACK;
x->p->p->color = RED;
left_rotate(x->p->p);
}
}
root->color = BLACK;
} /// 插入结点(结点)
void insert_node(RBTreeNode * z) {
RBTreeNode * x = root;
RBTreeNode * y = NIL;
while (x!=NIL) {
y = x;
if (z->key < x->key) x = x->left;
else x = x->right;
}
z->p = y;
if (y == NIL)
root = z;
else if (z->key < y->key)
y->left = z;
else
y->right = z;
insert_node_fixup(z);
} /// 调整删除结点
void delete_node_fixup(RBTreeNode * x) {
while(x != root && x->color == BLACK) {
if (x->p->left == x) {
RBTreeNode * w = x->p->right;
if (w->color == RED) { // case1: 兄弟结点是红色
x->p->color = RED;
w->color = BLACK;
left_rotate(x->p);
}
if (w->left->color == BLACK && w->right->color == BLACK) { // case2: 兄弟结点是黑色,并且双亲为黑色
w->color = RED;
x = x->p;
continue;
}
if (w->right->color != RED) { // case3: 兄弟结点是黑色,左孩子为红色
w->left->color = BLACK;
w->color = RED;
right_rotate(w);
}
// case4: 兄弟结点是黑色,右孩子是红色
w->color = x->p->color;
w->right->color = BLACK;
x->p->color = BLACK;
left_rotate(x->p);
x = root;
} else {
RBTreeNode * w = x->p->left;
if (w->color == RED) { // case1: 兄弟结点是红色
x->p->color = RED;
w->color = BLACK;
right_rotate(x->p);
}
if (w->right->color == BLACK && w->left->color == BLACK) { // case2: 兄弟结点是黑色,并且双亲为黑色
w->color = RED;
x = x->p;
continue;
}
if (w->left->color != RED) { // case3: 兄弟结点是黑色,左孩子为红色
w->right->color = BLACK;
w->color = RED;
left_rotate(w);
}
// case4: 兄弟结点是黑色,右孩子是红色
w->color = x->p->color;
w->left->color = BLACK;
x->p->color = BLACK;
right_rotate(x->p);
x = root;
}
}
x->color = BLACK;
} /// 删除结点(结点)
void delete_node(RBTreeNode * z) {
RBTreeNode * x; // 记录被删除的结点在树中所处的位置
RBTreeNode * y = z; // 记录实际被删除的结点
int y_origin_color = y->color;
if (z->left == NIL) {
x = z->right;
transplant_node(z, z->right);
} else if (z->right == NIL) {
x = z->left;
transplant_node(z, z->left);
} else { // 左右孩子都存在的情况
y = search_minimum(z->right); // 找后继节点
y_origin_color = y->color;
x = y->right;
if (y != x->right) { // 如果后继不是右孩子,需要变形。将后继节点提为右子树的根节点
transplant_node(y, y->right); // 后继节点的左孩子一定不存在,右孩子取代后继节点
y->right = z->right;
y->right->p = y;
}
// 后继就是右孩子
transplant_node(z, y);
y->left = z->left; // 替换后还需要修改与左子树的父子关系与颜色
z->left->p = y;
y->color = z->color;
}
delete z;
if (y_origin_color == BLACK) delete_node_fixup(x);
} /** --- */ bool insert_node(int key) {
RBTreeNode * node = search_node(key);
if (node != NIL) return false;
node = create_node(key);
insert_node(node);
return true;
} bool delete_node(int key) {
RBTreeNode * node = search_node(key);
if (node == NIL) return false;
delete_node(node);
return true;
} int main() {
init(); RBTreeNode * x = NIL;
if (x == NIL){
printf("==\n");
} else {
printf("!=\n");
} insert_node(1);
// insert_node(3);
// insert_node(5);
// insert_node(7);
// insert_node(9);
//
// insert_node(2);
// insert_node(4);
// insert_node(6);
// insert_node(8);
// insert_node(10);
//
// delete_node(3);
// delete_node(7);
// delete_node(6);
// delete_node(1); while (1) {
int k;
scanf("%d", &k);
RBTreeNode * p = search_node(k);
if (p == NIL) printf("NIL\n");
else printf("OK!\n");
} return 0;
}

红黑树实现(c/c++)的更多相关文章

  1. 红黑树——算法导论(15)

    1. 什么是红黑树 (1) 简介     上一篇我们介绍了基本动态集合操作时间复杂度均为O(h)的二叉搜索树.但遗憾的是,只有当二叉搜索树高度较低时,这些集合操作才会较快:即当树的高度较高(甚至一种极 ...

  2. jdk源码分析红黑树——插入篇

    红黑树是自平衡的排序树,自平衡的优点是减少遍历的节点,所以效率会高.如果是非平衡的二叉树,当顺序或逆序插入的时候,查找动作很可能会遍历n个节点 红黑树的规则很容易理解,但是维护这个规则难. 一.规则 ...

  3. 谈c++ pb_ds库(二) 红黑树大法好

    厉害了,没想到翻翻pb_ds库看到这么多好东西,封装好的.现成的splay.红黑树.avl... 即使不能在考场上使用也可以用来对拍哦 声明/头文件 #include <ext/pb_ds/tr ...

  4. 定时器管理:nginx的红黑树和libevent的堆

    libevent 发生超时后, while循环一次从堆顶del timer——直到最新调整的最小堆顶不是超时事件为止,(实际是del event),但是会稍后把这个timeout的 event放到ac ...

  5. 从2-3-4树到红黑树(下) Java与C的实现

    欢迎探讨,如有错误敬请指正 如需转载,请注明出处   http://www.cnblogs.com/nullzx/ 相关博客: 从2-3-4树到红黑树(上) 从2-3-4树到红黑树(中) 1. 实现技 ...

  6. 红黑树/B+树/AVL树

    RB Tree 红黑树  :http://blog.csdn.net/very_2/article/details/5722682 Nginx的RBTree实现   :http://blog.csdn ...

  7. 论AVL树与红黑树

    首先讲解一下AVL树: 例如,我们要输入这样一串数字,10,9,8,7,15,20这样一串数字来建立AVL树 1,首先输入10,得到一个根结点10 2,然后输入9, 得到10这个根结点一个左孩子结点9 ...

  8. DataStructure——红黑树学习笔记

    1.前言 本文伪码和解释参考: http://blog.csdn.net/v_JULY_v/article/details/6105630 C实现的源码本文未贴出,请见: http://blog.cs ...

  9. 红黑树(Red-Black tree)

    红黑树又称红-黑二叉树,它首先是一颗二叉树,它具体二叉树所有的特性.同时红黑树更是一颗自平衡的排序二叉树.我们知道一颗基本的二叉树他们都需要满足一个基本性质–即树中的任何节点的值大于它的左子节点,且小 ...

  10. map,hash_map, hash_table, 红黑树 的原理和使用

    在刷算法题的时候总是碰到好多题,号称可以用hash table来解题.然后就蒙圈了. 1.首先,map和hash_map的区别和使用: (1)map底层用红黑树实现,hash_map底层用hash_t ...

随机推荐

  1. LG4171/BZOJ1823 「JSOI2010」满汉全席 2-SAT

    问题描述 LG4171 BZOJ1823 题解 显然,每个评委对每个材料的满式/汉式要求是对\(n\)个元素的\(0,1\)取值限制. 显然想到\(\mathrm{2-SAT}\) 于是就可以切掉了. ...

  2. Spring Cloud微服务安全实战_3-8_API安全之登录

    前面的文章 https://www.cnblogs.com/lihaoyang/p/11967121.html  说了用过滤器实现HttpBasic 认证 ,在请求头里携带用户名和密码,存在的问题是, ...

  3. HTML中,input元素的 Disabled属性 所产生的后端无法接收数据的问题

    背景 今天从前端提交 form表单 数据时,发现 设置 Disabled 的 input 元素的字段数据在后端无法接收到 原因 查阅资料(来自W3school): disabled 属性规定应该禁用 ...

  4. RHEL7 安装Docker-CE

    rhel7官方有源可以直接使用,前提是需要订阅, 参考地址 通过添加CentOS7 源,进行安装: 通过添加CentOS7 源,进行安装 参考博客 安装container-selinux依赖(Requ ...

  5. 2019CCPC网络预选赛 八道签到题题解

    目录 2019中国大学生程序设计竞赛(CCPC) - 网络选拔赛 6702 & 6703 array 6704 K-th occurrence 6705 path 6706 huntian o ...

  6. 团队作业第五次—项目冲刺-Day4

    Day4 part1-SCRUM: 项目相关 作业相关 具体描述 所属班级 2019秋福大软件工程实践Z班 作业要求 团队作业第五次-项目冲刺 作业正文 hunter--冲刺集合 团队名称 hunte ...

  7. 初探Java设计模式4:一文带你掌握JDK中的设计模式

    转自https://javadoop.com/post/design-pattern 行为型模式 策略模式 观察者模式 责任链模式 模板方法模式 状态模式 行为型模式总结 本系列文章将整理到我在Git ...

  8. vue 学习注意事项

    一:插值方式: 1:数据绑定,最常见的形式就是使用 “Mustache” 语法(双大括号)的文本插值 <span>Message: {{ msg }}</span>  通过使用 ...

  9. Linux搭建Nexus仓库+高可用方案

    Linux搭建nexus仓库 1.安装jdk 1.1 获取安装包,解压到指定目录: tar xf jdk.tar.gz -C /opt/export 1.2 配置环境变量: # vim /etc/pr ...

  10. Adams宏导出

    var set var=ip integer_value=1 var set var=macro_name str="" for variable_name=the_macro o ...