简单红黑树入门

定义和性质

红黑树是种平衡二叉搜索树,是特化的AVL树,查找和二叉搜索树无异,但在插入和删除时通过调整保持二叉搜索树的平衡,相对于AVL树,牺牲部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树。可在O(logn)内查找、插入和删除,因此应用非常广泛,非常重要。

红黑树必须满足以下性质:

  • 每个结点要么是黑色,要么是红色。

  • 根结点是黑色。

  • 每个叶子结点(NIL)是黑色。

  • 每个红结点的两个子结点一定是黑色。(即不能连续红色,红结点定被黑父子相连)

  • 任意一结点到每个叶子结点的路径都包含数量相同的黑结点。(即黑高必须相等)

实际上红色结点只是方便调整(旋转和变色)。此外由后两条性质,可证明红黑树高h<=2*logn,先去所有红结点得全满二叉树,h1<=logn,再加上红节点,h2<=logn,因此得证

性能

  • 查找代价:红黑树红黑树虽不像AVL严格平衡,但平衡性能还是比BST好。其查找代价基本在O(logN)左右
  • 插入代价:插入结点最多只需要2次旋转,虽然变色操作需要O(logN),但变色十分简单代价小
  • 删除代价:删除操作代价要比AVL要好,删除一个结点最多只需要3次旋转操作

如果查找、插入、删除频率差不多,那么选择红黑树

应用

红黑树在工程上应用,主要包括两点:

  • 利用红黑树顺序(中序遍历)的功能,如找结点的前驱、后继结点
  • 利用红黑树快速查找的功能,适合做索引(索引常用红黑树、hash、b+树),如map的实现

适合在内存中,且数量不确定的应用场景,如实际应用有Linux进程调度器、nginx Timer事件管理、epoll事件块的管理、内存管理等

操作

红黑树能自平衡,靠的是旋转和变色的调整:

  • 旋转:和颜色无关,三对6个指针(旋转结点父结点和子结点的指向、旋转结点子节点的子节点指向,还包括各结点的父指针)指向要变,左旋逆时针,右旋顺时针
    • 左旋:以x为支点,其右子结点y变为其父结点,y的左子结点b变为x的右子结点,x的左子结点a不变,左旋影响旋转结点,并把右子树的结点往左子树移
    • 右旋:以y为支点,其左子结点x变为其父结点,x的右子结点b变为y的左子结点,y的右子结点c不变,右旋影响旋转结点,并把左子树的结点往右子树移
  • 变色:结点的颜色由红变黑或由黑变红

截图:6条指针分别对应下面代码中的注释
image-20220211152621068
代码:

void rbtree_left_rotate(rbtree *T,rbtree_node *x)
{
    rbtree_node *y = x->right;
    x->right = y->left;//1
    if(y->left!=T->nil)y->left->parent = x;//2

    y->parent=x->parent;//3
    //4
    if(x->parent==T->nil)T->root=y;
    else if(x->parent->left==x)x->parent->left=y;
    else x->parent->right=y;

    y->left=x;//5
    x->parent=y;//6
}

右旋只需交换x和y,交换left和right

插入

插入和二叉搜索树也是在末尾插入,不会旋转,而是插入后可能会调整引起旋转,插入共分3种:

  1. 树为空树:插入结点作为根并设为黑色
  2. 插入结点key已存在:更新值即可
  3. 插入结点父结点为黑:直接插入
  4. 插入结点父结点为红:父不为根则必有黑色祖父,按叔颜色分类调整
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 { //2 Exist
			return ;
		}
	}

	//now x is nil and y point to x's parent
	z->parent = y;
	if (y == T->nil) {
		T->root = z;//1
	} else if (z->key < y->key) {//3
		y->left = z;
	} else {//3
		y->right = z;
	}

	z->left = T->nil;
	z->right = T->nil;
	z->color = RED;

	rbtree_insert_fixup(T, z);//4
}

插入后的调整

为不影响黑高插入结点为红色,且只有在父为红色时才会调整(违反性质4),则祖父必定是黑色的,叔结点有两种(假定父结点是祖父结点的左子树,右子树同理),调整的情况有两类3种:

  1. 叔是红色:父和叔同祖父换色,黑高两边相同不旋转(无论结点是左还是右孩子)
    image-20220211152914180

  2. 叔是黑色:分两种,因为旋转方向不同:

    1. 当前结点是右孩子:对父节点左旋,转成下面情况

    image-20220211153052218
    3. 当前结点是左孩子:父和祖父换色,并对祖父右旋
    image-20220211153211336

