关闭

红黑树(2) - 插入操作

标签: 红黑树红黑树旋转红黑树插入红黑树规则
772人阅读 评论(0) 收藏 举报
分类:

1.插入介绍

首先以二叉排序树的方法增加节点并标记它为红色。(为何不是红色?因为如果设为黑色,就会导致根到叶子的所有路径中,有一条路径上会多出一个额外的黑节点,这个是很难调整的)。但是,设为红色节点后,可能会导致出现两个连续红色节点的冲突,则可以通过重新着色和旋转来调整。具体的调整操作取决于其他临近节点的颜色。

下面分析一下插入新节点后可能对红黑树性质产生的影响:
性质1-节点是红色或黑色。和性质3-所有叶子都是黑色。这两条总是可以维持不变。
性质4-每个红色节点的两个子节点都是黑色。只在增加红色节点、重绘黑色节点为红色,或做旋转时受到威胁。
性质5-从每个叶子到根的所有路径都包含相同数目的黑色节点。只在增加黑色节点、重绘红色节点为黑色,或做旋转时受到威胁。

2.插入时的算法

在AVL的插入操作中,我们会使用旋转这个工具来保持插入之后的平衡。在红黑树中,我们会使用到两个工具来保持平衡:
(1) 重新着色
(2) 旋转

首先,会先尝试去重新着色,如果它不能工作,则使用旋转。并且,空节点的颜色默认为黑色。
基于叔节点的颜色,这个算法主要有两种情形。如果叔节点为红色,则需要重新着色。如果是黑色,则使用旋转以及重新着色。

插入过程:
假设x代表将要插入的新节点。p为x的父节点,u为x的叔节点,g为x的祖父节点。
执行标准的二叉排序树(BST)的节点插入操作,并将x置为红色。
此时,会可能形成3种主要的场景,参考下面这一节的具体介绍。

3.场景分类(Scenario)


3.1 场景1:如果X的父结点是黑色,则无需进一步处理

分析:在这种情形下,树仍是有效的。性质4没有失效(新节点是红色的)。性质5也未受到威胁,尽管新节点x有两个黑色叶子节点;但由于x是红色,通过它的每个子节点的路径就都有同通过它所取代的黑色的叶子的路径相等数量的黑色节点,所以依然满足这个性质。

3.2 场景2:如果x是根节点,则将其置为黑色

分析:置为黑色是为了满足性质2。对于完全二叉树,此时黑色节点高度增加1。

3.3 场景3:如果x的父节点是红色,并且x的叔节点是红色

如果x的叔节点是红色(则根据性质4,祖父节点必定是黑色)
(a)将父节点以及叔节点置为黑色。
(b)将祖父节点置为红色。
      至此,现在我们的新节点x有了一个黑色的父节点p。因为通过父节点p或叔节点u的任何路径都必定通过祖父节点g,在这些路径上的黑节点数目没有改变。
      但是,红色的祖父节点g的父节点也有可能是红色的,这就违反了性质4。为了解决这个问题,我们需要在祖父节点g上递归地进行场景分析(即把g当成是新加入的节点进行各种情形的检查)。
(c)执行赋值操作"x = g",将祖父节点赋值给x后,然后递归的对新的x进行场景分析。
以上的步骤,可以使用下图来演示。



3.4 场景4:如果x的父节点是红色,并且x的叔节点是黑色

    在这种情形下,对于x, p以及g有下面4种情形(类似于AVL树)。

    (a)Left Left Case       (p是g的左孩子,且x是p的左孩子)
    (b)Left Right Case    (p是g的左孩子,且x是p的右孩子)
    (c)Right Right Case (p是g的右孩子,且x是p的右孩子)
    (d)Right Left Case    (p是g的右孩子,且x是p的左孩子)

这4种情形,可用下面的这4幅图来进行描述。
(a)Left Left Case (参考g, p以及x)


(b)Left Right Case (参考g, p 以及x)


(c)Right Right Case (参考g, p 以及x)


(d)Right Left Case (参考g, p 以及x)


4.插入操作的例子演示

下面的这个例子,演示了红黑树中插入新节点的过程,覆盖了上面描述的多种场景。

5.代码实现

下面的代码,是严格遵循第3节中所介绍的哪些场景写的。

// C++程序:实现红黑树的插入操作
#include <iostream>

//红黑树节点
struct Node
{
	int data;    //节点数据
	char color;  //颜色属性

	//左孩子,右孩子以及父节点指针
	Node *left, *right, *parent;
};

//左旋
/*
    y                           x
   / \     Right Rotation      /  \
  x   T3   – – – – – – – >    T1   y
 / \       < - - - - - - -        / \
T1  T2     Left Rotation         T2  T3
*/
void LeftRotate(Node **root, Node *x)
{
	//y保存x的右孩子
	Node *y = x->right;

	//将y的左孩子做为x的右孩子
	x->right = y->left;

	//更新x的右孩子的父指针
	if (x->right != NULL)
		x->right->parent = x;

	//更新y的父指针
	y->parent = x->parent;

	//如果x的父节点为null,则y成为新的根节点
	if (x->parent == NULL)
		(*root) = y;
	//将y保存在x的原始位置
	else if (x == x->parent->left)
		x->parent->left = y;
	else    
		x->parent->right = y;

	//将x做为y的左孩子
	y->left = x;

	//更新x的父节点为y
	x->parent = y;
}

