【简介】
【参考资料】
july的博文:
其他:
红黑树(red-black tree)算法,附AVL树的比较
红黑树(red-black tree)算法,附AVL树的比较
这些都是很好的教程,大家看完后一定有所收获。
【红黑树介绍】
红黑树是什么?
红黑树是一种有颜色的二叉树,首先,满足二叉查找树的特点:
父亲节点的左侧子节点必须小于父亲节点,右侧节点必须大于父亲节点。
还有一些性质:
1、红黑树的跟节点一定为黑色;
2、红黑树的根节点到各个叶子节点的路径上,黑色节点的数量一致;
3、父亲节点为红色则它的两个子节点都不能为红色(即路径上不能同时有两个红色节点)。
好的,这里有一个比较完整的定义【红黑树(red-black tree)算法,附AVL树的比较】:
红黑树的定义
正如在CLRS中定义的那样(译者: CLRS指的是一本著名的算法书Introduction to Algorithms,中文名应该叫算法导论,CLRS是该书作者Cormen, Leiserson, Rivest and Stein的首字母缩写),一棵红黑树是指一棵满足下述性质的二叉搜索树(BST, binary search tree):
1. 每个结点或者为黑色或者为红色。
2. 根结点为黑色。
3. 每个叶结点(实际上就是NULL指针)都是黑色的。
4. 如果一个结点是红色的,那么它的两个子节点都是黑色的(也就是说,不能有两个相邻的红色结点)。
5. 对于每个结点,从该结点到其所有子孙叶结点的路径中所包含的黑色结点数量必须相同。
数据项只能存储在内部结点中(internal node)。我们所指的"叶结点"在其父结点中可能仅仅用一个NULL指针表示,但是将它也看作一个实际的结点有助于描述红黑树的插入与删除算法,叶结点一律为黑色。
==================抄录完毕=====================
如下图所示,即是一颗红黑树(下图引自wikipedia:http://t.cn/hgvH1l):
假如您没有对红黑树有所了解,那么我认为您可能对红黑树的这部分性质不知所言,实际上我列出来的第二点及第三点保证了红黑树的最短路径及最长路径相差不会超过一倍。
只要记住这些性质,然后通过进行红黑树树的基本操作(插入,删除,左回旋,右回旋),您一定能够印象深刻。
【红黑树的基本操作】
【树的回旋】
在谈红黑树的操作时候,必须先谈一谈二叉树的左回旋及右回旋的问题,回旋这种操作可以保证局部树形结构满足二查查找树的性质(左节点比父亲小,右节点父亲大),是红黑树各种操作后恢复树形结构性质的利器。
以下来摘抄自自july的博客:教你透彻了解红黑树
当我们在对红黑树进行插入和删除等操作时,对树做了修改,那么可能会违背红黑树的性质。
为了保持红黑树的性质,我们可以通过对树进行旋转,即修改树种某些结点的颜色及指针结构,以达到对红黑树进行插入、删除结点等操作时,红黑树依然能保持它特有的性质(如上文所述的,五点性质)。
树的旋转,分为左旋和右旋,以下借助图来做形象的解释和介绍:
1.左旋
如上图所示:
当在某个结点pivot上,做左旋操作时,我们假设它的右孩子y不是NIL[T],pivot可以为树内任意右孩子而不是NIL[T]的结点。
左旋以pivot到y之间的链为“支轴”进行,它使y成为该孩子树新的根,而y的左孩子b则成为pivot的右孩子。
2.右旋
右旋与左旋差不多,再此不做详细介绍。
对于树的旋转,能保持不变的只有原树的搜索性质,而原树的红黑性质则不能保持,在红黑树的数据插入和删除后可利用旋转和颜色重涂来恢复树的红黑性质。
至于有些书如 STL源码剖析有对双旋的描述,其实双旋只是单旋的两次应用,并无新的内容,因此这里就不再介绍了,而且左右旋也是相互对称的,只要理解其中一种旋转就可以了。
=================================抄录完毕=================================
【红黑树的插入操作解释】
以下摘抄自 :
红黑树(red-black tree)算法,附AVL树的比较
如果性质4遭到了破坏,这一定是由于新插入结点的父结点也是红色造成的。由于红黑树的根结点必须是黑色的,因此新插入的结点一定会存在一个祖父结点,并 且根据性质4这个祖父结点必然是黑色的。此时,由新插入结点的祖父结点为根的子树的结构一共有四种可能性(译者,前面这句话我没有看明白原文,我是用我的 理解写出来的,如果有误请指正。),如下面的图解所示。在Okasaki插入方法中,每一种可能出现的子树都被转换为图解正中间的那种子树形式。
(A,B,C与D表示任意的子树。我们曾经说过新插入结点的子结点一定是叶结点,但很快我们就会看到上面的图解适用于更普遍的情况)。
首先,请注意在变换的过程中 另外,注意该变换不会改变从这颗子树的父结点到这颗子树中任何一个叶结点的路径中黑色结点的数量(当然前提是这颗子树有父结点)。我们再一次遇到了这样 的情形:即该红黑树只有可能违反性质2(如果y是根结点)或性质4(如果y的父结点是红色的),但这次变换带来了一个好处,即我们现在距离红黑树的根结点 靠近了两步。我们可以重复这种操作直到:或者y的父结点为黑色,在这种情况下插入操作完成;或者y成为根结点,在此情况下我们将y染为黑色后插入操作完 成。(将根结点染为黑色会对每条从根结点到叶结点的路径增加相同数量的黑色结点,因此如果在染色操作之前性质5没有遭到破坏那么操作之后也不会。)
上述步骤保持了红黑树的性质,并且所花的时间与树高是成比例的,也即O(log n)。
旋转
在红黑树中进行结构调整的操作常常可以用更清晰的术语"旋转"操作来表达,图解如下。
很显然,在旋转操作中的顺序保持不变。因此,如果操作前该树是一颗二叉搜索树,而且结构调整时只使用了旋转操作,那么调整后该树仍然是一颗二叉搜索树。在本文的余下部分,我们将仅仅使用旋转操作对树进行调整,因此我们无须再言明关于如何保持树中元素的正确排序问题。
在下面的图解中,Okasaki插入方法中的变换操作被表示为一个或者两个旋转操作。
=============================摘抄完毕========================
【红黑树的删除操作】
摘抄自
红黑树(red-black tree)算法,附AVL树的比较
为了从红黑树中删除一个结点,我们将从一颗标准二叉搜索树的删除操作开始(参见CLRS,第12章)。我们回顾一下标准二叉搜索树的删除操作的三种情况:
1. 要删除的结点没有子结点。在这种情况下,我们直接将它删除就可以了。如果这个结点是根结点,那么这颗树将成为空树;否则,将它的父结点中相应的子结点指针赋值为NULL。
2. 要删除的结点有一个子结点。与上面一样,直接将它删除。如果它是根结点,那么它的子结点变为根结点;否则,将它的父结点中相应的子结点指针赋值为被删除结点的子结点的指针。
3. 要删除的结点有两个子结点。在这种情况下,我们先找到这个结点的后继结点(successor),也就是它的右子树中最小的那个结点。然后我们将这两个结 点中的数据元素互换,之后删除这个后继结点。由于这个后继结点不可能有左子结点,因此删除该后继结点的操作必然会落入上面两种情况之一。
注意,在树中被删除的结点并不一定是那个最初包含要删除的数据项的那个结点。但出于重建红黑树性质的目的,我们只关心最终被删除的那个结点。我们称这个结点为v,并称它的父结点为p(v)。
v的子结点中至少有一个为叶结点。如果v有一个非叶子结点,那么v在这颗树中的位置将被这个子结点取代;否则,它的位置将被一个叶结点取代。我们用u来表示二叉搜索树删除操作后在树中取代了v的位置的那个结点。如果u是叶结点,那么我们可以确定它是黑色的。
【请注意:真实删除的节点删除后的参与节点为子节点---没有子节点的话就是叶子节点NIL了,这个参与节点非常重要】
【图--额外补充---p(v)-》v-》u,注意:根据上面的推断,v不一定有子节点,有的话一定是右子节点】
【没有右子节点】
【有右子节点】
【v是红色时候,删除操作的两种情况】
所以,我们着重考虑当v是黑色的情况。我们下面假定v是黑色的。删除了v之后,从根结点到v的所有 子孙叶结点的路径将会比树中其它的从根结点到叶结点的路径拥有更少的黑色结点,这会破坏红黑树的性质5。另外,如果p(v)与u都是红色的,那么性质4也 会遭到破坏。但实际上我们解决性质5遭到破坏的方案在不用作任何额外工作的情况下就可以同时解决性质4遭到破坏的问题,所以从现在开始我们将集中精力考虑 性质5的问题。
让我们在头脑中给u打上一个黑色记号(black token)。这个记号表示从根结点到这个带记号结点的所有子孙叶结点的路径上都缺少一个黑色结点(在一开始,这是由于v被删除了)。我们会将这个记号一 直朝树的顶部移动直到性质5重新恢复。在下面的图解中用一个黑色的方块表示这个记号。如果带有这个记号的结点是黑色的,那么我们称之为双黑色结点 (doubly black node)。
注意这个记号只是一个概念上的东西,在树的数据结构中并不存在物理实现。
我们要区分四种不同的情况。
A. 如果带记号的结点是红色的或者它是树的根结点(或两者皆是),只要将它染为黑色就可以完成删除操作。注意,这样就会恢复红黑树的性质4(不能存在两个相邻 的红色结点)。而且,性质5也会被恢复,因为这个记号表示从根结点到该结点的所有子孙叶结点的路径需要增加一个黑色结点以便使这些路径与其它的根结点到叶 结点路径所包含的黑色结点数量相同。通过将这个红色结点改变为黑色,我们就在这些缺少一个黑色结点的路径上添加了一个黑色结点。
如果带记号的结点是根结点并且为黑色,那么直接将这个标记丢掉就可以了。在这种情况下,树中每条从根结点到叶结点的路径的黑色结点数量都比删除操作前少了一个,并且依旧保持住了性质5。
在余下的情况里,我们可以假设这个带记号的结点是黑色的,并且不是根结点。
【补充说明:很多人可能会有,万一只是删除一个单独的黑色节点,譬如下图所示的0.0节点,那么如何办呢?】
答:
当删除的真实节点为黑色,只能将NIL节点拿出来参与到后续的修复调整工作中去。
B. 如果这个双黑色结点的兄弟结点以及两个侄子结点都是黑色的,那么我们就将它的兄弟结点染为红色之后将这个记号朝树根的方向移动一步。
下面的图解展示了两种可能出现的子情况。环绕y的虚线表示在此并我们不关心y的颜色,而在A,B,C和D的上面的小圆圈表示这些子树的根结点是黑色的 (译者:注意这个双黑色结点必然会有两个非叶结点的侄子结点。这是因为这个双黑色结点的记号表示从根结点到该结点的所有子孙叶结点的路径中的黑色结点数量 都比其它的根结点到叶结点路径所包含的黑色结点数量少1,而该双黑色结点本身就是一个黑色结点,因此从它的兄弟结点到其子孙叶结点的路径上的黑色结点数量 必然要大于1,我们很容易看出如果其兄弟结点的任何一个子结点为叶结点的话这一点是不可能满足的,因此这个双黑色结点的必然会有两个非叶结点的侄子结 点)。
将那个兄弟结点染为红色,就会从所有到该结点的子孙叶结点的路径上去掉一个黑色结点,因此现在这些路径上的黑色结点数量与到双黑色结点的子孙叶结点的路 径上的黑色结点数量一致了。我们将这个记号向上移动到y,这表明现在所有到y的子孙叶结点的路径上缺少一个黑色结点。此时问题仍然没有得到解决,但我们又 向树根推进了一步。
很显然,只有带记号的结点的两个侄子结点都是黑色时才能进行上述操作,这是因为如果有一个侄子结点是红色的那么该操作会导致出现两个相邻的红色结点。
C. 如果带记号的结点的兄弟结点是红色的,那么我们就进行一次旋转操作并改变结点颜色。下面的图解展示了两种可能出现的情况:
注意上面的操作并不会改变从根结点到任何叶结点路径上的黑色结点数量,并且它确保了在操作之后这个双黑色结点的兄弟结点是黑色的,这使得后续的操作或者属于情况B,或者属于情况D。
由于这个记号比起操作前离树的根结点更远了,所以看起来似乎我们向后倒退了。但请注意现在这个双黑色结点的父结点是红色的了,所以如果下一步操作属于情 况B,那么这个记号将会向上移动到那个红色结点,然后我们只要将它染为黑色就完成了。此外,下面将会展示,在情况D下,我们总是能够将这个记号消耗掉从而 完成删除操作。因此这种表面上的倒退现象实际上意味着删除操作就快要完成了。
D. 最终,我们遇到了双黑色结点有一个黑色兄弟结点并至少一个侄子结点是红色的情况。我们下面给出一个结点x的近侄子结点(near nephew)的定义:如果x是其父结点的左子结点,那么x的兄弟结点的左子结点为x的近侄子结点,否则x的兄弟结点的右子结点为x的近侄子结点;而另一 个侄子结点则为x的远侄子结点(far nephew)。(在下面的图解中可以看出,x的近侄子结点要比它的远侄子结点距离x更近。)
现在 我们会遇到两种子情况:(i)双黑色结点的远侄子结点是黑色的,在此情况下它的近侄子结点一定是红色的;(ii)远侄子结点是红色的,在此情况下它的近侄 子结点可以为任何颜色。如下面的图解所示,子情况(i)可以通过一次旋转和变色转换为子情况(ii),而在子情况(ii)下只要通过一次旋转和变色就可以 完成删除操作。根据双黑色结点是其父结点的左子结点还是右子结点,下面图解中的两行显示出两种对称的形式。
在这种情况下我们生成了一个额外的黑色结点,记号被丢掉,删除操作完成。从上面图解中很容易看出,所有到带记号结点的子孙叶结点的路径上的黑色结点数量增加了1,而其它的路径上的黑色结点数量保持不变。很显然,在此刻红黑树的任何性质都没有遭到破坏。
将上面的所有情况综合起来,我们可以看出在最坏的情况下我们必须沿着从叶结点到根结点的路径每次都执行常量次数的操作,因此删除操作的时间复杂度为O(log n)。
【红黑树插入及删除的演示】
步骤一:填充演示用的数据(演示用的数据我拿了july那篇文章的部分数据,反正都是为了demo):
【下面进行操作操作】
【插入主键9.8】
【插入主键9.9】
【插入主键10.8】
【插入主键9.7】
【插入主键9.6】
【插入主键9.5】
【插入主键9.65】
【插入主键9.68】----由于我采用了简陋的画图算法,它不会按照最合适的尺寸定位,只按照最大范围,所以看不全,没关系,我分成两部分了。
【没必要再插下去了,因为插入的都是按照那几种情形来处理,下面演示删除操作】
【删除主键9.8】
【删除主键9.65】
【删除主键9.0】
【删除主键9.5】
【删除主键0.0】
【删除主键12.0】
【删除主键15.0】
【删除主键11】
【删除主键4.0】
【删除主键2.0】
【删除主键7.0】
【删除主键1.0】
【红黑树核心源代码】
package RBTree;
public class TreeUnit {
public String color="red";
public boolean isRed=true;
public float indexNO=0.0f;
public TreeUnit _parent=null;
public TreeUnit _leftChild=null;
public TreeUnit _rightChild=null;
public boolean isNIL=false;
}
package RBTree;
public class RBTreeGen {
public TreeUnit _rootNode=null;
public TreeUnit _copyRootNode=null;
/**
* 获取根节点。
* */
public TreeUnit getRootNode(){
return _rootNode;
}
public RBTreeGen(TreeUnit rootNode){
TreeUnit _rootNode1=new TreeUnit();
recursion_copyRootNode(rootNode, _rootNode1);
_rootNode=_rootNode1;
}
public RBTreeGen(){}
private void recursion_copyRootNode(TreeUnit currentUnit,TreeUnit copiedUnit){
if(currentUnit==null){
return;
}
if(copiedUnit==null){
copiedUnit=new TreeUnit();
}
copiedUnit.color=currentUnit.color;
copiedUnit.indexNO=currentUnit.indexNO;
copiedUnit.isRed=currentUnit.isRed;
if(currentUnit._leftChild!=null){
TreeUnit leftChild=new TreeUnit();
leftChild._parent=copiedUnit;
copiedUnit._leftChild=leftChild;
recursion_copyRootNode(currentUnit._leftChild, leftChild);
}
if(currentUnit._rightChild!=null){
TreeUnit rightChild=new TreeUnit();
rightChild._parent=copiedUnit;
copiedUnit._rightChild=rightChild;
recursion_copyRootNode(currentUnit._rightChild, rightChild);
}
return;
}
public boolean containsKey(float indexNO){
search_result=false;
if(_rootNode==null){
return false;
}
recursion_search(_rootNode, indexNO);
return search_result;
}
private boolean search_result=false;
private void recursion_search(TreeUnit _currentNode,float indexNO){
if(_currentNode==null){
return;
}
if(indexNO==_currentNode.indexNO){
search_result=true;
return;
}
if(indexNO<_currentNode.indexNO){
if(_currentNode._leftChild==null){
return;
}
recursion_search(_currentNode._leftChild, indexNO);
return;
}
if(indexNO>_currentNode.indexNO){
if(_currentNode._rightChild==null){
return;
}
recursion_search(_currentNode._rightChild, indexNO);
}
}
private TreeUnit recursion_search_node(TreeUnit _currentNode,float indexNO){
if(_currentNode==null){
return null;
}
if(indexNO==_currentNode.indexNO){
return _currentNode;
}
if(indexNO<_currentNode.indexNO){
if(_currentNode._leftChild==null){
return null;
}
return recursion_search_node(_currentNode._leftChild, indexNO);
}
if(indexNO>_currentNode.indexNO){
if(_currentNode._rightChild==null){
return null;
}
return recursion_search_node(_currentNode._rightChild, indexNO);
}
return null;
}
private TreeUnit search_node(float indexNO){
return recursion_search_node(_rootNode, indexNO);
}
public boolean insert(float indexNO){
if(_rootNode==null){
_rootNode=new TreeUnit();
_rootNode.indexNO=indexNO;
_rootNode.isRed=false;
return true;
}
TreeUnit parentUnit=recursion_search_fitParentNode(_rootNode, indexNO);
if(parentUnit==null){
//--树为空
return false;
}
TreeUnit minNode=new TreeUnit();
minNode.isRed=true;
minNode.indexNO=indexNO;
minNode._parent=parentUnit;
if(indexNO< parentUnit.indexNO){
parentUnit._leftChild=minNode;
}
else if(indexNO>parentUnit.indexNO){
parentUnit._rightChild=minNode;
}
else{
return false;
}
recursion_fixup_after_insert(minNode);
return true;
}
private void recursion_fixup_after_insert(TreeUnit newNode){
if(newNode==null){
return;
}
//--假如这个没有父节点---即是根节点,那么直接渲染成黑色,然后结束。
if(newNode._parent==null){
newNode.isRed=false;
return;
}
//--假如父亲节点就是黑色的,那么还需要调整吗?立刻结束
if(newNode._parent.isRed==false){
return;
}
//--假如父亲节点是红色,那么必然有一个祖父节点,该节点必须为黑色(我们假设是在一颗红黑树上面进行局部调整的,
//只因为加入newNode了才违反规则,即,其他部分一定满足红黑树的性质,所以有祖父节点必然为黑色节点的推论。)
if(newNode._parent==null||newNode._parent._parent==null){
return;//--这根本不是红黑树,退出
}
TreeUnit currentNode=newNode;
TreeUnit parentNode=newNode._parent;
TreeUnit grandFatherNode=parentNode._parent;
boolean isLeft_son=true;
boolean isLeft_father=true;
if(parentNode._rightChild!=null&&parentNode._rightChild.indexNO==currentNode.indexNO){
isLeft_son=false;
}
if(grandFatherNode._rightChild!=null&&grandFatherNode._rightChild.indexNO==parentNode.indexNO){
isLeft_father=false;
}
//--可以看到最多只有四中情况,下面分成四中情况进行调整,在我的博客(csdn---码农下的天桥里面会有配图说明)
//父节点红,为祖父的左边分支,目前节点为红,为父节点的右边分支,于是我们需要调整这个子树,变成符合规范。
//步骤:以父节点为支点,左旋;二、以祖父节点为支点,右旋;三、这时候新节点就是子树的根节点了,新节点为红,左右子节点都
//变成黑色。然后以新节点为准再递归调整。
if(isLeft_son==false&&isLeft_father==true){
___turnLeft(parentNode);
___turnRight(grandFatherNode);
//--渲染颜色
currentNode.isRed=true;
parentNode.isRed=false;
grandFatherNode.isRed=false;
recursion_fixup_after_insert(currentNode);
return;
}
//父节点红,为祖父的左边分支,目前节点为红,为父节点的左边分支,于是我们需要调整这个子树,变成符合规范。
//步骤:二、以祖父节点为支点,右旋;三、这时候新节点就是子树的根节点了,新节点为红,左右子节点都
//变成黑色。然后以新节点为准再递归调整。
if(isLeft_father==true&&isLeft_son==true){
___turnRight(grandFatherNode);
//--颜色渲染
currentNode.isRed=false;
parentNode.isRed=true;
grandFatherNode.isRed=false;
recursion_fixup_after_insert(parentNode);
return;
}
//父节点红,为祖父的右边边分支,目前节点为红,为父节点的左边分支,于是我们需要调整这个子树,变成符合规范。
//步骤:以父节点为支点,右旋;二、以祖父节点为支点,左旋;三、这时候新节点就是子树的根节点了,新节点为红,左右子节点都
//变成黑色。然后以新节点为准再递归调整。
if(isLeft_father==false&&isLeft_son==true){
___turnRight(parentNode);
___turnLeft(grandFatherNode);
//颜色渲染
currentNode.isRed=true;
parentNode.isRed=false;
grandFatherNode.isRed=false;
recursion_fixup_after_insert(currentNode);
return;
}
//父节点红,为祖父的右边边分支,目前节点为红,为父节点的右边分支,于是我们需要调整这个子树,变成符合规范。
//步骤:二、以祖父节点为支点,左旋;三、这时候新节点就是子树的根节点了,新节点为红,左右子节点都
//变成黑色。然后以新节点为准再递归调整。
if(isLeft_father==false&&isLeft_son==false){
___turnLeft(grandFatherNode);
//颜色渲染
currentNode.isRed=false;
parentNode.isRed=true;
grandFatherNode.isRed=false;
recursion_fixup_after_insert(parentNode);
return;
}
return;
}
public boolean ___turnLeft(float indexNO){
TreeUnit node= search_node(indexNO);
return ___turnLeft(node);
}
public boolean ___turnRight(float indexNO){
TreeUnit currentNode=search_node(indexNO);
return ___turnRight(currentNode);
}
/**
* 将节点向右旋转
* */
public boolean ___turnRight(TreeUnit currentNode){
if(currentNode==null){
return false;
}
if(currentNode._leftChild==null){
return false;
}
//--假如父节点为空,那么:
TreeUnit originLeftChild=currentNode._leftChild;
if(currentNode._parent==null){
originLeftChild._parent=null;
currentNode._leftChild=originLeftChild._rightChild;
originLeftChild._rightChild=currentNode;
if(currentNode._leftChild!=null){
currentNode._leftChild._parent=currentNode;
}
currentNode._parent=originLeftChild;
_rootNode=originLeftChild;
return true;
}
else{
boolean isLeft=true;
if(currentNode._parent._rightChild!=null&¤tNode._parent._rightChild.indexNO==currentNode.indexNO){
isLeft=false;
}
if(isLeft==true){
currentNode._parent._leftChild=originLeftChild;
}
else{
currentNode._parent._rightChild=originLeftChild;
}
originLeftChild._parent=currentNode._parent;
currentNode._leftChild=originLeftChild._rightChild;
if(currentNode._leftChild!=null){
currentNode._leftChild._parent=currentNode;
}
originLeftChild._rightChild=currentNode;
currentNode._parent=originLeftChild;
return true;
}
}
/**
* 将节点向左旋转
* */
public boolean ___turnLeft(TreeUnit currentNode){
if(currentNode==null){
return false;
}
if(currentNode._rightChild==null){
return false;
}
//--假如父节点为空,那么:
TreeUnit originRightChild=currentNode._rightChild;
if(currentNode._parent==null){
originRightChild._parent=null;
currentNode._rightChild=originRightChild._leftChild;
originRightChild._leftChild=currentNode;
if(currentNode._rightChild!=null){
currentNode._rightChild._parent=currentNode;
}
currentNode._parent=originRightChild;
_rootNode=originRightChild;
return true;
}
else{
boolean isLeft=true;
if(currentNode._parent._rightChild!=null&¤tNode._parent._rightChild.indexNO==currentNode.indexNO){
isLeft=false;
}
if(isLeft==true){
currentNode._parent._leftChild=originRightChild;
}
else{
currentNode._parent._rightChild=originRightChild;
}
originRightChild._parent=currentNode._parent;
currentNode._rightChild=originRightChild._leftChild;
if(currentNode._rightChild!=null){
currentNode._rightChild._parent=currentNode;
}
originRightChild._leftChild=currentNode;
currentNode._parent=originRightChild;
return true;
}
}
public boolean ___insert(float indexNO){
if(_rootNode==null){
_rootNode=new TreeUnit();
_rootNode.indexNO=indexNO;
_rootNode.isRed=false;
return true;
}
TreeUnit parentUnit=recursion_search_fitParentNode(_rootNode, indexNO);
if(parentUnit==null){
//--树为空
return false;
}
TreeUnit minNode=new TreeUnit();
minNode.isRed=true;
minNode.indexNO=indexNO;
minNode._parent=parentUnit;
if(indexNO< parentUnit.indexNO){
parentUnit._leftChild=minNode;
}
else if(indexNO>parentUnit.indexNO){
parentUnit._rightChild=minNode;
}
else{
return false;
}
return false;
}
public void ___fillRed(float indexNO){
TreeUnit node=search_node(indexNO);
if(node!=null){
node.isRed=true;
node.color="red";
}
}
public void ___fillBlack(float indexNO){
TreeUnit node=search_node(indexNO);
if(node!=null){
node.isRed=false;
node.color="black";
}
}
/**
* 请先搜索到需要添加到的节点,即父节点。
* */
private TreeUnit recursion_search_fitParentNode(TreeUnit _currentUnit,float indexNO){
if(_currentUnit==null){
return null;
}
if(indexNO< _currentUnit.indexNO){
if(_currentUnit._leftChild==null){
return _currentUnit;
}
else{
return recursion_search_fitParentNode(_currentUnit._leftChild, indexNO);
}
}
if(indexNO>_currentUnit.indexNO){
if(_currentUnit._rightChild==null){
return _currentUnit;
}
else{
return recursion_search_fitParentNode(_currentUnit._rightChild, indexNO);
}
}
return null;
}
public boolean delete(float indexNO){
TreeUnit originNode=search_node(indexNO);
TreeUnit rec_node=null;
TreeUnit nilNode=new TreeUnit();
nilNode.isNIL=true;
nilNode.isRed=false;
if(originNode==null){
return false;
}
//--从目前需要删除的节点寻找真正需要删除的节点并且删除。
TreeUnit realDeletedNode=originNode;
if(originNode._rightChild==null){
realDeletedNode=originNode;
}
else{
realDeletedNode=recursion_search_real_deletedNode(originNode._rightChild);
}
//--从几种情况先删除相关节点:
/**
* 当前节点就是需要删除的节点。
* */
/**
* 根据寻找最右侧最小节点为真正需要删除节点的算法,当当前节点与真正需要删除节点为同一个时候,该节点不可能拥有右节点。
* */
if(realDeletedNode.indexNO==originNode.indexNO){
TreeUnit parentNode=originNode._parent;
/**
* 假设当前节点为跟节点。
* */
if(parentNode==null){
/**
* 没有左子节点,即当前红黑树只有一个根节点,直接清空红黑树,不需要对树进行调整。
* */
if(originNode._leftChild==null){
_rootNode=null;
rec_node=null;
return true;
}
/**
* 假如有一个根节点及有左子结点,那么根据红黑树的性质来推导,这种情况下只可能有一个黑色根节点及红色左子节点。
*
* */
else{
_rootNode=originNode._leftChild;
_rootNode._parent=null;
_rootNode.isRed=false;
return true;
}
}
/**
* 当前父节点不为空的时候,可以推导根据红黑树规则,之前寻找右侧最小节点为真实删除节点的规则等确定,
* 当当前节点与真实删除节点为同一个节点时候,该节点没有右子节点,且:
* 1、当前节点颜色为红色时候,没有左子结点;
* 2、当前节点颜色为黑色时候,可能有红色的左子结点。
* */
else if(parentNode!=null){
boolean isLeft=false;
if(parentNode._leftChild!=null&&parentNode._leftChild.indexNO==originNode.indexNO){
isLeft=true;
}
if(originNode.isRed==true){
if(isLeft==true){
parentNode._leftChild=null;
}
else{
parentNode._rightChild=null;
}
return true;//--这种情况,红黑树性质无任何改变,无须调整。
}
/**======为:黑色节点===**/
else{
boolean hasLeftChild=false;
if(originNode._leftChild!=null){
hasLeftChild=true;
}
if(isLeft==true){
parentNode._leftChild=originNode._leftChild;
if(originNode._leftChild!=null){
originNode._leftChild._parent=parentNode;
}
}
else if(isLeft==false){
parentNode._rightChild=originNode._leftChild;
if(originNode._leftChild!=null){
originNode._leftChild._parent=parentNode;
}
}
/**
* 当删除的真实节点为黑色,并且拥有一个左子结点(红色)时候,该左子结点作为后继节点,参与到后续的修复调整工作中。
* */
if(hasLeftChild==true){
if(isLeft==true){
recursion_fixup_afterDeletion(parentNode._leftChild);
}
else{
recursion_fixup_afterDeletion(parentNode._rightChild);
}
return true;
}
/**
* 当删除的真实节点为黑色,但没有左子结点时候,只能将NIL节点拿出来参与到后续的修复调整工作中去。
* */
else{
nilNode._parent=parentNode;
if(isLeft==true){
parentNode._leftChild=nilNode;
recursion_fixup_afterDeletion(parentNode._leftChild);
}
else{
parentNode._rightChild=nilNode;
recursion_fixup_afterDeletion(parentNode._rightChild);
}
delNILNode(nilNode);
return true;
}
}
}
/*
if(parentNode!=null){
boolean isLeft=true;
if(parentNode._rightChild!=null&&parentNode._rightChild.indexNO==originNode.indexNO){
isLeft=false;
}
if(isLeft==true){
if(originNode._leftChild!=null){
parentNode._leftChild=originNode._leftChild;
originNode._leftChild._parent=parentNode;
rec_node=originNode._leftChild;
}
else{
parentNode._leftChild=nilNode;
nilNode._parent=parentNode;
rec_node=nilNode;
}
}
else{
parentNode._rightChild=null;
if(originNode._leftChild!=null){
parentNode._rightChild=originNode._leftChild;
originNode._leftChild._parent=parentNode;
rec_node=originNode._leftChild;
}
else{
nilNode._parent=parentNode;
parentNode._rightChild=nilNode;
rec_node=nilNode;
}
}
originNode=null;
}
else{
if(originNode._leftChild==null&&originNode._rightChild==null){
_rootNode=null;
rec_node=null;
}
else if(originNode._rightChild!=null){
_rootNode=originNode._rightChild;
originNode._rightChild._parent=null;
_rootNode._leftChild=originNode._rightChild._leftChild;
if(_rootNode._leftChild!=null){
_rootNode._leftChild._parent=_rootNode._leftChild;
}
}
else{
_rootNode=originNode._leftChild;
originNode._leftChild._parent=null;
}
_rootNode=null;
}*/
}
/**
* 当前节点与需要删除的节点不一样,可以推断,
* 1、当前节点为真实删除节点的父节点时,真实删除节点不可能有左子结点,并且满足:
* 1A:真实删除节点颜色为红色时,不可能拥有右子节点;
* 1B:真实删除节点为黑色时,可能拥有红色右节点。
* 2、当前节点不是真实删除节点父亲时,真实删除节点不可能拥有左子节点,并且满足:
* 2A:真实删除节点颜色为红色时候,不可能拥有右子节点;
* 2B:真实删除节点为黑色时,可能拥有右子节点(红色)。
*
* 上面两种情况其实可以合并为一起。
*
* */
else{
originNode.indexNO=realDeletedNode.indexNO;
TreeUnit parentNode=realDeletedNode._parent;
boolean isLeft=true;
if(parentNode._rightChild!=null&&parentNode._rightChild.indexNO==realDeletedNode.indexNO){
isLeft=false;
}
/**
* 当真实节点为红色时,不可能拥有子节点,直接删除,无须调整。
* */
if(realDeletedNode.isRed==true){
if(isLeft==true){
parentNode._leftChild=null;
return true;
}
else{
parentNode._rightChild=null;
return true;
}
}
/**
* 当真实节点为黑色并且拥有红色右子节点时,删除真实节点,将右子节点提升到同样位置,右子节点参与到位置调整。
* */
if(realDeletedNode.isRed==false&&realDeletedNode._rightChild!=null&&realDeletedNode._rightChild.isNIL==false){
if(isLeft==true){
parentNode._leftChild=realDeletedNode._rightChild;
realDeletedNode._rightChild._parent=parentNode;
recursion_fixup_afterDeletion(realDeletedNode._leftChild);
return true;
}
else{
System.out.println("【当真实节点为黑色并且拥有红色右子节点时,删除真实节点,将右子节点提升到同样位置,右子节点参与到位置调整】");
parentNode._rightChild=realDeletedNode._rightChild;
realDeletedNode._rightChild._parent=parentNode;
recursion_fixup_afterDeletion(parentNode._rightChild);
return true;
}
}
/**
* 当真实节点为黑色并且没有子节点(除了NIL节点)时,那么将NIL节点放出来作为后继节点参与位置调整。
* */
if(realDeletedNode.isRed==false&&(realDeletedNode._rightChild==null||(realDeletedNode._rightChild!=null&&realDeletedNode._rightChild.isNIL==true))){
nilNode._parent=parentNode;
if(isLeft==true){
parentNode._leftChild=nilNode;
}
else{
parentNode._rightChild=nilNode;
}
//System.out.println("进入【当真实节点为黑色并且没有子节点(除了NIL节点)时,那么将NIL节点放出来作为后继节点参与位置调整】");
recursion_fixup_afterDeletion(nilNode);
delNILNode(nilNode);
return true;
}
/*
if(parentNode._rightChild!=null&&parentNode._rightChild.indexNO==realDeletedNode.indexNO){
if(realDeletedNode._rightChild!=null){realDeletedNode._rightChild._parent=parentNode;}
parentNode._rightChild=realDeletedNode._rightChild;
realDeletedNode=null;
}
else{
if(realDeletedNode._rightChild!=null){
realDeletedNode._rightChild._parent=parentNode;
}
parentNode._leftChild=realDeletedNode._rightChild;
realDeletedNode=null;
}
*/
}
return true;
}
/**
* 由于原来的数据结构算法里面没有考虑nilnode,所以在删除操作时候需要nilNode参与操作后,必须
* 删除该参与操作的nilNode
* */
private void delNILNode(TreeUnit nilNode){
if(nilNode==null){
return;
}
if(nilNode._parent==null){
return;
}
TreeUnit parentNode=nilNode._parent;
if(parentNode._leftChild!=null&&parentNode._leftChild.isNIL==true){
parentNode._leftChild=null;
}
if(parentNode._rightChild!=null&&parentNode._rightChild.isNIL==true){
parentNode._rightChild=null;
}
}
private TreeUnit recursion_search_real_deletedNode(TreeUnit currentUnit){
if(currentUnit==null){
return null;
}
if(currentUnit._rightChild==null&¤tUnit._leftChild==null){
return currentUnit;
}
else
if(currentUnit._leftChild!=null){
return recursion_search_real_deletedNode(currentUnit._leftChild);
}
else{
return currentUnit;
}
}
private void recursion_fixup_afterDeletion(TreeUnit currentUnit){
if(currentUnit==null){
return;
}
if(currentUnit._parent==null){
currentUnit.isRed=false;
return;
}
if(currentUnit.isRed==true){
currentUnit.isRed=false;
return;
}
/**
* 一般情况,简单的可以直接完成的情况都考虑到了,下面考虑主要是删除黑色节点情况下需要继续递归调整结构的情况。注意,假如
* 删除的黑色节点下面没有任何节点---错了,下面是有两个NIL节点,空节点的,届时只需要将NIL节点作为后继节点加到原来父节点的下面,进行运算,
* 调整结束后再删除这个辅助节点NIL就ok了,反正每一个节点都至少有一个NIL,NIL是无处不在的。
* */
boolean isLeft=true;
//---后继节点是空节点,特殊处理
if(currentUnit.isNIL==true){
if(currentUnit._parent._leftChild.isNIL==true){
isLeft=true;
}
else{
isLeft=false;
}
}
//--否则,一般方式判断
else{
if(currentUnit._parent._leftChild.indexNO==currentUnit.indexNO){
isLeft=true;
}
else{
isLeft=false;
}
}
TreeUnit grandFatherNode=null;
TreeUnit parentNode=currentUnit._parent;
float parentIndexNO=parentNode.indexNO;
TreeUnit brotherNode=null;//--兄弟节点。
TreeUnit nearNePhewNode=null;//近侄子节点
TreeUnit farNephewNode=null;//远侄子节点
grandFatherNode=parentNode._parent;
boolean isGrandFatherLeft=true;
if(grandFatherNode!=null){
if(grandFatherNode._rightChild!=null&&grandFatherNode._rightChild.indexNO==parentIndexNO){
isGrandFatherLeft=false;
}
}
if(parentNode!=null){
grandFatherNode=parentNode._parent;
}
if(isLeft==true){
brotherNode=currentUnit._parent._rightChild;
nearNePhewNode=brotherNode._leftChild;
farNephewNode=brotherNode._rightChild;
}
else{
brotherNode=currentUnit._parent._leftChild;
nearNePhewNode=brotherNode._rightChild;
farNephewNode=brotherNode._leftChild;
}
/**
*如果这个双黑色结点的兄弟结点以及两个侄子结点都是黑色的,那么我们就将它的兄弟结点染为红色之后将这个记号朝树根的方向移动一步。
* */
boolean isCaseB=false;
if(brotherNode.isRed==false){
if(((nearNePhewNode!=null&&nearNePhewNode.isRed==true)||(farNephewNode!=null&&farNephewNode.isRed==true))){
isCaseB=false;
}
else{
isCaseB=true;
}
}
if(isCaseB==true){
brotherNode.isRed=true;
recursion_fixup_afterDeletion(parentNode);
return;
}
/**
* 如果带记号的结点的兄弟结点是红色的,那么我们就进行一次旋转操作并改变结点颜色。
* */
boolean isCaseC=false;
if(brotherNode.isRed==true){
isCaseC=true;
}
if(isCaseC==true){
if(isLeft==true){
//--现有节点在左边,兄弟在右边,兄弟渲染成黑色,父节点渲染成红色,将父节点左旋
TreeUnit brotherLeftChild=brotherNode._leftChild;
brotherNode._leftChild=parentNode;
parentNode._parent=brotherNode;
parentNode._rightChild=brotherLeftChild;
if(brotherLeftChild!=null){
brotherLeftChild._parent=parentNode;
}
parentNode.isRed=true;
brotherNode.isRed=false;
brotherNode._parent=grandFatherNode;
if(grandFatherNode!=null){
if(isGrandFatherLeft==true){
grandFatherNode._leftChild=brotherNode;
}
else{
grandFatherNode._rightChild=brotherNode;
}
}
else{
_rootNode=brotherNode;
}
recursion_fixup_afterDeletion(currentUnit);
return;
}
else{
//--现有节点在右边,兄弟在左边, 兄弟渲染成黑色,父节点渲染成红色,将父节点右旋
TreeUnit brotherRightChild=brotherNode._rightChild;
brotherNode._rightChild=parentNode;
parentNode._parent=brotherNode;
parentNode._leftChild=brotherRightChild;
if(brotherRightChild!=null){
brotherRightChild._parent=parentNode;
}
brotherNode.isRed=false;
parentNode.isRed=true;
brotherNode._parent=grandFatherNode;
if(grandFatherNode!=null){
if(isGrandFatherLeft==true){
grandFatherNode._leftChild=brotherNode;
}
else{
grandFatherNode._rightChild=brotherNode;
}
}
else{
_rootNode=brotherNode;
}
recursion_fixup_afterDeletion(currentUnit);
return;
}
}
/**
* 最终,我们遇到了双黑色结点有一个黑色兄弟结点并至少一个侄子结点是红色的情况。
* 我们下面给出一个结点x的近侄子结点(near nephew)的定义:
* 如果x是其父结点的左子结点,那么x的兄弟结点的左子结点为x的近侄子结点,
* 否则x的兄弟结点的右子结点为x的近侄子结点;而另一 个侄子结点则为x的远侄子结点(far nephew)。
* (在下面的图解中可以看出,x的近侄子结点要比它的远侄子结点距离x更近。)
*
* */
boolean isCaseD=false;
boolean isCaseD_i=false;
boolean isCaseD_ii=false;
if(brotherNode.isRed==false){
if(nearNePhewNode!=null&&nearNePhewNode.isRed==true){
isCaseD=true;
}
else if(farNephewNode!=null&&farNephewNode.isRed==true){
isCaseD=true;
}
}
if(isCaseD==true){
if(farNephewNode!=null&&farNephewNode.isRed==true){
isCaseD_ii=true;
}
else if(nearNePhewNode!=null&&nearNePhewNode.isRed==true){
isCaseD_i=true;
}
}
//--开始转换。
boolean partRootIsRed=false;
//--记下局部根节点的原本颜色
if(isCaseD==true){
partRootIsRed= brotherNode._parent.isRed;
}
if(isCaseD_i==true){
TreeUnit tmpLeftChild=nearNePhewNode._leftChild;
TreeUnit tmpRightChild=nearNePhewNode._rightChild;
if(isLeft==true){
/*brotherNode._leftChild=tmpRightChild;
if(tmpRightChild!=null){
tmpRightChild._parent=brotherNode;
}*/
nearNePhewNode._leftChild=parentNode;
parentNode._parent=nearNePhewNode;
nearNePhewNode._rightChild=brotherNode;
brotherNode._parent=nearNePhewNode;
parentNode._rightChild=tmpLeftChild;
if(tmpLeftChild!=null){
tmpLeftChild._parent=parentNode;
}
brotherNode._leftChild=tmpRightChild;
if(tmpRightChild!=null){
tmpRightChild._parent=brotherNode;
}
parentNode.isRed=partRootIsRed;
nearNePhewNode.isRed=false;
nearNePhewNode._parent=grandFatherNode;
if(grandFatherNode!=null){
if(isGrandFatherLeft==true){
grandFatherNode._leftChild=nearNePhewNode;
}
else{
grandFatherNode._rightChild=nearNePhewNode;
}
}
if(nearNePhewNode._parent==null){
_rootNode=nearNePhewNode;
}
return;
}
else{
nearNePhewNode._leftChild=brotherNode;
brotherNode._parent=nearNePhewNode;
nearNePhewNode._rightChild=parentNode;
parentNode._parent=nearNePhewNode;
brotherNode._rightChild=tmpLeftChild;
if(tmpLeftChild!=null){
tmpLeftChild._parent=brotherNode;
}
parentNode._leftChild=tmpRightChild;
if(tmpRightChild!=null){
tmpRightChild._parent=parentNode;
}
nearNePhewNode._parent=grandFatherNode;
parentNode.isRed=false;
nearNePhewNode.isRed=partRootIsRed;
if(grandFatherNode!=null){
if(isGrandFatherLeft==true){
grandFatherNode._leftChild=nearNePhewNode;
}
else{
grandFatherNode._rightChild=nearNePhewNode;
}
}
if(nearNePhewNode._parent==null){
_rootNode=nearNePhewNode;
}
return;
}
}
if(isCaseD_ii==true){
if(isLeft==true){
TreeUnit tmpChild=brotherNode._leftChild;
brotherNode._leftChild=parentNode;
parentNode._parent=brotherNode;
parentNode._rightChild=tmpChild;
if(tmpChild!=null){
tmpChild._parent=parentNode;
}
brotherNode._parent=grandFatherNode;
if(grandFatherNode!=null){
if(isGrandFatherLeft==true){
grandFatherNode._leftChild=brotherNode;
}
else{
grandFatherNode._rightChild=brotherNode;
}
}
brotherNode.isRed=partRootIsRed;
parentNode.isRed=false;
farNephewNode.isRed=false;
if(brotherNode._parent==null){
_rootNode=brotherNode;
}
return;
}
else{
TreeUnit tmpChild=brotherNode._rightChild;
brotherNode._rightChild=parentNode;
parentNode._parent=brotherNode;
parentNode._leftChild=tmpChild;
if(tmpChild!=null){
tmpChild._parent=parentNode;
}
parentNode.isRed=false;
farNephewNode.isRed=false;
brotherNode._parent=grandFatherNode;
brotherNode.isRed=partRootIsRed;
if(grandFatherNode!=null){
if(isGrandFatherLeft==true){
grandFatherNode._leftChild=brotherNode;
}
else{
grandFatherNode._rightChild=brotherNode;
}
}
if(brotherNode._parent==null){
_rootNode=brotherNode;
}
return;
}
}
}
}
【红黑树演示程序下载】
想必每一位在学习红黑树的同学们都或多或少会有疑惑吧?只要坚持看,坚持写,那么就会做出来的,下面我将演示程序打包放出来:
步骤一:填充演示用的数据(演示用的数据我拿了july那篇文章的部分数据,反正都是为了demo):
【下面进行操作操作】
【插入主键9.8】
【插入主键9.9】
【插入主键10.8】
【插入主键9.7】
【插入主键9.6】
【插入主键9.5】
【插入主键9.65】
【插入主键9.68】----由于我采用了简陋的画图算法,它不会按照最合适的尺寸定位,只按照最大范围,所以看不全,没关系,我分成两部分了。
【没必要再插下去了,因为插入的都是按照那几种情形来处理,下面演示删除操作】
【删除主键9.8】
【删除主键9.65】
【删除主键9.0】
【删除主键9.5】
【删除主键0.0】
【删除主键12.0】
【删除主键15.0】
【删除主键11】
【删除主键4.0】
【删除主键2.0】
【删除主键7.0】
【删除主键1.0】
想必每一位在学习红黑树的同学们都或多或少会有疑惑吧?只要坚持看,坚持写,那么就会做出来的,下面我将演示程序打包放出来: