红黑叔的简要描述和详细实现
特点:
- 平衡二叉树
- 结点非黑即红
- (默认-红:根必须黑,默认红可减少更改颜色次数,提高效率)
- 可通过”红黑规则“平衡高度
1.成员定义:
public int qa; //值
public TreeNode left; //左节点
public TreeNode right; //右节点
public TreeNode father; //父节点
public boolean color = Red; //默认根节点为红
public static int size; //红黑树的结点个数
public static TreeNode head; //根结点
public static boolean Red = false;
public static boolean Black = true;
2.红黑规则:
- 非黑即红;
- 根必须为黑色;
- 若结点无子结点,则指向nil,nil视为叶节点,nil为黑色;
- 若结点为红,则子、父节点必须为黑,不能双红相连;
- 每个结点对其后代所有叶结点(nil)的路径,都有相同的黑色结点个数;
3.旋转:
代码比较简单,简单判断各个节点是否存在,然后将指向进行变换就行了
注:有3条线切换,顺序如下:
- 先进行 cur-父 -> cur-祖 的变换(先执行下面的可能会导致cur节点消失);
- 再查看 祖.father 是否存在,存在就变换(先执行3可能会导致无法连接上 祖.father);
- 最后切换 祖-父 的切换;
右旋实现,左旋可以参考自写:
public void rightRotate(TreeNode node) {
if (node != null && node.left != null) {
//右旋结点的左节点-left
TreeNode left = node.left;
//让左节点.right - node.left,同时让左节点.right.father - node-------------先处理左节点和node的指向会导致左节点.right的丢失,
node.left = left.right;
if (left.right != null) {
left.right.father = node;
}
//让node.father - left.father--------------------先处理左节点和node的指向,后处理node.father-left的指向会导致node.father的丢失,
left.father = node.father;
if (node.father == null) {
//node原为根结点--将left指为新根结点
head = left;
} else if (node.father.left == node) {
//node在node.father的左子树
left.father.left = left;
} else {
//node在node.father的右子树
left.father.right = left;
}
//最后处理left 和 node 的指向关系
left.right = node;
node.father = left;
}
}
4.添加结点—红黑规则的调整思想:
插入节点为cur:值为50
注:寻找节点位置的内容就不在这里展示了,简单二分查找就行了—添加位置一定会是子树的最底层节点
先分cur的父节点是在祖节点的左子树还是右子树写代码,一分为二,可以少些很多代码
-
添加情况-根据2-3-4树的演化:
-
2树
-
2-3-4树:直接添加到已有的节点处,成为2树;
-
红黑树:
-
1.添加到黑色下:因为添加为红色,直接添加,无需调整;
-
2.添加为红色节点下:红黑树根为黑,即是转下面的3、4树情况添加;
如果有叔叔节点,则是4树添加,没有叔叔节点则是3树添加;
-
-
-
3树
-
2-3-4树:根据大小合并到已有的2节点,成为3树
-
红黑树:分为4种情况需要调整:
- 2种情况不用调整-添加为50:
- 2种情况需要旋转2次(可以先旋转一次,然后变成情况3):
- 2种情况只需要旋转一次:
- 2种情况不用调整-添加为50:
-
-
-
4树
-
2-3-4树:先将原3树中间节点进行上移,成为父节点,然后根据大小合并到下面的2个节点中;
-
红黑树-红黑树的4树调整情况,父节点为黑直接省略:
-
叔叔节点为红色:
将父、叔节点变黑,祖节点变红,使祖节点及其以下满足红黑树规则,然后对祖节点进行调整调用即可;
-
祖父节点为黑色(即向上递归调整的情况-因为当前侧颜色红多,因此必然失衡,不过借用后续的代码也能实现调整完成-与三树调整策略相同):
-
-
红黑叔添加调整实现代码:
/**
* 平衡调整代码:2-3-4树与红黑树:---2树即1节点插入1节点
* 1. 2树
* ---2-3-4树:直接添加,进行节点合并;
* ---红黑树:无需调整---(1-父节点为黑,直接插入红节点, 2-父节点为红,则不是2树添加)
* 2. 3树
* ---2-3-4树:根据大小合并成3树
* ---红黑树: 6种情况(其中2种直接成为 左-中-右,无需调整)
* ------根左左 根右右 : 进行一次旋转就行;
* ------根左右 根右左 : 先进行一次旋转,成为左左、右右情况,在3树进行旋转即可
* 3. 4树
* ---2-3-4树: 先将原3节点的中节点上升成为父节点,然后将添加节点根据情况与下面的2节点合并
* ---红黑树: 将其转换成 爷-红,父叔-黑,使爷节点满足红黑树要求,然后进行旋转,向上递归调用调整即可
*/
public void Balance(TreeNode cur) {
//祖节点为空
if (fatherIsequals(fatherIsequals(cur)) == null) {
//就把父节点设置为根,并且改颜色,然后直接退出;
head = cur.father;
cur.father.color = Black;
return;
}
//添加节点的父节点需要为red,不然没有调整的必要-cur.color=red
while (cur != null && head != null && cur.father.color==Red) {
//父节点是祖节点的左节点
if (fatherIsequals(fatherIsequals(cur)).left == fatherIsequals(cur)) {
TreeNode rfN = rightIsequals(fatherIsequals(fatherIsequals(cur)));
/**
* 叔节点不为空,即是4树
*/
if (rfN!=null && rfN.color==Red) {
//将祖-爷节点设置为红,父叔节点颜色设置为黑---满足红黑树规则
fatherIsequals(fatherIsequals(cur)).color = Red;
rightIsequals(fatherIsequals(fatherIsequals(cur))).color = Black;
fatherIsequals(cur).color = Black;
//对祖节点进行操作,相当于递归调用
cur = fatherIsequals(fatherIsequals(cur));
} else {
//没有叔节点,为3树,
if (cur == rightIsequals(fatherIsequals(cur))) {
//插入为父节点的右边,即 根左右,先左旋--根左左
cur = fatherIsequals(cur);
leftRotate(cur);
}
//3树对应红黑树的根左左情况-右旋+颜色调整
fatherIsequals(cur).color = Black;
fatherIsequals(fatherIsequals(cur)).color = Red;
rightRotate(fatherIsequals(fatherIsequals(cur)));
}
} else {
//叔节点是否存在并且是否为红,存在即为4树插入
if (leftIsequals(fatherIsequals(fatherIsequals(cur))).color==Red) {
//4树插入我们对其祖、叔、父节点进行变色
fatherIsequals(fatherIsequals(cur)).color = Red;
leftIsequals(fatherIsequals(fatherIsequals(cur))).color = Black;
fatherIsequals(cur).color = Black;
//整个4树符合红黑树要求,于是将根-祖节点视为插入节点进行调整
cur = fatherIsequals(fatherIsequals(cur));
} else {
//叔节点不存在为3树,或叔节点为黑(此时cur为向上递归的子树头节点)我们对其判断,一次旋转还是二次旋转(插入右树左节点)
if (cur == leftIsequals(fatherIsequals(cur))) {
//将插入节点与父节点进行右旋,此时原父节点被视为插入节点
cur = fatherIsequals(cur);
rightRotate(cur);
}
//进行颜色变换,再左旋
fatherIsequals(fatherIsequals(cur)).color = Red;
fatherIsequals(cur).color = Black;
leftRotate(fatherIsequals(fatherIsequals(cur)));
}
}
}
//根节点颜色为黑
head.color = Black;
}
4.后驱节点:
在红黑树、二叉搜索树等规律数据结构中,大于当前节点的最小节点即为后驱节点,即中序遍历当前节点值的后一个即使后驱节点-这里我们删除红黑树用后驱节点:
代码:
/**
* 寻找后驱节点
*/
public TreeNode seccessor(TreeNode node) {
if (node == null) {
return null;
}
TreeNode cur;
//先寻找非叶节点情况
if (node.right != null) {
//来到初始node节点的左数最右节点
cur = node.right;
while (cur != null) {
cur = cur.left;
}
return cur;
} else if (node.father == null) {
//无右数,无父节点,没有后驱节点
return null;
} else {
/**同理
* node没有右子树,有父节点, 在红黑树里面不需要找后驱节点,但找后驱节点这里补一下-(红黑树:
* 叶节点直接删除,有左节点就代替node-不会出现左子树多节点情况,因为右旋调整)
*/
cur = node.father;
while (cur != null && node==cur.right) {
node = cur;
cur = cur.father;
}
return cur;
}
}
5.删除结点:
这里删除使用后驱节点,即中序遍历每个值的后驱节点便是下一个值
注:每个节点的后驱节点在2-3-4树中都只是在最底层
删除也分为3种情况(画图太麻烦了,就不画了)
-
删除节点存在2个子树:
- 找到该删除节点的后驱节点,然后将后驱节点值赋给删除节点,然后删除后驱节点即可,每个后驱节点都可以转换成下面的2种情况;
-
删除节点只有1个子树-分为2种情况:
先获得那个子节点replacement
-
1.删除为根节点-这里直接可以将叶子节点也包括内:
先将head=replacement,然后判断replacement不为空,则将replacement的父指针指向null,最后将删除节点所有指针指null即可;
-
2.只有replacement子节点,这里有2种思路:
- 1.将replacement子节点的值赋给replacement,然后删除子节点即可;
- 2.直接删除replacement节点,然后进行删除后的平衡调整;
-
-
删除节点为叶子节点:
- 先进行调整调用,然后进行删除,要判断一下删除节点是哪个子树,对应父节点的指针也要指空;
删除代码:
/**
* 删除值-:3种情况:
* 对应节点:cur,一个子节点:replacement,父节点:father
* 1.当删除节点有 左右 子树:
* ---可以直接找到删除节点的后驱节点(前驱节点效果一样),然后将后驱节点值赋给cur,然后删除后驱节点即可(后驱节点必然是2-3-4树的底层节点,为第2.3种情况)
* 2.当删除节点存在一个节点:
* ---情况1:删除节点为head根节点:
* ------将cur所有指针指向空,replacement赋给head(当replacement为空,即为叶节点同样有效)
* ---情况2:正常节点(cur只能是黑,replacement只能是红---因当cur为红,replacement只能为黑,而cur没有另一颗子树,于是红黑树不平衡):
* ------将father和replacement建立上下层关系(判断cur是father哪个子树),然后将cur所有指针指向空
* ------调整平衡(例:cur只有2个节点,但另一棵树可以存在7个节点)-(cur为黑时)
* 3.删除叶子节点:
* ---先调整(cur为黑时),然后根据cur是father的子树情况,将father.子树设空,然后cur指向都设空0
*/
public void remove(int value) {
//判断值是否存在,不存在直接退出
TreeNode cur = exist(value);
if (cur == null) {
return;
}
//第1种情况:当删除节点有 左、右 子树时
if (leftIsequals(cur) != null && rightIsequals(cur) != null) {
//找到删除节点的 后驱节点,并将删除节点换成后驱节点值,然后去删除后驱节点(也就是下面2种情况),
TreeNode secc = seccessor(cur);
cur.qa = secc.qa;
cur = secc;
}
//第2种情况:只有一个子树存在
//将子节点赋给replacement
TreeNode replacement = leftIsequals(cur) == null ? rightIsequals(cur) : leftIsequals(cur);
//删除节点为根节点
if (fatherIsequals(cur) == null) {
//将子节点作为新根
head = replacement;
if (replacement != null) {
//即红黑树只有cur和replacement的情况,还必须让新根节点.father指向空
replacement.father = null;
}
//同时将删除节点指向都 指向空
cur.left = cur.right = null;
size--;
} else if (replacement != null) {
//将子节点的父节点-父节点,同时将父节点指向子节点replacement
replacement.father = cur.father;
if (cur == leftIsequals(fatherIsequals(cur))) {
father.left = replacement;
}
if (cur == rightIsequals(fatherIsequals(cur))) {
father.right = replacement;
}
//同时将删除节点指向都 指向空
cur.left = cur.right = cur.father = null;
size--;
if (colorIsequals(cur) == Black) {
/**
* 做调整-有可能删除之后导致红黑树不平衡(例:cur子树只有cur+replacement,但是cur的父节点的另一颗子树可以存在7个节点):
* 先调整-叶子节点为红,没必要调整;
* (正常添加cur是黑,replacement只能是红---因当cur为红,replacement只能为黑,而cur没有另一颗子树,于是红黑树不平衡):
*/
pl(cur);
}
} else {
//第3种情况:叶子节点的删除
//先调整-叶子节点为红,没必要调整
if (colorIsequals(cur) == Black) {
pl(cur);
}
//再删除
if (cur == leftIsequals(fatherIsequals(cur))) {
father.left = null;
}
if (cur == rightIsequals(fatherIsequals(cur))) {
father.right = null;
}
//同时将删除节点指向都 指向空
cur.left = cur.right = cur.father = null;
size--;
}
if(head!=null){
head.color = Black;
}
}
6.删除的调整:
删除节点cur-Black,父节点-father,兄弟节点-rN,这里也分为几种情况—以cur是father的左子节点为例:
-
删除节点为红色节点,直接可以删除,不会影响红黑树的规则;
-
删除节点为黑色
-
兄弟节点存在并且为红-其下必有2个黑色子节点B1,B2,删除cur会导致红黑树不平衡:
先将rN-Red,father-Black(这样father左子树黑色cur、B1,右子树黑B2-满足平衡),然后左旋;再将rN重新定位,这样就满足下面的第3种的情况:
-
兄弟节点为黑-又分3种情况:
-
1.当兄弟节点有子节点时:
因为cur是father的左子节点,所以兄弟节点 (只有右节点) 和 (满子节点)的处理情况一样;
-
兄弟节点只有左节点-先变成只有右节点情况:
将 (rN) 和 (rN.left) 颜色进行对调,然后对rN进行右旋,需要重新定位cur的兄弟节点rN;
-
先将rN的颜色变成父节点颜色,父节点颜色变红,rN右节点颜色变红,然后对父节点进行左旋,就完成调整了,退出循环;
-
-
2.当兄弟节点为叶子节点-2种情况:
-
1.父节点为红:
只需要将父节点颜色变黑,rN颜色变红即可,退出循环;
-
2.父节点为黑:
同样先将rN颜色变红,但因为原父节点路径有2黑,现变成1黑,所以我们需要向上递归,将调整节点设为父节点;
-
-
-
代码实现:
/**
* 删除的调整-以cur=father.left为例:
* 1.cur为红,直接删除无需调整
* 2.cur为黑,并且不为根:判断兄弟节点rN颜色
* ---2.1:rN为红(cur-黑,所以rN必然有2个黑子节点):
* ------为左旋做调整-左旋后右边只有一个黑节点,所以father变红(rN.left会变father.right),rN变黑,左旋,然后rN重新指向father.right
* ------变成第2.2.2情况情况去处理
* ---2.2:rN为黑(rN子节点的3种情况):
* ------2.2.1:rN有节点-节点必然为红(cur子树只有1黑):
* —————————1.rN只有左子节点-变换成只有右子树情况:
* ============右旋前颜色调整 rN变Red,rN.left变Black,然后右旋,rN重新指向father.right
* —————————2.rN 只有右子节点、有2个子节点调整情况一样:
* ============rN.color变成father.color(以成为左旋后的新顶节点),father.color变黑,rN.right变黑,然后左旋,左旋前的rN.left存在的话是红不管;然后退出
* ------2.2.2:rN没有子节点:
* —————————1.father为红,只需将father变黑,rN变红即可,然后退出
* —————————2.father为黑,那么father节点有2个黑色节点路径,删除cur后最多一个黑色路径,所以要向上调整:
* ===========rN变红,然后以father节点while调整
* @param cur
*/
private void pl(TreeNode cur) {
//当cur为根或者颜色为红不需要调整,直接删除
if(cur!=head && colorIsequals(cur)==Black){
//cur为左节点的情况
while(cur == leftIsequals(fatherIsequals(cur))){
//记录cur的兄弟节点
TreeNode rNode = rightIsequals(cur);
if(colorIsequals(rNode) == Red){
/**
* 兄弟节点为红色,需要调整:
* 左树:只有cur-Black,而右树:兄弟节点为红,红黑树路径黑节点一样,所以肯定>=3
* ---将rNode颜色变Black,father节点变Red,然后左旋
* ---注:此时father这颗红黑树是平衡的,
* ------cur为黑,兄弟为红,兄弟肯定有2黑子节点,我们左旋会把rnode.left旋到cur.father的右子节点,
* ------而rnode下只有一个黑,所以father变Red-不能双红,rnode变black,就平衡了
*/
rNode.color = Black;
fatherIsequals(cur).color = Red;
//对父节点左旋
leftRotate(fatherIsequals(cur));
//左旋导致现在cur的兄弟节点变换了,所以要重新赋值
rNode = rightIsequals(fatherIsequals(cur));
}
//-------开始情况判断,是兄弟节点帮忙,还是向上递归
if(leftIsequals(rNode)==null && rightIsequals(rNode)==null){
/**
* 兄弟节点没有子节点情况,即只剩父、兄节点,存在2种情况(兄节点必然为Black-cur为Black):
* 1.父节点为Red:
* ---将父节点变Black,兄节点变Red,满足平衡
* 2.父节点为Black-原父节点路径2个黑节点最多剩1个,所以需要调整:
* ---将兄节点变Red,然后将父节点视为调整节点,递归调用;
*/
if(colorIsequals(fatherIsequals(cur))==Red){
//情况1:将父节点变Black,兄节点变Red,满足平衡
fatherIsequals(cur).color = Black;
rNode.color = Red;
//然后退出-cur=head不满足循环条件
cur = head;
}else {
rNode.color = Red;
//情况2:对father进行调整
cur = fatherIsequals(cur);
}
}else if(rightIsequals(rNode)!=null){
/**
* 兄弟节点有子节点情况:cur为黑,兄弟节点也为黑-其子节点为红,所以有3种情况,
* 1.兄弟节点有2个子树
* 2.兄弟节点只有右子树
* 3.兄弟节点只有左节点-兄弟节点为右子树,1.2情况处理方法一样,所以先将3情况调整成2情况
* 同时因为上一个情况有while,所以将cur指向head来退出while
*/
if(rightIsequals(cur)==null){
/**
*情况3:兄弟节点只有左节点情况-cur为Black,所以兄弟节点也为Black:
* ---将兄弟节点左节点B1与兄弟节点颜色互换,然后对兄弟节点右旋,
*/
leftIsequals(rNode).color = Black;
rNode.color = Red;
rightRotate(rNode);
//重新定位cur的兄弟节点
rNode = rightIsequals(fatherIsequals(cur));
}
/**
* 然后做统一处理:
* ---先将rNode颜色换成原father颜色-以成为新father,原father颜色成Black,同时rNode.right成Black
*/
rNode.color = fatherIsequals(cur).color;
fatherIsequals(cur).color = Black;
rightIsequals(rNode).color = Black;
//然后对father做左旋,最后退出
leftRotate(fatherIsequals(cur));
cur = head;
}
}else {
//cur为右节点的情况
//记录cur的兄弟节点
TreeNode rNode = leftIsequals(cur);
if(colorIsequals(rNode) == Red){
/**
* 兄弟节点为红色,需要调整:
* 右树:只有cur-Black,而左树:兄弟节点为红,红黑树路径黑节点一样,所以肯定>=3
* ---将rNode颜色换Black,father变Red,然后右旋
*/
rNode.color = Black;
fatherIsequals(cur).color = Red;
//对父节点右旋
rightRotate(fatherIsequals(cur));
//右旋导致现在cur的兄弟节点变换了,所以要重新赋值
rNode = leftIsequals(fatherIsequals(cur));
}
if(leftIsequals(rNode)==null && rightIsequals(rNode)==null){
/**
* 兄弟节点没有子节点情况,即只剩父、兄节点,存在2种情况(兄节点必然为Black-cur为Black):
* 1.父节点为Red:
* ---将父节点变Black,兄节点变Red,满足平衡
* 2.父节点为Black-原父节点路径2个黑节点最多剩1个,所以需要调整:
* ---将兄节点变Red,然后将父节点视为调整节点,递归调用;
*/
if(colorIsequals(fatherIsequals(cur))==Red){
//情况1:将父节点变Black,兄节点变Red,满足平衡
fatherIsequals(cur).color = Black;
rNode.color = Red;
//然后退出-cur=head不满足循环条件
cur = head;
}else {
//情况2:需要对father进行调整
rNode.color = Red;
cur = fatherIsequals(cur);
}
}else if(leftIsequals(rNode)!=null){
/**
* 兄弟节点为黑,cur也为黑,所以有3种情况,
* 1.兄弟节点有2个子树
* 2.兄弟节点只有左子树
* 3.兄弟节点只有右节点-因为1.2情况处理方法一样,所以先将3情况调整成2情况
* 同时因为上一个情况有while,所以将cur指向head来退出while
*/
if(leftIsequals(cur)==null){
/**
*情况3:兄弟节点只有右节点情况-cur为Black,所以兄弟节点也为Black:
* ---将兄弟节点右节点B1与兄弟节点颜色互换,然后对兄弟节点左旋,
*/
rightIsequals(rNode).color = Black;
rNode.color = Red;
leftRotate(rNode);
//原先的leftIsequals(rNode),现在是新的cur的兄弟节点
rNode = leftIsequals(fatherIsequals(cur));
}
/**
* 然后做统一处理:
* ---先将rNode颜色换成原father颜色-以成为新father,原father颜色成Black,同时rNode.right成Black
*/
rNode.color = fatherIsequals(cur).color;
fatherIsequals(cur).color = Black;
leftIsequals(rNode).color = Black;
//然后对father做右旋,最后退出
rightRotate(fatherIsequals(cur));
cur = head;
}
}
}
cur.color = Black;
head.color = Black;
}