【数据结构】平衡二叉树—红黑树
红黑树有什么特征,如何保持平衡的?
它或者是一颗空树,或者是具有一下性质的二叉查找树:
1.节点非红即黑。
2.根节点是黑色。
3.所有NULL节点称为叶子节点,且认为颜色为黑。
4.所有红节点的子节点都为黑色。
5.从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点。
平衡分析:
稍微根据以上特征进一步扩展描述:
1、根据树的名称就可以知道,树的节点只有黑色和红色两种颜色,这也是为了保持平衡的一种限制方式(特征1)。
2、树的头(根)跟尾(叶子节点)都是黑色的(特征2、3)。
3、而红节点必须存在父节点和子节点,并且都必须为黑节点,说白红色都被包围在黑色里面(特征1、3、4)。
4、在以上特性的铺垫所在,再加上任一节点到叶子节点的黑色节点数要相等(特性5)的要求,这棵树就不能自己随意乱长了,这也是红黑树平衡的一个重要特点。
通过以上分析不难想象,树节点的任意变更(插入或删除),特征4和5都很容易被破坏。就是因为这些特征的约束,当这棵树发生变革的时候,并且特征被破坏,必须通过必要的手段对树进行“干预”,确保这是一个红黑树。而这个干预的手段,有“变色”和“旋转”。“变色”无非就是把节点(除根节点和叶子节点)红变黑、黑变红,至于“旋转”在平衡二叉树学习已经有相当的了解了。
红黑树与AVL树的区别是什么?
红黑树和AVL树的查询、插入和删除的时间复杂度最坏都是log(n)。如果说到差别,只能往更细一层去分析了:
(1)红黑树的平衡要求没有AVL树要求的严格。AVL是确保左子树和右子树的高度差不能超过1,而红黑树的特性决定它的高度最坏情况不会超过2log(n),所以严格来说,AVL的查找会比红黑树的效率高,但实际不会相差太远;
(2)红黑树和AVL树在插入时,节点旋转次数都是O(1),平衡因子调整次数都是O(logn)。
(3)红黑树是牺牲了严格的高度平衡的优越条件为代价,它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。因此,任何不平衡都会在三次旋转之内解决。红黑树在删除时节点旋转次数是O(1),平衡因子调整次数是O(logn),而AVL树则两者都是O(logn)。
从时间复杂度来看,两者相差不大。但从两者的实现方式来看,AVL树的查询相对优势,红黑树的删除同样有优势,具体的选择得看具体使用场景了。
操作解析:插入
由于红黑树性质5的限制,每次插入的节点都为空色节点,因为如果是黑色的话,直接就破坏了性质5的规则。但插入红色也同样会破坏性质4,但树被破坏的概率不是100%,所以还是以红色为插入节点比较好。如果树的特性被破坏 ,则可通过“旋转”和“变色”来重新平衡红黑树。为了能理解好红黑树的插入操作,以下一一为各种插入情况分析(N为插入节点;P为父节点;U为叔叔节点;G为祖父节点):
情形1:该树为空树,直接插入根节点的位置,违反性质1,把节点颜色有红改为黑即可。
情形2:插入节点N的父节点P为黑色,不违反任何性质,无需做任何修改。
情形3:N为红,P为红,(祖节点一定存在,且为黑)U也为红,这里不论P是G的左孩子,还是右孩子;不论N是P的左孩子,还是右孩子。
操作:把P、U改为黑色,G改为红色,但并未结束。因为G节点的父节点有可能为红色,所以需要把G当新增节点重新判断情况所属再作处理。
情形3
情形4:N为红,P为红,U为黑,P为G的左孩子,N为P的左孩子(或者P为G的右孩子,N为P的左孩子;反正就是同向的)。
操作:把P、G变色,P、G变换即左左单旋(或者右右单旋),结束。
情形4
情形5:N为红,P为红,U为黑,P为G的左孩子,N为P的右孩子(或者P为G的右孩子,N为P的左孩子;反正两方向相反)。
操作:需要进行两次变换(旋转),图中只显示了一次变换-----首先P、N变换,颜色不变;然后就变成了情形4的情况,按照情况4操作,即结束。
情形5
插入案例分析:
1、如下图所示,插入节点(4)后变情形3;
情形3状态
2、按情形3处理后,节点(7)看作新增节点变情形5,如下图所示:
情形5状态
3、因为情形5的处理包含了情形4,所以连续两个操作后,红黑树变正常了,如下图所示:
情形4状态
正常状态
操作解析:删除
学习了AVL树都知道,删除节点前需要找到替身节点,即删除节点的左子树最大值节点(即前驱)或者删除节点右子树的最小值节点(即后驱)。删除后需要从删除点(替换点)的父节点位置往上遍历通过旋转调整好树的高度维持平衡因子。其实,红黑树的删除过程也差不过,先找到删除的替换点(即前驱或后驱节点),再从根据情况从替换点的父节点开始往上遍历检查是否违反红黑树特征并作出相应的变色或旋转整改。以下从替换点的父节点开始按各种情况一一分析。
需先找到“替代点”来替代删除点而被删除,也就是删除的是替代点,而替代点N的至少有一个子节点为NULL。例如以找前驱为例子(也是个人习惯,后驱同理),也就是删除节点的左子树的最大值节点N,而这个N就是其删除节点左子树最右节点,所以这个N要么两个子节点均为NULL,要不有一个左子节点,毕竟N已经是删除节点的最右节点了,没可能再有更右的了,相信这比较好理解吧,理解没问题继续分析了:
- 场景1:若N为红色,则两个子节点一定都为NULL(特性4决定的),那么直接把N删了,不违反任何性质,删除结束;
- 场景2:若N为黑色,另一个节点M不为NULL,则另一个节点M一定是红色的,且M的子节点都为NULL(可根据特性自己想象一下),那么把N删掉,M占到N的位置,并改为黑色,不违反任何性质,删除结束;
- 场景3:若N为黑色,另一个节点也为NULL,则把N删掉,该位置置为NULL,显然这个黑节点被删除了,破坏了性质5。接下就是删除的重头戏了,为了维护红黑树的特性,需要从检索点(不平衡点)开始往上遍历整棵树,进行相应的调整。这个开始检索点就是原来N的位置,此时N的位置为NULL(后续的遍历中这个检索位置N可能就不是NULL)了。P为N的父节点,S为兄弟节点,理解完N、P、S就好理解接下来5种不同的调整情况了。
情形1:S为红色(P一定是黑,子节点一定是黑),而N是P的左孩子(如果N是P的有孩子同理):
将P和S都变色(P变红,S变黑),再以P为中心进行左旋转(RR),但并未结束,因为P的左边N还是跟原来一样少了一个黑点,需要继续对N的其它情况机进行检索。
情形1
情形2:P、S及S的孩子们都为黑:
将S改为红色,但并未结束。如下图所示,因为P的右子树黑点少了1,和左子树一样了,但想想,原来N这边的黑节点数目已经少了1,现在只是S这边也少了1,P到其各叶子节点的黑节点数目确实相等了,但所以身为P的父节点G到其叶子节点数目还是不一样,所以继续以P为起点继续往上按检索(即以看作N继续判断情况)。
情形2
情形3:P为红(S一定为黑),S的孩子们都为黑:
将P该为黑,S改为红,结束。因为N这边少了一个黑色节点,那么把P变黑,就多了一个黑色节点了,而S变红了,所以黑色节点没变,就完事了。
情形3
情形4:P任意色,S为黑,N是P的左孩子,S的右孩子SR为红,S的左孩子任意:
将SR改为黑色,P改为黑色,S改为P的颜色,再执行一次左旋转(RR)就结束了。如下图所示,N这边多了一个黑色节点P,原S就替代P,那么原S这边就少了一个黑色节点,但SR变黑补上,那就等于没变了,所以结束。如果N是P的右孩子,S的左孩子为红,S的右孩子任意情况是同理的。
情形4
情形5:P任意色,S为黑,N是P的左孩子,S的左孩子SL为红,S的右孩子SR为黑:
将SL变为黑色,S变为红色,以S为中心向右旋转一次,未结束,但此时就变成了情形4了,接着再按情形4操作一次就完事了。如果N是P的右孩子,S的右孩子为红,S的左孩子为黑同理。
情形5
红黑树的实现练习(Java)
public class RedBlackTree {
private TreeNode root=null;
private TreeNode leafNode = new TreeNode(-1);
/**
* 中序遍历
* @param subTree
*/
public void inOrder(TreeNode subTree){
if(subTree!=null){
inOrder(subTree.leftChild);
if(subTree.data != -1){
visted(subTree);
}
inOrder(subTree.rightChild);
}
}
public void visted(TreeNode subTree){
System.out.print(subTree.data+"("+(subTree.isRed?"Red":"Black")+")"+",");
}
/**
* 右旋转(LL)
* @param subTree 转轴节点
*/
public void r_Rotate(TreeNode sn){
TreeNode pn = sn.parent;
TreeNode ln = sn.leftChild;
sn.leftChild = ln.rightChild;
if(ln.rightChild !=null){
ln.rightChild.parent = sn;
}
ln.rightChild = sn;
sn.parent = ln;
ln.parent = pn;
if(pn != null && pn.rightChild==sn){
pn.rightChild = ln;
}else if(pn != null && pn.leftChild==sn){
pn.leftChild = ln;
}
if(pn == null){
this.root = ln;
}
}
/**
* 左旋转(RR)
* @param subTree 转轴节点
*/
public void l_Rotate(TreeNode sn){
TreeNode pn = sn.parent;
TreeNode rn = sn.rightChild;
sn.rightChild = rn.leftChild;
if(rn.leftChild !=null){
rn.leftChild.parent = sn;
}
rn.leftChild = sn;
sn.parent = rn;
rn.parent = pn;
if(pn != null && pn.rightChild==sn){
pn.rightChild = rn;
}else if(pn != null && pn.leftChild==sn){
pn.leftChild = rn;
}
if(pn == null){
this.root = rn;
}
}
/**
* 插入节点
* @param subTree
* @param iv
*/
public void insertNote(TreeNode subTree, int iv){
if(subTree == null){
TreeNode n = new TreeNode(iv);
n.leftChild = this.leafNode;
n.rightChild = this.leafNode;
this.root = n;
insertCaseHandle(n);
}else if(subTree.data > iv){
if(subTree.leftChild.data == -1){
TreeNode n = new TreeNode(iv);
subTree.leftChild = n;
n.parent = subTree;
n.leftChild = this.leafNode;
n.rightChild = this.leafNode;
insertCaseHandle(n);
}else{
insertNote(subTree.leftChild, iv);
}
}else if(subTree.data < iv){
if(subTree.rightChild.data == -1){
TreeNode n = new TreeNode(iv);
subTree.rightChild = n;
n.parent = subTree;
n.leftChild = this.leafNode;
n.rightChild = this.leafNode;
insertCaseHandle(n);
}else{
insertNote(subTree.rightChild, iv);
}
}
}
/**
* 插入情况判断处理
* @param n
*/
private void insertCaseHandle(TreeNode n){
/*N、P、U初始化*/
TreeNode p = n.parent;
TreeNode u = null;
if(p != null && p.parent != null && p.parent.leftChild == p){
u = p.parent.rightChild;
}else if(p != null && p.parent != null && p.parent.rightChild == p){
u = p.parent.leftChild;
}
if(p == null && n.isRed){
/*情形1和情形3结束点*/
n.isRed = false;
}else if(p != null && !p.isRed){
/*情形2不用处理*/
}else if(p != null && u!=null && n.isRed && p.isRed && u.isRed){
/*情形3*/
p.isRed = false;
u.isRed = false;
p.parent.isRed = true;
insertCaseHandle(p.parent);
}else if(p != null && u!=null && n.isRed && p.isRed && !u.isRed){
boolean flag = true;
/*情形4*/
if(p.parent.leftChild == p && p.leftChild == n){
/*LL*/
p.isRed = false;
p.parent.isRed = true;
r_Rotate(p.parent);
flag = false;
}else if(p.parent.rightChild == p && p.rightChild == n){
/*RR*/
p.isRed = false;
p.parent.isRed = true;
l_Rotate(p.parent);
flag = false;
}
/*情形5*/
if(flag && p.parent.leftChild == p && p.rightChild == n){
/*LR*/
l_Rotate(p);
insertCaseHandle(p);
}else if(flag && p.parent.rightChild == p && p.leftChild == n){
r_Rotate(p);
insertCaseHandle(p);
}
}
}
/**
* 删除节点
* @param subTree
* @param dv
*/
public void deleteNote(TreeNode subTree, int dv){
if(subTree == null || subTree.data == -1){
System.out.println("delete nothing.");
}else if(subTree.data > dv){
deleteNote(subTree.leftChild, dv);
}else if(subTree.data < dv){
deleteNote(subTree.rightChild, dv);
}else if(subTree.data == dv){
TreeNode N = subTree;
if(subTree.leftChild != leafNode){
N = findDeleteRepalceNode(subTree.leftChild);
}
TreeNode P = N.parent;
TreeNode M = N.leftChild;
boolean N_colour = N.isRed;
/*替换节点*/
if(this.root == N){
this.root = null;
}else{
if(P != null && P.rightChild == N){
P.rightChild = M;
}else if(P != null && P.leftChild == N){
P.leftChild = M;
}
if(M != leafNode){
M.parent = P;
if(P == null){
this.root = M;
}
}
N.parent = null;
N.leftChild = null;
N.rightChild = null;
if(N != subTree){
N.parent = subTree.parent;
N.leftChild = subTree.leftChild;
N.rightChild = subTree.rightChild;
if(subTree.parent != null){
if(subTree.parent.rightChild == subTree){
subTree.parent.rightChild = N;
}else{
subTree.parent.leftChild = N;
}
}
if(subTree.leftChild != leafNode){
subTree.leftChild.parent = N;
}
if(subTree.rightChild != leafNode){
subTree.rightChild.parent = N;
}
N.isRed = subTree.isRed;
subTree.parent = null;
subTree.leftChild = null;
subTree.rightChild = null;
if(P == subTree){
P = N;
}
}
}
/*若N为红色,结束*/
/*若N为黑色,另一个节点M不为NULL*/
if(!N_colour && M != leafNode){
M.isRed = false;
}
/*N为黑色,另一个节点也为NULL*/
if(!N_colour && M == leafNode){
deleteCaseHandle(M, P);
}
}
}
/**
* 寻找删除替换结点(后驱,即其左子树最大值结点)
* @param subTree
* @return
*/
private TreeNode findDeleteRepalceNode(TreeNode subTree){
if(subTree.data == -1){
return subTree.parent;
}else if(subTree.rightChild.data == -1){
return subTree;
}else{
return findDeleteRepalceNode(subTree.rightChild);
}
}
/**
* 删除节点情况处理
* @param replaceNode
*/
private void deleteCaseHandle(TreeNode N, TreeNode P){
boolean NatLeft = true; //N的所在位置
TreeNode S;
if(P.leftChild == N){
S = P.rightChild;
}else{
S = P.leftChild;
NatLeft = false;
}
/*情况1*/
if(S.isRed){
P.isRed = true;
S.isRed = false;
if(NatLeft){
l_Rotate(P);
}else{
r_Rotate(P);
}
deleteCaseHandle(N, P);
/*情况2*/
}else if(!P.isRed && !S.isRed && !S.leftChild.isRed && !S.rightChild.isRed){
S.isRed = true;
if(P.parent != null){
deleteCaseHandle(P, P.parent);
}
/*情况3*/
}else if(P.isRed && !S.isRed && !S.leftChild.isRed && !S.rightChild.isRed){
P.isRed = false;
S.isRed = true;
/*情况4*/
}else if(NatLeft && !S.isRed && S.rightChild.isRed){
S.rightChild.isRed = false;
S.isRed = P.isRed;
P.isRed = false;
l_Rotate(P);
}else if(!NatLeft && !S.isRed && S.leftChild.isRed){
S.leftChild.isRed = false;
S.isRed = P.isRed;
P.isRed = false;
r_Rotate(P);
/*情况5*/
}else if(NatLeft && !S.isRed && S.leftChild.isRed && !S.rightChild.isRed){
S.leftChild.isRed = false;
S.isRed = true;
r_Rotate(S);
deleteCaseHandle(N, P);
}else if(!NatLeft && !S.isRed && !S.leftChild.isRed && S.rightChild.isRed){
S.rightChild.isRed = false;
S.isRed = true;
l_Rotate(S);
deleteCaseHandle(N, P);
}
}
/**
* 红黑树的结点数据结构
*/
private class TreeNode{
private int data;
private TreeNode parent = null;
private TreeNode leftChild=null;
private TreeNode rightChild=null;
private boolean isRed = true;
/**
* 结点构造函数
* @param data -1默认为叶子结点
*/
public TreeNode(int data){
this.data=data;
if(data == -1){
isRed = false;
}
}
}
public static void insertDemo(RedBlackTree rbt, int iv){
System.out.print("【insert "+iv+"");
rbt.insertNote(rbt.root, iv);
System.out.println(",root="+rbt.root.data+"】 ");
rbt.inOrder(rbt.root);
System.out.println();
}
public static void deleteDemo(RedBlackTree rbt, int dv){
System.out.print("【delete "+dv+"");
rbt.deleteNote(rbt.root, dv);
System.out.println(",root="+rbt.root.data+"】 ");
rbt.inOrder(rbt.root);
System.out.println();
}
public static void main(String[] args) {
RedBlackTree rbt = new RedBlackTree();
/*插入节点*/
insertDemo(rbt, 11);
insertDemo(rbt, 2);
insertDemo(rbt, 14);
insertDemo(rbt, 1);
insertDemo(rbt, 7);
insertDemo(rbt, 15);
insertDemo(rbt, 5);
insertDemo(rbt, 8);
insertDemo(rbt, 4);
/*删除节点*/
/*场景1*/
// rbt.deleteDemo(rbt, 4);
/*场景2*/
// rbt.deleteDemo(rbt, 5);
/*场景3-5、3-4*/
// rbt.deleteDemo(rbt, 1);
}
}
运行结果:
【insert 11,root=11】
11(Black),
【insert 2,root=11】
2(Red),11(Black),
【insert 14,root=11】
2(Red),11(Black),14(Red),
【insert 1,root=11】
1(Red),2(Black),11(Black),14(Black),
【insert 7,root=11】
1(Red),2(Black),7(Red),11(Black),14(Black),
【insert 15,root=11】
1(Red),2(Black),7(Red),11(Black),14(Black),15(Red),
【insert 5,root=11】
1(Black),2(Red),5(Red),7(Black),11(Black),14(Black),15(Red),
【insert 8,root=11】
1(Black),2(Red),5(Red),7(Black),8(Red),11(Black),14(Black),15(Red),
【insert 4,root=7】
1(Black),2(Red),4(Red),5(Black),7(Black),8(Black),11(Red),14(Black),15(Red),