红黑树详解

查找算法有哪些?

暴力:遍历for
二分:能做二分查找的条件:有序
哈希:最高效O(1),hash冲突,JDK1.8里面HashMap:数组+链表+红黑树(处理Hash冲突的)
插值:
索引:
bfs&dfs:
平衡树:
B+树:
B-Tree:
红黑树:高效的查找算法数据结构

二叉搜索树:也叫二叉查找树,也叫二叉排序树
特点:右边的节点比左边的节点大,右子树节点大于根节点,左子树的节点小于根节点
二叉树
二叉查找树:
时间复杂度就是我们树的深度
时间复杂度分析:logn=> 2^x=n(树的高度) => x=log2 n(2是底) =>logn
时间复杂度与树高度(x)关系:
1. 时间复杂度:一般指代码运行的次数
2. 代码运行次数=树高度(x)
	比如:假设该树高度为3, 从根节点下来,最坏情况要比较3次,相当于运行3次代码。
3. 所以,时间复杂度=树高度(x)
树高度(x)与树节点数(n)关系
1. 平衡二叉树理想情况需要做到左右高度相同
2. 那么,基于上述情况, x与n的关系就为 2^x=n
	n=2^0+2^1+...+2^(x-1)=2^x-1。-1这种常数可以舍弃。
时间复杂度=x=log2n,2是底。

二叉查找树存在退化成链表的情况,如何解决?

不要让它变成一条链表

AVL树:平衡二叉树,它的左右子树高度之差不超过1,这样确实可以避免一条直线型的结构,
      但还不是我们最理想的可以认为是理想状态
红黑树的底层数据结构是什么?(特殊的二叉树)二叉查找树
JDK1.7HashMap:数组+链表 --> 1.8推出了红黑树
数据结构的推算过程:
链表(暴力)->二叉树->二叉查找树->特殊的二叉查找树(自平衡的二叉树)

红黑树的性质(重点)
1.每个结点不是红色就是黑色 
2.不可能有连在一起的红色结点
3.根节点都是黑色root(入度为0)
4.每个红色结点的两个子节点都是黑色,叶子结点都是黑色,
  出度为0,满足了性质就可以近似的平衡了,不一定要变黑,可以为其他的。  

红黑树的 3 种变换规则: 变色,左旋,右旋

1.改变颜色:最简单,红变黑,黑变红

变颜色条件:两个连续红色节点,并且叔叔节点是红色

变色规则: 把父节点变为黑色,把叔叔节点变为黑色,把爷爷节点变为红色

2.左旋 :

左旋条件:两个连续红节点,并且叔叔节点是黑色,下面的红色节点在右子树

假设点旋节点为E,右子树根节点为S,

第一步: S 左旋作为点旋节点(也就是根节点)

第二步:E 左旋为左子树的根节点,E的左分支原节点不变

第三步:之前S的右子树的左子树(左半部分)挂到E的右分支上

其实还有变色,后边实例有详细描述

注意:挂载的不只是单独的一个节点,而是一个子树

动图如下:

(左旋条件:两个连续红节点,并且叔叔节点是黑色,下面的红色节点在右子树)

3.右旋:和左旋原理一样,自行推敲

右旋条件:两个连续红节点,并且叔叔节点是黑色,下边的红色节点在左子树

旋转和颜色变换规则:所有插入的点默认为红色

1.变色实例:

变颜色条件:两个连续红色节点,并且叔叔节点是红色

这里违反了性质4,有两个红节点相连

注意:78的父节点【89】是红色,78的爷爷节点的另一个子节点(叔叔节点)【45】也是红色
满足变颜色规则,更改规则如下

(1)把父结点设为黑色

(2)把叔叔结点也设为黑色

(3)把祖父结点,也就是父亲的父亲设置为红色(爷爷结点)

2.左旋实例:

左旋条件:两个连续红节点,并且叔叔节点是黑色,下面的红色节点在右子树

情况1: 如果当前节点是右子树,并且父节点是左子树

例如:(【900】是新插入的节点)

要根据父节点左旋【899】(根据谁左旋,谁就变成子节点)

【900】的左子树 挂到 【899】的右子树上 , 【899】变为子节点 , 【900】变为父节点
此时不变色
因为还需要右旋

情况2: 如果当前节点是右子树,并且父节点是右子树

例如:(【100】是当前节点)

 根据祖父节点左旋【56】(根据谁左旋,谁就变成子节点) 
【56】变为子节点,【89】变为父节点,【89】的左子树 挂到 【56】的右子树
同时: 父节点变黑(【89】变黑),祖父节点变红(【56】变红 )

3.右旋实例

右旋条件:两个连续红节点,并且叔叔节点是黑色 , 下面的红色节点在左子树

情况1:如果当前节点是左子树,并且父节点也是右子树

