红黑树(2) - 插入操作

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不是根节点,则执行下面操作

场景3.3.1:如果x的叔节点是红色(则根据性质4,祖父节点必定是黑色)

(a)将父节点以及叔节点置为黑色。

(b)将祖父节点置为红色。

至此,现在我们的新节点x有了一个黑色的父节点p。因为通过父节点p或叔节点u的任何路径都必定通过祖父节点g,在这些路径上的黑节点数目没有改变。

但是,红色的祖父节点g的父节点也有可能是红色的,这就违反了性质4。为了解决这个问题,我们需要在祖父节点g上递归地进行场景分析(即把g当成是新加入的节点进行各种情形的检查)。

(c)执行赋值操作"x = g",将祖父节点赋值给x后,然后递归的对新的x进行场景分析。

以上的步骤,可以使用下图来演示。

场景3.3.2:如果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.1]
		//(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。 [对应情形3.2]
		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

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-15 16:32:59

红黑树(2) - 插入操作的相关文章

红黑树的插入操作

1 package Tree; 2 3 import org.junit.Test; 4 5 class RedBlackTreeNode { 6 7 int key = 0; 8 RedBlackTreeNode left = null; 9 RedBlackTreeNode right = null; 10 RedBlackTreeNode parent = null; 11 // 色域,0表示黑色,1表示红色 12 int color; 13 14 public RedBlackTreeN

红黑树(3) - 删除操作

在本系列的前面两篇文章中,已经介绍了红黑树以及其插入操作.具体可参考下面两个链接: 红黑树(1) - 介绍 红黑树(2) - 插入操作 1.删除操作介绍 类似于插入操作,红黑树进行删除节点时,也使用重新着色以及旋转这两种方式,来维护它的属性.在插入操作中,我们主要是依靠检测叔节点的颜色来决定哪种场景.在删除操作中,我们使用检测兄弟的颜色,来决定是哪种场景. 在插入操作中,最常见的违反红黑树属性的一种情况是存在两个连续的红色节点.而在删除操作中,常见的情况是,当删除节点是黑色时,会影响从根节点到叶

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

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

红黑树的插入

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

红黑树之插入

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

红黑树的删除操作详解

注:本文转载自博客园,博主原址:http://www.cnblogs.com/tongy0/p/5460623.html,感谢博主帮我弄清楚了红黑树删除操作,转载做收藏用. 红黑树的删除操作 1:节点命名约定 D表示要被删除的节点.即:取 Delete 的首字母: P 表示父节点.即:取 Parent 的首字母: S表示兄弟姐妹节点.即:取 Sibling的首字母: U表示叔伯节点.即:取Uncle的首字母: G表示祖父节点.即:取 Grandfather的首字母: L表示左树.即:取Left的

红黑树的插入与删除

红黑树(Red Black Tree) 是一种自平衡二叉查找树.红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能.红黑树可以在O(log n)时间内完成查找,插入和删除操作. 二叉搜索树可以看 二叉搜索树 AVL树可以看 AVL树的插入与删除 1. 红黑树的性质 红黑树的自平衡依赖于它的以下性质: 性质1. 结点是红色或黑色. 性质2. 根结点是黑色. 性质3. 每个结点节点(NIL结点,空结点,与其它二叉搜索树不同,红黑树将叶子结点的孩子

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

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

说说红黑树——不谈操作,只讲理解

一.前言 ??这几天想学一学红黑树这种数据结构,于是上网找了很多篇博客,初看吓了一跳,红黑树竟然如此复杂.连续看了几篇博客后,算是对红黑树有了一些了解,但是它的原理却并不是特别理解.网上的博客,千篇一律的都是在叙述红黑树的操作,如何插入节点.删除节点,旋转.变色等,只关注如何正确构建一棵红黑树,但是却很少提及为什么这么做.这篇博客我就来记录一些我所知道的红黑树中比较重要的东西,以及谈一谈我的理解. ??我不会描述红黑树的具体实现,因为阅读红黑树具体实现的过程中,我发现这真的不是很重要,没有太大的