红黑树的插入与删除

红黑树(Red Black Tree) 是一种自平衡二叉查找树。红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。红黑树可以在O(log n)时间内完成查找,插入和删除操作。

二叉搜索树可以看 二叉搜索树

AVL树可以看 AVL树的插入与删除

1. 红黑树的性质

红黑树的自平衡依赖于它的以下性质:

性质1. 结点是红色或黑色。

性质2. 根结点是黑色。

性质3. 每个结点节点(NIL结点,空结点,与其它二叉搜索树不同,红黑树将叶子结点的孩子连接到NIL结点,以简化边界条件)是黑色的。

性质4. 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)

性质5. 从任一结点到其每个叶子结点的所有路径都包含相同数目的黑色结点。

这里给出一个定义,一个结点到其叶子结点的路径上的黑色结点的个数,称为该结点的黑高,根据性质5可以知道,一个结点的左子树和右子树的黑高应当相同。

根据性质5,我们还可以知道红黑树的平衡度:一个结点的左右两颗子树的高度相差最多一倍。极端情况下,一颗子树a中全部都是黑色结点,另一颗子树b中则是红黑相间,由于它们的黑高相同,所以b中的红色结点与黑色结点的数量相同,因此b的高度是a的两倍。

一颗红黑树如下图所示。

红黑树的所有“叶子结点”都指向同一个NIL哨兵结点,这个NIL哨兵结点是红黑树T的一个成员变量,此时这些“叶子结点”就不再是叶子结点了,所以它们可以使黑色的也可以是红色的,哨兵NIL结点一定是黑色的。此外,T.Root即红黑树的根结点的父结点也指向哨兵NIL结点。我们只需要记住一点,NIL结点只是用来简化插入和删除后调整树的平衡的边界条件而已,对于树本身的性质没有任何影响。这一点在后续的算法介绍和示例代码中会体现到。

先给出旋转的程序,这与AVL树的旋转是一样的,只是不需要调整结点的高度而已。

void leftRotate(Tree *tree, Node *x)
{
	Node *y = x->right;

	x->right = y->left;
	if (y->left != tree->nil && y->left != NULL)
	{
		y->left->parent = x;
	}

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

	y->left = x;
	x->parent = y;
}

void rightRotate(Tree *tree, Node *x)
{
	Node *y = x->left;

	x->left = y->right;
	if (y->right != tree->nil && y->right != NULL)
	{
		y->right->parent = x;
	}

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

	y->right = x;
	x->parent = y;
}

上述代码分别是对tree中的结点x进行左旋和右旋的操作。旋转操作说到底就是一个修改指针指向的操作,因此它可以在常数时间内完成。

2. 红黑树的插入

和AVL树一样,在插入和删除结点之后,红黑树也是通过旋转来调整树的平衡的。红黑树插入结点z的方法和普通二叉搜索树一样,都是将新结点z作为一个叶子结点插入到树的底部。不同的是,红黑树将新结点z作为一个红色结点,将其孩子指针指向NIL结点,然后当新结点z的父结点为红色时,由于违反了性质4,因此需要对其进行调整(如果新结点z的父结点为黑色,则不会违反任何性质,尤其是因为z是红色,所以不会违反性质5,即黑高不变)。

红黑树调整算法的设计要遵循一个原则:同一时刻红黑树只能违反至多一条性质。

红黑树插入结点z后的调整有3种情况。

情况1. z的叔结点y是红色的。

左图中插入的新结点z是一个红色结点,其父结点A是红色的,违反了性质4,所以需要进行调整(由于结点A是红色的,根据性质4,由于树本身是平衡的,所以结点C必然是黑色的)。因为其叔结点y是红色的,于是可以修改结点A,结点B为黑色,此时结点C的黑高就会发生变化,从原来的0(忽略子树a、b、r、d、e的黑高)变成了1,因此,还需要将结点C变成红色以保持其黑高不变。此时,由于结点C由黑色变成了红色,如果结点C的父结点是红色的,那么就会违反性质4,于是结点C变成了新的结点z,从这里开始向上回溯调整树。

对于新插入的结点z是结点A的左子树的情况与上述一致。

对于新插入的结点z是结点C的右子树的结点的情况与上述对称。

情况1是一种比较简单的情况。

情况2. z的叔结点y是红色的且z是一个右孩子

情况2不能像情况1一样通过修改z的父结点的颜色来维持性质4,因为如果将z的父结点变成了黑色,那么树的黑高就会发生变化,必然会引起对性质5的违反。以上面情况1的图为例,假设此时结点y为黑色,那么结点C的右子树高为1(忽略子树d和e),左子树高也相同,如果简单的修改结点A为黑色,那么结点C的左子树的黑高会比右子树大1,此时即使将结点C修改为红色也于事无补。

