Loading

红黑树介绍

红黑树

1 红黑树的引入

有了二叉搜索树,为什么还需要平衡二叉树?

  1. 二叉搜索树容易退化成一条链,
  2. 这个时候查找时间复杂度从 O(log2N) 退化成 O(N)
  3. 引入对左右子树高度差有限制的平衡二叉树,保证查找操作的最坏时间复杂度也为 O(log2N)

有了平衡二叉树,为什么还需要红黑树?

  1. AVL 的左右子树高度差不能超过1,每次进行插入/删除操作时,几乎都需要通过旋转操作保持平衡
  2. 在频繁进行插入/删除的场景中,频繁的旋转操作使得 AVL 的性能大打折扣
  3. 红黑树通过牺牲严格的平衡,换取插入/删除时少量的旋转操作,整体性能 优于 AVL
    1. 红黑树插入时的不平衡,不超过两次旋转就可以解决;删除时的不平衡,不超过三次旋转就能解决
  4. 红黑树的红黑规则,保证最坏的情况下,也能在 O(log2N) 时间内完成查找操作

红黑树的应用

  1. Linux 进程调度 CFS
  2. Nginx Timer 时间管理
  3. Epoll 事件块的管理

红黑树的性质 (红黑规则)

  1. 每个节点不是黑的就是红的
  2. 根节点是黑的
  3. 每个叶子节点是黑的
  4. 如果一个节点是红的,则它的两个孩子都是黑的
  5. 每个节点到叶子节点的所有路径,都包含相同数目的黑色节点(相同的黑色高度)

image

一些说明

  1. 约束4和5,保证了红黑树的大致平衡:根到叶子的所有路径中,最长路径不会超过最短路径的2倍。
  2. 使得红黑树在最坏的情况下,也能有 O(log2N)
  3. 默认新插入的节点为红色:因为父节点为黑色的概率较大,插入新节点为红色,可以避免颜色冲突
  4. 黑色叶子节点指的是 NULL

2 红黑树的左旋和右旋

红黑树左右旋转是为了恢复黑高

2.1 红黑树的定义

typedef int KEY_TYPE;

#define RED	1 // 黑
#define BLACK 2 // 红
// 定义节点
typedef struct _rbtree_node {
	unsigned char color; // 颜色
	struct _rbtree_node *right; // 左子树
	struct _rbtree_node *left; // 右子树
	struct _rbtree_node *parent; // 父亲
	KEY_TYPE key; // 键
	void *value; // 值
} rbtree_node;

// 定义树
typedef struct _rbtree {
	rbtree_node *root; // 跟节点
	rbtree_node *nil; // 小技巧,统一空的叶子节点
} rbtree;
  • 当红黑规则不满足时,需要对节点进行变色或旋转操作

2.2 左右旋转

image

左旋代码:

主要思想:右孩子变爹

主要步骤:

  1. 备份右孩子
  2. 处理新右孩子
  3. 给原有孩子认新爹
  4. 处理处理旋转好后的关系
// T 指的是树, x(原爹)指的对应上图,即轴节点
void rbtree_left_rotate(rbtree *T, rbtree_node *x) {
	// 1. 记录 x 的右孩子 y
	rbtree_node *y = x->right;
	
    // 2. 处理新右孩子
	x->right = y->left; // 然后先处理 x 的右孩子, 即赋值为新的 y->left
	if (y->left != T->nil) { // 如果 y->left 存在,记得给他认一下爹,因为红黑树记录了父亲是谁
		y->left->parent = x;
	}

    // 3. 给 y(原右孩子) 认新爹
	y->parent = x->parent; // x 的爹现在应该是 y 的爹
	if (x->parent == T->nil) { // 如果 x 是根节点,那它没有爹,那么 y 也没有爹,直接变成根节点
		T->root = y;
    // 否则判断一下是 x 是它爹的左孩子还是右孩子,相应的给 y 认好爹
	} else if (x == x->parent->left) { 
		x->parent->left = y;
	} else {
		x->parent->right = y;
	}
	
    // 4 处理一下旋转好后的 x(原爹) 和 y(新爹) 的关系
	y->left = x; // x 是 y 的左孩子
	x->parent = y; // x 的爹是 y
}