形如:(【8000】是当前节点)

根据父节点右旋【9000】(根据谁右旋,谁就变成子节点)
【9000】变为子节点,【8000】变为父节点,【8000】的右子树 挂到 【9000】的左子树
此时不变色

其实现在还是不满足性质,应该继续左旋,这里不多赘述,和上面左旋情况2   一样

情况2: 如果当前节点是左子树,并且父节点也是左子树

形如:(【899】是当前节点)

根据祖父节点右旋【1000】(根据谁右旋,谁就变成子节点)
【1000】变为子节点,【900】变为父节点,【900】的右子树 挂到 【1000】的左子树
同时: 父节点变黑(【900】变黑),祖父节点变红(【1000】变红)

平衡插入算法(插入的节点默认为红色)

static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
   
            //新插入的节点默认为红色 
            x.red = true;
            //循环
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
   
            	//判断插入的父节点 是不是 null
                if ((xp = x.parent) == null) {
   
                	//是null代表当前节点是根节点 ,根节点要置成黑色(根据性质)
                    x.red = false;
                    return x;
                }
                //现在 xp是父节点 
                //父节点不是红色 或者 xpp(祖父节点) 是 null
                /** 父节点不是红色:满足性质 【!xp = red】如图: xpp(祖父节点) / \ xp【黑】 / x【红】 祖父节点是null: 当前是第二层的节点(根当作第一层,根节点一定黑色)满足性质 【(xpp = xp.parent) == null】如图: xp【黑】 / x【红】 */
				else if (!xp.red || (xpp = xp.parent) == null)
                    return root;
                //父节点是左子树
                /** xpp(祖父节点) / \ xppl(xp)【红色】 / x【红色】 */
                if (xp == (xppl = xpp.left)) {
   
                	//存在右侧的叔叔节点 ,并且右侧叔叔节点 是红色
                	//满足红黑树变颜色的规则【插入节点红色,父亲节点红色,叔叔节点红色】
                	
                	 /** xpp(祖父节点)【黑】 / \ xppl(xp)【红色】 xppr(叔叔节点)【红】 / x【红色】 */
                    if ((xppr = xpp.right) != null && xppr.red) {
   
                    	//右叔叔节点变黑色
                        xppr.red = false;
                        //父亲节点变黑色
                        xp.red = false;
                        //祖父节点变红色
                        xpp.red = true;
						/** xpp(祖父节点)【红】 / \ xppl(xp)【黑】 xppr(叔叔节点)【黑】 / x【红色】 */
						//祖父节点赋值给 x , 继续循环,因为可能祖父节点的上一级也是红色
						//所以要以祖父节点为基准,继续循环判断
                        x = xpp;
                    }
                    //不满足变色的规则
                    /** xpp(祖父节点)【黑】 / \ xppl(xp)【红色】 xppr(叔叔节点)【黑/Null】 / x【红色】 */
                    //那么就得进行旋转
                    else {
   
                    	//插入的节点是右子节点
                    	/** xpp(祖父节点)【黑】 / \ xppl(xp)【红色】 xppr(叔叔节点)【黑/Null】 \ x【红色】 */
                        if (x == xp.right) {
   
                        	//因为xp的父节点可能又是红色,不满足性质,
                        	//所以要以父节点为基准,继续循环判断
                        	//左旋【以父节点为轴】将x设置为父节点
                        	//(因为左旋后 子节点将会变成父节点)
                            root = rotateLeft(root, x = xp);
							/** 左旋后 xpp(祖父节点)【黑】 / \ xp(原来的x)【红色】 xppr(叔叔节点)【黑/Null】 / x(原来的xp)【红色】 */
                            //xpp(插入节点的祖父节点)是null,说明父节点是根节点
                            //否则说明有祖父节点,设置xpp为(插入节点的)祖父节点
                           	//旋转后 节点位置发生变化,须要重新判断xpp
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        //父节点不是null
                        if (xp != null) {
   
                        	//父节点 变黑
                            xp.red = false;
                            //祖父节点不是Null
                            if (xpp != null) {
   
                            	//祖父节点变红
                                xpp.red = true;
                                //右旋 【以祖父节点为轴】
                                root = rotateRight(root, xpp);
                                /** 右旋后 xp【黑】 / \ x【红色】 xpp(原祖父节点)【红】 \ xppr等等..... */
                            }
                        }
                    }
                }
				/** else是指xp在右子树上,如图: xpp(祖父节点)【黑】 / \ xppr(叔叔节点)【黑/Null】 xppl(xp)【红色】 \ x【红色】 */
                else {
   
                	//满足变色规则
                    if (xppl != null && xppl.red) {
   
                        xppl.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
   
                    	//x是左子节点
                    	/** xpp(祖父节点)【黑】 / \ xppr(叔叔节点)【黑/Null】 xppl(xp)【红色】 / x【红色】 */
                        if (x == xp.left) {
   
                        	//右旋,根据xp节点
                            root = rotateRight(root, x = xp);
                            //旋转之后 效果如下
                            /** xpp(祖父节点)【黑】 / \ xppr(叔叔节点)【黑/Null】 xp(原来的x)【红色】 \ x(原来的xp)【红色】 */
		                    //赋值
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        //xp不为空
                        if (xp != null) {
   
                        	//设置xp为黑色
                        	/** xpp(祖父节点)【黑】 / \ xppr(叔叔节点)【黑/Null】 xp(原来的x)【黑色】 \ x(原来的xp)【红色】 */
                            xp.red = false;
                            //xpp不为空
                            if (xpp != null) {
   
                            	//设置xpp为红色
                                xpp.red = true;
                                //以祖父节点为轴,左旋 
                                root = rotateLeft(root, xpp);
                                /** xp【黑】 / \ xpp(原来的祖父节点) 【红】 x【红色】 / xppr(叔叔节点)【黑/Null】 */
                            }
                        }
                    }
                }
            }
        }

