平衡二叉树
为了避免出现"瘸子"的现象,减少树的高度,提高我们的搜素效率,又存在一种树的结构:"平衡二叉树"右称为AVl树
规则:它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
上图中左图为一颗平衡二叉树,而右图虽然根节点左右俩子树的高度差是0,但是右子树15的左右字数高度差为2,不符合平衡二叉树的性质,所以右图不为AVL树
而怎么将右图类似情况转换为AVL树呢?
为此引入旋转来解决
旋转
在构建一棵平衡二叉树的过程中,当有新的节点要插入时,检查是否因插入后而破坏了树的平衡,如果是,则需要做旋转去改变树的结构。
左旋:
左旋就是将节点的右支往左拉,右子节点变成父节点,并把晋升之后多余的左子节点出让给降级节点的右子节点;
/**
* 左旋
* p pr
* / \ / \
* pl pr p rr
* / \ / \
* rl rr pl rl
*/
代码实现:
public void leftRotate(RBNode p){
if(p!=null){
RBNode r = p.right;
//pr-rl 变为 p-rl
p.right = r.left;
if (r.left!=null){
r.left.parent = p;
}
//判断p是否有父节点
r.parent = p.parent;
if (p.parent == null){
root = r;
}else if (p.parent.left ==p){
p.parent.left = r;
}else {
p.parent.right = r;
}
//最后设置p为r的左子节点
r.left = p;
p.parent = r;
}
}
右旋:
将节点的左支往右拉,左子节点变成了父节点,并把晋升之后多余的右子节点出让给降级节点的左子节点
/**
* 右旋
* pf pf
* \ \
* p (l)pl
* / \ => / \
*(l)pl pr ll p
* / \ / \
* ll lr lr pr
*
代码实现
private void rightRotate(RBNode p) {
if (p != null) {
RBNode l = p.left;
p.left = l.right;
if (l.right != null) {
l.right.parent = p;
}
l.parent = p.parent;
if (p.parent == null) {
root = l;
} else if (p.parent.right == p) {
p.parent.right = l;
} else {
p.parent.left = l;
}
l.right = p;
p.parent = l;
}
}
由于在构建平衡二叉树的时候,当有新节点插入时,都会判断插入后时候平衡,这说明了插入新节点前,都是平衡的,也即高度差绝对值不会超过1。当新节点插入后,
有可能会有导致树不平衡,这时候就需要进行调整,而可能出现的情况就有4种,分别称作左左,左右,右左,右右。
左左
左左即为在原来平衡的二叉树上,在节点的左子树的左子树下,有新节点插入,导致节点的左右子树的高度差为2,如下即为"10"节点的左子树"7",的左子树"4",插入了节点"5"或"3"导致失衡
左左调整其实比较简单,只需要对节点进行右旋即可,如下图,对节点"10"进行右旋,
左右
左右即为在原来平衡的二叉树上,在节点的左子树的右子树下,有新节点插入,导致节点的左右子树的高度差为2,如上即为"11"节点的左子树"7",的右子树"9",
插入了节点"10"或"8"导致失衡。
左右的调整就不能像左左一样,进行一次旋转就完成调整。我们不妨先试着让左右像左左一样对"11"节点进行右旋,结果图如下,右图的二叉树依然不平衡,而右图就是接下来要
讲的右左,即左右跟右左互为镜像,左左跟右右也互为镜像。
左右这种情况,进行一次旋转是不能满足我们的条件的,正确的调整方式是,将左右进行第一次旋转,将左右先调整成左左,然后再对左左进行调整,从而使得二叉树平衡。
即先对上图的节点"7"进行左旋,使得二叉树变成了左左,之后再对"11"节点进行右旋,此时二叉树就调整完成,如下图:
右左
右左即为在原来平衡的二叉树上,在节点的右子树的左子树下,有新节点插入,导致节点的左右子树的高度差为2,如上即为"11"节点的右子树"15",的左子树"13",
插入了节点"12"或"14"导致失衡。
前面也说了,右左跟左右其实互为镜像,所以调整过程就反过来,先对节点"15"进行右旋,使得二叉树变成右右,之后再对"11"节点进行左旋,此时二叉树就调整完成,如下图:
右右
右右即为在原来平衡的二叉树上,在节点的右子树的右子树下,有新节点插入,导致节点的左右子树的高度差为2,如下即为"11"节点的右子树"13",的左子树"15",插入了节点
"14"或"19"导致失衡。
右右只需对节点进行一次左旋即可调整平衡,如下图,对"11"节点进行左旋。
234树
224树是四阶B树,他属于一种多路查找树,它的结构有以下限制
1. 所有叶子节点都拥有相同的深度
2. 节点只能是2节点3节点4节点之一
3. 元素始终保持排序,整体上保持二叉查找树的性质,即父节点大于左子节点,小于右子节点;而且结点有多个元素时每个元素必须大于它左边的和它的左子树中元素
下图为一个234树
由于节点元素不确定,2-3-4 树在多数编程语言中实现起来相对困难,因为在树上的操作涉及大量的特殊情况。红黑树实现起来更简单一些,所以可以用它来替代
将上图转换为红黑树
将234树转换为红黑树主要是结点的转换
二节点:
三节点:
四节点:
正因如此我选择将红黑树和234树一起分析增加和删除。
红黑树:
红黑树是一种特化的AVL树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色。 [3] 在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 结点是红色或黑色。 [3]
性质2. 根结点是黑色。 [3]
性质3. 所有叶子都是黑色。(叶子是NIL结点) [3]
性质4. 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
性质5. 从任一节结点其每个叶子的所有路径都包含相同数目的黑色结点。
234树的新增操作
第1个节点不需要合并
2.新增一个节点,与2节点合并直接合并
3新增一个节点,与3节点合并
4新增一个节点与4节点合并,此时需要分裂
代码实现:
其中1,2直接添加(二叉树的添加)
3中需旋转后变色
4需要判断父节点是否为根
区别3,4的方法是判断插入节点是否有叔叔节点。
红黑树节点的新增
1先查询到插入的位置
2将kv封装为node对象插入到tree中
3红黑树的平衡(调整 旋转加变色)、
辅助方法:
private boolean colorOf(RBNode node){
return node == null ?BLACK :node.color;
}
private RBNode parentOf(RBNode node){
return node !=null ?node.parent:null;
}
private RBNode leftOf(RBNode node){
return node !=null?node.left:null;
}
private RBNode rightOf(RBNode node){
return node !=null ? node.right:null;
}
private void setColor(RBNode node,boolean color){
if(node!=null){
node.setColor(color);
}
}
新增方法:
public void put(K key,V value){
//得到根节点
RBNode t = this.root;
if (t == null){
root = new RBNode(key,value==null?key:value,null);
setColor(root,BLACK);
return;
}
//查找插入的位置
int cmp;
//记录寻找节点的父节点
RBNode parent;
if (key == null){
throw new NullPointerException();
}
do{
parent = t;
cmp = key.compareTo((K)t.key);
if(cmp<0){
t =t.left;
}else if (cmp>0){
t=t .right;
}else {
t.setValue(value == null?key:value);
return;
}
}while (t != null);
//表示找到了插入的位置 parent的子节点
RBNode e = new RBNode(key,value==null?key:value,parent);
if (cmp<0){
parent.left = e;
}else{
parent.right = e;
}
//做平衡处理
fixAfterPut(e);
}
红黑树平衡的处理
1、2-3-4树:新增元素+2节点合并(节点中只有1个元素)=3节点(节点中有2个元素)
红黑树:新增一个红色节点+黑色父亲节点=上黑下红(2节点)--------------------不要调整
2、2-3-4树:新增元素+3节点合并(节点中有2个元素)=4节点(节点中有3个元素)
这里有4种小情况(左3,右3,还有2个左中右不需要调整)------左3,右3需要调整,其余2个不需要调整
红黑树:新增红色节点+上黑下红=排序后中间节点是黑色,两边节点都是红色(3节点)
3、2-3-4树:新增一个元素+4节点合并=原来的4节点分裂,中间元素升级为父节点,新增元素与剩下的其中一个合并
红黑树:新增红色节点+爷爷节点黑,父节点和叔叔节点都是红色=爷爷节点变红,父亲和叔叔变黑,如果爷爷是根节点,则再变黑
private void fixAfterPut(RBNode x) {
setColor(x,RED);
while (x!=null&&x!=root&&parentOf(x).color == RED){
//x的父节点是x的爷爷节点的左子节点4种需要处理的情况
if (parentOf(x)==parentOf(parentOf(x)).left){
//满足条件的四种情况跟据是否有叔叔节点又一分为二
//获取当前的叔叔节点
RBNode y = rightOf(parentOf(parentOf(x)));
if (colorOf(y)==RED){
//叔叔节点存在
setColor(parentOf(x),BLACK);
setColor(y,BLACK);
setColor(parentOf(parentOf(x)),RED);
//递归处理
x = parentOf(parentOf(x));
}else {
//2
if(x == rightOf(parentOf(x))){
//插入节点是父节点的右侧结点 需要跟据父节点做一次左旋操作
x = parentOf(x);
leftRotate(x);
}
setColor(parentOf(x),BLACK);
setColor(parentOf(parentOf(x)),RED);
rightRota(parentOf(parentOf(x)));
}
}else{
//与上面相反操作
//满足条件的四种情况跟据是否有叔叔节点又一分为二
//获取当前的叔叔节点
RBNode y = leftOf(parentOf(parentOf(x)));
if (colorOf(y)==RED){
//叔叔节点存在
setColor(parentOf(x),BLACK);
setColor(y,BLACK);
setColor(parentOf(parentOf(x)),RED);
//递归处理
x = parentOf(parentOf(x));
}else {
//2
if (x == leftOf(parentOf(x))) {
//插入节点是父节点的右侧结点 需要跟据父节点做一次左旋操作
x = parentOf(x);
rightRota(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
leftRotate(parentOf(parentOf(x)));
}
}
}
//根节点都为黑色
setColor(root, BLACK);
}
红黑树的删除和234树的删除
234树的删除可以全部转换为叶子节点的删除,删除原则是先看能不能和下面的叶子节点合并,,能合并的直接合并删除,不能合
删除操作:
情况1删除叶子结点
(1) 如果是红色,直接删除
(2) 如果是黑色,对应红黑树从根节点出发到所有的叶子,有一条路径上黑色结点数比其他路径上的黑色结点少1个,不满足红黑树的性质,需要变色等调整
见下(3)借兄弟结点无子节点分析
情况2删除的结点有一个儿子结点,且儿子结点一定是红色
情况3除的节点有2个子节点,此时需要找到前驱节点或者后继节点来替代并的就要找个元素顶替上去,最终保持平衡即是将删除节点操作转换成删除前驱或后结点
被删除的前驱节点或者后继节点只有2种情况:
1、被删节点是叶子节点
其中有两种情况
(1)兄弟结点有子节点(图中要删除的结点为4,本文中替换结点为后继节点且8不是后继结点的真正的兄弟结点先将6左旋)
再将6结点左旋将5,6,7,7.5,变色
最后将5删参考情况1
(1) 借兄弟结点无子节点
1.被删结点的父节点为红色
此时1的兄弟结点3无子节点,如删1红黑树不在黑色平衡,因为根结点的左侧少一个黑色结点,所以我们可以将3染红先让局部平衡,在扩展到全面平衡
因为2和3不能同时为红且对于结点5未达到黑色平衡
所有将2结点变为黑色这样达到全局平衡
或
2.被删结点的父节点为黑色
此时删除0
由于兄弟结点无子结点,便将兄弟结点染红,此方法需要有递归的思想,为左边局部平衡,将被删结点的父节点的兄弟结点染红,此时根结点左侧局部平衡
此时7的左右子树不平衡,而且3的兄弟结点11也借不了所以将3的兄弟结点染红来实现黑色平衡。此方法通过递归不断染红兄弟结点达到黑红平衡
(一)
若兄弟结点中已是红色怎么办?
此时兄弟结点并不是真正的兄弟节点
通过将节点3左旋后3变色得到真正的兄弟节点3.3
并将兄弟节点变色
此时将节点3染黑便实现了黑红平衡。
(二)若在递归维持局部平衡时能遇到能借的兄弟节点时如下图
删除0节点将2节点变色后,递归此时1的兄弟节点为5,此时可以将5节点右旋
此时兄弟节点为4.5,可以将4借出,通过3节点左旋将3节点下压并将5节点变色从而达到全局平衡
2、被删节点只有一个孩子
此时就是情况二删除
红黑树所有代码:
package com.cn;
public class RBTree<K extends Comparable<K>,V > {
private static final boolean RED = false;
private static final boolean BLACK= true;
private RBNode root;
public RBNode getRoot() {
return root;
}
public void setRoot(RBNode root) {
this.root = root;
}
/**
* 添加
* 红黑树节点的新增
* 1.普通红黑树的平衡
* 先查询到插入的位置
* 将kv封装为node对象插入到tree中
* 2红黑树的平衡(调整 旋转加变色)
*
*/
public void put(K key,V value){
//得到根节点
RBNode t = this.root;
if (t == null){
root = new RBNode(key,value==null?key:value,null);
setColor(root,BLACK);
return;
}
//查找插入的位置
int cmp;
//记录寻找节点的父节点
RBNode parent;
if (key == null){
throw new NullPointerException();
}
do{
parent = t;
cmp = key.compareTo((K)t.key);
if(cmp<0){
t =t.left;
}else if (cmp>0){
t=t .right;
}else {
t.setValue(value == null?key:value);
return;
}
}while (t != null);
//表示找到了插入的位置 parent的子节点
RBNode e = new RBNode(key,value==null?key:value,parent);
if (cmp<0){
parent.left = e;
}else{
parent.right = e;
}
//做平衡处理
fixAfterPut(e);
}
private boolean colorOf(RBNode node){
return node == null ?BLACK :node.color;
}
private RBNode parentOf(RBNode node){
return node !=null ?node.parent:null;
}
private RBNode leftOf(RBNode node){
return node !=null?node.left:null;
}
private RBNode rightOf(RBNode node){
return node !=null ? node.right:null;
}
private void setColor(RBNode node,boolean color){
if(node!=null){
node.setColor(color);
}
}
/**红黑树平衡的处理
* * 1、2-3-4树:新增元素+2节点合并(节点中只有1个元素)=3节点(节点中有2个元素)
* * 红黑树:新增一个红色节点+黑色父亲节点=上黑下红(2节点)--------------------不要调整
* *
* * 2、2-3-4树:新增元素+3节点合并(节点中有2个元素)=4节点(节点中有3个元素)
* * 这里有4种小情况(左3,右3,还有2个左中右不需要调整)------左3,右3需要调整,其余2个不需要调整
* * 红黑树:新增红色节点+上黑下红=排序后中间节点是黑色,两边节点都是红色(3节点)
* *
* * 3、2-3-4树:新增一个元素+4节点合并=原来的4节点分裂,中间元素升级为父节点,新增元素与剩下的其中一个合并
* * 红黑树:新增红色节点+爷爷节点黑,父节点和叔叔节点都是红色=爷爷节点变红,父亲和叔叔变黑,如果爷爷是根节点,则再变黑
* *
* @param x
*/
private void fixAfterPut(RBNode x) {
setColor(x,RED);
while (x!=null&&x!=root&&parentOf(x).color == RED){
//x的父节点是x的爷爷节点的左子节点4种需要处理的情况
if (parentOf(x)==parentOf(parentOf(x)).left){
//满足条件的四种情况跟据是否有叔叔节点又一分为二
//获取当前的叔叔节点
RBNode y = rightOf(parentOf(parentOf(x)));
if (colorOf(y)==RED){
//叔叔节点存在
setColor(parentOf(x),BLACK);
setColor(y,BLACK);
setColor(parentOf(parentOf(x)),RED);
//递归处理
x = parentOf(parentOf(x));
}else {
//2
if(x == rightOf(parentOf(x))){
//插入节点是父节点的右侧结点 需要跟据父节点做一次左旋操作
x = parentOf(x);
leftRotate(x);
}
setColor(parentOf(x),BLACK);
setColor(parentOf(parentOf(x)),RED);
rightRota(parentOf(parentOf(x)));
}
}else{
//与上面相反操作
//满足条件的四种情况跟据是否有叔叔节点又一分为二
//获取当前的叔叔节点
RBNode y = leftOf(parentOf(parentOf(x)));
if (colorOf(y)==RED){
//叔叔节点存在
setColor(parentOf(x),BLACK);
setColor(y,BLACK);
setColor(parentOf(parentOf(x)),RED);
//递归处理
x = parentOf(parentOf(x));
}else {
//2
if (x == leftOf(parentOf(x))) {
//插入节点是父节点的右侧结点 需要跟据父节点做一次左旋操作
x = parentOf(x);
rightRota(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
leftRotate(parentOf(parentOf(x)));
}
}
}
//根节点都为黑色
setColor(root, BLACK);
}
/**
* 查找node节点的前驱节点
*
*/
private RBNode predecessor(RBNode node){
if(node == null){
return null;
}
else if (node.left !=null){
RBNode p = node.left;
while (p.right!=null){
p=p.right;
}
return p;
}
else {
RBNode p = node.parent;
RBNode ch = node;
while (p!=null&&ch==p.left){
ch = p ;
p = p.parent;
}
return p;
}
}
/**
* 查找node节点的后继节点
*
*/
private RBNode successor(RBNode node){
if(node == null){
return null;
}
else if (node.right !=null){
RBNode p = node.right;
while (p.left!=null){
p=p.left;
}
return p;
}
else {
RBNode p = node.parent;
RBNode ch = node;
while (p!=null&&ch==p.right){
ch = p ;
p = p.parent;
}
return p;
}
}
public V remove(K key){
RBNode node = getNode(key);
if (node ==null){
return null;
}
V oldValue = (V) node.value;
deleteNode(node);
return oldValue;
}
/**
* 删除操作:
* 1、删除叶子节点,直接删除
* 2、删除的节点有一个子节点,那么用子节点来替代
* 3、如果删除的节点有2个子节点,此时需要找到前驱节点或者后继节点来替代
* @param node
*/
private void deleteNode(RBNode node) {
//3 node有两个孩子
if (node.left!=null&&node.right!=null){
RBNode successor = successor(node);
node.key= successor.key;
node.value = successor.value;
node = successor;
}
RBNode replacement = node.left!=null?node.left:node.right;
//2
if (replacement !=null){
replacement.parent = node.parent;
if(node.parent==null){
root = replacement;
}else if (node==node.parent.left){
node.parent.left = replacement;
}else {
node.parent.right=replacement;
}
//node处于游离状态等待垃圾回收
node.left = node.right=node.parent=null;
//替换后需要调整平衡
if (node.color==BLACK){
//需要调整
//这种情况一定是红色(替代节点一定是红色)此时只要变色
fixAfterRemove(node);
}
}
else if (node.parent==null){
root = null;
}
//1
//叶子节点直接删除
else{
//先调整再删除
if (node.color==BLACK){
fixAfterRemove(node);
}
//再删除
if (node.parent!=null){
if (node == node.parent.left){
node.parent.left = null;
}
else if (node == node.parent.right){
node.parent.right =null;
}
//双向脱钩
node.parent = null;
}
}
}
private void fixAfterRemove(RBNode x) {
while (x!=root&&colorOf(x)==BLACK){
//x在左
if (x==leftOf(parentOf(x))){
RBNode rnode = rightOf(parentOf(x));
//判断此时是否为真正兄弟节点
if (colorOf(rnode)== RED){
setColor(rnode,BLACK);
setColor(parentOf(x),RED);
leftRotate(parentOf(x));
rnode = rightOf(parentOf(x));
}
//情况三
if(colorOf(leftOf(rnode))==BLACK&&colorOf(rightOf(rnode))==BLACK){
setColor(rnode,RED);
x=parentOf(x);
}
//情况二
else {
//分两种情况
//兄弟节点无右子节点时
if (colorOf(rightOf(rnode))==BLACK){
setColor(leftOf(rnode),BLACK);
setColor(rnode,RED);
rightRota(rnode);
rnode = rightOf(parentOf(x));
}
setColor(rnode,colorOf(parentOf(x)));
setColor(parentOf(x),BLACK);
setColor(rightOf(rnode),BLACK);
leftRotate(parentOf(x));
x=root;
}
}//x在右
else {
//兄弟节点
RBNode rnode = leftOf(parentOf(x));
//判断此时兄弟节点是否是真正的兄弟节点
if(colorOf(rnode)==RED){
setColor(rnode,BLACK);
setColor(parentOf(x),RED);
rightRota(parentOf(x));
//找到真正的兄弟节点
rnode=leftOf(parentOf(x));
}
//情况三,找兄弟借,兄弟没得借
if(colorOf(rightOf(rnode))==BLACK&&colorOf(leftOf(rnode))==BLACK){
//情况复杂,暂时不写
setColor(rnode,RED);
x=parentOf(x);
}
//情况二,找兄弟借,兄弟有的借
else{
//分2种小情况:兄弟节点本来是3节点或者是4节点的情况
if(colorOf(leftOf(rnode))==BLACK){
setColor(rightOf(rnode),BLACK);
setColor(rnode,RED);
leftRotate(rnode);
rnode=leftOf(parentOf(x));
}
setColor(rnode,colorOf(parentOf(x)));
setColor(parentOf(x),BLACK);
setColor(leftOf(rnode),BLACK);
rightRota(parentOf(x));
x=root;
}
}
}
//情况一自己搞定 替代节点是红色,则直接染黑
setColor(x,BLACK);
}
private RBNode getNode(K key) {
RBNode node = this.root;
while (node!=null){
int cmp = key.compareTo((K) node.key);
if (cmp<0){
node = node.left;
}else if (cmp>0){
node = node.right;
}else {
return node;
}
}
return null;
}
/**
* 左旋
* p pr
* / \ / \
* pl pr p rr
* / \ / \
* rl rr pl rl
*/
public void leftRotate(RBNode p){
if(p!=null){
RBNode r = p.right;
//pr-rl 变为 p-rl
p.right = r.left;
if (r.left!=null){
r.left.parent = p;
}
//判断p是否有父节点
r.parent = p.parent;
if (p.parent == null){
root = r;
}else if (p.parent.left ==p){
p.parent.left = r;
}else {
p.parent.right = r;
}
//最后设置p为r的左子节点
r.left = p;
p.parent = r;
}
}
/**
* 右旋
* @param p
*/
public void rightRota(RBNode p){
if(p!=null){
RBNode r = p.left;
//pr-rl 变为 p-rl
p.left = r.right;
if (r.right!=null){
r.left.parent = p;
}
//判断p是否有父节点
r.parent = p.parent;
if (p.parent == null){
root = r;
}else if (p.parent.left ==p){
p.parent.left = r;
}else {
p.parent.right = r;
}
//最后设置p为r的左子节点
r.right = p;
p.parent = r;
}
}
//节点类型
static class RBNode<K extends Comparable<K>,V>{
private RBNode parent;
private RBNode right;
private RBNode left;
private boolean color;
private K key;
private V value;
public RBNode(RBNode parent, RBNode right, RBNode left, boolean color, K key, V value) {
this.parent = parent;
this.right = right;
this.left = left;
this.color = color;
this.key = key;
this.value = value;
}
public RBNode(K key, V value,RBNode parent) {
this.parent = parent;
this.key = key;
this.value = value;
}
public RBNode() {
}
public RBNode getParent() {
return parent;
}
public void setParent(RBNode parent) {
this.parent = parent;
}
public RBNode getRight() {
return right;
}
public void setRight(RBNode right) {
this.right = right;
}
public RBNode getLeft() {
return left;
}
public void setLeft(RBNode left) {
this.left = left;
}
public boolean isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
}