右旋代码:

技巧: 将左旋的所有left 和 right 互换, x 和 y 互换 即可,因为本来就是镜像的

void rbtree_right_rotate(rbtree *T, rbtree_node *y) {

    // 1. 备份左孩子
	rbtree_node *x = y->left;

    // 2. 处理新左孩子
	y->left = x->right;
	if (x->right != T->nil) {
		x->right->parent = y;
	}

    // 3. 认爹
	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;
	}

    // 4. 调节
	x->right = y;
	y->parent = x;
}

3 红黑树插入

口诀:左旋/右旋,三个方向,六个指针;

插入的颜色默认是红色,因为红色不会改变黑的高度。
违背的是性质4,当前节点是红色,父节点也是红色。

3.1 先插入

先将新节点插入,主要分为五步:

  1. 设置节点
  2. 找爹
  3. 认孩子
  4. 完善信息
  5. 调整树形
// T 是树, z是要插入的节点
void rbtree_insert(rbtree *T, rbtree_node *z) {

    // 1. 设置节点
	rbtree_node *y = T->nil; // y 表示为 z 的爹,默认为 nil
	rbtree_node *x = T->root; // x 表示为根节点

    // 2. 找爹
    // 找插入的位置, 即找 z 的爹
	while (x != T->nil) { // x 存在
		y = x; // 用 y 记录 z 的爹
		if (z->key < x->key) { // 在根左
			x = x->left;
		} else if (z->key > x->key) { // 在根右
			x = x->right;
		} else { // 已经存在了,不要插了,滚
			return ;
		}
	}

    // 3. 认孩子
	z->parent = y;
	if (y == T->nil) { // 没有爹,说明 z 是根节点,即是个空树
		T->root = z;
    // 有爹,判断自己是爹的左孩子还是右孩子
	} else if (z->key < y->key) {
		y->left = z;
	} else {
		y->right = z;
	}

    // 4. 登记户口,完善信息
    // 完善 z 的基本信息
	z->left = T->nil;
	z->right = T->nil;
	z->color = RED;

    // 5. 调整
	rbtree_insert_fixup(T, z);
}

3.2 再调整

父亲是祖父的左孩子:调整分三个情况

  1. 叔节点是红色(直接处理)

    1. 爹、叔叔变黑,爷爷变红
  2. 叔节点是黑色,当前节点是 右孩子 (左旋爹变成情况3处理)

  3. 叔节点是黑色,当前节点是 左孩子 (直接处理)

    1. 爹变黑,爷爷变红,右旋爷爷(目的是为了不改变右子树的黑高,因为爷爷变红了,右子树黑高变低了)

父亲是祖父的右孩子:和左旋右旋的规律一样,左边右,右边左,就行了,还是这三个情况,只不过是镜像的

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; // y是叔节点
			// 情况1,直接搞
            if (y->color == RED) { 
				z->parent->color = BLACK; // 父亲变黑
				y->color = BLACK; // 叔叔变黑
				z->parent->parent->color = RED; // 祖父变红

				z = z->parent->parent; //z --> RED
            // 情况2,3
			} else {
				// 情况2,左旋爹变成情况3
				if (z == z->parent->right) {
					z = z->parent;
					rbtree_left_rotate(T, z);
				}
				// 情况3,直接搞
				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; //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;
}

4 红黑树的删除

和插入如出一辙,也可以说简单了,因为虽然是4种情况,实际都是为了最后一种做变换, 其他啥也不是,非常简单,背就完了。

4.1 认清情况

对了,要分清楚我们真正删除的是哪一个就好理解了,例如我们想删除 z = 172,我们是用 y 的值覆盖了 z, 然后调整了 x 的位置。如下图:

image

4.1 先删除

  1. 判断z是不是俩孩子都在,都在就得找后继节点y
    1. 不是都在就 y = z直接删了
  2. 找到要用来调整的轴心节点 x, 即 y 的孩子
rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {

	rbtree_node *y = T->nil; // y 是要删除的节点
	rbtree_node *x = T->nil; // x 是要调整的轴心节点

    // 如果不是左右孩子都存在,需要找后继节点
    // 找中序遍历的后继节点,剑指offer原题,即y, 也就是我们要删除的节点
	if ((z->left == T->nil) || (z->right == T->nil)) {
		y = z;
	} else {
		y = rbtree_successor(T, z);
	}

    // 找到 y 的孩子,也就是 x(要调整的轴心节点)
	if (y->left != T->nil) {
		x = y->left;
	} else if (y->right != T->nil) {
		x = y->right;
	}

    // 安顿好 x 的新爹
	x->parent = y->parent;
	if (y->parent == T->nil) {
		T->root = x;
	} else if (y == y->parent->left) {
		y->parent->left = x;
	} else {
		y->parent->right = x;
	}

    // 这里只有在 z 的左右孩子都有的时候才会满足
	if (y != z) {
		z->key = y->key; // 覆盖节点z
		z->value = y->value;
	}

    // 只有当 y 是黑才调整,因为删除一个红色节点又不影响黑高
	if (y->color == BLACK) {
		rbtree_delete_fixup(T, x); // 注意要调整的是谁
	}

	return y; // 返回删除的那个节点,到时候你想free,想干啥都行
}

上面用到的辅助函数:

// 找最左,即最小的那个点
rbtree_node *rbtree_mini(rbtree *T, rbtree_node *x) {
	while (x->left != T->nil) {
		x = x->left;
	}
	return x;
}

// 找中序遍历的下一个节点
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);
	}

    // 如果没有右子树,找到第一个是左孩子的爹就行
	while ((y != T->nil) && (x == y->right)) { // 只要还是右孩子就一直找,找到是左孩子为止
		x = y;
		y = y->parent;
	}
	return y;
}

4.2 再调整

注意要调整的是 x, 然后就没啥了

当前节点是左子树:调整分四个情况

  1. 兄弟是红色(调整为情况2,3或4)

    1. 兄弟变黑,父节点变红,左旋父亲,更新兄弟

      image

  2. 兄弟是黑色,兄弟的俩孩子都是黑的(调整为情况3或4)

    1. 兄弟变红,x指向父节点

      image

  3. 兄弟是黑的,兄弟的左孩子是红的,右孩子是黑的(调整为情况4)

    1. 左侄子变黑,兄弟变红,右旋兄弟,更新兄弟

    image

  4. 兄弟是黑的,兄弟的右孩子是红色的,直接干

    1. 兄父同色,右侄子父亲都变黑,左旋父亲,x指向根节点结束

    image

void rbtree_delete_fixup(rbtree *T, rbtree_node *x) {
	// x不是根节点,x是黑才调整
	while ((x != T->root) && (x->color == BLACK)) {
		if (x == x->parent->left) {// 是左子树

			rbtree_node *w= x->parent->right; // 兄弟
            // 情况1,调整为情况2、3或4
			if (w->color == RED) {
				w->color = BLACK; // 兄黑
				x->parent->color = RED; // 父红

				rbtree_left_rotate(T, x->parent); // 左旋父亲
				w = x->parent->right; // 更新兄弟,左侄子变右兄弟了
			}
			
            // 情况2,调整为情况3或4
			if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
				w->color = RED; // 兄弟变红
				x = x->parent; // x 指向父节点
			} else {
				
                // 情况3,调整为情况4
				if (w->right->color == BLACK) {
					w->left->color = BLACK; // 兄弟左儿子变黑
					w->color = RED; // 兄弟变红
					rbtree_right_rotate(T, w); // 右旋兄弟
					w = x->parent->right; // 更新兄弟,左侄子成兄弟了
				}

                // 情况4, 直接干
				w->color = x->parent->color; // 兄父同色
				x->parent->color = BLACK; // 父亲变黑
				w->right->color = BLACK; // 右侄子变黑
				rbtree_left_rotate(T, x->parent); // 左旋

				x = T->root; // x指向根节点,结束了
			}
		
        // 和上面是镜像的
		} else {

			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;
}

5 总结

  1. 调整的原因都是因为违背了性质4或性质5;

    1. 插入是红色,所以可能违背性质4
      1. 插入的时候情况2是为了情况3做准备
    2. 删除可能删除了黑节点,所以可能改变黑高违背性质5
      1. 删除调整的时候都是在为情况4做准备
  2. 左旋右旋的目的都是因为黑高改变了需要调整,不信你仔细思考一下,思考不明白文章最后有个很好的参考链接,去看看就知道了

  3. 另一种都是镜像的,只需要记住一种即可

参考链接

参考链接

总代码




#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define RED				1
#define BLACK 			2

typedef int KEY_TYPE;

typedef struct _rbtree_node {
	unsigned char color;
	struct _rbtree_node *right;
	struct _rbtree_node *left;
	struct _rbtree_node *parent;
	KEY_TYPE key;
	void *value;
} rbtree_node;

typedef struct _rbtree {
	rbtree_node *root;
	rbtree_node *nil;
} rbtree;

rbtree_node *rbtree_mini(rbtree *T, rbtree_node *x) {
	while (x->left != T->nil) {
		x = x->left;
	}
	return x;
}

rbtree_node *rbtree_maxi(rbtree *T, rbtree_node *x) {
	while (x->right != T->nil) {
		x = x->right;
	}
	return x;
}

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);
	}

	while ((y != T->nil) && (x == y->right)) {
		x = y;
		y = y->parent;
	}
	return y;
}