此时可以通过旋转结点z的父结点使情况2转变成情况3进行处理。

情况3. z的叔结点y是红色的且z是一个左孩子

情况2转变成情况3然后针对情况3进行处理的流程可以看下图。

情况2通过对结点A进行一次左旋转变成情况3,此时结点z不再是原来的B,而是结点A了,此时树依然只是违反性质4。情况3通过对结点C进行一次右旋,然后改变结点B和结点C的颜色,得到右图。

先来证明这一操作的正确性:

对于左图,由于刚插入结点z的时候,只违反了性质4,性质5依然满足,假设子树a的黑高为ha,子树b的黑高为hb,依次类推,可以知道 hb==hr==ha==hd,hC=hd+1,对结点A进行左旋转变成情况3,即中图时,树依然只违反性质4,新的结点z为结点A,之后再对结点C右旋并修改颜色得到右图,此时结点A和结点C均为平衡的,结点B也是平衡的,而且结点B的黑高为hd+1。由此可知,整个操作后,该树的黑高不变,且满足所有红黑树的性质。

在红黑树的调整过程中,z始终指向一个红色的结点,因此z永远不会影响其所在树的黑高,于是我们始终关注结点z的父结点是否为红色,如果是,则意味着违反了性质4,需要调整,否则就可以退出循环了。在算法的最后,我们还需要关注性质2,将根结点的颜色改为黑色,根结点的颜色改变也是绝对不会引起树的不平衡的,而将其改为黑色也是不会引起对性质4的违反的。

下面是红黑树及其结点的定义

typedef struct Node
{
	int value;
	int color;	// 结点颜色
	struct Node *parent;
	struct Node *left;
	struct Node *right;
} Node;

typedef struct Tree
{
	Node *root;
	Node *nil;	// 哨兵
} Tree;

下面是C实现的红黑树插入的程序:

void insertRBTree(Tree *tree, int value)
{
	if (tree->root == NULL)
	{
		tree->root = createRBNode(tree, value);
		RBInsertFixup(tree, tree->root);
		return;
	}

	Node *node = createRBNode(tree, value);
	Node *n = tree->root;
	while (1)
	{
		if (value < n->value)
		{
			if (n->left == tree->nil)
			{
				n->left = node;
				node->parent = n;
				break;
			}
			n = n->left;
		}
		else
		{
			if (n->right == tree->nil)
			{
				n->right = node;
				node->parent = n;
				break;
			}
			n = n->right;
		}
	}

	RBInsertFixup(tree, node);
}

总的来说,该程序与一般二叉搜索树的插入式类似的,只是在插入完成后需要对新插入的结点node调用RBInsertFixup方法来调整。

void RBInsertFixup(Tree *tree, Node *z)
{
	while (z->parent->color == RED)
	{
		if (z->parent == z->parent->parent->left)
		{
			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
					z = z->parent;
					leftRotate(tree, z);
				}
				// 情况3
				z->parent->color = BLACK;
				z->parent->parent->color = RED;
				rightRotate(tree, z->parent->parent);
			}
		}
		else if (z->parent == z->parent->parent->right)
		{
			// 与上面对称
			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;
					rightRotate(tree, z);
				}
				z->parent->color = BLACK;
				z->parent->parent->color = RED;
				leftRotate(tree, z->parent->parent);
			}
		}
	}
	tree->root->color = BLACK;
}

RBInsertFixup方法循环检测结点z的父结点是否为红色,如果不是则退出循环。当结点z指向根结点时,由于根结点的父结点指针指向NIL结点,而NIL结点是黑色的,因此也会退出循环。这就是上述所说的“设置NIL结点简化边界问题”的具体体现。

3. 红黑树的删除

红黑树只有在黑色结点被删除的时候才需要进行调整,因为只有这种情况才会引起对性质的违反(性质5,或许还有性质4)。

和插入结点一样,红黑树删除结点也要先执行二叉搜索树的删除过程。