左旋算法

static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
   
            TreeNode<K,V> r, pp, rl;
            
			/** xpp(祖父节点)【黑】 / \ xppl(xp)【红色】 xppr(叔叔节点)【黑/Null】 \ x【红色】 */
			//p就是上图的xp,也就是插入节点x的父节点
            if (p != null && (r = p.right) != null) {
   
            	/** pp(祖父节点)【黑】 / \ p【红色】 xppr(叔叔节点)【黑/Null】 \ r【红色】 / rl(r的左子节点) */
			    //这句话的啥意思是 r.left(rl)也就是r的左子树 挂到 p.right也就是p的右子树上
			    //然后rl.parent(就是r) = p 意思是将rl(r的左子树) 的父节点设置为 p
			    //这样就完成了 r的左子树 挂到 p的右子树操作!!!!!!!!!!!
			    
			   
                if ((rl = p.right = r.left) != null)
                    rl.parent = p;
                //pp = r.parent = p.parent 意思是将r变成xpp的子节点(r取代了p原本的位置)
                //pp == null 如果p的父节点是null
                 //执行下面if语句后 旋转后
			    /** pp(祖父节点)【黑】 / \ r【红色】 xppr(叔叔节点)【黑/Null】 / p【红色】 \ rl(r的左子节点) */
                if ((pp = r.parent = p.parent) == null)
                	//如果if成立 ,说明r现在就是最顶级的节点
                	//让r变为根节点 因为现在r就是顶级的节点
                	//并且根节点设置为黑色【根据红黑树性质,根节点必须是黑色】
                    (root = r).red = false;
                    /** r(root)【黑色】 / p【红色】 \ rl(r的左子节点) */
                //否则说明r还有父节点(pp), 如果pp的左子节点是p
                else if (pp.left == p)
                	//将r设置成pp的左子节点 之前只只设置了r.parent是pp,现在设置pp.left是r
                	//这样 r真正变成pp的左子节点
                    pp.left = r;
                else
                	//如果p在右子树上,那么旋转都是一样的,只不过 设置为pp的右子节点为r
            		//【这里的图只是用作演示p在右子树情况,不和上边的图发生关联关系】
                	/** pp(祖父节点)【黑】 / \ xppr(叔叔节点)【黑/Null】 p【红色】【这里要设置为r】 \ r【红色】 / rl(r的左子节点) */
                    pp.right = r;
				//将p设置成r的左子节点 之前只只设置了p.parent是r,现在设置r.left是p
				//这样 p就真正变成了r的左子节点
                r.left = p;
                //r变成p的父亲
                //这样r和p也完成了位置替换
                p.parent = r;
                /** pp(祖父节点)【黑】 / \ r【红色】 xppr(叔叔节点)【黑/Null】 / p【红色】 \ rl(r的左子节点) */
            }
            return root;
        }
全部评论

相关推荐

futureQAQ:开发:这咋啥都不知道 这怎么测试的 这死去的回忆疯狂攻击我 哈哈哈
点赞 评论 收藏
分享
一个菜鸡罢了:哥们,感觉你的简历还是有点问题的,我提几点建议,看看能不能提供一点帮助 1. ”新余学院“别加粗,课程不清楚是否有必要写,感觉版面不如拿来写一下做过的事情,教育经历是你的弱势就尽量少写 2. “干部及社团经历”和“自我评价”删掉 3. 论文后面的“录用”和“小修”啥的都删掉,默认全录用,问了再说,反正小修毕业前肯定能发出来 4. 工作经验和研究成果没有体现你的个人贡献,着重包装一下个人贡献
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务