红黑树的所有搜索操作,包括get() min() max() ceiling() floor() rank() select() 等(参数未列出),均和普通二叉树一样,搜索时无视节点颜色即可。
红黑树主要在于其插入操作,上一篇我们讲了对双节点插入的几种情况(文章链接)我们会发现即使最复杂的情况,插入值在两值中间,也可以转化为基础操作。
下图展示了红黑树插入所有可能的情况:插入值小于左端值,插入值在两值中间,插入值大于右端值。我们可以发现其实所有情况都可以以下列规律化解:
1 如红键在右节点,左旋
2 如红键在左节点,且左节点左子节点也为红键,右旋
3 如左节点和右节点均为红键,变色
插入操作代码如下:
public void put(Key key, Value value) {
root = put(root, key, value);
}
private Node put(Node x, Key key, Value value) {
if (x == null) {
return new Node(key, value, 1, RED);
}
// find the correct position
int cmp = key.compareTo(x.key);
if (cmp > 0) {
x.right = put(x.right, key, value);
} else if (cmp < 0) {
x.left = put(x.left, key, value);
} else {
x.value = value;
}
// rotation and flip color
if (isRed(x.left)) {
x = rotateLeft(x);
}
if (isRed(x.left) && isRed(x.left.left)) {
x = rotateRight(x);
}
if (isRed(x.left) && isRed(x.right)) {
flipColor(x);
}
x.count = size(x.left) + size(x.right) + 1;
return x;
}
前一部分程序和二叉树一样,目的是找到正确插入位置。然后插入后对键进行变换以保证红键永远在左边
红黑树性能分析:
由于不可能连续出现两个红键,且根节点到树底各个路径黑键数相等,红黑树最糟情况高度(所有红键和黑键交替出现),不可能大于最优情况高度(所有键为黑键)的两倍。
因此,红黑树查找及插入操作最糟情况时间复杂度为2logN
对于平均情况,时间复杂度约为logN
最后,给出红黑树目前完整程序(暂时还没有删除操作)
public class RedBlackBSTSymbolTable <Key extends Comparable, Value> {
public static final boolean RED = true;
public static final boolean BLACK = false;
private class Node {
private Key key;
private Value value;
private Node left;
private Node right;
private int count;
private boolean color;
private Node(Key key, Value value, int count, boolean color) {
this.key = key;
this.value = value;
this.count = count;
this.color = color;
}
}
public Node root;
public int size() {
return size(root);
}
private int size(Node x) {
if (x == null) {
return 0;
} else {
return x.count;
}
}
// constructor
public RedBlackBSTSymbolTable () {
root = null;
}
private boolean isRed(Node x) {
return x.color;
}
private Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = h.color;
h.color = RED;
x.count = h.count;
h.count = size(h.left) + size(h.right) + 1;
return x;
}
private Node rotateRight(Node h) {
Node x = h.left;
x.right = h.left;
x.right = h;
x.color = h.color;
h.color = RED;
x.count = h.count;
h.count = size(h.left) + size(h.right) + 1;
return x;
}
private void flipColor(Node h) {
h.left.color = BLACK;
h.right.color = BLACK;
h.color = RED;
}
public void put(Key key, Value value) {
root = put(root, key, value);
}
private Node put(Node x, Key key, Value value) {
if (x == null) {
return new Node(key, value, 1, RED);
}
// find the correct position
int cmp = key.compareTo(x.key);
if (cmp > 0) {
x.right = put(x.right, key, value);
} else if (cmp < 0) {
x.left = put(x.left, key, value);
} else {
x.value = value;
}
// rotation and flip color
if (isRed(x.left)) {
x = rotateLeft(x);
}
if (isRed(x.left) && isRed(x.left.left)) {
x = rotateRight(x);
}
if (isRed(x.left) && isRed(x.right)) {
flipColor(x);
}
x.count = size(x.left) + size(x.right) + 1;
return x;
}
// The methods below are the same as those in a regular BST
// since the searching operations are the same
// find a certain value according to its key
public Value get(Key key) {
Node result = get(key, root);
if (result == null) {
return null;
}
return result.value;
}
private Node get(Key key, Node x) {
if (x == null) { // no this key
return null;
}
if (x.key.compareTo(key) > 0) { // smaller, move to the left
return get(key, x.left);
} else if (x.key.compareTo(key) < 0) { // larger, move to the right
return get(key, x.right);
} else {
return x; // find it
}
}
// find the smallest key
public Key min() {
return min(root).key;
}
private Node min(Node x) {
if (x.left == null) {
return x;
} else {
return min(x.left);
}
}
// find the largest key
public Key max() {
return max(root).key;
}
private Node max(Node x) {
if (x.right == null) {
return x;
} else {
return max(x.right);
}
}
// find the largest key less or equals k
public Key floor (Key k) {
Node x = floor(root, k);
if (x == null) {
return null;
} else {
return x.key;
}
}
private Node floor(Node x, Key k) {
if (x == null) {
return null;
}
int cmp = k.compareTo(x.key);
if (cmp == 0) { // the same key, return it
return x;
} else if (cmp < 0) {
return floor(x.left, k); // smaller, move to the left
} else { // larger, attempt to move to the right
Node t = floor(x.right, k);
if (t != null) {
return t;
} else {
return x;
}
}
}
// find the smallest key larger or equals k
public Key ceiling (Key k) {
Node x = ceiling(root, k);
if (x == null ) {
return null;
}
return x.key;
}
private Node ceiling(Node x, Key k) {
if (x == null) {
return null;
}
int cmp = k.compareTo(x.key);
if (cmp == 0) { // the exact value
return x;
} else if (cmp > 0) {
return ceiling(x.right, k); // larger, move to the left
} else { // smaller, attempt to move to the right
Node t = ceiling(x.left, k);
if (t != null) {
return t;
} else {
return x;
}
}
}
// return the kth smallest key
public Key select(int k) {
Node x = select(k, root);
if (x == null) {
return null;
}
return x.key;
}
private Node select(int k, Node x) {
if (x == null) {
return null;
}
int rank = size(x.left);
if (rank == k) { // find the correct index
return x;
} else if (rank > k) { // move to the left
return select(k, x.left);
} else { // move to the right
return select(k - rank - 1, x.right);
}
}
// find the rank of a given key
public int rank(Key key) {
return rank(key, root);
}
private int rank(Key key, Node x) {
if (x == null) {
return 0;
}
int cmp = key.compareTo(x.key);
if (cmp == 0) { // find the key
return size(x.left);
} else if (cmp > 0) { // smaller, move to the right
return size(x.left) + 1 + rank(key, x.right);
} else { // larger, move to the left
return rank(key, x.left);
}
}