void deleteFromRBTree(Tree *tree, Node *node)
{
	if (node == tree->nil)	return;

	int node_original_color = node->color;
	Node *changeNode = node;

	if (node->left == tree->nil)
	{
		changeNode = node->right;
		transplant(tree, node, node->right);
	}
	else if (node->right == tree->nil)
	{
		changeNode = node->left;
		transplant(tree, node, node->left);
	}
	else
	{
		Tree t;
		t.root = node->right;
		t.nil = tree->nil;
		Node *min = RBMinimum(&t);
		Node *end = min->right;

		node_original_color = min->color;
		changeNode = end;

		min->size = node->size - 1;
		if (node == tree->root)	tree->root = min;

		if (node->right != min)
		{
			RBTransplant(tree, min, min->right);
			min->right = node->right;
			node->right->parent = min;
		}
		min->left = node->left;
		node->left->parent = min;
		RBTransplant(tree, node, min);
		min->color = node->color;
	}

	free(node);

	if (node_original_color == BLACK)
	{
		RBDeleteFixup(tree, changeNode);
	}
}

我们主要关注deleteFromRBTree方法的最后,这里做了一个判断,当node_original_color == BLACK,则对changNode结点执行RBDeleteFixup方法。node_original_color记录的是实际被删除的结点的颜色。changeNode指向被移动的结点。针对二叉搜索树删除结点的3种情况,可以有以下结论:

情况1. 被删除结点x的左子树为空,changeNode指向右孩子,node_original_color为x的颜色;

情况2. 被删除结点x的右子树为空,changeNode指向左孩子,node_original_color为x的颜色;

情况3. 被删除结点x的左右子树均不为空,那么实际被删除的结点其实是x的后驱结点,changeNode指向x的后驱结点y的右孩子,node_original_color为y的颜色。因为这里y才是实际被删除的结点,其可能会引起从y向上回溯的路径的黑高产生变化。

无论是哪一种情况,得到的changeNode都是平衡的,因为它到达的叶子结点的路径都不经过结点x。

注意,上述3种情况都有可能使changeNode指向一个NIL结点,此时我么你需要先将NIL结点的父结点指针做出相应修改,对应上述3种情况,有如下2种修改:

情况1,2:NIL(changeNode)的父结点指针指向被删除结点的父结点;

情况3:NIL(changeNode)的父结点指针指向被删除结点的后驱结点的父结点。

其实上面这修改的论述有点多余,因为它其实就跟二叉搜索树的删除操作对指针指向的修改是完全一样的。在这里,我只是要强调,我们应该将NIL结点视为一个普通的叶子结点,虽然我们只是使用它来简化边界操作的。至此,在插入和删除,我们都已经说明了NIL结点对简化边界操作的作用。总的来说就是,因为有了NIL结点,我们再也不用考虑根结点的父结点为NULL,或者是叶子结点的孩子为NULL这种边界问题了。

先给出RBDeleteFixup方法的代码,然后再针对代码解释。

void RBDeleteFixup(Tree *tree, Node *x)
{
	while (x != tree->root && x->color == BLACK)
	{
		if (x == x->parent->left)
		{
			Node *w = x->parent->right;
			if (w->color == RED)
			{
				// 情况1
				w->color = BLACK;
				x->parent->color = RED;
				leftRotate(tree,x->parent);
				w = x->parent->right;
			}
			if (w->left->color == BLACK && w->right->color == BLACK)
			{
				// 情况2
				w->color = RED;
				x = x->parent;
			}
			else
			{
				if (w->right->color == BLACK)
				{
					// 情况3
					w->left->color = BLACK;
					w->color = RED;
					rightRotate(tree, w);
					w = x->parent->right;
				}
				// 情况4
				w->color = x->parent->color;
				x->parent->color = BLACK;
				w->right->color = BLACK;
				leftRotate(tree, x->parent);
				x = tree->root;
			}
		}
		else if (x == x->parent->right)
		{
			// 与上面对称
			Node *w = x->parent->left;
			if (w->color == RED)
			{
				w->color = BLACK;
				x->parent->color = RED;
				rightRotate(tree, 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;
					leftRotate(tree, w);
					w = x->parent->left;
				}
				w->color = x->parent->color;
				x->parent->color = BLACK;
				w->left->color = BLACK;
				rightRotate(tree, x->parent);
				x = tree->root;
			}
		}
	}
	x->color = BLACK;
}

针对删除结点的3种情况,对changeNode的调整又有不同的情况:

(1). 被删除结点x只有一个孩子

此时x的孩子changeNode就会带着自己的孩子们替换掉x,假如changeNode是黑色的,那么changeNode往上的路径的黑高会因为x的删除而少1,引起树的不平衡,对这种情况的处理后面会讲述,假如changeNode是红色的,我们只需要简单地修改changeNode为黑色即可解决这个问题;

(2). 被删除结点x有两个孩子

此时x会被x的后驱结点y所替换,y原来的位置变成了y原来的右孩子changeNode。此后,我们只需要将y的颜色修改成黑色就可以使y不会引起树的不平衡,然后再集中关注changeNode的颜色,针对changeNode,就可以像上面的(1)那样处理了。

