作者:disappearedgod
时间:2014-4-24
前记
本想在“查找 与 树”中完成有关树的介绍,但是由于树的东西实在太多,而面试笔试也是一个重点,所以分出来写了一个。
本文还是主要根据Robert Sedgewick & Kevin Wayne的《算法》第四版来写的。
并参考了Thomas H. Cormen Charles E. Leiserson的《算法导论》
红色字体是可点击的链接。
正文
1.3.2 红黑树
1.3.2.0 问题的提出
红黑树最吸引人的一点是它的实现中
1.3.2.1 介绍
由于2-3树实现起来比较难,想改变2-3树为二叉树,则为红黑树。红黑树是许多“平衡的”查找树中的一种,它能保证在最坏情况下,基本的动态集合操作的时间为O(lgn)。
PS: 一棵高度为h的二叉查找树可以实现任何一种基本的动态集合操作,如SEARCH,PREDECESOR,SUCCESSOR,MINIMUM,MAXIMUN,INSERT & DELETE,其时间都是O(h)。
红黑二叉查找树的基本思想:用标准的二叉查找树(完全由2-结点构成)和一些额外的信息(替换3-结点)来表示2-3树。
- 红链接:将2个2-Node链接起来构成一个3-Node`
- 黑链接:2-3树中普通的链接。
优点:无需修改可以直接使用标准二叉查找树的get()方法。
1.3.2.2 定义
红黑树也是一种树,就免不了要实现。根据多个博客的教训,我觉得还是要上一点代码来解决大家的饥渴的问题。对于树来说,一般就是先用内部类定义点和操作。
首先是点的定义
// BST helper node data type
private class Node {
private Key key; // key
private Value val; // associated data
private Node left, right; // links to left and right subtrees
private boolean color; // color of parent link
private int N; // subtree count
public Node(Key key, Value val, boolean color, int N) {
this.key = key;
this.val = val;
this.color = color;
this.N = N;
}
}
/*************************************************************************
* Node helper methods
*************************************************************************/
// is node x red; false if x is null ?
private boolean isRed(Node x) {
if (x == null) return false;
return (x.color == RED);
}
// number of node in subtree rooted at x; 0 if x is null
private int size(Node x) {
if (x == null) return 0;
return x.N;
}
/*************************************************************************
* Size methods
*************************************************************************/
// return number of key-value pairs in this symbol table
public int size() { return size(root); }
// is this symbol table empty?
public boolean isEmpty() {
return root == null;
}
这些代码都是《algorithm 4 Edition》里面的,看了代码的你会产生一个类似“红色的link哪里去了?”的质疑。
这个问题很好解释,那就是在实现中,红色链接变成了红色节点了。由于每个node有2个子节点,但是
只有一个父节点。而,一个链接是父节点指向子节点的。那么我们就用红色子节点来表示父节点与子节点之间的链接是红色的。
其实从2-3树看过了后,我们知道了:红色其实链接其实表示的就是连接中子节点的右子树是介于子节点和父节点之间的数。我们再看一遍下图就了解了:
如果把红色的link拉平你会发现这就是2-3Tree
1.3.2.3 性质
红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 节点是红色或黑色。
性质2. 根是黑色。
性质3. 所有叶子都是黑色(叶子是NIL节点)。
性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
性质1. 节点是红色或黑色。
性质2. 根是黑色。
性质3. 所有叶子都是黑色(叶子是NIL节点)。
性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
PS:
NIL是哨兵。对一棵红黑树T来说,哨兵nil[T]是一个与树内普通节点有相同域的对象。它的color域为
BLACK,而它的其他域——p,left,right以及key取值如何并不很重要。(所有的叶子节点都指向了
NIL)
在进行更多的探寻之前我们必须要了解这么一个关键的引理,这也是为什么红黑树要存在的意义。
引理:一棵由n个内节点的红黑树的高度至多为2lg(n+1)。
这个我们还是参考中文wikipedia中关于渐进边界证明
------------------------------------------------------------------------------------------------------------------
包含n个内部节点的红黑树的高度是 O(log(n))。
定义:
- h(v) = 以节点v为根的子树的高度。
- bh(v) = 从v到子树中任何叶子的黑色节点的数目(如果v是黑色则不计数它)(也叫做黑色高度)。
引理: 以节点v为根的子树有至少个内部节点。
引理的证明(通过归纳高度):
基础: h(v) = 0
如果v的高度是零则它必定是 nil,因此 bh(v) = 0。所以:
归纳假设: h(v) = k 的v有 个内部节点暗示了 h() = k+1 的 有 个内部节点。
因为 有 h() > 0 所以它是个内部节点。同样的它有黑色高度要么是 bh() 要么是 bh()-1 (依据是红色还是黑色)的两个儿子。通过归纳假设每个儿子都有至少 个内部接点,所以 有:
个内部节点。
使用这个引理我们现在可以展示出树的高度是对数性的。因为在从根到叶子的任何路径上至少有一半的节点是黑色(根据红黑树属性4),根的黑色高度至少是h(root)/2。通过引理我们得到:
因此根的高度是O(log(n))。
------------------------------------------------------------------------------------------------------------------1.3.2.4 平衡与旋转
类似于二叉树的平衡,红黑树的平衡也有类似的平衡过程。像二叉树一样,树的平衡需要一些操作:旋转、插入、颜色转换、红链接传递。
我们这里不采取书上的顺序,首先先讲一下红黑树的插入。
旋转操作可以保持红黑树的两个重要性质:有序性和完美平衡性。
1.3.2.4.1 插入
我们还是比对着2-3树来看如何插入,先从2-Node 讲到 3-Node。
1.3.2.4.1.1 向2-Node中插入key
1.3.2.4.1.2 向树地步的2-Node 插入key
1.3.2.4.1.3 向一棵双键树(即一个3-Node)中插入key
1.3.2.4.1.4 想树底部的3-Node 插入key
1.3.2.4.2 颜色转换
1.3.2.4.3 红链接传递
1.3.2.4.4 旋转
/*************************************************************************
* red-black tree helper functions
*************************************************************************/
// make a left-leaning link lean to the right
private Node rotateRight(Node h) {
// assert (h != null) && isRed(h.left);
Node x = h.left;
h.left = x.right;
x.right = h;
x.color = x.right.color;
x.right.color = RED;
x.N = h.N;
h.N = size(h.left) + size(h.right) + 1;
return x;
}
// make a right-leaning link lean to the left
private Node rotateLeft(Node h) {
// assert (h != null) && isRed(h.right);
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = x.left.color;
x.left.color = RED;
x.N = h.N;
h.N = size(h.left) + size(h.right) + 1;
return x;
}
// flip the colors of a node and its two children
private void flipColors(Node h) {
// h must have opposite color of its two children
// assert (h != null) && (h.left != null) && (h.right != null);
// assert (!isRed(h) && isRed(h.left) && isRed(h.right))
// || (isRed(h) && !isRed(h.left) && !isRed(h.right));
h.color = !h.color;
h.left.color = !h.left.color;
h.right.color = !h.right.color;
}
// Assuming that h is red and both h.left and h.left.left
// are black, make h.left or one of its children red.
private Node moveRedLeft(Node h) {
// assert (h != null);
// assert isRed(h) && !isRed(h.left) && !isRed(h.left.left);
flipColors(h);
if (isRed(h.right.left)) {
h.right = rotateRight(h.right);
h = rotateLeft(h);
}
return h;
}
// Assuming that h is red and both h.right and h.right.left
// are black, make h.right or one of its children red.
private Node moveRedRight(Node h) {
// assert (h != null);
// assert isRed(h) && !isRed(h.right) && !isRed(h.right.left);
flipColors(h);
if (isRed(h.left.left)) {
h = rotateRight(h);
}
return h;
}
// restore red-black tree invariant
private Node balance(Node h) {
// assert (h != null);
if (isRed(h.right)) h = rotateLeft(h);
if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
if (isRed(h.left) && isRed(h.right)) flipColors(h);
h.N = size(h.left) + size(h.right) + 1;
return h;
}
1.3.2.4.5 插入
/*************************************************************************
* Red-black insertion
*************************************************************************/
// insert the key-value pair; overwrite the old value with the new value
// if the key is already present
public void put(Key key, Value val) {
root = put(root, key, val);
root.color = BLACK;
// assert check();
}
// insert the key-value pair in the subtree rooted at h
private Node put(Node h, Key key, Value val) {
if (h == null) return new Node(key, val, RED, 1);
int cmp = key.compareTo(h.key);
if (cmp < 0) h.left = put(h.left, key, val);
else if (cmp > 0) h.right = put(h.right, key, val);
else h.val = val;
// fix-up any right-leaning links
if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h);
if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
if (isRed(h.left) && isRed(h.right)) flipColors(h);
h.N = size(h.left) + size(h.right) + 1;
return h;
}
1.3.2.4.6 删除
/*************************************************************************
* Red-black deletion
*************************************************************************/
// delete the key-value pair with the minimum key
public void deleteMin() {
if (isEmpty()) throw new NoSuchElementException("BST underflow");
// if both children of root are black, set root to red
if (!isRed(root.left) && !isRed(root.right))
root.color = RED;
root = deleteMin(root);
if (!isEmpty()) root.color = BLACK;
// assert check();
}
// delete the key-value pair with the minimum key rooted at h
private Node deleteMin(Node h) {
if (h.left == null)
return null;
if (!isRed(h.left) && !isRed(h.left.left))
h = moveRedLeft(h);
h.left = deleteMin(h.left);
return balance(h);
}
// delete the key-value pair with the maximum key
public void deleteMax() {
if (isEmpty()) throw new NoSuchElementException("BST underflow");
// if both children of root are black, set root to red
if (!isRed(root.left) && !isRed(root.right))
root.color = RED;
root = deleteMax(root);
if (!isEmpty()) root.color = BLACK;
// assert check();
}
// delete the key-value pair with the maximum key rooted at h
private Node deleteMax(Node h) {
if (isRed(h.left))
h = rotateRight(h);
if (h.right == null)
return null;
if (!isRed(h.right) && !isRed(h.right.left))
h = moveRedRight(h);
h.right = deleteMax(h.right);
return balance(h);
}
// delete the key-value pair with the given key
public void delete(Key key) {
if (!contains(key)) {
System.err.println("symbol table does not contain " + key);
return;
}
// if both children of root are black, set root to red
if (!isRed(root.left) && !isRed(root.right))
root.color = RED;
root = delete(root, key);
if (!isEmpty()) root.color = BLACK;
// assert check();
}
// delete the key-value pair with the given key rooted at h
private Node delete(Node h, Key key) {
// assert contains(h, key);
if (key.compareTo(h.key) < 0) {
if (!isRed(h.left) && !isRed(h.left.left))
h = moveRedLeft(h);
h.left = delete(h.left, key);
}
else {
if (isRed(h.left))
h = rotateRight(h);
if (key.compareTo(h.key) == 0 && (h.right == null))
return null;
if (!isRed(h.right) && !isRed(h.right.left))
h = moveRedRight(h);
if (key.compareTo(h.key) == 0) {
Node x = min(h.right);
h.key = x.key;
h.val = x.val;
// h.val = 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);
}