C/C++全栈开发学习笔记
1.1.1 随处可见的红黑树

学习目标:
红黑树的性质:
- 每个结点是红的或者黑的
- 根结点是黑的
- 每个叶子结点是黑的
- 如果一个结点是红的,则它的两个儿子都是黑的(不存在相邻红色)
- 对每个结点,从该结点到其子孙结点的所有路径包含相同数目的黑节点(即黑高度相同)
- 最长路径长度不超过最短路径长度的2倍(2n-1,一条黑红黑红,一条全黑)
红黑树的优点:插入和删除的时间复杂度优于平衡二叉搜索树,又没有二叉搜索树可能退化为链表的Bug
- rbTree查询元素:O(log(N))
- rbTree插入元素:插入最多2次旋转,加上查询的时间O(log(N)),插入的复杂度O(log(N))
- rbTree删除元素:删除最多需要3次旋转,加上查询的时间,删除的复杂度O(log(N))
红黑树的应用场景
- c++ stl map,set(红黑树的封装)
- 进程调度cfs(用红黑树存储进程的集合,把调度的时间作为key,那么树的左下角时间就是最小的)
- 内存管理(每次使用malloc的时候都会分配一块小内存出来,那么这么块就是用红黑树来存,如何表述一段内存块呢,用开始地址+长度来表示,所以key->开始地址,val->大小)
- epoll中使用红黑树管理socketfd
- nginx中使用红黑树管理定时器,中序遍历第一个就是最小的定时器
红黑树结点的定义
1 2 3 4 5 6 7 8 9 10 11 12 13
| typedef struct _rbtree_node { unsigned char color; struct _rbtree_node *parent; struct _rbtree_node *left; struct _rbtree_node *right;
KEY_TYPE key;
} rbtree_node;
|
红黑树的定义
1 2 3 4 5 6
| struct rbtree { rbtree_node *root; rbtree_node *nil; };
|
红黑树的四个难点:删除、插入、调整、左旋右旋
红黑树结点的左旋与右旋

红黑树的左旋操作:左旋就是向左倾斜,记住要操作三条线,六个指针,分别是:
- x->right = y->left | y->left->parent = x
- y->parent = x->parent | x->parent->left/right = y
- x->parent->left/right = y | y->left->parent = x
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void rbtree_left_rotate(struct rbtree *T, rbtree_node *x) { if(x == T->nil) return; rbtree_node *y = x->right; x->right = y->left; if(y->left != T->nil) { y->left->parent = x; } y->parent = x->parent; if(x->parent == T->nil) { T->root = y; } else if(x == x->parent->left) { x->parent->left = y; } else { x->parent->right = y; } y->left = x; x->parent = y; }
|
红黑树的右旋操作:右旋就是向右倾斜,由于左旋和右旋是完全对称的,因此在代码上可以直接替换来实现右旋
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
void rbtree_right_rotate(struct rbtree *T, rbtree_node *y) { if (y == T->nil) return ; rbtree_node *x = y->left; y->left = x->right; if (x->right != T->nil) { x->right->parent = y; } x->parent = y->parent; if (y->parent == T->nil) { T->root = x; } else if (y == y->parent->right) { y->parent->right = x; } else { y->parent->left = x; } x->right = y; y->parent = x; }
|
注意红黑树的左旋右旋代码执行顺序
红黑树插入结点后的调整
当插入一个结点时,有时要对红黑树进行调整,包括变色和左旋右旋以及回溯这三个操作
CASE 1:父节点是爷结点的左子树 且 叔结点是红色的
无需旋转,只需要将父节点和叔结点变黑,将爷结点变红,然后令z指向爷结点即回溯调整
CASE 2:父节点是叶结点的左子树的情况 且 叔结点是红色 以及 当前结点是右孩子
此时需要先旋转变为第三种情况,然后按照第三种情况处理,即让当前结点指向其父结点并进行一次左旋
CASE 3:父节点是祖父结点的左子树 且 叔节点是黑色的 以及 当前结点是左孩子
此时需要先将当前结点的父节点变为黑色,以及让爷结点变为红色,然后让爷结点进行一次右旋
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| void rbtree_insert_fixup(rbtree *T, rbtree_node *z) { while (z->parent->color == RED) { if (z->parent == z->parent->parent->left) { rbtree_node *y = z->parent->parent->right; if (y->color == RED) { z->parent->color = BLACK; y->color = BLACK; z->parent->parent->color = RED; z = z->parent->parent; } else { if (z == z->parent->right) { z = z->parent; rbtree_left_rotate(T, z); } z->parent->color = BLACK; z->parent->parent->color = RED; rbtree_right_rotate(T, z->parent->parent); } } else { rbtree_node *y = z->parent->parent->left; if (y->color == RED) { z->parent->color = BLACK; y->color = BLACK; z->parent->parent->color = RED; z = z->parent->parent; } else { if (z == z->parent->left) { z = z->parent; rbtree_right_rotate(T, z); } z->parent->color = BLACK; z->parent->parent->color = RED; rbtree_left_rotate(T, z->parent->parent); } } } T->root->color = BLACK; }
|
红黑树插入结点
插入其实很简单,不过是判断大小找到合适的插入位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| void rbtree_insert(rbtree *T, rbtree_node *z) { rbtree_node *y = T->nil; rbtree_node *x = T->root; while (x != T->nil) { y = x; if (z->key < x->key) { x = x->left; } else if (z->key > x->key) { x = x->right; } else { return ; } } z->parent = y; if (y == T->nil) { T->root = z; } else if (z->key < y->key) { y->left = z; } else { y->right = z; } z->left = T->nil; z->right = T->nil; z->color = RED; rbtree_insert_fixup(T, z); }
|
红黑树删除结点
待办