当changeNode为黑色时,对changeNode的处理有4种情况,下面将changeNode称为结点x,注意,x始终是黑色而且平衡的:

情况1. x的兄弟结点w是红色的

删除结点后,x的父结点xp经过x到达叶子结点的路径的黑高产生变化,以xp为根的树不再平衡。

针对情况1,x是平衡的,但是因为结点B的左子树被删除了一个黑色的结点,导致结点B的左子树的黑高少了1,所以结点B是不平衡的。此时,ha==hb==hr-1,hr==hd==he==hf。可以对结点B进行一次左旋,然后修改结点B和结点D的颜色得到右图,转变成情况2、3、4进行处理。

情况2. x的兄弟结点w是黑色的而且w的两个子结点都是黑色的

与情况1一样,在删除结点后,结点B不平衡,其中ha==hb==hr-1,hr==hd==he==hf。于是我们可以直接修改结点D为黑色,就可使得结点B达到平衡,但是这又会使得结点B的黑高比原来少1,会引起结点B往上的树不平衡。此时,如果结点B为红色,那么RBDeleteFixup的循环就会结束,然后将结点B修改为黑色,此时结点B的黑高就又恢复如初,不影响其它树的平衡。如果结点B为黑色,则从该结点开始继续向上回溯调整树的黑高。

情况3. x的兄弟结点w是黑色的而且w的左孩子是红色的,w的右孩子是黑色的

这个跟插入结点的情况2类似,可以通过旋转将其转变成情况4进行处理。

简单证明一下对结点w右旋后结点w的红黑性质不会被破坏。旋转前,结点w是平衡的,所以hr==hd==he+1==hf+1。旋转后,结点w指向了结点C,此时结点w的左子树高为hr,右子树高为he+1==hr,所以结点w依然是平衡的,再看结点D,hd==he+1,所以结点D也是平衡的。综上所述,这一旋转操作不会影响结点B的右子树的红黑性质,仅仅是将其转变成请款4进行处理而已。

情况4. x的兄弟结点w是黑色的,而且w的右孩子是红色的

考察这种情况,首先和上面一样,因为删除结点导致结点B不平衡,其中hr==hd==he==hf,ha==hb==hr-1。对结点B进行一次左旋并修改结点B、D、E的颜色可以得到右图。此时结点B达到平衡结点D的左子树黑高为ha+2,右子树黑高为he+1==ha+2,所以结点D也达到平衡,该树从根开始的黑高在删除前和删除并旋转操作后不变,因此不会影响到其它树的平衡。也就是说,执行完情况4的操作之后,整棵树应当已经平衡了,除非旋转后的结点x是根结点,违反了性质2。根据上面给出的RBDeleteFixup程序,当x为根结点时就要退出循环,最后将x染成黑色,此时,整棵树就达到了平衡了。

上述4种情况是针对结点x是一颗左子树而言的,当x是一颗右子树时其操作与上述操作完全对称。

4. 红黑树的插入和旋转操作的时间复杂度

红黑树插入需要O(lg(n))次,对插入结点后的调整所做的旋转操作不会超过2次。删除结点后的调整所做的旋转操作不会操作3次,沿树回溯至多O(lg(n))次。总而言之,红黑树的插入和删除的时间复杂度均为O(lg(n))。

C实现的代码可以参考我的github项目,里面还有其它一些数据结构和算法的实现。该项目持续更新哦~

数据结构与算法

时间: 2024-10-29 19:05:31

红黑树的插入与删除的相关文章

红黑树从头至尾插入和删除结点的全程演示图

红黑树插入和删除结点的全程演示 作者:July.saturnman.时间:二零一一年三月二十八日.出处:http://blog.csdn.net/v_JULY_v.声明:版权所有,侵权必究.----------------------------------- 引言: 目前国内图书市场上,抑或网上讲解红黑树的资料层次不齐,混乱不清,没有一个完整而统一的阐述.而本人的红黑树系列四篇文章(详见文末的参考文献),虽然从头至尾,讲的有根有据,层次清晰,然距离读者真正做到红黑树了然于胸,则还缺点什么. 而

红黑树的插入和删除

一.红黑树的简介 红黑树是一种平衡的二叉查找树,是一种计算机科学中常用的数据结构,最典型的应用是实现数据的关联,例如map等数据结构的实现. 红黑树有以下限制: 1. 节点必须是红色或者是黑色 2. 根节点是黑色的 3. 所有的叶子节点是黑色的. 4. 每个红色节点的两个子节点是黑色的,也就是不能存在父子两个节点全是红色 5. 从任意每个节点到其每个叶子节点的所有简单路径上黑色节点的数量是相同的. 要说明一下限制3.一般在红黑树中,每个节点空指针处还会加空的黑色的孩子(称为岗哨).所以限制3一定

