红黑树详解

查找算法有哪些?

暴力:遍历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)把祖父结点,也就是父亲的父亲设置为红色(爷爷结点)

为什么这样变换而不是把78变黑?
-------------------------------
因为78变黑,后边的都要变,体量太大了,
倒不如变前边的,体量小点。

在这里插入图片描述

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;
        }
  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

忧郁的叮当猫

Thanks♪(・ω・)ノ

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值