红黑树的插入

在介绍红黑树之前我们先介绍一下BST树和AVL树:

        BST树又叫做二叉搜索树;该树的特点是:树中的数值不能重复;任意结点的左孩子的值比该结点的值小;任意结点的右孩子的值比该结点的值大。这样的树就叫做BST树,图示如下:

这就是一颗BST树,此结构拥有二叉树结构的优点,理论上能到达logn的插入和删除效率,但是很不幸,此结构在存储单调变化的数据是会退化为链表;当我们插入单递增的数据{12,34,45,56,67,78,89}时,BST树就退化为如下的链表:

这样的话就不具备二叉树的 logn的增删改查效率 ,处理这个问题我们引入了 AVLTree

       AVLTree是一个可自平衡的二叉搜索树,它满足BST树的所有性质,但是它额外要求树中任意结点的左右树高只差小于等于 1,这样的话在存储单调变化的数据时,整个结构就可以拥有二叉树的logn的效率。它的每一个结点都多了一个成员:balance,该成员的取值为-1,0,1三者中的一个,balance值的确定是依靠 Depth(root->right) - Depth(root->left) 的结果。假设我现在插入的数据还是{12,34,45,56,67,78,89},AVLTree的存储结果如下:

该树的生成过程可在:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html  查看,当然也可以测试其他的数据来看AVLTree的平横过程。

 

在这里讲一下树的旋转:(要能看懂,之前需要对旋转有一点了解)

旋转的目的是为了降低树的高度,其分为"单旋"和"双旋",旋转整体的模式图为:

 

接下来以一个旋转的实例来进行说明:

struct AVLnode
{
    int val;
    struct AVLnode* left;
    struct AVLnode* right;
    struct AVLnode* parent;
}


//下述代码阅读建议:将三个修改父指针的代码去掉后再查看逻辑,修改父指针只是为了旋转后维持树的结构
void LeftRotate(AVLnode *ptr) //左旋代码;右旋的实现是一个镜像的过程
{
    AVLnode *newroot = ptr->right; //保存左旋后替代ptr的结点newroot
    newroot->parent = ptr->parent; //第1个修改父指针
    ptr->right = newroot->left; //保存之后,让ptr->right指向newroot的内侧节点
    newroot->left->parent = ptr; //第2个修改父指针
    newroot->left = ptr;  //newroot的内测结点位置被换为ptr

    AVLnode *pa = ptr->parent;
    if(ptr == pa->left)   //newroot替换原来ptr在树中的位置
	    pa->left = newroot;
    else
        pa->right = newroot;

    ptr->parent = newroot; //第3个修改父指针
}     

说明:
代码的实现一定要结合自己所写的树的结构来写参数列表;上述
代码完成了旋转前后的父子指针的修改,逻辑完全正确!但是为
了保证代码的易读性没有将其他的内容参杂进来,读者在利用的
时候要结合自己树的结构进行一定的修补。

 

接下来让我们进入到红黑树的插入模块的介绍:

在介绍插入的时候,我们我们要知道红黑树是一个BST树,其次对红黑树的5大性质一定要了解,5大性质如下:

     1.  每个节点或是红的或是黑的

     2.  根节点为黑色

     3.  外部节点是黑色的

     4.  如果一个结点是红色的,那么它的两个儿子都是黑色的  (红红不能相连)

     5.  对每个结点的,从该节点出发到外部结点的所有路径上包含相同个数的黑色结点(黑高相同)

 

        因此我们在插入结点时,要让待插入节点的颜色为红色,然后直接插入,这样插入一个红色结点后不会影响性质5,但是有可能使性质4无法满足,所以插入后我们需要进行调整,调整策略如下(记插入的结点为X):

(1)X的父节点为黑色,不用调整,满足红黑树的性质!直接退出调整函数。原因如图:

(2)X的父节点为红色,此时不满足性质4,需要进行调整(记X的父节点为 pa):

       (2.1)pa为红色,pa的兄弟节点也为红色,展示如下:

                    此情况下的处理是将pa和pa的兄弟结点的颜色都变为黑色,X=pa->parent ;

                    这么做的后果:1.  从pa->parent到外外部节点的黑高不变;

                                             2.  但是pa->parent变为红色后,由于我们不知道pa->parent->parent的颜色是红是黑,所以我们以                                                      pa->parent当作新的X再次进入整个调整函数

                                             3.  这个X有可能会连续出现多次上移

                                             4.  当我们的新的X为根节点时,根节点的颜色已经变成红色不满足“跟必须为黑色”的性质,需要将                                                      跟变黑

         (2.2)不满足2.1的话,那么肯定目前的情况肯定就是:pa为红色,pa的兄弟节点为黑色

                  (2.2.1)  X是其爷爷的外部孙子,模式如下:

                                    模式说明:未插入X前整个树是红黑树,pa为红色,则pa->parent必定为黑色,所以该模式的一定为                                                        图示的结构!

                                    该模式的变化方式为下(仅以X为其爷爷结点的右侧孙子为例;但X为其爷爷的左侧孙子为镜像操作):

                                    变换后的后果:符合红黑树中所有的性质,结束调整!

 

                  (2.2.2)  X是其爷爷的内部孙子,模式如下: 

                                    模式说明:未插入X前整个树是红黑树,pa为红色,则pa->parent必定为黑色,所以该模式的一定为                                                        图示的结构!

                                    该模式的变化方式为下(仅以X为其爷爷结点的右侧孙子为例;但X为其爷爷的左侧孙子为镜像操作):

                                   变换后的后果:符合红黑树中所有的性质,结束调整!

 

 

总结:

        插入的结点都是红色!

        每次调整结束都要保证跟为黑色!

        整个插入的调整最多执行一个单旋或者一个双旋就可以完成调整!

 

伪代码:

上述的2.1对应的case1

 2.2.1对应的是case2

 2.2.2对应的是case3

 

翻译代码如下:

typedef struct rb_node
{
	int val;
	int color;
	struct rb_node *parent;
	struct rb_node *leftchild;
	struct rb_node *rightchild;
}rb_node;


void Adjust_RBTree(rb_tree &myt, rb_node *ptr)
{
	rb_node *_X = ptr;
	rb_node *_Y = NULL;
	for (; _X->parent != myt.head && _X->parent->color == RED;) //当父节点是红色,或者回溯到跟 时,就不必在继续了
	{
		if (_X->parent == _X->parent->parent->rightchild) //即在爷爷结点的右端插入
		{
			_Y = _X->parent->parent->leftchild; //表_X的叔父
			if (_Y->color == RED)
			{
				_X->parent->color = BLACK;
				_Y->color = BLACK;
				_X->parent->parent->color = RED;

				_X = _X->parent->parent;
			}
			else
			{
				if (_X == _X->parent->leftchild) //内节点,进行双旋转第一转
				{
					_X = _X->parent; //旋转一次  将_X位置变化   就适用于下面的几行代码
					RotateRight(myt, _X);
				}

				_X->parent->color = BLACK;
				_X->parent->parent->color = RED;
				RotateLeft(myt, _X->parent->parent); //外结点,则单旋转
			}
		}
		else
		{
			_Y = _X->parent->parent->rightchild;
			if (_Y->color == RED)
			{
				_Y->color = BLACK;
				_X->parent->color = BLACK;;
				_X->parent->parent->color = RED;

				_X = _X->parent->parent;
			}
			else
			{
				if (_X = _X->parent->rightchild)
				{
					_X = _X->parent;
					RotateLeft(myt, _X);
				}
				_X->parent->color = BLACK;
				_X->parent->parent->color = RED;
				RotateRight(myt, _X->parent->parent);
			}
		}

	}

    myt.head->parent->color = BLACK; //根节点无论此时啥颜色直接置为黑色
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值