Java数据结构之红黑树的实现方法和原理详解
作者:小扳
红黑树的说明
红黑树是一种自平衡的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是红色或黑色。
与 AVL 树相比之下,红黑树放宽了对平衡的要求,通过牺牲一定的平衡性能来换取更高的插入、删除和查找操作的性能。红黑树的旋转操作相对较少,因此在实际应用中,红黑树更常用于需要高效的动态数据结构,如集合、映射等。而 AVL 树则更适用于对平衡性要求较高的场景,如数据库索引等。
总的来说,红黑树也是一种自平衡的二叉搜索树,较之 AVL 树,插入和删除时旋转次数更少。
红黑树的特性
- 所有节点都有两种颜色:红与黑
- 所有 null 视为黑色
- 红色节点不能相邻
- 根节点是黑色
- 从根节点到任意一个叶子节点,路径中的黑色节点数一样(黑色完美平衡)
前四个规则都很容易理解,接下来详细说明一下最后一个规则,到底什么是从根节点到叶子节点的所有路径都要保证黑色节点数一样?
如图 1:
从根节点出发到叶子节点,首先从左子树方向开始,6 -> 2 -> 1 -> null ,这一条路径的黑色节点一共有 3 个;现在从左子树方向出发,6 -> 2 -> null ,这一条路径的黑色节点一共有 3 个;现在从右子树方向开始,6 -> 8 -> null ,这一条路径的黑色节点一共也是有 3 个黑色节点;这几条路径的黑色节点都为 3 。因此满足红黑树的最后一条规则。
如图 2:
同理,从根节点出发到叶子节点,先从左子树方向开始,6 -> 2 -> 1 -> null ,这一条路径的黑色节点一共有三个;再从 6 -> 2 -> null ,但是这一条路径的黑色节点只有 2 个黑色节点,不满足红黑树的最后一个规则。
在构建红黑树需要满足以上规则,无论插入、删除等都要满足红黑树的特性。
红黑树的成员变量及其构造方法
- 节点类 TreeNode 作为内部类,该内部类的成员变量有:
int key : 关键字,用于比较大小
Object value : 值
TreeNode left : 左节点
TreeNode right : 右节点
Color color :颜色,默认设置为红色
TreeNode parent :该节点的父亲节点
- 该内部类的构造方法:
节点类的构造方法主要是参数为 (int key, Object value) 的构造方法
- 红黑树的外部类的成员变量主要为:
TreeNode root :根节点,一般默认为 null
- 红黑树的外部类的构造方法:
主要采取默认的参数为 null 的构造方法
代码如下:
import static TreeNode.RedBlackTree.Color.BLACK; import static TreeNode.RedBlackTree.Color.RED; public class RedBlackTree { enum Color { RED,BLACK; } private TreeNode root; private static class TreeNode { int key; Object value; TreeNode left; TreeNode right; TreeNode parent; Color color = RED; //构造方法 public TreeNode(int key, Object value) { this.key = key; this.value = value; } }
实现红黑树的核心方法
为了更好的实现插入、删除等方法,需要先实现基础的方法 "打好基础"。主要分为实现内部类与外部类的核心方法。
红黑树内部类的核心方法
判断当前节点是否为左孩子节点 - isLeftChild()
实现思路为:先判断该父亲节点是否为 null ,若 parent == null 时,则当前节点为根节点;若 parent != null ,则需要继续判断 this == parent.left ,若满足,则说明当前节点为左孩子节点,若不满足,则说明当前节点为右孩子节点。
代码如下:
//判断是否为左孩子 public boolean isLeftChild() { return parent != null && parent.left == this; }
获取叔叔节点 - uncle()
叔叔节点即为跟父亲节点的同一辈的节点,跟父亲节点的父亲是同一个父亲。
实现思路为:需要先判断该爷爷节点是否为 null 与 父亲节点是否为 null ,因为该两种情况都不具备叔叔节点。若以上情况都不为 null 时,接着还需要判断当前节点的父亲节点的位置,若父亲节点为左孩子,则叔叔节点为右孩子;若父亲节点为右孩子,则叔叔节点为左孩子。
代码如下:
//获取叔叔节点 public TreeNode uncle() { if (this.parent == null || this.parent.parent == null) { return null; } if (this.isLeftChild()) { return this.parent.parent.right; } else { return this.parent.parent.left; } }
获取兄弟节点 - brother()
跟当前节点是同一辈的节点,同一个父亲节点。
实现思路为:先判断当前节点的父亲节点是否为 null ,若parent == null 时,说明该节点不存在兄弟节点;若 parent != null 时,说明该节点存在兄弟节点,然后继续判断当前节点的位置,若当前节点为左孩子,则兄弟节点为:parent.right ;若当前节点为右孩子,则兄弟节点为:parent.left 。
代码如下:
//获取兄弟节点 public TreeNode brother() { if (this.parent == null) { return null; } if (this.isLeftChild()) { return this.parent.right; }else { return this.parent.left; } }
红黑树外部类的核心方法
判断是否为红色节点 isRed - (TreeNode node)
实现思路为:根据红黑的规则可以知道,除了根节点与 null 之外, 当前节点的 color == RED 时,则该节点为红色节点。
代码如下:
//判断是否为红色节点 private boolean isRed(TreeNode node) { return node != null && node.color == RED; }
判断是否为黑色节点 isBlack - (TreeNode node)
实现思路为:有两种情况下: null 或者 color == BLACK 的节点为黑色节点。
代码如下:
//判断是否为黑色节点 private boolean isBlack(TreeNode node) { return node == null || node.color == BLACK; }
右旋 - rightRotate(TreeNode node)
实现思路为:跟 AVL 树的右旋会有一定的区别,因为新添加了 parent 成员,所以正常右旋完之后,需要维护 parent 变量。
1. parent 的处理
2. 旋转后新根的父子关系
如图:
现需要将节点 8 进行右旋,假设节点 8 为 node ,那么先记录节点5、节点6,nodeLeft = node.left、nodeLeftRight = nodeLeft.right 。接着更新节点 5 的右孩子,将其更新为节点 8 作为右孩子。还需要更新节点 8 的左孩子 ,将其更新为节点 6 作为左孩子。从表面上已经完成右旋,但还需要维护节点的父亲节点 parent 。对于节点 6 来说,之前的父亲节点为节点 5 ,现在需要更新父亲节点为节点8,不过需要先判断节点 6 是否为 null ,若节点 6 为 null ,则不需要更新父亲节点的操作。若节点 6 不为 null,则需要更新父亲节点这一操作。对于节点 5 来说,节点 5 的父亲节点之前为节点 8,先父亲节点更新为节点 8 的父亲节点,因此还需要记录节点 8 的父亲节点。对于节点 8 来说,将其父亲节点更新为节点 5 。最后,由于现在的节点与节点之间时双互的,所以还需要维护新根的父子关系,不过需要判断节点 8 的父亲节点是否为 null ,若为 parent == null 时,则说明节点 5 为根节点,因此需要进行 root = node.left 调整;若 parent != null ,需要判断 node 的位置,若 node 是左孩子,那么 parent.left = node.left 进行链接;若 node 是右孩子,那么 parent.right = node.left 进行链接。那么现在就可以根据这个逻辑写代码了。
最后调整完之后的图:
代码如下:
//右旋 //1.考虑旋转后节点的维护parent 2.重新与上一个节点建立联系 private void rightRotate(TreeNode node) { TreeNode parent = node.parent; TreeNode nodeLeft = node.left; TreeNode nodeLeftRight = nodeLeft.right; if (nodeLeftRight != null) { nodeLeftRight.parent = node; } nodeLeft.right = node; nodeLeft.parent = parent; node.left = nodeLeftRight; node.parent = nodeLeft; if (parent == null) { root = nodeLeft; } else if (parent.left == node) { parent.left = nodeLeft; }else { parent.right = nodeLeft; } }
左旋 - leftRotate(TreeNode node)
跟右旋的逻辑是一摸一样的,这里就不多赘述了。
代码如下:
//左旋 //1.考虑旋转后节点的维护parent 2.重新与上一个节点建立联系 private void leftRotate(TreeNode node) { TreeNode parent = node.parent; TreeNode nodeRight = node.right; TreeNode nodeRightLeft = nodeRight.left; if (nodeRightLeft != null) { nodeRightLeft.parent = node; } nodeRight.left = node; nodeRight.parent = parent; node.right = nodeRightLeft; node.parent = nodeRight; //2.重新与上一个节点建立联系 if (parent == null) { root = nodeRight; } else if (parent.left == node) { parent.left = nodeRight; }else { parent.right = nodeRight; } }
更新、添加节点 - put(int key,Object value)
实现思路为:正常删除节点、更新节点,若遇到红红节点不平衡,则需要进行调整
对于正常删除、更新节点的逻辑为:根据 key 来寻找需要更新或者删除的节点、若找到了 key 的节点,那么直接更新该节点的 value 即可;若没有找到 key 的节点,需要进行添加节点。需要用到 parent 变量,记录每一次的当前节点,一旦当前节点为 node == null 时,找到了相应的空位,接着判断 key 与 parent.key 的大小。若 parent.key > key ,则 parent.left = node;若 parent.key < key 则 parent.right = node;若 parent == null 时,说明该红黑树为空树,所以 root = node 。最后对于更新来说,不会改变红黑树的平衡关系,对于添加完节点,很有可能会改变红黑树的平衡关系,所以需要进行红红修复。
接下来详细聊一下红红节点不平衡后进行的调整:
插入节点均视为红色
- case 1:插入节点为根节点,将根节点变为黑色
- case 2:插入节点的父亲若为黑色,树的红黑性质不变,无需调整
插入节点的父亲节点为红色,触发红红相邻
- case3:叔叔为红色
将父亲节点变为黑色,为了保证黑色平衡,连带的叔叔也变为黑色,祖父如果是黑色不变色,会造成这颗子树黑色过多,因此祖父节点变为红色。但是祖父变为红色,可能也会继续触发红红相邻,因此对讲祖父进行递归调整。
如图 :对于 case 3 详细说明
插入节点为节点 1 设为 node ,该父亲节点为节点 2 设为 parent 。对于 node 节点来说,遇到了红红不平衡的情况,且该节点 4 即为叔叔节点为红色。
调整红黑树平衡:需要将父亲节点、叔叔节点的颜色都要置为黑色,parent.color = BLACK,uncle.color = BALCK ,将节点 3 即祖父节点的颜色置为红色,parent.parent.color = RED 。可以发现节点 3 与节点 5 又再一次出现了触发了红红相邻了,那么利用递归来解决这一情况,一旦遇到 node == root 时,即可停止递归了。
最后调整后之后的图:
- case 4:叔叔为黑色
1. 父亲为左孩子,插入节点也是左孩子,此时即 LL 不平衡
2. 父亲为左孩子,插入节点是右孩子,此时即 LR 不平衡
3. 父亲为右孩子,插入节点也是右孩子,此时即 RR 不平衡
4. 父亲为右孩子,插入节点是左孩子,此时即 RL 不平衡
如图:对于 case 4 中LL 不平衡的详细说明
插入节点为节点 2 设为 node,父亲节点为节点 3 设为 parent ,该叔叔节点为 null 该节点为黑色,又满足 node 为左孩子,parent 也为左孩子, LL 不平衡。该调整需要将 parent.color = BLACK,parent.parent.color = RED,把爷爷节点 rightRotate(parent.parent) 右旋一次即可。
调整完之后的图为:
如图:对于 case 4 中 LR 不平衡的详细说明
插入节点为节点 4 设为 node,父亲节点为节点 3 设为 parent,node 的叔叔节点为 null 所以该颜色为黑色。又 parent 是左孩子,node 是右孩子,满足 LR 不平衡。
需要做的第一步,将 LR 转变为 LL ,只需要将 node 节点左旋。
接着,将 node.color = BLACK,parent.parent.color = RED 处理,再将爷爷节点右旋,rightRotate(parent.parent) 即可。
调整完之后的图:
其余的 case 4 的情况大致与以上的情况相同,所以这里不再过多赘述了。
代码如下:
//更新、增添节点 //正常更新、删除,遇到红红不平衡则需要进行调整 public void put (int key, Object value) { TreeNode p = root; TreeNode parent = null; while (p != null) { parent = p; if (p.key > key) { p = p.left; }else if (p.key < key) { p = p.right; }else { p.value = value; return; } } TreeNode node = new TreeNode(key,value); if (parent == null) { root = node; }else { if (node.key > parent.key) { parent.right = node; }else { parent.left = node; } node.parent = parent; } //可能会发生红红不平衡,则需要调整 fixRedRed(node); } //调整红红不平衡 private void fixRedRed(TreeNode node) { //case1: 插入节点为根节点,将根节点变黑 if(node == root) { node.color = BLACK; return; } if (isBlack(node.parent)) { //case2:插入节点的父亲若为黑,树的红黑性质不变,无需调整 //无需调整 return; } // 插入节点的父亲为红色,触发红红相邻 //case3:叔叔为红色 TreeNode parent = node.parent; TreeNode grandparent = parent.parent; TreeNode uncle = node.uncle(); if (isRed(uncle)) { //进行变色处理即可 //将其父亲、叔叔变为黑色,爷爷变为红色 //若爷爷触发了红红,则继续递归调用该函数 parent.color = BLACK; uncle.color = BLACK; grandparent.color = RED; fixRedRed(grandparent); return; } //case4:叔叔为黑色 //该父亲为左孩子,该插入点也为左孩子,则触发 ll if (parent.isLeftChild() && node.isLeftChild()) { //先将父亲变为黑色、爷爷变为红色,再右旋转 parent.color = BLACK; grandparent.color = RED; rightRotate(grandparent); }else if (parent.isLeftChild()) { //该插入节点为右孩子、该父亲为左孩子,则触发 lr //先左旋变为 ll 情况 leftRotate(parent); node.color = BLACK; grandparent.color = RED; rightRotate(grandparent); } else if (!node.isLeftChild()) { //插入节点为右孩子、父亲节点也为右孩子 rr parent.color = BLACK; grandparent.color = RED; leftRotate(grandparent); }else { //插入节点为左孩子、父亲节点为右孩子 rl rightRotate(parent); node.color = BLACK; grandparent.color = RED; leftRotate(grandparent); } }
删除节点 - remove(int key)
实现思路:正常删除节点,若遇到黑黑不平衡,则需要进行调整
正常删除节点的逻辑:先找到需要删除的节点,为了后续的代码简洁,因此单独实现寻找删除节点的方法,寻找代码的逻辑这里就不过多展开了。又单独实现寻找删除节点的替代节点的方法,这个方法的简单逻辑为:若删除节点没有左右孩子,则替代的节点为 null ;若删除节点只有一个孩子,则返回不为 null 的孩子;若删除节点有两个节点,则找到右子树的最小节点返回即可。
现在找到了删除节点,判断该删除节点是否为 null ,若 delete == null ,则说明没有找到删除节点,结束删除过程;若 delete != null ,则说明可以找到删除节点,则继续通过根据删除节点查找替换节点的方法找到替换节点,最后就可以删除节点了。对于删除节点是一个复杂的操作,所以单独为一个方法 doRemove(TreeNode deleted) 。
对于实现 doRemove(TreeNode deleted) 方法,需要考虑以下情况:
- case0: 对于删除节点有两个孩子来说,需要将其转换为只有一个孩子或者没有孩子情况。这里用到了李代桃僵技巧,例如,需要删除节点 deleted ,替代节点 replace ,将该两个节点的关键字 key 与值 value 交换,那么现在需要删除的节点不再是 deleted 了,而是 replace 。递归调用 doRemove(replace) 。这样子就可以将两个孩子的情况转变为一个孩子或者没有孩子的情况了。
- case1: 删除节点为根节点的情况。先考虑根节点没有左右孩子的情况,则直接将 root == null 。再考虑根节点有一个孩子的情况,则利用到李代桃僵的技巧,将孩子节点的关键字 key 与值 value 跟root 节点的关键字 key 与值 value 分别进行交换,这样删除的节点就是孩子节点了,将根节点的左右孩子置为 null 即可。最后要不要考虑根节点有两个孩子的情况呢?
其实是不用的,因为已经通过李代桃僵的技巧将有两个孩子节点转换为一个孩子或者没有孩子的节点。
- case2: 删除节点不为根节点的情况。同样,先考虑删除的节点没有左右孩子的情况,也说明删除的节点就是叶子节点,所以先判断该删除节点的位置,若删除节点是左孩子,那么父亲节点的左孩子置为 null ,parent.left = null;若删除节点是右孩子,那么父亲节点的右孩子置为 null ,parent.right = null 。最后还需要将删除节点的父亲节点置为 null ,方便后续的内存自动回收。
接着再来考虑删除节点有一个孩子的情况,删除节点设为 deleted,替换节点设为 repalce 。 先判断 deleted 的位置,若 deleted 是左孩子,则 parent.left = repalce 进行链接;若 deleted 是右孩子,则 parent.right = replace 进行链接。由于红黑树的节点与节点之间链接是相互的,所以,replace 节点的父亲节点需要链接到 parent 上, replace.parent = parent 。最后需要将删除节点删除干净,方便内存自动回收,所以 deleted.left = deleted.right = deleted.parent = null 。同样,对于删除节点有两个孩子的情况不需要考虑,已经转换为一个孩子或者没有孩子的情况了。
删除节点之后,很有可能会导致红黑树的性质改变,所以删除节点之前或者删除节点之后,需要进行调整,使其红黑树确保性质不会发生改变。主要有以下情况:
对于删除节点没有左右孩子来说:如果删除节点的颜色是红色,且没有左右孩子,则直接删除完之后,不会发生红黑树性质改变;如果删除节点的颜色是黑色,且没有左右孩子,则会触发黑黑相邻,直接删除会导致红黑树性质发生改变,所以需要进行复杂调整。
对于删除节点有一个节点来说:如果删除节点的颜色为黑色,替换节点为红色,则删除节点之后,需要将替换节点的颜色变为黑色即可;如果删除节点的颜色为黑色,替换节点也为黑色,则触发了黑黑相邻,需要进行复杂的调整。
进行复杂的调整,修复黑黑不平衡的方法 fixBlackBlack(TreeNode node)
删除节点与剩下节点颜色都是黑色,触发双黑,双黑意思是,少了一个黑。
- case3: 删除节点或者剩余节点的兄弟为红色,此时两个侄子定位黑色。
如图:
删除节点为节点 4 设为 deletde ,兄弟节点为节点 8 设为 brother,父亲节点为节点 6 设为 parent 。现在删除节点 4 的颜色为黑色,且剩余节点为 null 该颜色为黑色,触发双黑,又兄弟节点为红色。满足以上情况时,先判断兄弟节点的置为,若兄弟节点为右孩子,则需要将该父亲节点进行左旋;若兄弟节点为左孩子,则需要将该父亲节点进行右旋。旋转完之后,需要变色处理,将父亲节点的颜色变为红色,兄弟节点的颜色变为黑色。最后,继续将删除节点递归调用该函数。该方法的目的就是将删除节点的兄弟节点的颜色由红色变为黑色,好对以下两种情况处理。简单说明,这个方法就是一个过度的方法。
- case4: 被调整节点的兄弟为黑色,两个侄子都为黑色
步骤一:将兄弟节点的颜色变为红,目的是将删除节点和兄弟那边的黑色高度同时减少1
步骤二:如果父亲是红,则需要将父亲变为黑色,避免红红相邻,此时路径黑色节点数目不变。
步骤三:如果父亲是黑色,说明这条路径则少了一个黑,再次让父亲节点触发双黑。
如图:
调整节点为节点 1 设为 node ,兄弟节点为节点 2 设为 brother,父亲节点为节点 2 设为 parent 。该情况满足了删除节点与剩余节点都是黑色,触发双黑,且兄弟节点的颜色也为黑色,父亲节点的颜色为红色。调整的方法为:将兄弟节点的颜色变为红色,将父亲节点的颜色变为黑色即可。
调整完之后的图:
如图:
调整节点为节点 1 设为 node ,兄弟节点为节点 3 设为 brother ,父亲节点为节点 2 设为 parent 。该情况满足调整节点与剩余节点的颜色都为黑色,触发双黑,且兄弟节点的颜色也为黑色,父亲节点也都为黑色。调整方法:将兄弟节点的颜色变为红色,再让父亲节点递归调用该函数,继续触发双黑。
调整完之后的图:
- case5: 被调整节点的兄弟为黑色,至少有一个红色节点的侄子节点。
如果兄弟是左孩子,左侄子是红色,触发 LL 不平衡
如果兄弟是左孩子,右侄子是红色,触发 LR 不平衡
如果兄弟是右孩子,右侄子是红色,触发 RR 不平衡
如果兄弟是右孩子,左侄子是红色,触发 RL 不平衡
如图: 触发 LL 不平衡情况
调整节点为节点 4 设为 node ,兄弟节点为节点 2 设为 brother ,父亲节点为节点 3 设为 parent ,侄子节点为节点 1 。兄弟节点与侄子节点都是左孩子,且侄子节点为红色。调整方法:将父亲节点进行右旋,再把侄子节点的颜色变为黑色,兄弟节点的颜色变为原来父亲节点的颜色,父亲节点的颜色变为黑色。
调整完之后的图:
如图:触发 LR 不平衡情况
调整节点为节点 4 设为 node ,父亲节点为节点 3 设为 parent ,兄弟节点为节点 1 设为 brother ,侄子节点为节点 2 。兄弟节点是左孩子,侄子为右孩子,触发了 LR 不平衡。调整方法:先将兄弟节点左旋,变成了 LL 不平衡情况。
再把侄子节点的颜色变为原来父亲节点的颜色,再到父亲节点的颜色变为黑色,最后右旋父亲节点即可。
调整完之后的图:
还有 RR 、RL 的情况与以上的情况大致是一样的,所以这里就不过多赘述了。
删除节点的代码如下:
//查找删除节点 private TreeNode findDelete(int key) { TreeNode p = root; while(p != null) { if (p.key > key) { p = p.left; } else if (p.key < key) { p = p.right; }else { return p; } } //若没有找到则返回null return null; } //查找剩余节点 private TreeNode findReplaced(TreeNode deleted) { //没有孩子的情况: if (deleted.left == null && deleted.right == null) { return null; } if (deleted.left == null) { return deleted.right; } if (deleted.right == null) { return deleted.left; } //有两个孩子的情况,找后继节点即可 TreeNode p = deleted.right; while(p.left != null) { p = p.left; } return p; } //删除节点 //正常删除节点,遇到黑黑不平衡则需要进行调整 public void remove(int key) { TreeNode delete = findDelete(key); if (delete == null) { return; } doRemove(delete); } private void doRemove(TreeNode deleted) { TreeNode replaced = findReplaced(deleted); TreeNode parent = deleted.parent; //没有孩子的情况: if (replaced == null) { //删除的节点为根节点情况下: if (deleted == root) { root = null; return; }else { if (isRed(deleted)) { //无需任何操作 }else { //触发黑黑不平衡,需要进行复杂的操作 fixBlackBlack(deleted); } if (deleted.isLeftChild()) { parent.left = null; }else { parent.right = null; } deleted.parent = null; } return; } //有一个孩子的情况 if (deleted.left == null || deleted.right == null) { if (deleted == root) { root.key = replaced.key; root.value = replaced.value; root.left = root.right = null; }else { if (deleted.isLeftChild()) { parent.left = replaced; } else { parent.right = replaced; } replaced.parent = parent; deleted.left = deleted.right = deleted.parent = null; if (isRed(replaced) && isBlack(deleted)) { //却少一个黑色,则将替换的节点换为红色即可 replaced.color = BLACK; }else { //遇到黑黑不平衡情况,则需要进行复杂调整 fixBlackBlack(replaced); } } return; } //有两个孩子的情况,需要将用到李代桃僵技巧 int key = deleted.key; deleted.key = replaced.key; replaced.key = key; Object value = deleted.value; deleted.value = replaced.value; replaced.value = value; doRemove(replaced); } private void fixBlackBlack(TreeNode node) { if (node == root) { return; } TreeNode parent = node.parent; TreeNode brother = node.brother(); if (isRed(node.brother())) { //先进行旋转调整,再换色暂时达到平衡 if (brother.isLeftChild()) { rightRotate(parent); }else { leftRotate(parent); } parent.color = RED; brother.color = BLACK; fixBlackBlack(node); return; } //两个侄子都为黑色 if (brother == null) { fixBlackBlack(parent); }else { //case 4 兄弟是黑色,两个侄子也是黑色 if (isBlack(brother.left) && isBlack(brother.right)) { brother.color = RED; if (isRed(parent)) { parent.color = BLACK; }else { fixBlackBlack(parent); } } //case 5 兄弟是黑色,侄子有红色 else { //其中某一个侄子不为黑色 //兄弟为左孩子、侄子为左孩子,触发 ll if (brother.isLeftChild() && isRed(brother.left)) { rightRotate(parent); brother.left.color = BLACK; brother.color = parent.color; parent.color = BLACK; } else if (brother.isLeftChild() && isRed(brother.right)) { //兄弟为左孩子、侄子为右孩子,先触发 lr //需要将 lr 转变为 ll 情况再处理 brother.right.color = parent.color; leftRotate(brother); rightRotate(parent); parent.color = BLACK; } else if ( !brother.isLeftChild() && isRed(brother.right)) { //兄弟为右孩子,侄子为右孩子,触发 rr leftRotate(parent); brother.right.color = BLACK; brother.color = parent.color; parent.color = BLACK; }else { //最后一种情况兄弟为右孩子、侄子为左孩子,触发 rl //需要将 rl 转变为 rr 情况再处理 brother.left.color = parent.color; rightRotate(brother); leftRotate(parent); parent.color = BLACK; } } } }
实现红黑树核心方法的完整代码
import static TreeNode.RedBlackTree.Color.BLACK; import static TreeNode.RedBlackTree.Color.RED; public class RedBlackTree { enum Color { RED,BLACK; } private TreeNode root; private static class TreeNode { int key; Object value; TreeNode left; TreeNode right; TreeNode parent; Color color = RED; //构造方法 public TreeNode(int key, Object value) { this.key = key; this.value = value; } public TreeNode(int key, Object value, TreeNode left, TreeNode right, TreeNode parent) { this.key = key; this.value = value; this.left = left; this.right = right; this.parent = parent; } //判断是否为左孩子 public boolean isLeftChild() { return parent != null && parent.left == this; } //获取叔叔节点 public TreeNode uncle() { if (this.parent == null || this.parent.parent == null) { return null; } if (this.isLeftChild()) { return this.parent.parent.right; } else { return this.parent.parent.left; } } //获取兄弟节点 public TreeNode brother() { if (this.parent == null) { return null; } if (this.isLeftChild()) { return this.parent.right; }else { return this.parent.left; } } } //判断是否为红色节点 private boolean isRed(TreeNode node) { return node != null && node.color == RED; } //判断是否为黑色节点 private boolean isBlack(TreeNode node) { return node == null || node.color == BLACK; } //右旋 //1.考虑旋转后节点的维护parent 2.重新与上一个节点建立联系 private void rightRotate(TreeNode node) { TreeNode parent = node.parent; TreeNode nodeLeft = node.left; TreeNode nodeLeftRight = nodeLeft.right; if (nodeLeftRight != null) { nodeLeftRight.parent = node; } nodeLeft.right = node; nodeLeft.parent = parent; node.left = nodeLeftRight; node.parent = nodeLeft; if (parent == null) { root = nodeLeft; } else if (parent.left == node) { parent.left = nodeLeft; }else { parent.right = nodeLeft; } } //左旋 //1.考虑旋转后节点的维护parent 2.重新与上一个节点建立联系 private void leftRotate(TreeNode node) { TreeNode parent = node.parent; TreeNode nodeRight = node.right; TreeNode nodeRightLeft = nodeRight.left; if (nodeRightLeft != null) { nodeRightLeft.parent = node; } nodeRight.left = node; nodeRight.parent = parent; node.right = nodeRightLeft; node.parent = nodeRight; //2.重新与上一个节点建立联系 if (parent == null) { root = nodeRight; } else if (parent.left == node) { parent.left = nodeRight; }else { parent.right = nodeRight; } } //更新、增添节点 //正常更新、删除,遇到红红不平衡则需要进行调整 public void put (int key, Object value) { TreeNode p = root; TreeNode parent = null; while (p != null) { parent = p; if (p.key > key) { p = p.left; }else if (p.key < key) { p = p.right; }else { p.value = value; return; } } TreeNode node = new TreeNode(key,value); if (parent == null) { root = node; }else { if (node.key > parent.key) { parent.right = node; }else { parent.left = node; } node.parent = parent; } //可能会发生红红不平衡,则需要调整 fixRedRed(node); } //调整红红不平衡 private void fixRedRed(TreeNode node) { //case1: 插入节点为根节点,将根节点变黑 if(node == root) { node.color = BLACK; return; } if (isBlack(node.parent)) { //case2:插入节点的父亲若为黑,树的红黑性质不变,无需调整 //无需调整 return; } // 插入节点的父亲为红色,触发红红相邻 //case3:叔叔为红色 TreeNode parent = node.parent; TreeNode grandparent = parent.parent; TreeNode uncle = node.uncle(); if (isRed(uncle)) { //进行变色处理即可 //将其父亲、叔叔变为黑色,爷爷变为红色 //若爷爷触发了红红,则继续递归调用该函数 parent.color = BLACK; uncle.color = BLACK; grandparent.color = RED; fixRedRed(grandparent); return; } //case4:叔叔为黑色 //该父亲为左孩子,该插入点也为左孩子,则触发 ll if (parent.isLeftChild() && node.isLeftChild()) { //先将父亲变为黑色、爷爷变为红色,再右旋转 parent.color = BLACK; grandparent.color = RED; rightRotate(grandparent); }else if (parent.isLeftChild()) { //该插入节点为右孩子、该父亲为左孩子,则触发 lr //先左旋变为 ll 情况 leftRotate(parent); node.color = BLACK; grandparent.color = RED; rightRotate(grandparent); } else if (!node.isLeftChild()) { //插入节点为右孩子、父亲节点也为右孩子 rr parent.color = BLACK; grandparent.color = RED; leftRotate(grandparent); }else { //插入节点为左孩子、父亲节点为右孩子 rl rightRotate(parent); node.color = BLACK; grandparent.color = RED; leftRotate(grandparent); } } //查找删除节点 private TreeNode findDelete(int key) { TreeNode p = root; while(p != null) { if (p.key > key) { p = p.left; } else if (p.key < key) { p = p.right; }else { return p; } } //若没有找到则返回null return null; } //查找剩余节点 private TreeNode findReplaced(TreeNode deleted) { //没有孩子的情况: if (deleted.left == null && deleted.right == null) { return null; } if (deleted.left == null) { return deleted.right; } if (deleted.right == null) { return deleted.left; } //有两个孩子的情况,找后继节点即可 TreeNode p = deleted.right; while(p.left != null) { p = p.left; } return p; } //删除节点 //正常删除节点,遇到黑黑不平衡则需要进行调整 public void remove(int key) { TreeNode delete = findDelete(key); if (delete == null) { return; } doRemove(delete); } private void doRemove(TreeNode deleted) { TreeNode replaced = findReplaced(deleted); TreeNode parent = deleted.parent; //没有孩子的情况: if (replaced == null) { //删除的节点为根节点情况下: if (deleted == root) { root = null; return; }else { if (isRed(deleted)) { //无需任何操作 }else { //触发黑黑不平衡,需要进行复杂的操作 fixBlackBlack(deleted); } if (deleted.isLeftChild()) { parent.left = null; }else { parent.right = null; } deleted.parent = null; } return; } //有一个孩子的情况 if (deleted.left == null || deleted.right == null) { if (deleted == root) { root.key = replaced.key; root.value = replaced.value; root.left = root.right = null; }else { if (deleted.isLeftChild()) { parent.left = replaced; } else { parent.right = replaced; } replaced.parent = parent; deleted.left = deleted.right = deleted.parent = null; if (isRed(replaced) && isBlack(deleted)) { //却少一个黑色,则将替换的节点换为红色即可 replaced.color = BLACK; }else { //遇到黑黑不平衡情况,则需要进行复杂调整 fixBlackBlack(replaced); } } return; } //有两个孩子的情况,需要将用到李代桃僵技巧 int key = deleted.key; deleted.key = replaced.key; replaced.key = key; Object value = deleted.value; deleted.value = replaced.value; replaced.value = value; doRemove(replaced); } private void fixBlackBlack(TreeNode node) { if (node == root) { return; } TreeNode parent = node.parent; TreeNode brother = node.brother(); if (isRed(node.brother())) { //先进行旋转调整,再换色暂时达到平衡 if (brother.isLeftChild()) { rightRotate(parent); }else { leftRotate(parent); } parent.color = RED; brother.color = BLACK; fixBlackBlack(node); return; } //两个侄子都为黑色 if (brother == null) { fixBlackBlack(parent); }else { //case 4 兄弟是黑色,两个侄子也是黑色 if (isBlack(brother.left) && isBlack(brother.right)) { brother.color = RED; if (isRed(parent)) { parent.color = BLACK; }else { fixBlackBlack(parent); } } //case 5 兄弟是黑色,侄子有红色 else { //其中某一个侄子不为黑色 //兄弟为左孩子、侄子为左孩子,触发 ll if (brother.isLeftChild() && isRed(brother.left)) { rightRotate(parent); brother.left.color = BLACK; brother.color = parent.color; parent.color = BLACK; } else if (brother.isLeftChild() && isRed(brother.right)) { //兄弟为左孩子、侄子为右孩子,先触发 lr //需要将 lr 转变为 ll 情况再处理 brother.right.color = parent.color; leftRotate(brother); rightRotate(parent); parent.color = BLACK; } else if ( !brother.isLeftChild() && isRed(brother.right)) { //兄弟为右孩子,侄子为右孩子,触发 rr leftRotate(parent); brother.right.color = BLACK; brother.color = parent.color; parent.color = BLACK; }else { //最后一种情况兄弟为右孩子、侄子为左孩子,触发 rl //需要将 rl 转变为 rr 情况再处理 brother.left.color = parent.color; rightRotate(brother); leftRotate(parent); parent.color = BLACK; } } } } }
到此这篇关于Java数据结构之红黑树的实现方法和原理详解的文章就介绍到这了,更多相关Java红黑树内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!