C语言实现红黑树

红黑树的概述:

        平衡树的搜索效率最高,但是由于平衡因子的限制,如果是进行插入和删除操作时,没有达到左右子树高度相差不超过1这个平衡条件,就会执行旋转操作使其达到平衡状态,频繁的调整会使其性能下降,那么如何弱化平衡条件,使其不在频繁的调整就可以很好的达到我们所需要的平衡呢?为了实现这一目的,红黑树应运而生。红黑树是一种二叉搜索树,因此它也满足二叉搜索的特征:任意节点所包含的值,大于左孩子的值,小于有孩子的值。不仅如此,它还有一些其他的特征。

红黑树的特性:

1.每个节点是红色或是黑色。
2.根节点是黑色。
3.每个叶子节点是黑色(这里指的是为空的叶子节点)
4.红色节点的左右孩子为黑色(不能出现两个连续的红色节点)。
5.任一节点到叶所有路径黑节点数量相同

口诀:左根右  根叶黑  不红红  黑路同

引出的结论:最长路径不超过最短路径的两倍(AVL左右高度不会超过1,由于AVL树比较严格,因此插入删除调整更加频繁)
因此AVL树在查询上面更高效,红黑树在插入删除上面更高效

红黑树的插入:

        根据红黑树的五条性质,那么我们应该如何正确的插入节点。首先是要需明确的是插入节点的颜色。如果插入节点的颜色是黑色,那么就会违反黑路同的性质,调整起来比较麻烦,但如果插入的是红色节点,有可能不会破坏红黑树的性质,有可能破坏(这时两个红色相邻,或者违反根叶黑),相对于黑色节点来说,插入红色影响更小,因此红黑树插入节点默认红色

现在我们考虑插入位置
插入位置为根节点,直接变为黑色。

插入位置的叔叔是红色,叔父爷变色,爷爷变插入节点,看是否违反红黑树性质。

插入节点的叔叔是黑色,按照(LL,RR,LR,RL)进行旋转,然后变色

左右旋转:

左右旋转的代码如下:

/* 将x进行左旋,将左、右、父节点进行更新
 *    px                              px
 *    |                               |
 *    x                                y
 *   /  \      --(左旋)-->    / \
 *  lx   y                          x  ry
 *     /   \                     /  \
 *    ly   ry                   lx  ly
*/
static void leftRotate(RBTree *tree, RBNode *x) {
	RBNode *y = x->right;
	x->right = y->left;
	if (y->left) {
		y->left->parent = x;
	}        // 将x与ly接上
	y->parent = x->parent;  // 将x的位置异位给y
	if (x->parent) {
		if (x->parent->left == x) {
			x->parent->left = y;  
		} else {
			x->parent->right = y;   // px和y接上
		}
	} else {
		tree->root = y;  // 否则顶点为y
	}
	y->left = x;
	x->parent = y;
}

/* 将y进行左旋,将左、右、父节点进行更新
 *          py                               py
 *          |                                |
 *          y                                x
 *         /  \      --(右旋)-->            /  \
 *        x   ry                           lx   y
 *       / \                                   / \
 *      lx  rx                                rx  ry
 * */
static void rightRotate(RBTree* tree, RBNode *y) {
	RBNode *x = y->left;
	y->left = x->right;
	if (x->right) {
		x->right->parent = y;
	}
	x->parent = y->parent;
	if (y->parent) {
		if (y->parent->right == y) {
			y->parent->right = x;
		} else {
			y->parent->left =x;
		}
	} else {
		tree->root = x;
	}
	x->right = y;
	y->parent = x;
}


LL型:插入的节点在开始不平衡的点(这里指的是根节点)的左侧,而且在父节点的左侧 -- 直接进行右旋,旋转完成后对旋转中心点和旋转点进行变色

RR型:插入的节点在开始不平衡的点(这里指的是根节点)的右侧,而且在父节点的右侧 -- 直接进行左旋,旋转完成后对旋转中心点和旋转点进行变色

LR型:插入节点在开始不平衡的点(这里指的是根节点)左侧,在父节点的右侧,先左旋左孩子,再右旋“根节点”,旋转完成后对旋转中心点和旋转点进行变色

RL型:插入节点在开始不平衡的点(这里指的是根节点)右侧,在父节点的左侧,先右旋右孩子,再左旋“根节点”,旋转完成后对旋转中心点和旋转点进行变色

以 17 18 23 34 27 15 9 6 8 5 25为例构建一棵红黑树,为方便省略掉空节点(暗红是新插入的节点)

红黑树插入调整代码:
 

static void insertFixUp(RBTree *tree, RBNode *node) {
	RBNode *parent = NULL, *grandParent = NULL;
	RBNode *uncle = NULL;
	RBNode *tmp;
	parent = node->parent;
	// 判断父节点是否为红色,父节点是黑色直接插入不需要调整
	while (parent && parent->color == RED) {
		// 找祖父节点  -- 为了找叔叔
		grandParent = parent->parent;
		if (parent == grandParent->left) {
			uncle = grandParent->right;
		} else {
			uncle = grandParent->left;
		}

		if (uncle && uncle->color == RED) {   // 叔叔是红色
			uncle->color = BLACK;
			parent->color = BLACK;
			grandParent->color = RED;	// 叔父爷变色 + cur换指向
			node = grandParent;
			parent = node->parent;  // 将父节点指向当前节点的父节点,因为当前结点变成了grandParent
			continue;
		}
		// 叔叔是黑色
		if (grandParent->left == parent) {		// L,左边为父节点
			if (parent->right == node) {		// R
				leftRotate(tree, parent);		// 左旋父节点
				tmp = parent;				// 交换父节点和插入的 node 节点
				parent = node;
				node = tmp;
			}
			// LL
			rightRotate(tree, grandParent);	// 旋转爷爷,旋转点和旋转中心的进行变色(旋转点是爷爷节点,旋转中心点是父节点)
			grandParent->color = RED;
			parent->color = BLACK;
		} else {							// 同上
			if (parent->left == node) {		// RL
				rightRotate(tree, parent);
				tmp = parent;
				parent = node;
				node = tmp;
			} // RR
			leftRotate(tree, grandParent);
			grandParent->color = RED;
			parent->color = BLACK;
		}

	}
	tree->root->color = BLACK;   // 将根节点置为黑
}

红黑树的删除:

对于AVL树来说:
删除的是叶节点,直接删除;
删除的是只有左/右子树的节点,直接让左/右子树代替即可
删除的节点有左右子树,找到前驱(左子树最大的)或者后继(右子树最小的),用它代替删除的节点,删除前驱/后继

对于红黑树也相同,如果按照上述方法删除时破坏了红黑树的性质,直接调整即可,由于第三种情况会转化为第一和第二种情况,那么讨论第一和第二种情况即可。

对于只有一个(平衡状态)子树来说:只能是一黑一红,而且黑色节点是红色节点的父节点。为什么呢?

如果是一红一黑,无论是在左还是有右子树上加一个黑色节点,都会违反黑路同的性质。

因此可以引出删除的套路:

1.只有左右孩子,直接代替后变黑

2.没有孩子
        2.1红节点:删除后不做任何调整(因为不破坏红黑树的性质)
        2.2黑节点(一定会破坏黑路同)
                2.2.1兄弟是黑色
                        1)兄弟至少有一个红孩子(根据LL,LR,RL,RR变色+旋转)
                        2)兄弟孩子都是黑色(兄弟变红,双黑上移)
                2.2.2兄弟是红色:兄父变色,朝双黑旋转

为了好理解,将删除的黑色节点变成"双黑节点",要做的就是将双黑变为单黑
2.2.1.1兄弟至少一个红孩子
1)LL 变色(r变成s的颜色,s变成p的颜色,p变成黑色),如果有两个红色节点,将其归为LL,完成变色后进行右转,如果不违反了黑路同,将双黑变为单黑

2)RR变色(与LL同理),变色后进行左旋,如果是两个红色节点,归为RR

3)LR变色,将r变为p的颜色,p变黑,之后进行旋转

4)RL变色同理

2.2.1.1兄弟孩子都是黑色节点
将兄弟节点变为了红色,满足这一条路径的黑路同的性质,但是可能不满足从根节点出发黑路同的性质,因此将双黑节点进行上移(移到删除节点的父节点上),如果父节点是黑色,此时就有可能转化为兄弟节点至少一个孩子的情况,根据上面进行调整

如果是双黑移动到的节点是红色,直接变成单黑

当双黑遇到根节点,直接变成单黑

2.2.2 删除没有孩子的黑节点,而且兄弟是红色:父兄变色,朝双黑旋转,按照上面的进行调整,此时应该为,兄弟变红,双黑上移,遇到红色变单黑

完整的删除过程:

相关代码:04_find/03_RBTree · 柚求bing/C语言实现的数据结构 - 码云 - 开源中国 (gitee.com)icon-default.png?t=O83Ahttps://gitee.com/ice-ben/C_data-structure/tree/master/04_find/03_RBTree

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值