void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {

	while (z->parent->color == RED) { //z ---> RED
		if (z->parent == z->parent->parent->left) {
			rbtree_node *y = z->parent->parent->right;
			if (y->color == RED) {//1
				z->parent->color = BLACK;
				y->color = BLACK;
				z->parent->parent->color = RED;

				z = z->parent->parent; //循环向上调整
			} else {

				if (z == z->parent->right) {//2,情况2后面转成情况3
					z = z->parent;
					rbtree_left_rotate(T, z);
				}

				z->parent->color = BLACK;//3
				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; //z --> RED
			} 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;
}

删除

删除最麻烦,删除结点是红色,未改变黑高不需调整,删除结点是黑色,则改变黑高需调整。先执行二叉搜索树的删除过程,再调整使得符合红黑树性质,删除有三种情况:

  • 删除结点无子结点:直接删除
  • 删除结点仅一子结点:用删除结点的孩子替换删除结点
  • 删除结点有两子结点:找到删除结点的后继替换删除结点
rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {

	rbtree_node *y = T->nil;//y是z的后继或z
	rbtree_node *x = T->nil;//x是y的孩子或nil

	if ((z->left == T->nil) || (z->right == T->nil)) {
		y = z;
	} else {
		y = rbtree_successor(T, z);
	}
	//2
	if (y->left != T->nil) {
		x = y->left;
	} else if (y->right != T->nil) {
		x = y->right;
	}

	x->parent = y->parent;//包含1和2的情况,修改父指针
	if (y->parent == T->nil) {
		T->root = x;
	} else if (y == y->parent->left) {
		y->parent->left = x;
	} else {
		y->parent->right = x;
	}

	//用y替换z
	if (y != z) {
		z->key = y->key;
		z->value = y->value;
	}

	if (y->color == BLACK) {//黑色需调整
		rbtree_delete_fixup(T, x);
	}

	return y;
}

前两种较简单,第三种又分两种子情形:

  • 删除结点的后继位于右子树且无左孩子
    image-20220211180145422

  • 删除结点的后继位于右子树,但不是删除结点的右孩子
    image-20220211180106678

rbtree_node *rbtree_successor(rbtree *T, rbtree_node *x) {
	rbtree_node *y = x->parent;

	if (x->right != T->nil) {
		return rbtree_mini(T, x->right);//1x有右子树则找x右子树的最小值
	}

	while ((y != T->nil) && (x == y->right)) {//2x无右子树则x是左子树的最右边,找到x的后继结点(即x所属子树的最近的根)
		x = y;
		y = y->parent;
	}
	return y;
}

删除后的调整

当前结点是父结点的左子树的情况

  • 当前结点的兄弟结点是红色,父和兄弟变色且父左旋
    image-20220211183422826

  • 当前结点的兄弟结点是黑色,而且兄弟结点的两个孩子结点都是黑色,兄弟变色
    image-20220211184422629

  • 当前结点的兄弟结点是黑色,且该节点左孩子是红色,右孩子是黑色,兄弟及兄弟左子变色并对兄弟右旋,变为最后一种情况

image-20220211185224647

  • 当前结点的兄弟结点是黑色,且该结点的右孩子是红色,又可分为4种(该结点左孩子黑和红,父黑和红),父颜色赋值给兄弟且父为黑,兄弟右孩子变色且父左旋
    image-20220211185710406
    image-20220211185958043
    image-20220211190100479
    image-20220211190213986
void rbtree_delete_fixup(rbtree *T, rbtree_node *x) {

	while ((x != T->root) && (x->color == BLACK)) {
		if (x == x->parent->left) {

			rbtree_node *w= x->parent->right;
			if (w->color == RED) {//1x的兄弟节点w是红色的,交换父叔颜色,并左旋父
				w->color = BLACK;
				x->parent->color = RED;

				rbtree_left_rotate(T, x->parent);
				w = x->parent->right;
			}

			//2x的兄弟节点w是黑色的,并且w的两个子节点都是黑色的,叔变色即可
			if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
				w->color = RED;
				x = x->parent;
			} else {

				if (w->right->color == BLACK) {//3x的兄弟节点w是黑色的,而且w的左孩子是红色的,w的右孩子是黑色的
					w->left->color = BLACK;
					w->color = RED;
					rbtree_right_rotate(T, w);
					w = x->parent->right;
				}

				w->color = x->parent->color;//4x的兄弟节点w是黑色的,并且w的右孩子是红色的
				x->parent->color = BLACK;
				w->right->color = BLACK;
				rbtree_left_rotate(T, x->parent);

				x = T->root;
			}

		} else {//x是右子树,与上面完全对称

			rbtree_node *w = x->parent->left;
			if (w->color == RED) {
				w->color = BLACK;
				x->parent->color = RED;
				rbtree_right_rotate(T, x->parent);
				w = x->parent->left;
			}

			if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
				w->color = RED;
				x = x->parent;
			} else {

				if (w->left->color == BLACK) {
					w->right->color = BLACK;
					w->color = RED;
					rbtree_left_rotate(T, w);
					w = x->parent->left;
				}

				w->color = x->parent->color;
				x->parent->color = BLACK;
				w->left->color = BLACK;
				rbtree_right_rotate(T, x->parent);

				x = T->root;
			}

		}
	}

	x->color = BLACK;
}

