Skip to content

Commit 6203ed6

Browse files
committed
红黑树相关
1 parent c3f9302 commit 6203ed6

File tree

8 files changed

+241
-0
lines changed

8 files changed

+241
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
### 红黑树
2+
3+
AVL树保证了对数级的插入,删除和搜索,但是在数据随机插入和删除的情况下,产生了大量的旋转,甚至一些不必要的反向旋转。
4+
5+
在红黑树种,节点拥有颜色,红色或者黑色。在操作节点的时候,颜色可以切换。但是它们时钟遵循如下的条件:
6+
1. 根节点是黑色。
7+
2. 红色节点的子节点必须是黑色。
8+
3. 从一个节点到该节点的叶节点的所有路径行的黑色节点数相同。
9+
4. 所有叶节点被设置为Null,并且被认为是黑色,他们作为红黑树的仅有的叶节点。
10+
11+
红黑树通过保证黑节点的平衡,牺牲了一定的严格平衡,节省了大量的旋转操作。
12+
13+
为了便于说明,我们在后面的例子和代码中将空叶节点去掉,空叶节点只为了便于计算树的高度。例如:
14+
![](example-red-black-tree.png)
15+
树的黑节点高度为4。
16+
17+
#### 插入后的平衡调整
18+
相较BST,红黑树的插入和删除更加复杂。 红黑树的插入和BST中的插入一样,在插入完成后,将插入的节点标为红色。这样保证了黑节点的高度不变。但是可能导致
19+
一个节点的父节点为红色,违反了第二条规则。 我们需要做一些操作来修复这种情况。 共分为4种情况。
20+
1. 父节点为黑色,在这种情况下,直接添加节点,不需要其他操作。
21+
1. 插入节点的父节点和叔节点都是红色,这种情况下,我们需要重新绘制父节点,叔节点,祖父节点,让黑节点的高度保持不变。如果祖父节点是根节点,保持根节点
22+
的颜色为黑色不变,在这种情况下,整个树的黑节点高度会加1。如果祖父节点不是根节点,那么在祖父改变为红色后,树可能失去平衡,所以需要重新对祖父节点进行
23+
平衡操作。
24+
![](red-black-insert-case1&2.png)
25+
1. 父节点是红色,且叔节点为黑色。且插入节点与父节点的左右方向和父节点与祖父节点的左右方向一致。这种情况下,我们对父节点和祖父节点互换颜色,并对祖父
26+
节点做和插入节点左右方向相反的旋转。
27+
1. 和第三种情况类似,但是插入节点与父节点的左右方向和父节点与祖父节点的左右方向相反。这种情况下,我们对父节点做一次和插入节点左右方向相反的旋转,
28+
这样会转变成第三种情况,下面的步骤就可以按照第三种情况来处理。
29+
![](red-black-insert-case3&4.png)
30+
31+
#### 删除后的平衡调整。
32+
红黑树种删除一个节点和在BST中删除一个节点一样,删除后为了让树达到再次平衡,还需要执行调整操作。当删除一个红色节点时,我们不需要进行调整;当我们删除
33+
的是黑色节点,且子节点为红色,我们只需要将子节点绘制为黑色即可;下面我们讨论删除黑色节点且子节点(包括了null)为黑色的情况。调整依赖于父节点,兄弟
34+
节点和兄弟节点的子节点(即子侄节点)的颜色情况。我们规定删除的节点位置为当前节点。
35+
36+
1. 父节点,兄弟节点,子侄节点都为黑色。这种情况下,我们只需要简单地将兄弟节点修改为红色,让父节点子树平衡,但是父节点子树的黑节点高度减少了1,整个
37+
树是不平衡的,接下来的步骤需要再调整父节点子树在整个树中的平衡。
38+
1. 父节点和兄弟节点为黑,但是远的子侄节点为红色。这种情况下,我们需要将远的子侄节点设置为黑色,然后将父节点朝着当前节点的方向旋转。
39+
1. 父节点和兄弟节点为黑,但是近的子侄节点为红色。这种情况下,我们先将近子侄节点设置为红色,先对兄弟节点向当前节点的反方向旋转,然后再将父节点向当前
40+
节点的方向旋转。即双旋转。
41+
![](red-black-delete-case1&2&3.png)
42+
1. 父节点和子侄节点为黑,兄弟节点为红色。这种情况下,我们先将父节点和兄弟节点的颜色互换,然后将父节点朝着当前节点旋转。这样做之后,任何节点的黑节点
43+
高度都没有改变,所以没达到我们的效果,我们需要再次对红色节点进行平衡调整。情况会转换为下面的两种情况。
44+
1. 父节点为红色,近的子侄节点为黑色。只需要将父节点向当前节点的方向旋转即可。
45+
1. 父节点为红色,近的子侄节点为红色,需要做双先转,首先将兄弟节点向当前节点相反的方向旋转,然后将父节点向当前节点的方向做旋转。
46+
![](red-black-delete-case4&5&6.png)
47+
48+
49+
#### 红黑树最坏的情况
50+
所谓最坏的情况就是高度为h的树拥有最少的节点数。
51+
由于红黑树的规则,红色节点的子节点必须为黑色节点,所以一个高度为h的树,黑节点的高度最大为h/2=l。则黑节点高度为l的最少节点数
52+
`f(l)=2<sup>(l+1)</sup>-2`
53+
f(l)=2<sup>(l+1)</sup>-2
54+
55+
56+
57+
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package zhaoyu.DataStructures.Trees.RedBlack;
2+
3+
import zhaoyu.DataStructures.Trees.BST.BinarySearchTree;
4+
import zhaoyu.DataStructures.Trees.BST.BinaryTree;
5+
6+
/**
7+
* @Description:
8+
* @Author: zhaoyu
9+
* @Date: 2021/3/8
10+
*/
11+
public class RedBlackTree<E extends Comparable<E>> extends BinarySearchTree<E>{
12+
/**
13+
* 红黑树使用的节点,包含了黑节点高度和节点颜色
14+
* @author: zhaoyu
15+
* @date: 2021/3/9
16+
*/
17+
public static class Node<E> extends BinaryTree.Node<E>{
18+
protected int blackHeight=0;
19+
protected boolean black=false;
20+
public Node(BinaryTree.Node<E> parent, BinaryTree<E> containerTree, E value) {
21+
super(parent, containerTree, value);
22+
}
23+
}
24+
25+
protected BinaryTree.Node<E> newNode(BinaryTree.Node<E> parent,BinaryTree<E> containerTree,E value){
26+
return new Node<>(parent, containerTree, value);
27+
}
28+
29+
/**
30+
* 节点是否为黑色,null返回为黑色。
31+
* @param node
32+
* @return boolean
33+
*/
34+
protected boolean nullSafeBlack(Node<E> node){
35+
if (node == null) {
36+
return true;
37+
} else {
38+
return node.black;
39+
}
40+
}
41+
42+
/**
43+
* 插入节点后,对节点进行调整,参考md中的插入。
44+
* @param node
45+
* @return void
46+
*/
47+
protected void rebalanceForInsert(Node<E> node){
48+
if (node.getParent() == null) {
49+
node.black = true;
50+
} else {
51+
Node<E> parent=(Node<E>) node.getParent();
52+
if (parent.black) {
53+
//case1
54+
return;
55+
} else {
56+
Node<E> grandParent= (Node<E>) parent.getParent();
57+
//父节点在祖父节点中的左右方向。
58+
boolean nodeLeftGrandChild=grandParent.getLeft()==parent;
59+
Node<E> uncle= (Node<E>) (nodeLeftGrandChild?grandParent.getRight():grandParent.getLeft());
60+
//case2
61+
if (!nullSafeBlack(uncle)) {
62+
if (grandParent != root) {
63+
grandParent.black = false;
64+
}
65+
uncle.black = true;
66+
parent.black = true;
67+
rebalanceForInsert(grandParent);
68+
} else {
69+
//节点在父节点中的左右方向。
70+
boolean nodeLeftParent=nodeLeftGrandChild?parent.getRight()==node:parent.getLeft()==node;
71+
72+
//case4
73+
if (nodeLeftParent) {
74+
rotate(parent, nodeLeftGrandChild);
75+
node=parent;
76+
parent= (Node<E>) node.getParent();
77+
}
78+
79+
//case3
80+
parent.black=true;
81+
grandParent.black=false;
82+
rotate(grandParent,!nodeLeftGrandChild);
83+
}
84+
}
85+
}
86+
}
87+
88+
/**
89+
* 插入节点
90+
* @param value
91+
* @return zhaoyu.DataStructures.Trees.BST.BinaryTree.Node<E>
92+
*/
93+
@Override
94+
public BinaryTree.Node<E> insertValue(E value){
95+
Node<E> node = (Node<E>) super.insertValue(value);
96+
if (node != null) {
97+
rebalanceForInsert(node);
98+
}
99+
return node;
100+
}
101+
102+
/**
103+
* 删除后的重新平衡,参考md
104+
* @param parent
105+
* @param nodeDirectionLeft
106+
* @return void
107+
*/
108+
protected void rebalanceForDelete(Node<E> parent,boolean nodeDirectionLeft){
109+
if (parent == null) {
110+
return;
111+
}
112+
//当前节点
113+
Node<E> node = (Node<E>) (nodeDirectionLeft ? parent.getLeft() : parent.getRight());
114+
//操作的节点一定是黑色的。
115+
if (!nullSafeBlack(node)) {
116+
node.black=true;
117+
return;
118+
}
119+
//兄弟节点
120+
Node<E> sibling = (Node<E>) (nodeDirectionLeft? parent.getRight(): parent.getLeft());
121+
//近子侄几点
122+
Node<E> nearNephew = (Node<E>) (nodeDirectionLeft? sibling.getLeft():sibling.getRight());
123+
//远子侄节点
124+
Node<E> awayNephew = (Node<E>) (nodeDirectionLeft? sibling.getRight():sibling.getLeft());
125+
if(parent.black){
126+
if(sibling.black){
127+
//case1
128+
if(nullSafeBlack(nearNephew) && nullSafeBlack(awayNephew)){
129+
sibling.black = false;
130+
if(parent.getParent()!=null){
131+
rebalanceForDelete (
132+
(Node<E>) parent.getParent(),
133+
parent.getParent().getLeft() == parent);
134+
}
135+
//case2
136+
}else if(!nullSafeBlack(awayNephew)){
137+
awayNephew.black = true;
138+
rotate(parent, nodeDirectionLeft);
139+
//case3
140+
}else{
141+
nearNephew.black = true;
142+
rotate(sibling, !nodeDirectionLeft);
143+
rotate(parent, nodeDirectionLeft);
144+
}
145+
//case4
146+
}else{
147+
parent.black = false;
148+
sibling.black = true;
149+
rotate(parent, nodeDirectionLeft);
150+
rebalanceForDelete(parent, nodeDirectionLeft);
151+
}
152+
}else{
153+
//case5
154+
if(nullSafeBlack(nearNephew)){
155+
rotate(parent, nodeDirectionLeft);
156+
//case6
157+
}else{
158+
parent.black = true;
159+
rotate(sibling, !nodeDirectionLeft);
160+
rotate(parent, nodeDirectionLeft);
161+
}
162+
}
163+
164+
}
165+
166+
@Override
167+
public BinaryTree.Node<E> deleteValue(E value){
168+
Node<E> deleted = (Node<E>) super.deleteValue(value);
169+
170+
if (deleted != null && deleted.black && deleted.getParent() != null) {
171+
Node<E> deletedNodeChild = (Node<E>) (deleted.getLeft() == null ? deleted.getRight() : deleted.getLeft());
172+
boolean isLeftChild;
173+
if (deletedNodeChild != null) {
174+
//删除节点无子节点或者有一个子节点的情况,参照BST的删除方法。
175+
isLeftChild = deletedNodeChild.getParent().getLeft() == deletedNodeChild;
176+
} else {
177+
//删除节点有两个子节点的情况,参照BST的删除方法。
178+
isLeftChild = deleted.getParent().getRight() != null;
179+
}
180+
rebalanceForDelete((Node<E>) deleted.getParent(), isLeftChild);
181+
}
182+
return deleted;
183+
}
184+
}
24.5 KB
Loading
85.2 KB
Loading
57.3 KB
Loading
31.3 KB
Loading
51.3 KB
Loading
20.9 KB
Loading

0 commit comments

Comments
 (0)