从上篇《2-3-4树的插入和删除》了解到了底层原理和操作的逻辑思路。尽管我们完成 了平衡树的逻辑,但按照对应逻辑实现代码和各种情况的处理,却不容易。所以我们要减少由于2-3-4树为了实现平衡,而导致的实现复杂度上升的情况。我们现在使用普通的二叉树+颜色来表示2-3-4树(红黑树是多路平路查找树的一种实现)
红黑树的定义:
文章里的红黑树的定义了参考《算法》第四版
-
红链接必须是左链接,根结点必须是黑色的
-
不能同时存在两条连续的红链接
-
任一空链接到根节点的路径上经历的黑色节点个数一样
下面我们使用1-3的插入来观察红黑树是如何保持平衡的?
根据上面根据上面的操作我们可以发现红黑树对2-3-4树的实现原理:
-
使用黑+红两个节点来实现3节点(如上图插入2后)
-
使用三个黑色节点实现4节点(如上图插入3后)
节点对象的定义
RedBlackNode<T extends Comparable<T>> { /*颜色的常量定义 red:false black:true 新建节点默认为红色*/ public static final boolean RED = false; public static final boolean BLACK = true; private T data; private RedBlackNode<T> left; private RedBlackNode<T> right; private boolean color; }
操作
我们将红黑树的操作分开描述
查找
查找和普通的二叉搜索树一致,不再赘叙。
可以参考二叉搜索树关于查找的部分
旋转和变色
左旋转
实现步骤:
-
右子节点的颜色 = 原根结点的颜色
-
根结点node作为右子节点的左子节点,刷新为红色节点
-
将右子节点的左子节点设置为原根结点的右子节点
代码示例:
RedBlackNode<T> rotateLeft(RedBlackNode<T> node){ RedBlackNode<T> right = node.getRight(); right.setColor(node.isColor()); RedBlackNode<T> middle = right.getLeft(); node.setRight(middle); node.setColor(RedBlackNode.RED); right.setLeft(node); return right; }
右旋转
将根结点的左子节点替换到根结点,将左子节点作为根结点返回
实现步骤:
-
左子节点的颜色 = 原根结点的颜色
-
根结点node替换到左子节点的右子节点,刷新为红色节点
-
将左子节点的右子节点设置为原根结点的左子节点
代码示例:
RedBlackNode<T> rotateRight(RedBlackNode<T> node){ RedBlackNode<T> result = node.getLeft(); result.setColor(node.isColor()); RedBlackNode<T> resultRight = result.getRight(); node.setLeft(resultRight); result.setRight(node); node.setColor(RedBlackNode.RED); return result; }
变色
/**如果左右节点都是红色的那么将左右子节点修改为黑色,父节点修改为红色*/ void flushColor(RedBlackNode<T> node){ node.setColor(RedBlackNode.RED); RedBlackNode<T> left = node.getLeft(); left.setColor(RedBlackNode.BLACK); RedBlackNode<T> right = node.getRight(); right.setColor(RedBlackNode.BLACK); }
插入
向2节点插入
向3节点插入
插入算法代码示例:
RedBlackNode<T> insert(RedBlackNode<T> node, T data){ if (Objects.isNull(node)) { node = new RedBlackNode<>(); node.setData(data); node.setColor(RedBlackNode.RED); return node; } T nodeData = node.getData(); int flag = data.compareTo(nodeData); if (flag < 0) { //插入数据小于节点数据,入左子树 RedBlackNode<T> left = insert(node.getLeft(), data); node.setLeft(left); } else if (flag > 0) { //插入数据大于节点数据,入右子树 RedBlackNode<T> right = insert(node.getRight(), data); node.setRight(right); } //插入位置在右子节点,且左子树非红色,进行左旋转 if (isRed(node.getRight()) && !isRed(node.getLeft())) { node = rotateLeft(node); } //插入的节点在左子树的左子节点上,右旋 if (isRed(node.getLeft()) && isRed(node.getLeft().getLeft())) { node = rotateRight(node); } if (isRed(node.getLeft()) && isRed(node.getRight())) { flushColor(node); } return node; }
删除
由于我们在二叉搜索树BST里介绍过,我们可以将节点删除的逻辑调整为极值的删除
2-3-4树文章里,已经知道单独的2节点是不能直接删除的,需要将2节点转换为3或4节点(2节点对应红黑树中的黑色节点)
综上所述:我们需要极大/小值的删除和2节点的删除方法
删除最小值
主要分为3节点和4节点删除最小值(其中4节点根结点有红或黑两种颜色。CASE比较多,请放大查看)
代码示例:
/** 最小值的删除方法,返回删除后的根节点 */ RedBlackNode<T> deleteMin(RedBlackNode<T> node){ RedBlackNode<T> left = node.getLeft(); //左节点不为null,最小值在node的左节点,继续向左 if (Objects.isNull(left)) { return null; } //左右节点都不是红色,需要将黑色节点调整为红色Del-2至Del-5示 RedBlackNode<T> ll = left.getLeft(); if (!isRed(left) && !isRed(ll)) { node = removeRedLeft(node); } left = deleteMin(node.getLeft()); node.setLeft(left); return blance(node); } /** 移除红色最小节点 */ RedBlackNode<T> removeRedLeft(RedBlackNode<T> node) { flipsColor(node); RedBlackNode<T> right = node.getRight(); RedBlackNode<T> rl = Objects.isNull(right) ? null : right.getLeft(); //如果右左节点是红色节点(对应图中的Del-3、Del-5图) if (isRed(rl)) { right = rotateRight(right); node.setRight(right); node = rotateLeft(node); } return node; } /**变色Del-2至Del-5示*/ void flipsColor(RedBlackNode<T> node) { node.setColor(RedBlackNode.BLACK); RedBlackNode<T> left = node.getLeft(); RedBlackNode<T> right = node.getRight(); if (Objects.nonNull(left)) { left.setColor(RedBlackNode.RED); } if (Objects.nonNull(right)) { right.setColor(RedBlackNode.RED); } } /** 节点删除后的平衡调整方法 */ RedBlackNode<T> balance(RedBlackNode<T> node){ if (isRed(node.getRight())) { //右节点为红,左旋(图中的2列) node = rotateLeft(node); } if (isRed(node.getRight()) && !isRed(node.getLeft())) { node = rotateLeft(node); } if (isRed(node.getLeft()) && isRed(node.getLeft().getLeft())) { node = rotateRight(node); } if (isRed(node.getLeft()) && isRed(node.getRight())) { flushColor(node); } return node; }
删除最大值
最大值的删除逻辑如下图示
代码示例:
/** 最大值的删除方法,返回删除后的根节点 */ RedBlackNode<T> deleteMax(RedBlackNode<T> node){ if(isRed(node.getLeft())){ node = rotateRight; } RedBlackNode<T> right = node.getRight(); if(right == null){ return null; } if (!isRed(right) && !isRed(right.getLeft())) { node = removeRedRight(node); } right = deleteMax(right); node.setRight(right); return balance(node); } /** 移除红色右节点 */ RedBlackNode<T> removeRedRight(RedBlackNode<T> node) { flipsColor(node); RedBlackNode<T> left = node.getLeft(); RedBlackNode<T> lr = Objects.isNull(left) ? null : left.getRight(); //如果左右节点是红色节点 if (!isRed(rl)) { return rotateRight(node); } return node; }
删除
我们将以上两个方法结合就可以得到红黑树的删除方法,不再赘叙。
至此,我们就将二叉搜索树的内容介绍完毕了。如果你觉得对你有帮助,记得点个赞。同时也期待大家的留言讨论。欢迎关注公众号:javascript艺术
系列
欢迎关注公众号:javascript艺术