nginx中红黑树源码分析

后续准备分析nginx中的红黑树的代码实现。。。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
红黑树是一种自平衡的二叉搜索树,它具有以下特性: 1. 每个节点要么是红色,要么是黑色。 2. 根节点是黑色的。 3. 每个叶子节点(NIL节点,空节点)是黑色的。 4. 如果一个节点是红色的,则它的两个子节点都是黑色的。 5. 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点。 下面是一个简单的C++实现红黑树的代码示例: ```cpp #include <iostream> enum Color { RED, BLACK }; struct Node { int data; Color color; Node* left; Node* right; Node* parent; Node(int data) : data(data), color(RED), left(nullptr), right(nullptr), parent(nullptr) {} }; class RedBlackTree { private: Node* root; void rotateLeft(Node* node) { Node* rightChild = node->right; node->right = rightChild->left; if (rightChild->left != nullptr) { rightChild->left->parent = node; } rightChild->parent = node->parent; if (node->parent == nullptr) { root = rightChild; } else if (node == node->parent->left) { node->parent->left = rightChild; } else { node->parent->right = rightChild; } rightChild->left = node; node->parent = rightChild; } void rotateRight(Node* node) { Node* leftChild = node->left; node->left = leftChild->right; if (leftChild->right != nullptr) { leftChild->right->parent = node; } leftChild->parent = node->parent; if (node->parent == nullptr) { root = leftChild; } else if (node == node->parent->left) { node->parent->left = leftChild; } else { node->parent->right = leftChild; } leftChild->right = node; node->parent = leftChild; } void fixInsertion(Node* node) { while (node != root && node->parent->color == RED) { if (node->parent == node->parent->parent->left) { Node* uncle = node->parent->parent->right; if (uncle != nullptr && uncle->color == RED) { node->parent->color = BLACK; uncle->color = BLACK; node->parent->parent->color = RED; node = node->parent->parent; } else { if (node == node->parent->right) { node = node->parent; rotateLeft(node); } node->parent->color = BLACK; node->parent->parent->color = RED; rotateRight(node->parent->parent); } } else { Node* uncle = node->parent->parent->left; if (uncle != nullptr && uncle->color == RED) { node->parent->color = BLACK; uncle->color = BLACK; node->parent->parent->color = RED; node = node->parent->parent; } else { if (node == node->parent->left) { node = node->parent; rotateRight(node); } node->parent->color = BLACK; node->parent->parent->color = RED; rotateLeft(node->parent->parent); } } } root->color = BLACK; } public: RedBlackTree() : root(nullptr) {} void insert(int data) { Node* newNode = new Node(data); Node* current = root; Node* parent = nullptr; while (current != nullptr) { parent = current; if (data < current->data) { current = current->left; } else { current = current->right; } } newNode->parent = parent; if (parent == nullptr) { root = newNode; } else if (data < parent->data) { parent->left = newNode; } else { parent->right = newNode; } fixInsertion(newNode); } void printInorder(Node* node) { if (node == nullptr) { return; } printInorder(node->left); std::cout << node->data << " "; printInorder(node->right); } void printTree() { printInorder(root); } }; int main() { RedBlackTree tree; tree.insert(7); tree.insert(3); tree.insert(18); tree.insert(10); tree.insert(22); tree.insert(8); tree.insert(11); tree.insert(26); tree.insert(2); tree.insert(6); tree.insert(13); std::cout << "红黑树中序遍历结果:" << std::endl; tree.printTree(); return 0; } ``` 这段代码实现了红黑树的插入操作,并提供了一个简单的测试例子。你可以根据需要进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值