红黑树的插入

一.红黑树的介绍 先来看下算法导论对R-B Tree的介绍: 红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black.通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的. 前面说了,红黑树,是一种二叉查找树,既然是二叉查找树,那么它必满足二叉查找树的一般性质.下面,在具体介绍红黑树之前,咱们先来了解下 二叉查找树的一般性质:1.在一棵二叉查找树上,执行查找.插入.删除等操作,的时间复杂度为O(

红黑树之插入

1.红黑树 (1).概念 i>每个结点不是红的就是黑的: ii>根结点为黑的: iii>红结点的孩子必为黑结点: iv>(除了根结点)任一结点不管通过什么路径,到达叶子节点的黑结点数目一定相同: 总结概括:一头一脚黑,黑同红不连:根为黑,到脚(叶子节点)的黑结点相同,红结点不相连: 2.递归--->一般先写if结束语句 化非递归------>用while()循环和栈; enum{RED, BLACK}; 这个枚举是有值得,分别为0.1: 3.红黑树与AVL树  AVL树

图解集合7:红黑树概念、红黑树的插入及旋转操作详细解读

原文地址http://www.cnblogs.com/xrq730/p/6867924.html,转载请注明出处,谢谢! 初识TreeMap 之前的文章讲解了两种Map,分别是HashMap与LinkedHashMap,它们保证了以O(1)的时间复杂度进行增.删.改.查,从存储角度考虑,这两种数据结构是非常优秀的.另外,LinkedHashMap还额外地保证了Map的遍历顺序可以与put顺序一致,解决了HashMap本身无序的问题. 尽管如此,HashMap与LinkedHashMap还是有自己

红黑树(2) - 插入操作

1.插入介绍 首先以二叉排序树的方法增加节点并标记它为红色.(为何不是红色?因为如果设为黑色,就会导致根到叶子的所有路径中,有一条路径上会多出一个额外的黑节点,这个是很难调整的).但是,设为红色节点后,可能会导致出现两个连续红色节点的冲突,则可以通过重新着色和旋转来调整.具体的调整操作取决于其他临近节点的颜色. 下面分析一下插入新节点后可能对红黑树性质产生的影响: 性质1-节点是红色或黑色.和性质3-所有叶子都是黑色.这两条总是可以维持不变. 性质4-每个红色节点的两个子节点都是黑色.只在增加红

红黑树的插入和遍历时间复杂度分析

红黑树的插入和遍历时间复杂度分析 在平常的工作中,最常用的一种数据结构恐怕是std::map了.因此对其的时间复杂度分析是有必要的,编写程序时做到心中有底. 一.理论分析 在stl中std::map和std::set都采用红黑树的方式实现.我们知道插入一个元素到红黑树的时间为log(N),其中N为当前红黑树的元素个数,因此,采用插入方式构建元素个数为N的红黑树的时间复杂度为: log(1) + log(2) + log(N-1) = log((N-1)!) = Nlog(N) 那么采用迭代器遍历

红黑树、插入删除操作

二叉排序树 一棵自平衡的二叉排序树(二叉搜索树) 生成二叉排序树的过程是非常容易失衡的,最坏的情况就是一边倒(只有右/左子树),这样会导致二叉树的检索效率大大降低(O(n)). 为了维持二叉树的平衡,有各种的算法,如:AVL,SBT,伸展树,TREAP ,红黑树等等. 红黑树 红黑树需要满足5条性质: - 节点非红即黑 - 根节点是黑色 - 所有NULL结点称为叶子节点,且认为颜色为黑 - 所有红节点的子节点都为黑色,一条路径上不能出现相邻的两个红色结点 - 从任一节点到其叶子节点的所有路径上都

红黑树(三)删除

删除操作比插入复杂一些.首先我们先来了解一些红黑树的特性.这些是我随意列举的,供大家参考. 1.红色节点的父亲黑孩子一定是黑色.(nil是黑节点) 2.单支节点,只能是黑红.(红黑,黑黑,不符合规则4,到树尾黑节点个数相同) 3.真正的删除节点一定是单支节点或者叶子节点.(没有孩子的节点) 接下来我们讲如何找真正的删除节点. 有左右子树的情况 如果8是删除节点,那么10就是真正的删除节点. 查找方法是,找8节点的右子树中的最小的节点,根据二叉树的性质,我们可以知道,8节点的右孩子的左孩子的左孩子