void rbtree_left_rotate(rbtree *T, rbtree_node *x) {

	rbtree_node *y = x->right;  // x  --> y  ,  y --> x,   right --> left,  left --> right

	x->right = y->left; //1 1
	if (y->left != T->nil) { //1 2
		y->left->parent = x;
	}

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

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


void rbtree_right_rotate(rbtree *T, rbtree_node *y) {

	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;
}

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; // y是叔节点
			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->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; //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;
}


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 { //Exist
			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);
}

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) {
				w->color = BLACK;
				x->parent->color = RED;

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

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

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

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

				x = T->root;
			}

		} else {

			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;
}

rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {

	rbtree_node *y = T->nil;
	rbtree_node *x = T->nil;

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

	if (y->left != T->nil) {
		x = y->left;
	} else if (y->right != T->nil) {
		x = y->right;
	}

	x->parent = y->parent;
	if (y->parent == T->nil) {
		T->root = x;
	} else if (y == y->parent->left) {
		y->parent->left = x;
	} else {
		y->parent->right = x;
	}

	if (y != z) {
		z->key = y->key;
		z->value = y->value;
	}

	if (y->color == BLACK) {
		rbtree_delete_fixup(T, x);
	}

	return y;
}

rbtree_node *rbtree_search(rbtree *T, KEY_TYPE key) {

	rbtree_node *node = T->root;
	while (node != T->nil) {
		if (key < node->key) {
			node = node->left;
		} else if (key > node->key) {
			node = node->right;
		} else {
			return node;
		}	
	}
	return T->nil;
}


void rbtree_traversal(rbtree *T, rbtree_node *node) {
	if (node != T->nil) {
		rbtree_traversal(T, node->left);
		printf("key:%d, color:%d\n", node->key, node->color);
		rbtree_traversal(T, node->right);
	}
}

int main() {

	int keyArray[20] = {24,25,13,35,23, 26,67,47,38,98, 20,19,17,49,12, 21,9,18,14,15};

	rbtree *T = (rbtree *)malloc(sizeof(rbtree));
	if (T == NULL) {
		printf("malloc failed\n");
		return -1;
	}
	
	T->nil = (rbtree_node*)malloc(sizeof(rbtree_node));
	T->nil->color = BLACK;
	T->root = T->nil;

	rbtree_node *node = T->nil;
	int i = 0;
	for (i = 0;i < 20;i ++) {
		node = (rbtree_node*)malloc(sizeof(rbtree_node));
		node->key = keyArray[i];
		node->value = NULL;

		rbtree_insert(T, node);
		
	}

	rbtree_traversal(T, T->root);
	printf("----------------------------------------\n");

	for (i = 0;i < 20;i ++) {

		rbtree_node *node = rbtree_search(T, keyArray[i]);
		rbtree_node *cur = rbtree_delete(T, node);
		free(cur);

		rbtree_traversal(T, T->root);
		printf("----------------------------------------\n");
	}
	

	
}






posted @   Christopher_James  阅读(77)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
点击右上角即可分享
微信分享提示