算法导论——红黑树插入算法C++实现

一、概念

红黑树是一棵二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是RED或BLACK。通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长2倍,因而是近似于平衡的。

二、定义

一棵红黑树是满足下面红黑性质的二叉搜索树

1、每个结点或是红色,或是黑色;

2、根节点是黑色的;

3、每个叶节点(NIL)是黑色的;

4、如果一个结点是红色的,则它的两个子结点都是黑色的;

5、对每个结点,从该结点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点(此黑色结点的数目称为黑高)。

三、和平衡二叉树的区别

平衡二叉树是完全平衡的,而红黑树是局部平衡的,能确保没有一条路径会比其他路径长2倍,并且调整少,性能高,所以得到广泛应用,在STL里map和set都由红黑树实现。

四、数据结构

#pragma once
#define RED 0
#define BLACK 1

struct Node{
	Node(int key){
		this->key=key;
	}
	int key;
	int color;
	Node* parent;
	Node* left;
	Node* right;
};

struct Tree{
	Tree(){
	}
	Node* root;
	Node* nil;
};


五、示例(图中所有空指针指向叶结点NIL)

算法导论——红黑树插入算法C++实现_第1张图片

六、旋转

目的:对红黑树进行插入和删除,可能会导致该树不在满足红黑树的性质,为了维护红黑树的性质,必须要改变树中某些结点的颜色以及指针结构。改变指针结构即通过旋转来完成,这是一种能保持二叉搜索树性质的搜索树局部操作。

6.1 左旋(LEFT_ROTATE)

图示:由三部完成该操作。

算法导论——红黑树插入算法C++实现_第2张图片


代码:

//节点左旋
void LEFT_ROTATE(Tree* &T, Node* x){
	Node* y;

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


6.2 右旋(LEFT_ROTATE)
图示:由三部完成该操作。

算法导论——红黑树插入算法C++实现_第3张图片

代码:

//节点右旋
void RIGHT_ROTATE(Tree* &T, Node* y){
	Node *x;

	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->left)
		y->parent->left=x;
	else
		y->parent->right=x;
	x->right=y;
	y->parent=x;
}


七、插入

插入操作与上一篇博文二叉排序树插入操作基本相同,除了细节之处稍有改变。

代码:

//插入节点
void RB_INSERT(Tree* &T,Node* z){
	Node* y=T->nil;
	Node* x=T->root;
	while(x!=T->nil){
		y=x;
		if(z->key < x->key)
			x=x->left;
		else
			x=x->right;
	}
	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;
	RB_INSERT_FIXUP(T,z);
}


八、染色和调整

由于插入操作可能破坏红黑树性质,通过RB_INSERT_FIXUP(T,z)函数调整结点颜色和树的结构,使其保持红黑性质。

//红黑树调整
void RB_INSERT_FIXUP(Tree* &T, Node* z){
	Node* y;

	while(z->parent->color==RED){
		if(z->parent==z->parent->parent->left){     //z节点父节点为其祖父节点的左孩子
			y=z->parent->parent->right;
			if(y->color==RED){                      //case1
				z->parent->color=BLACK;
				y->color=BLACK;
				z->parent->parent->color=RED;
				z=z->parent->parent;
			}
			else{                                       
				if(z==z->parent->right){            //case2
					z=z->parent;
					LEFT_ROTATE(T,z);
				}
				z->parent->color=BLACK;             //case3
				z->parent->parent->color=RED;
				RIGHT_ROTATE(T,z->parent->parent);
			}
		}
		else{									    //z节点父节点为其祖父节点的右孩子
			y=z->parent->parent->left;
			if(y->color==RED){						//case 1
				z->parent->color=BLACK;
				y->color=BLACK;
				z->parent->parent->color=RED;
				z=z->parent->parent;
			}
			else{
				if(z==z->parent->left){            //case2
					z=z->parent;
					RIGHT_ROTATE(T,z);
				}
				z->parent->color=BLACK;             //case3
				z->parent->parent->color=RED;
				LEFT_ROTATE(T,z->parent->parent);
			}
		}
	}
	T->root->color=BLACK;
}


九、测试函数

int main(){
	Tree* T;
	Node* z[MAXSIZE];
	int arr[MAXSIZE]={12,1,9,2,0,11,7,19,4,15, 18, 5, 14, 13, 10, 16, 6, 3, 8, 17};

	T=new Tree();
	T->nil=new Node(0);
	T->nil->color=BLACK;
	T->root=T->nil;
	for(int i=0;iroot,T);
	cout << endl;
	InOrder(T->root,T);
	
	return 0;
}

十、实验总结

本次实验红黑树插入算法实现中遇到了很多bug,最难以察觉的bug是在节点旋转的过程中,很可能把根节点旋丢了,经过多次分析和变量追踪,最后采取的方法是:为红黑树单独创建一个结构体,专门用来跟踪根节点和nil节点,最终问题得到完美解决,另外试验中还出现的错误包括边界条件处理,尤其是给空节点指定父指针这样的错误,还有if条件语句中误把“==”比较运算符,写为“=”赋值运算符,最终通过数据测试和断点追踪顺利找出错误。另外经过实验发现红黑树是一种效率比较高的树,可在O(logn)的时间内完成节点查找和插入。


你可能感兴趣的:(数据结构)