//右旋(与左旋正好相反)
/*
    y                           x
   / \     Right Rotation      /  \
  x   T3   – – – – – – – >    T1   y
 / \       < - - - - - - -        / \
T1  T2     Left Rotation         T2  T3
*/
void rightRotate(Node **root, Node *y)
{
	//x保存y的右孩子
	Node *x = y->left;

	//将x的右孩子做为y的左孩子
	y->left = x->right;

	//更新x的右孩子的父指针
	if (x->right != NULL)
		x->right->parent = y;

	//更新x的父指针
	x->parent = y->parent;

	//如果x的父节点为null,则x成为新的根节点
	if (x->parent == NULL)
		(*root) = x;
	//将x保存在y的原始位置
	else if (y == y->parent->left)
		y->parent->left = x;
	else
		y->parent->right = x;

	//将y做为x的右孩子
	x->right = y;

	//更新y的父节点为x
	y->parent = x;
}

//功能函数,在执行标准的BST插入后,调整红黑树。
void insertFixUp(Node **root, Node *z)
{
	//遍历树,直到z不是根节点,并且z的父节点是红色[对应情形3]
	while (z != *root && z->parent->color == 'R')
	{
		Node *y;

		//找到叔节点,保存为y
		if (z->parent == z->parent->parent->left)
			y = z->parent->parent->right;
		else
			y = z->parent->parent->left;

		//如果叔节点为红色,执行下面操作。[对应情形3]
		//(a) 将父节点以及叔节点置为黑色
		//(b) 将祖父节点置为红色
		//(c) 将祖父节点赋值给z,继续检测
		if (y->color == 'R')
		{
			y->color = 'B';
			z->parent->color = 'B';
			z->parent->parent->color = 'R';
			z = z->parent->parent;
		}
		//如果叔节点是黑色,则存在4种情形,LL,LR,RL,RR。 [对应情形4]
		else
		{
			//Left-Left (LL) case
			//(a) 交换父节点与祖父节点的颜色
			//(b) 祖父节点进行右旋
			if (z->parent == z->parent->parent->left &&
				z == z->parent->left)
			{
				char ch = z->parent->color;
				z->parent->color = z->parent->parent->color;
				z->parent->parent->color = ch;
				rightRotate(root, z->parent->parent);
			}

			//Left-Right (LR) case
			//(a) 交换当前节点与祖父节点的颜色
			//(b) 父节点进行左旋
			//(c) 祖父节点进行右旋
			if (z->parent == z->parent->parent->left &&
				z == z->parent->right)
			{
				char ch = z->color;
				z->color = z->parent->parent->color;
				z->parent->parent->color = ch;
				LeftRotate(root, z->parent);
				rightRotate(root, z->parent->parent);
			}

			//Right-Right (RR) case
			//(a) 交换父节点与祖父节点的颜色
			//(b) 祖父节点进行左旋
			if (z->parent == z->parent->parent->right &&
				z == z->parent->right)
			{
				char ch = z->parent->color;
				z->parent->color = z->parent->parent->color;
				z->parent->parent->color = ch;
				LeftRotate(root, z->parent->parent);
			}

			//Right-Left (RL) case
			//(a) 交换当前节点与祖父节点的颜色
			//(b) 父节点进行右旋
			//(c) 祖父节点进行左旋
			if (z->parent == z->parent->parent->right &&
				z == z->parent->left)
			{
				char ch = z->color;
				z->color = z->parent->parent->color;
				z->parent->parent->color = ch;
				rightRotate(root, z->parent);
				LeftRotate(root, z->parent->parent);
			}
		}
	}
	(*root)->color = 'B'; //根节点总是黑色
}

//功能函数:插入新节点至红黑树
void insert(Node **root, int data)
{
	//分配新节点内存
	Node *z = new Node;
	z->data = data;
	z->left = z->right = z->parent = NULL;

	//如果根节点为null,则z成为根节点
	if (*root == NULL)
	{
		z->color = 'B';  //根节点总是黑色
		(*root) = z;
	}
	else
	{
		Node *y = NULL;
		Node *x = (*root);

		// Follow standard BST insert steps to first insert the node
		while (x != NULL)
		{
			y = x;
			if (z->data < x->data)
				x = x->left;
			else
				x = x->right;
		}
		z->parent = y;
		if (z->data > y->data)
			y->right = z;
		else
			y->left = z;
		z->color = 'R';   //新插入节点总是红色

		// 调用insertFixUp来修复红黑树的属性,因为它可能因为插入操作而被破坏。
		insertFixUp(root, z);
	}
}

//功能函数:中序遍历红黑树
void inorder(Node *root)
{
	if (root == NULL)
		return;
	inorder(root->left);
	std::cout << root->data << "-->";
	inorder(root->right);
}

int main()
{
	Node *root = NULL;
	insert(&root, 5);
	insert(&root, 3);
	insert(&root, 7);
	insert(&root, 2);
	insert(&root, 4);
	insert(&root, 6);
	insert(&root, 8);
	insert(&root, 11);
	std::cout<<"Inorder Traversal Is: \n";
	inorder(root);
	std::cout << "null"<<std::endl;

	return 0;
}
运行结果:
Inorder Traversal Is:
2-->3-->4-->5-->6-->7-->8-->11-->null

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:122295次
    • 积分:2857
    • 等级:
    • 排名:第12940名
    • 原创:168篇
    • 转载:0篇
    • 译文:0篇
    • 评论:10条