红黑二叉树的基本思想,使用标准二叉查找树,在此的基础上

利用红色连接将两个2-结点连接成为3-结点,其中一个2结点是另一个的左子节点.黑结点就是2-3树中的普通连接

这样的话,我们可以直接使用,二叉查找树中get方法

红黑树是含有红黑连接并且满足以下条件的二叉查找树\

红连接均为左连接

没有任何一个结点同时和两个红连接相联系

完美黑色平衡,任意节点连接到根节点的路径上的黑连接数量相同,(自底向上,无论走什么轮径,走过的黑连接数量都一致)

图片

上面图中,横线都为红

具体实现如下

1.因为基本二叉树中,每一个结点都只有一个连接指向自己,故我们将颜色保存在Node数据类型的布尔类型变量color中

false为黑色,而且专门为此写一个isRed()方法来测试结点颜色

2.空链接为黑的

图片

3.利用上面两个旋转方法进行旋转,以此来保持红黑树的性质-有序性和完美平衡

其左旋是将某个节点旋转为其右孩子的左孩子,而右旋是节点旋转为其左孩子的右孩子.

比如我们在只有一个键的树一个-2的结点插入一个键,使其变成一个-3的结点.

如果新键比旧键小,那么不需要任何操作,直接插入一个红色链接就行

如果新键比旧键大,那么需要在插入红色链接之后,调用之前 rotateLeft()方法旋转其红色连接使其变成上面的

同样在树底部的-2结点插入一个新键

那么同样,插入左边不必担心,直接使用,如果插入在右边(比其节点大),需要进行左旋转

在3结点插入新键的时候

分为新键最大,新键最小,和介于两者之间

图片

通过这样的方式,我们让各种-3结点最后转化成-2结点,一个平衡的二叉树

在通过相同方式把其变成黑色

故创建一个方法filpColors();来转换一个结点中两个红子结点颜色为黑

同时再把父节点的黑色变红

图片

同样,如果根节点是红色,说明根节点是3-结点的一部分,

但是往往并非如此,我们每次插入后都会把根节点设置为黑色(故每次我们去插入数组,红色向上传递都会让根节点变红,然后再最后再把根节点再变回来,并且让树高度+1)

根节点由红变黑色就说明树的高度+1

我们分解3-结点,使其中间键上传,直到遇到-2结点,或者父节点

再上传的过程中,不断将其父节点设置为红的,作用同插入新节点

故我们一直进行左右转换,颜色转换这种方式,就可以一直保持红黑树结构

总结一下

如果右子节点是红色而左子节点为黑色的,进行左旋转

如果左子节点为红色的且它的左子节点也是红色,进行右旋转

左右子节点皆为红色的,进行颜色旋转

这方法和二叉树的put方法相似,但在最后进行了旋转操作,维持确定了红黑树的顺序

具体代码:https://github.com/HeavenXin/Data_Structure_Search/blob/master/RedBlackST.java

核心流程在于put中

private Node put(Node node, Key key, Value val) {

if (node == null) {// 如果为空

return new Node(key, val, 1, Red);// 返回一个新Node

}

// 仍然采用递归的方式

int tmp = node.key.compareTo(key);// 进行比较

if (tmp > 0) {

node.left = put(node.left, key, val);

} else if (tmp < 0) {

node.right = put(node.right, key, val);// 递归向下

} else

node.value = val; // 相同更新

if (isRed(node.right) && !(isRed(node.left))) {// 如果右子节点是红色而左子节点为黑色的

node = rotataLeft(node);// 进行左旋转

}

if (isRed(node.left) && isRed(node.left.left)) {// 如果左子节点为红色的且它的左子节点也是红色

node = rotataRight(node);// ,进行右旋转

}

if (isRed(node.left) && isRed(node.right)) {// 左右子节点皆为红色的

filpColors(node);

} // 这三次方式让其进行向上传递

node.N = size(node.left) + size(node.right) + 1;// 数量+1

return node;

}

然后是红黑树的删除

红黑树的删除也是比较困难理解的

如果直接删除,删除-2结点的话会留下一个空结点,一般情况下我们会将其替换为一个空链接(这样会破坏红黑树结构,因为从上面到根节点的黑连接不相同了)所以我们必须确保不删除一个-2结点

图片

只删除3节点,方便补位

所以删除最小键过程中检查每个左子节点,如果发现左子节点中只含有一个键则进行分裂的逆操作,把该节点再添加一个键,这样在删除最小键后,节点不会为空,从而保持了树的平衡性。

那么,我们就需要考虑为左子节点添加一个键

分为三种情况

1.如果左子节点含有2个以上的键则不做操作,

2.如果左子节点只有一个键,而它的兄弟节点有2个以上的键,则从兄弟节点中借一个键过来

图片

3.如果左子节点和兄弟节点都只有一个键,则从父节点借一个最小节点与左子节点和兄弟节点组合在一起

那么我们来看下最基础的删除最小键操作是如何实现的

public void deleteMin() {// 删除最小值

if (!isRed(root.left) && !isRed(root.right)) {

root.color = Red;// 如果左右子节点都是-2结点,可以将根为红结点

}

root = deleteMin(root);

root.color = Black;// 借完以后,我们将根节点的颜色复原

}

private Node deleteMin(Node x) {

if (x.left == null)

return null;

if (!isRed(x.left) && !isRed(x.left.left)) // 判断x的左节点是不是2-节点

x = moveRedLeft(x);

x.left = deleteMin(x.left);

return balance(x); // 解除临时组成的4-节点

}

对应的删除最大值的如下

图片

最后的删除,则是结合了deleteMin/deleteMax和二叉查找树的delete操作

不断的查找,并且进行左移或者右移,确保代码无误

private Node delete(Node h, Key key) {

int tmp = key.compareTo(h.key);

if (tmp<0) { // 当目标键小于当前键的时候,我们做类似于寻找最小的操作,向树左边移动,合并父子结点来消除2-结点

if (h.left == null) {

return null;

}//为空直接返回

if (isRed(h.left) && !isRed(h.left.left)) {//是-2结点

h = moveRedLeft(h);//开借

}

h.left = delete(h.left, key);//递归

} else { // 当目标键大于当前键的时候,我们向右移动,并做与deleteMax相同的操作,

//如果相同的话,我们使用和二叉树的删除一样的操作,获取当前键的右子树的最小健,然后交换,并将目标键删除

if (isRed(h.left)) {

h = rotataRight(h);

}

if (key != h.key && h.right == null) { // 我们没有找到目标键,我们删除

return null;

}

if (!isRed(h.right) && isRed(h.right.left)) {

h = moveRedRight(h);

}

if (key == h.key) {//如果相同

h.value = get(h.right, min(h.right).key);//与其后驱交换

h.key = min(h.right).key;

h.right = deleteMin(h.right);//删除后驱

} else

h.right = delete(h.right, key);//都不满足,继续查找啊

}

return balance(h);

}

发表评论

邮箱地址不会被公开。 必填项已用*标注