红黑树
一、概述
图示:
红黑树是二分搜索树,并在二分搜索树的基础上添加一些性质确保其不会退化为链表,保证其是平衡二叉树
《算法导论》中的具体规定:
1.每个节点要么是红色,要么是黑色;
2.根节点是黑色的
3.每个叶子节点(最后的空节点)是黑色的
4.如果一个节点是红色的,则其两个孩子节点都是黑色的
5.从任意一个节点到叶子节点(最后的空节点),经过的黑色节点是一样的
想要更好地理解 红黑树 ,就要知道什么是 2-3树
2-3 树
1.满足二分搜索树的基本性质
2.不是一种二叉树,它有两种节点,一种可存放一个元素【左孩子<a<右孩子】,一种可存放两个元素【3个孩子,左孩子<b<中间孩子<c<右孩子】
3.每个节点有 2 或 3 个 孩子,故称为 2-3 树
图示:
2-3 树性质:它是一颗绝对平衡的树【从根节点到任意叶子节点所经过的节点数量一定是相同的,对任意节点,左右子树的高度一定是相等的】
二、树如何维持绝对的平衡
2-3树 中添加节点
1. 42为根节点
2.添加元素 37 ,因为 37 < 42 ,所以 37 应放在 42 的左子树中,而由于 42 的左子树为空,【新节点永远不会去 空 的位置,只会和最后找到的叶子节点做融合】新节点 37 将融合到 在添加过程中所找到的最后一个叶子节点上,叶子节点就是 42 这个节点,所以,会发生 节点的融合,由 2节点 变为 3节点
3.再添加元素 12 ,因为 12 < 37 ,所以 12 应放在 37 的左子树中,而由于 37 的左子树为空【新节点永远不会去 空 的位置,只会和最后找到的叶子节点做融合】故虽然已经是 3节点,但仍然先融合,暂时形成 4节点
4. 2-3树 中最多有 3节点,所以将 4节点 分裂成 一棵子树, 4节点 转换为 由 3个 2节点 组成的一棵平衡的树,如图所示,可以将其理解为二分搜索树与 2-3树,只不过每个节点都是 2节点,同时这棵树保持着绝对的平衡
5.在树中再添加新元素 18,根节点是37 ,要将 18 添加到其左子树12 中 ,18 > 12 故将其添加到 12 的右子树中去 ,此时 12 的右子树 已经为空【新节点永远不会去 空 的位置,只会和最后找到的叶子节点做融合】这棵树保持着绝对的平衡
6.在树中再添加新元素 6,根节点是37 ,要将 18 添加到其左子树(包含 12 18 的3节点) 中 ,6 < 12 故将其添加到 3节点 的左子树中去 ,此时 3 的左子树 为空【新节点永远不会去 空 的位置,只会和最后找到的叶子节点做融合】,故先融合为 4节点
7.将 4节点 拆解,变为 包含 3个 2节点 的子树
如果拆分为上图所示,此时的 2-3 树不是 绝对 平衡的树,对 2-3树 来说,如果其叶子节点本身已经是一个 3节点 了,添加一个节点变为 4节点 ,变为如图所示,则 12 需要与其父节点 37 进行融合,37 是 2节点,12 和 37 可以直接融合为 3节点,此时该树仍然 绝对平衡
8.在树中再添加新元素 11,根节点是 12 37 的3节点,要将 11 添加到其左子树6中 ,6 < 11 故将其添加到 6 的右子树中去 ,此时 6 的右子树 为空【新节点永远不会去 空 的位置,只会和最后找到的叶子节点做融合】,故先融合为 3节点
9.在树中再添加新元素 5,根节点是 12 37 的3节点,要将 5 添加到其左子树 6 11 的3节点 中 ,5 < 6 故将其添加到 6 11 的3节点左子树中去 ,此时 6 11 的3节点 的左子树 为空【新节点永远不会去 空 的位置,只会和最后找到的叶子节点做融合】,故先融合为 4节点
将 4节点 拆解,变为 包含 3个 2节点 的子树
如果拆分为上图所示,此时的 2-3 树不是 绝对 平衡的树,对 2-3树 来说,如果其叶子节点本身已经是一个 3节点 了,添加一个节点变为 4节点 ,变为如图所示,则 6 需要与其父节点 12 37的 3节点 进行融合,变为 4节点,此时该树仍然 绝对平衡
将 4节点 拆解,变为 包含 3个 2节点 的子树,此时,树仍然 绝对平衡并保持着 2-3树的性质
总结:
如果插入 2节点,直接融合为 3节点
如果插入 3节点 ,且为根节点,先融合再拆分
如果插入 3节点 ,父亲节点为 2节点,先融合再拆分,再融合
如果插入 3节点 ,父亲节点为 3节点,先融合再拆分,再融合再拆分
三、红黑树与2-3树的等价性
表示 b 和 c 是并列关系(一起存放在 3节点 中),则将其 连线 设置为 红色; 将其还原为 二分搜索树中的样子
表示 红色连线 的方法:由于每一个节点只有一个父亲(每一个节点和其父亲节点相连接的边只有一条线),可以对 b节点 做特殊标识,使其为红色,表示 b节点 与 c节点 在 2-3树 中是一起存放在 3节点中【所有的红色节点都是左倾斜的】
如图所示
红黑树性质:
1.每个节点要么是红色,要么是黑色;
2.根节点是黑色的
3.每个叶子节点(最后的空节点)是黑色的【定义 空 为黑色】
4.如果一个节点是红色的,则其两个孩子节点都是黑色的【黑色节点 右孩子 必为 黑色】
5.从任意一个节点到叶子节点(最后的空节点),经过的黑色节点是一样多的【核心】
分析:2-3树是 绝对平衡的,所以 所有的叶子节点都在同一层上,其深度是一样的;从任意节点出发,有固定的深度,从该节点向下到达(可到达)的叶子节点,向下的深度也是一样的,其经过的节点数量是一样的;对应到 红黑树 中,则经过的黑色节点是一样多的。【红黑树 是保持 “黑平衡” 的二叉树,最大高度:2log n ,时间复杂度:O(log n)】
代码实现:RBTree.java
import java.util.ArrayList;
public class RBTree<K extends Comparable<K>, V> {
private static final boolean RED = true; //定义红色为 true
private static final boolean BLACK = false;//定义黑色为 false
private class Node{
public K key;
public V value;
public Node left, right;
public boolean color; //定义节点颜色
public Node(K key, V value){
this.key = key;
this.value = value;
left = null;
right = null;
color = RED; //节点默认为 红色【添加时要先融合,故设为红色】
}
}
private Node root;
private int size;
public RBTree(){
root = null;
size = 0;
}
public int getSize(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
// 判断节点node的颜色
private boolean isRed(Node node){
if(node == null)
return BLACK;
return node.color;
}
// 向二分搜索树中添加新的元素(key, value)
public void add(K key, V value){
root = add(root, key, value);
}
// 向以node为根的二分搜索树中插入元素(key, value),递归算法
// 返回插入新节点后二分搜索树的根
private Node add(Node node, K key, V value){
if(node == null){
size ++;
return new Node(key, value);
}
if(key.compareTo(node.key) < 0)
node.left = add(node.left, key, value);
else if(key.compareTo(node.key) > 0)
node.right = add(node.right, key, value);
else // key.compareTo(node.key) == 0
node.value = value;
return node;
}
// 返回以node为根节点的二分搜索树中,key所在的节点
private Node getNode(Node node, K key){
if(node == null)
return null;
if(key.equals(node.key))
return node;
else if(key.compareTo(node.key) < 0)
return getNode(node.left, key);
else // if(key.compareTo(node.key) > 0)
return getNode(node.right, key);
}
public boolean contains(K key){
return getNode(root, key) != null;
}
public V get(K key){
Node node = getNode(root, key);
return node == null ? null : node.value;
}
public void set(K key, V newValue){
Node node = getNode(root, key);
if(node == null)
throw new IllegalArgumentException(key + " doesn't exist!");
node.value = newValue;
}
// 返回以node为根的二分搜索树的最小值所在的节点
private Node minimum(Node node){
if(node.left == null)
return node;
return minimum(node.left);
}
// 删除掉以node为根的二分搜索树中的最小节点
// 返回删除节点后新的二分搜索树的根
private Node removeMin(Node node){
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
node.left = removeMin(node.left);
return node;
}
// 从二分搜索树中删除键为key的节点
public V remove(K key){
Node node = getNode(root, key);
if(node != null){
root = remove(root, key);
return node.value;
}
return null;
}
private Node remove(Node node, K key){
if( node == null )
return null;
if( key.compareTo(node.key) < 0 ){
node.left = remove(node.left , key);
return node;
}
else if(key.compareTo(node.key) > 0 ){
node.right = remove(node.right, key);
return node;
}
else{ // key.compareTo(node.key) == 0
// 待删除节点左子树为空的情况
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
// 待删除节点右子树为空的情况
if(node.right == null){
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
// 待删除节点左右子树均不为空的情况
// 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
// 用这个节点顶替待删除节点的位置
Node successor = minimum(node.right);
successor.right = removeMin(node.right);
successor.left = node.left;
node.left = node.right = null;
return successor;
}
}
public static void main(String[] args){
System.out.println("Pride and Prejudice");
ArrayList<String> words = new ArrayList<>();
if(FileOperation.readFile("pride-and-prejudice.txt", words)) {
System.out.println("Total words: " + words.size());
RBTree<String, Integer> map = new RBTree<>();
for (String word : words) {
if (map.contains(word))
map.set(word, map.get(word) + 1);
else
map.add(word, 1);
}
System.out.println("Total different words: " + map.getSize());
System.out.println("Frequency of PRIDE: " + map.get("pride"));
System.out.println("Frequency of PREJUDICE: " + map.get("prejudice"));
}
System.out.println();
}
}
四、 红黑树添加元素和左旋转
红黑树添加元素:
保持 根节点 为 黑色
public void add(K key, V value){
root = add(root, key, value);
root.color = BLACK; // 最终根节点为黑色节点
}
添加过程:整棵树为空时,添加元素 42,使 根 为 42 即可
但红黑树中,根节点必须为 黑色 ,将其变为 黑色
插入元素 37,插入到 42 的 左孩子的位置,相当于 2-3树 中的 3节点
但如果 根节点为 37,要插入元素为 42,则不满足红黑树的定义(红色节点 左倾斜)
解决方法:左旋转,上图 经过 左旋转 变为 下图
左旋转过程:1.假设根节点为 node,node 的右孩子为 x
2.node.right = x.left,让 node 的右孩子 为 x 的左子树, 仍然保持二分搜索树性质
3.x.right = node, x 的左子树连接上 node ,完成左旋转,37 从原来 42 的父亲 变为 42 的右孩子,相当于逆时针旋转,在 37 来看是左旋转
4.对于红黑树来说,需要维护颜色信息
x.color = node.color ,新的根节点是 x节点,x节点的颜色要和以前的节点 node 的颜色是一样的
node 变为 x 的左孩子,对应到 2-3 树中,还是临时的 4节点,要让 node 的颜色变为 红色,node.color = RED;表示它和它的父亲节点 42 是融合在一起的 ;这样就完成了整个左旋转过程
代码实现:RBTree.java
import java.util.ArrayList;
public class RBTree<K extends Comparable<K>, V> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node{
public K key;
public V value;
public Node left, right;
public boolean color;
public Node(K key, V value){
this.key = key;
this.value = value;
left = null;
right = null;
color = RED;
}
}
private Node root;
private int size;
public RBTree(){
root = null;
size = 0;
}
public int getSize(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
// 判断节点node的颜色
private boolean isRed(Node node){
if(node == null)
return BLACK;
return node.color;
}
// node x
// / \ 左旋转 / \
// T1 x ---------> node T3
// / \ / \
// T2 T3 T1 T2
private Node leftRotate(Node node){
Node x = node.right;
// 左旋转【核心】
node.right = x.left;
x.left = node;
x.color = node.color;
node.color = RED;
return x;
}
// 向红黑树中添加新的元素(key, value)
public void add(K key, V value){
root = add(root, key, value);
root.color = BLACK; // 最终根节点为黑色节点
}
// 向以node为根的红黑树中插入元素(key, value),递归算法
// 返回插入新节点后红黑树的根
private Node add(Node node, K key, V value){
if(node == null){
size ++;
return new Node(key, value); // 默认插入红色节点
}
if(key.compareTo(node.key) < 0)
node.left = add(node.left, key, value);
else if(key.compareTo(node.key) > 0)
node.right = add(node.right, key, value);
else // key.compareTo(node.key) == 0
node.value = value;
return node;
}
// 返回以node为根节点的二分搜索树中,key所在的节点
private Node getNode(Node node, K key){
if(node == null)
return null;
if(key.equals(node.key))
return node;
else if(key.compareTo(node.key) < 0)
return getNode(node.left, key);
else // if(key.compareTo(node.key) > 0)
return getNode(node.right, key);
}
public boolean contains(K key){
return getNode(root, key) != null;
}
public V get(K key){
Node node = getNode(root, key);
return node == null ? null : node.value;
}
public void set(K key, V newValue){
Node node = getNode(root, key);
if(node == null)
throw new IllegalArgumentException(key + " doesn't exist!");
node.value = newValue;
}
// 返回以node为根的二分搜索树的最小值所在的节点
private Node minimum(Node node){
if(node.left == null)
return node;
return minimum(node.left);
}
// 删除掉以node为根的二分搜索树中的最小节点
// 返回删除节点后新的二分搜索树的根
private Node removeMin(Node node){
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
node.left = removeMin(node.left);
return node;
}
// 从二分搜索树中删除键为key的节点
public V remove(K key){
Node node = getNode(root, key);
if(node != null){
root = remove(root, key);
return node.value;
}
return null;
}
private Node remove(Node node, K key){
if( node == null )
return null;
if( key.compareTo(node.key) < 0 ){
node.left = remove(node.left , key);
return node;
}
else if(key.compareTo(node.key) > 0 ){
node.right = remove(node.right, key);
return node;
}
else{ // key.compareTo(node.key) == 0
// 待删除节点左子树为空的情况
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
// 待删除节点右子树为空的情况
if(node.right == null){
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
// 待删除节点左右子树均不为空的情况
// 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
// 用这个节点顶替待删除节点的位置
Node successor = minimum(node.right);
successor.right = removeMin(node.right);
successor.left = node.left;
node.left = node.right = null;
return successor;
}
}
public static void main(String[] args){
System.out.println("Pride and Prejudice");
ArrayList<String> words = new ArrayList<>();
if(FileOperation.readFile("pride-and-prejudice.txt", words)) {
System.out.println("Total words: " + words.size());
RBTree<String, Integer> map = new RBTree<>();
for (String word : words) {
if (map.contains(word))
map.set(word, map.get(word) + 1);
else
map.add(word, 1);
}
System.out.println("Total different words: " + map.getSize());
System.out.println("Frequency of PRIDE: " + map.get("pride"));
System.out.println("Frequency of PREJUDICE: " + map.get("prejudice"));
}
System.out.println();
}
}
五、颜色翻转和右旋转
红黑树添加新元素
1.红黑树本身是空的时候,直接将节点添加进来,让其保持 黑色 即可
2.添加新节点到左右子树均为空的黑色节点上,如果为 黑节点 的左孩子,直接添加即可
3.如果为 黑节点 的右孩子,需要进行左旋转,【将一个新的元素放进 2节点 中,2节点 转换为 3节点在红黑树中的操作】
4.向红黑树的 3节点 中添加元素
5.添加元素 66 ,则 【红:该节点与其父节点融合在一起,37 和 66 都是红色,均为融合在一起的,可以理解为临时的 4节点】
在 2-3树 中相当于如图,4节点拆分为 3个 2节点
在红黑树中,4节点拆分为 3个 2节点 代表 3个节点 均为黑色的节点,黑色节点左侧没有 红色节点,则代表单独的 2节点,故
在 2-3树 中,如果插入 3节点 ,父亲节点为 2节点,先融合再拆分,再融合,融合在红黑树中为
以上添加元素完成,发现红黑树中节点颜色正好相反,称为 颜色翻转(filpColors)
在下图红黑树中添加元素 12
由二分搜索树添加元素法则,知 12 添加到 37 的左孩子中
等同于 2-3树,形成临时 4节点
4 节点要拆分
在红黑树中,需要用到 右旋转
1.假设根节点为 node,node 的左孩子为 x ,
2.node.left = T1,让 node 的左子树 为 左孩子的右子树, 仍然保持二分搜索树性质
3.x.right = node ,完成右旋转,42 从原来 37的父亲 变为 37的右孩子,相当于顺时针旋转,在 37 来看是右旋转
4.对于红黑树来说,需要维护颜色信息
x.color = node.color ,新的根节点是 x节点,x节点的颜色要和以前的节点 node 的颜色是一样的
node 变为 x 的右孩子,对应到 2-3树中,还是临时的 4节点,要让 node 的颜色变为 红色,node.color = RED;表示它和它的父亲节点 42 是融合在一起的 ;这样就完成了整个右旋转过程
代码实现:颜色翻转
RBTree.java
import java.util.ArrayList;
public class RBTree<K extends Comparable<K>, V> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node{
public K key;
public V value;
public Node left, right;
public boolean color;
public Node(K key, V value){
this.key = key;
this.value = value;
left = null;
right = null;
color = RED;
}
}
private Node root;
private int size;
public RBTree(){
root = null;
size = 0;
}
public int getSize(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
// 判断节点node的颜色
private boolean isRed(Node node){
if(node == null)
return BLACK;
return node.color;
}
// node x
// / \ 左旋转 / \
// T1 x ---------> node T3
// / \ / \
// T2 T3 T1 T2
private Node leftRotate(Node node){
Node x = node.right;
// 左旋转
node.right = x.left;
x.left = node;
x.color = node.color;
node.color = RED;
return x;
}
// node x
// / \ 右旋转 / \
// x T2 -------> y node
// / \ / \
// y T1 T1 T2
private Node rightRotate(Node node){
Node x = node.left;
// 右旋转
node.left = x.right;
x.right = node;
x.color = node.color;
node.color = RED;
return x;
}
// 颜色翻转
private void flipColors(Node node){
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
// 向红黑树中添加新的元素(key, value)
public void add(K key, V value){
root = add(root, key, value);
root.color = BLACK; // 最终根节点为黑色节点
}
// 向以node为根的红黑树中插入元素(key, value),递归算法
// 返回插入新节点后红黑树的根
private Node add(Node node, K key, V value){
if(node == null){
size ++;
return new Node(key, value); // 默认插入红色节点
}
if(key.compareTo(node.key) < 0)
node.left = add(node.left, key, value);
else if(key.compareTo(node.key) > 0)
node.right = add(node.right, key, value);
else // key.compareTo(node.key) == 0
node.value = value;
return node;
}
// 返回以node为根节点的二分搜索树中,key所在的节点
private Node getNode(Node node, K key){
if(node == null)
return null;
if(key.equals(node.key))
return node;
else if(key.compareTo(node.key) < 0)
return getNode(node.left, key);
else // if(key.compareTo(node.key) > 0)
return getNode(node.right, key);
}
public boolean contains(K key){
return getNode(root, key) != null;
}
public V get(K key){
Node node = getNode(root, key);
return node == null ? null : node.value;
}
public void set(K key, V newValue){
Node node = getNode(root, key);
if(node == null)
throw new IllegalArgumentException(key + " doesn't exist!");
node.value = newValue;
}
// 返回以node为根的二分搜索树的最小值所在的节点
private Node minimum(Node node){
if(node.left == null)
return node;
return minimum(node.left);
}
// 删除掉以node为根的二分搜索树中的最小节点
// 返回删除节点后新的二分搜索树的根
private Node removeMin(Node node){
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
node.left = removeMin(node.left);
return node;
}
// 从二分搜索树中删除键为key的节点
public V remove(K key){
Node node = getNode(root, key);
if(node != null){
root = remove(root, key);
return node.value;
}
return null;
}
private Node remove(Node node, K key){
if( node == null )
return null;
if( key.compareTo(node.key) < 0 ){
node.left = remove(node.left , key);
return node;
}
else if(key.compareTo(node.key) > 0 ){
node.right = remove(node.right, key);
return node;
}
else{ // key.compareTo(node.key) == 0
// 待删除节点左子树为空的情况
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
// 待删除节点右子树为空的情况
if(node.right == null){
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
// 待删除节点左右子树均不为空的情况
// 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
// 用这个节点顶替待删除节点的位置
Node successor = minimum(node.right);
successor.right = removeMin(node.right);
successor.left = node.left;
node.left = node.right = null;
return successor;
}
}
public static void main(String[] args){
System.out.println("Pride and Prejudice");
ArrayList<String> words = new ArrayList<>();
if(FileOperation.readFile("pride-and-prejudice.txt", words)) {
System.out.println("Total words: " + words.size());
RBTree<String, Integer> map = new RBTree<>();
for (String word : words) {
if (map.contains(word))
map.set(word, map.get(word) + 1);
else
map.add(word, 1);
}
System.out.println("Total different words: " + map.getSize());
System.out.println("Frequency of PRIDE: " + map.get("pride"));
System.out.println("Frequency of PREJUDICE: " + map.get("prejudice"));
}
System.out.println();
}
}
总结:
1.
2.
3.
这种解决方法:1.左旋转
2.再右旋转
3.维持颜色
4.颜色翻转
最复杂情况的添加元素:新添加节点在对应 2-3树 中间
对2个红节点左旋转
对黑节点右旋转
颜色翻转
在等价于 2-3树中的 3节点 中添加比这2个元素还要小的元素,则
在等价于 2-3树中的 3节点 中添加比这2个元素最大值【黑】还要大的元素,则
维护的时机:和 AVL树 一样,添加节点后回溯向上维护
红黑树 【最终版】代码:RBTree.java
import java.util.ArrayList;
public class RBTree<K extends Comparable<K>, V> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node{
public K key;
public V value;
public Node left, right;
public boolean color;
public Node(K key, V value){
this.key = key;
this.value = value;
left = null;
right = null;
color = RED;
}
}
private Node root;
private int size;
public RBTree(){
root = null;
size = 0;
}
public int getSize(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
// 判断节点node的颜色
private boolean isRed(Node node){
if(node == null)
return BLACK;
return node.color;
}
// node x
// / \ 左旋转 / \
// T1 x ---------> node T3
// / \ / \
// T2 T3 T1 T2
private Node leftRotate(Node node){
Node x = node.right;
// 左旋转
node.right = x.left;
x.left = node;
x.color = node.color;
node.color = RED;
return x;
}
// node x
// / \ 右旋转 / \
// x T2 -------> y node
// / \ / \
// y T1 T1 T2
private Node rightRotate(Node node){
Node x = node.left;
// 右旋转
node.left = x.right;
x.right = node;
x.color = node.color;
node.color = RED;
return x;
}
// 颜色翻转
private void flipColors(Node node){
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
// 向红黑树中添加新的元素(key, value)
public void add(K key, V value){
root = add(root, key, value);
root.color = BLACK; // 最终根节点为黑色节点
}
// 向以node为根的红黑树中插入元素(key, value),递归算法
// 返回插入新节点后红黑树的根
private Node add(Node node, K key, V value){
if(node == null){
size ++;
return new Node(key, value); // 默认插入红色节点
}
if(key.compareTo(node.key) < 0)
node.left = add(node.left, key, value);
else if(key.compareTo(node.key) > 0)
node.right = add(node.right, key, value);
else // key.compareTo(node.key) == 0
node.value = value;
if (isRed(node.right) && !isRed(node.left))
node = leftRotate(node);
if (isRed(node.left) && isRed(node.left.left))
node = rightRotate(node);
if (isRed(node.left) && isRed(node.right))
flipColors(node);
return node;
}
// 返回以node为根节点的二分搜索树中,key所在的节点
private Node getNode(Node node, K key){
if(node == null)
return null;
if(key.equals(node.key))
return node;
else if(key.compareTo(node.key) < 0)
return getNode(node.left, key);
else // if(key.compareTo(node.key) > 0)
return getNode(node.right, key);
}
public boolean contains(K key){
return getNode(root, key) != null;
}
public V get(K key){
Node node = getNode(root, key);
return node == null ? null : node.value;
}
public void set(K key, V newValue){
Node node = getNode(root, key);
if(node == null)
throw new IllegalArgumentException(key + " doesn't exist!");
node.value = newValue;
}
// 返回以node为根的二分搜索树的最小值所在的节点
private Node minimum(Node node){
if(node.left == null)
return node;
return minimum(node.left);
}
// 删除掉以node为根的二分搜索树中的最小节点
// 返回删除节点后新的二分搜索树的根
private Node removeMin(Node node){
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
node.left = removeMin(node.left);
return node;
}
// 从二分搜索树中删除键为key的节点
public V remove(K key){
Node node = getNode(root, key);
if(node != null){
root = remove(root, key);
return node.value;
}
return null;
}
private Node remove(Node node, K key){
if( node == null )
return null;
if( key.compareTo(node.key) < 0 ){
node.left = remove(node.left , key);
return node;
}
else if(key.compareTo(node.key) > 0 ){
node.right = remove(node.right, key);
return node;
}
else{ // key.compareTo(node.key) == 0
// 待删除节点左子树为空的情况
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
// 待删除节点右子树为空的情况
if(node.right == null){
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
// 待删除节点左右子树均不为空的情况
// 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
// 用这个节点顶替待删除节点的位置
Node successor = minimum(node.right);
successor.right = removeMin(node.right);
successor.left = node.left;
node.left = node.right = null;
return successor;
}
}
public static void main(String[] args){
System.out.println("Pride and Prejudice");
ArrayList<String> words = new ArrayList<>();
if(FileOperation.readFile("pride-and-prejudice.txt", words)) {
System.out.println("Total words: " + words.size());
RBTree<String, Integer> map = new RBTree<>();
for (String word : words) {
if (map.contains(word))
map.set(word, map.get(word) + 1);
else
map.add(word, 1);
}
System.out.println("Total different words: " + map.getSize());
System.out.println("Frequency of PRIDE: " + map.get("pride"));
System.out.println("Frequency of PREJUDICE: " + map.get("prejudice"));
}
System.out.println();
}
}
输出:
六、 红黑树的性能测试
Main.java
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
System.out.println("Pride and Prejudice");
ArrayList<String> words = new ArrayList<>();
if(FileOperation.readFile("pride-and-prejudice.txt", words)) {
System.out.println("Total words: " + words.size());
// Collections.sort(words);
// Test BST
long startTime = System.nanoTime();
BST<String, Integer> bst = new BST<>();
for (String word : words) {
if (bst.contains(word))
bst.set(word, bst.get(word) + 1);
else
bst.add(word, 1);
}
for(String word: words)
bst.contains(word);
long endTime = System.nanoTime();
double time = (endTime - startTime) / 1000000000.0;
System.out.println("BST: " + time + " s");
// Test AVL
startTime = System.nanoTime();
AVLTree<String, Integer> avl = new AVLTree<>();
for (String word : words) {
if (avl.contains(word))
avl.set(word, avl.get(word) + 1);
else
avl.add(word, 1);
}
for(String word: words)
avl.contains(word);
endTime = System.nanoTime();
time = (endTime - startTime) / 1000000000.0;
System.out.println("AVL: " + time + " s");
// Test RBTree
startTime = System.nanoTime();
RBTree<String, Integer> rbt = new RBTree<>();
for (String word : words) {
if (rbt.contains(word))
rbt.set(word, rbt.get(word) + 1);
else
rbt.add(word, 1);
}
for(String word: words)
rbt.contains(word);
endTime = System.nanoTime();
time = (endTime - startTime) / 1000000000.0;
System.out.println("RBTree: " + time + " s");
}
System.out.println();
}
}
输出:红黑树性能不占优:大多数操作在查询,红黑树擅长 添加/删除【相对于 AVL树】;
只测试添加操作:Main2.java
import java.util.ArrayList;
import java.util.Random;
public class Main2 {
public static void main(String[] args) {
// int n = 20000000;
int n = 20000000;
Random random = new Random(n);
ArrayList<Integer> testData = new ArrayList<>(n);
for(int i = 0 ; i < n ; i ++)
testData.add(random.nextInt(Integer.MAX_VALUE));
// Test BST
long startTime = System.nanoTime();
BST<Integer, Integer> bst = new BST<>();
for (Integer x: testData)
bst.add(x, null);
long endTime = System.nanoTime();
double time = (endTime - startTime) / 1000000000.0;
System.out.println("BST: " + time + " s");
// Test AVL
startTime = System.nanoTime();
AVLTree<Integer, Integer> avl = new AVLTree<>();
for (Integer x: testData)
avl.add(x, null);
endTime = System.nanoTime();
time = (endTime - startTime) / 1000000000.0;
System.out.println("AVL: " + time + " s");
// Test RBTree
startTime = System.nanoTime();
RBTree<Integer, Integer> rbt = new RBTree<>();
for (Integer x: testData)
rbt.add(x, null);
endTime = System.nanoTime();
time = (endTime - startTime) / 1000000000.0;
System.out.println("RBTree: " + time + " s");
}
}
输出:【随机】添加/删除 操作中,红黑树 比 AVL 有优势
红黑树性能总结: