算法學習----紅黑樹
紅黑樹的介紹
先來看下算法導論對R-BTree的介紹:
紅黑樹,一種二叉查找樹,但在每個結點上增加一個存儲位表示結點的顏色,可以是Red或Black。
通過對任何一條從根到葉子的路徑上各個結點着色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長出倆倍,因而是接近平衡的。
簡單的說,紅黑樹具有以下5條性質:
1每個節點都包含 5個域:color,key ,leftNode,rightNode,parentNode.
2 根節點是黑色的。
3每個葉節點是黑色的。
4如果一個節點是紅色的,那麼他的兩個兒子一定是黑色的。
5對於每個節點,從該節點到其他子孫節點的所有路徑上包含相同數目的黑色節點。
紅黑樹的效率問題:
1.在一棵二叉查找樹上,執行查找、插入、刪除等操作,的時間複雜度爲O(lgn)
因爲,一棵由n個結點,隨機構造的二叉查找樹的高度爲lgn,所以順理成章,一般操作的執行時間爲O(lgn)
2.但若是一棵具有n個結點的線性鏈,則此些操作最壞情況運行時間爲O(n)
而紅黑樹,能保證在最壞情況下,基本的動態幾何操作的時間均爲O(lgn)。
紅黑樹的應用:
紅黑樹用在關聯數組、字典的實現上。需要的空間比散列表小。
任何鍵值對應,需要隨機存儲和鍵有序的情況都可以用。
實例:
內存中比如緩存的(區塊-數據),如編號對應內容,引索號對應數據項,日期對應日程,價格對應商品等。應用遍及,在內存中使用效率比較高。
C++ STL中的關聯式容器:集合set、多重集合multiset、映射map、多重映射multimap等均採用了紅黑樹的變體set<int> s; map<int, string>s等
Java中的容器Set,Map的構造器TreeSet,TreeMap也使用了紅黑樹
在Linux內核中,用於組織虛擬區間的數據結構也是紅黑樹。
接下來,根據定義看看紅黑樹節點的實現:Java語言實現
package org.bupt.qyl;
/**
* 紅黑樹
* 首先是一個二叉搜索樹
* 然後具有五個性質
* 性質1. 每個節點都包含 5個域 :color,key ,leftNode,rightNode,parentNode.
* 性質2. 根是黑色
* 性質3. 每個葉節點是黑色的。
* 性質4. 每個紅色結點的兩個子結點都是黑色 (從每個葉子到根的所有路徑上不能有兩個連續的紅色結點)
* 性質5. 從任一結點到其每個葉子的所有路徑都包含相同數目的黑色結點
* @author qyl
*
* @param <E>
*/
public class RBTree<E> {
//紅黑樹的根結點
private RBTreeNode<E> rootNode;
//構造方法
public RBTree(){
this.rootNode = null;
}
//構造方法結束
//get set方法
public RBTreeNode<E> getRootNode(){
return this.rootNode;
}
public void setRootNode(RBTreeNode<E>rootNode){
this.rootNode = rootNode;
}
//get set方法結束
public RBTreeNode<E> parentOf(RBTreeNode<E>node){
return node == null?null:node.getParentNode();
}
public RBTreeNode<E> leftOf(RBTreeNode<E>node){
return node == null?null:node.getLeftNode();
}
public RBTreeNode<E> rightOf(RBTreeNode<E>node){
return node == null?null:node.getRightNode();
}
public boolean colorOf(RBTreeNode<E>node){
return node == null?RBTreeNode.BLACK:node.getColor();
}
public void setColor(RBTreeNode<E>node,boolean color){
if(node == null) return;
node.setColor(color);
}
}
這個是C語言實現的紅黑樹節點的定義:typedef int key_t;
typedef int data_t;
typedef enum color_t
{
RED = 0,
BLACK = 1
}color_t;
typedef struct rb_node_t
{
key_t key;
color_t color;
struct rb_node_t *parent;
struct rb_node_t *left;
struct rb_node_t *right,
}rb_node_t;
紅黑樹的旋轉
當我們在對紅黑樹進行插入和刪除等操作時,對樹做了修改,那麼可能會違背紅黑樹的性質。
爲了保持紅黑樹的性質,我們可以通過對樹進行旋轉,即修改樹種某些結點的顏色及指針結構,以達到對紅黑樹進行插入、刪除結點等操作時,紅黑樹依然能保持它特有的性質。
左、右旋轉的圖示:注:旋轉過程中二叉搜索樹(BST)性質不變:α≤x≤β≤y≤γ
步驟解釋:需要變動的是3根粗鏈
①y←right[x] //記錄指向y節點的指針
②right[x]←left[y], p[left[y]]←x //β連到x右
③p[y]←p[x], p[x]的左或右指針指向y //y連到p[x]
④ Left[y]←x, p[x]←y //x連到y左
/**
* 圍繞某個結點左旋轉
* @param node
*/
private void rotateLeft(RBTreeNode<E>node){
if (node != null) {
/**
* 得到node的右子結點
*/
RBTreeNode<E> rightNode = node.getRightNode();
/**
* 把node的右子結點設爲rightNode的左子結點
*/
node.setRightNode(rightNode.getLeftNode());
/**
* 如果rightNode的左子結點非空
* 就把rightNode的左子結點的父結點設爲node
*/
if(rightNode.getLeftNode()!=null)
rightNode.getLeftNode().setParentNode(node);
/**
* 把rightNode的父結點設爲node的父結點
*/
rightNode.setParentNode(node.getParentNode());
/**
* node的父結點爲空
* 表明node是紅黑樹的根結點
*/
if(node.getParentNode() == null)
this.setRootNode(rightNode);
/**
* node父結點的左子結點存儲的值與node相同
* 表明node是父結點的左子結點
*/
else if(node.getParentNode().getLeftNode() == node)
node.getParentNode().setLeftNode(rightNode);
/**
* node是父結點的右子結點
*/
else
node.getParentNode().setRightNode(rightNode);
/**
* rightNode的左子結點設爲node
*/
rightNode.setLeftNode(node);
/**
* node的父結點設爲rightNode
*/
node.setParentNode(rightNode);
}
}
右旋與左旋一樣,只是方向改變了下:
/**
* 圍繞某個結點右旋轉
* 原理同左旋轉相同
* 只不過方向換了下
* @param node
*/
private void rotateRight(RBTreeNode<E>node){
if(node != null){
/**
* 得到node的左子結點leftNode
*/
RBTreeNode<E>leftNode = node.getLeftNode();
/**
* 把node的左子結點設爲leftNode的右子結點
*/
node.setLeftNode(leftNode.getRightNode());
/**
* 如果leftNode的右子結點非空
* 那麼把leftNode的右子結點的父結點設爲node
*/
if(leftNode.getRightNode() != null) leftNode.getRightNode().setParentNode(node);
/**
* leftNode的父結點設爲node的父結點
*/
leftNode.setParentNode(node.getParentNode());
/**
* node爲根結點
*/
if(node.getParentNode() == null)
this.rootNode = leftNode;
else if(node.getParentNode().getRightNode() == node)
node.getParentNode().setRightNode(leftNode);
else node.getParentNode().setLeftNode(leftNode);
leftNode.setRightNode(node);
node.setParentNode(leftNode);
}
}
紅黑樹的插入:
step 1:將z節點按BST樹規則插入紅黑樹中,z是葉子節點;
step 2:將z塗紅;
step 3:調整使其滿足紅黑樹的性質;
在對紅黑樹進行插入操作時,我們一般總是插入紅色的結點,因爲這樣可以在插入過程中儘量避免對樹的調整。
那麼,我們插入一個結點後,可能會使原樹的哪些性質改變列?
由於,我們是按照二叉樹的方式進行插入,因此元素的搜索性質不會改變。
如果插入的結點是根結點,性質2會被破壞,
如果插入結點的父結點是紅色,則會破壞性質4。
因此,插入一個紅色結點只會破壞性質2或性質4。
我們的回覆策略很簡單:
其一、把出現違背紅黑樹性質的結點向上移,如果能移到根結點,那麼很容易就能通過直接修改根結點來恢復紅黑樹的性質。直接通過修改根結點來恢復紅黑樹應滿足的性質。
其二、窮舉所有的可能性,之後把能歸於同一類方法處理的歸爲同一類,不能直接處理的化歸到下面的幾種情況,
情況1:插入的是根結點。
原樹是空樹,此情況只會違反性質2。
對策:直接把此結點塗爲黑色。
情況2:插入的結點的父結點是黑色。
此不會違反性質2和性質4,紅黑樹沒有被破壞。
對策:什麼也不做。
情況3:當前結點的父結點是紅色且祖父結點的另一個子結點(叔叔結點)是紅色。
此時父結點的父結點一定存在,否則插入前就已不是紅黑樹。
與此同時,又分爲父結點是祖父結點的左子還是右子,對於對稱性,我們只要解開一個方向就可以了。
在此,我們只考慮父結點爲祖父左子的情況。
同時,還可以分爲當前結點是其父結點的左子還是右子,但是處理方式是一樣的。我們將此歸爲同一類。
對策:將當前節點的父節點和叔叔節點塗黑,祖父結點塗紅,把當前結點指向祖父節點,從新的當前節點重新開始算法。
紅黑樹插入節點算法的Java實現:
public void insertNode(RBTreeNode<E> node) {
if (node == null) return;
if (node.getValue() == null) return;
/**
* rootnode爲空,
* 空的紅黑樹
*/
if (this.getRootNode() == null) {
rootNode = new RBTreeNode<E>(node.getValue());
return;
}
/**
* rootnode非空
*/
RBTreeNode<E> tempNode = this.rootNode;
while (true) {
/**
* 紅黑樹中已經存在此結點
* 無須插入
* 直接返回
*/
if (tempNode.compareTo(node) == 0) {
return;
}else if (tempNode.compareTo(node) > 0) {
if (tempNode.getLeftNode() != null) {
tempNode = tempNode.getLeftNode();
}else {
tempNode.setLeftNode(node);
node.setParentNode(tempNode);
fixAfterInsertion(node);
return;
}
}else {
if (tempNode.getRightNode() != null) {
tempNode = tempNode.getRightNode();
}else {
tempNode.setRightNode(node);
node.setParentNode(tempNode);
fixAfterInsertion(node);
return;
}
}
}
}
private void fixAfterInsertion(RBTreeNode<E>node){
if(node == null) return;
/**
* node標記爲紅結點
*/
node.setColor(RBTreeNode.RED);
/**
* while循環條件
* node非空 並且
* node非根結點 並且
* node的父結點的顏色爲紅色
*/
while(node != null && ( node != rootNode) && node.getParentNode().getColor() == RBTreeNode.RED){
/**
* node的父結點是node祖父結點的左子結點
*/
if(parentOf(node) == leftOf(parentOf(parentOf(node)))){
/**
* y是node祖父結點的右子結點
*/
RBTreeNode<E> y = rightOf(parentOf(parentOf(node)));
/**
* 如果y的顏色是紅色
*/
if(colorOf(y) == RBTreeNode.RED){
/**
* 把node父結點的顏色設爲黑色
*/
setColor(parentOf(node),RBTreeNode.BLACK);
/**
* 把y的顏色設爲黑色
*/
setColor(y,RBTreeNode.BLACK);
/**
* 把node祖父結點的顏色設爲紅色
*/
setColor(parentOf(parentOf(node)),RBTreeNode.RED);
/**
* node指向node的祖父結點
*/
node = parentOf(parentOf(node));
} else {
/**
* 這塊y的顏色是黑色
*/
/**
* 如果node是父結點的右子結點
*/
if(node == rightOf(parentOf(node))){
/**
* node指向node的父結點
*/
node = parentOf(node);
/**
* 圍繞node進行左旋轉
*/
rotateLeft(node);
}
/**
* 把node的父結點設置爲黑色
*/
setColor(parentOf(node),RBTreeNode.BLACK);
/**
* 把node的祖父結點設置爲紅色
*/
setColor(parentOf(parentOf(node)),RBTreeNode.RED);
/**
* 圍繞node的祖父結點進行右旋轉
*/
rotateRight(parentOf(parentOf(node)));
}
/**
* node的父結點是node祖父結點的右子結點
*/
} else {
/**
* y指向node祖父結點的左子結點
*/
RBTreeNode<E> y = leftOf(parentOf(parentOf(node)));
/**
* 如果y的顏色是紅色
*/
//if (colorOf(y) == RED) {
if(colorOf(y) == RBTreeNode.RED){
/**
* 把node的父結點設爲黑色
*/
setColor(parentOf(node),RBTreeNode.BLACK);
/**
* 把y的顏色設爲黑色
*/
setColor(y,RBTreeNode.BLACK);
/**
* 把node的祖父結點設爲紅色
*/
setColor(parentOf(parentOf(node)),RBTreeNode.RED);
/**
* node結點指向其祖父結點
* 然後向上逐層調整
*/
node = parentOf(parentOf(node));
} else {
/**
* node是node父結點的左子結點
*/
if(node == leftOf(parentOf(node))){
/**
* node指向node的父結點
*/
node = parentOf(node);
/**
* 圍繞node進行右旋轉
* 其實node已經是老node的父結點了
*/
rotateRight(node);
}
/**
* 把node的父結點設爲黑色
*/
setColor(parentOf(node),RBTreeNode.BLACK);
/**
* 把node的祖父結點設爲紅色
*/
setColor(parentOf(parentOf(node)),RBTreeNode.RED);
/**
* 圍繞node的祖父結點進行左旋轉
*/
rotateLeft(parentOf(parentOf(node)));
}
}
}
/**
* 把根結點設成黑色
*/
setColor(rootNode,RBTreeNode.BLACK);
}
- 找到真正的刪除點:當被刪除結點n存在左右孩子時,真正的刪除點應該是n的中序遍在前驅(或後繼)
- 所以可以推斷出,在進行刪除操作時,真正的刪除點必定是隻有一個孩子或沒有孩子的結點。而根據紅黑樹的性質可以得出以下兩個結論:
- 刪除操作中真正被刪除的必定是隻有一個紅色孩子或沒有孩子的結點。
- 如果真正的刪除點是一個紅色結點,那麼它必定是一個葉子結點。
public void deleteNode(RBTreeNode<E> p) {
//modCount++;
//size--;
// If strictly internal, copy successor's element to p and then make p
// point to successor.
/**
* p的兩個子結點都非空
*/
if (p.getLeftNode() != null && p.getRightNode() != null) {
RBTreeNode<E> s = successor(p);
p.setValue(s.getValue());
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
RBTreeNode<E> replacement = (p.getLeftNode() != null ? p.getLeftNode() : p.getRightNode());
if (replacement != null) {
// Link replacement to parent
//replacement.parent = p.parent;
replacement.setParentNode(p.getParentNode());
/**
* p的父結點爲空
* p是根結點
*/
if (p.getParentNode() == null)
rootNode = replacement;
/**
* p是父結點的左子結點
*/
else if (p == p.getParentNode().getLeftNode())
p.getParentNode().setLeftNode(replacement);
/**
* p是父結點的右子結點
*/
else
p.getParentNode().setRightNode(replacement);
/**
* ok
* 把p的左右子結點,父結點指針全部置空
* p被刪除了
*/
// Null out links so they are OK to use by fixAfterDeletion.
p.setLeftNode(null);
p.setRightNode(null);
p.setParentNode(null);
// Fix replacement
if (p.getColor() == RBTreeNode.BLACK)
fixAfterDeletion(replacement);
} else if (p.getParentNode() == null) {
rootNode = null;
} else { // No children. Use self as phantom replacement and unlink.
/**
* 在這個else裏面
* replacement是null
* 表明p的左右兩個子結點都爲空
*/
if (p.getColor() == RBTreeNode.BLACK)
fixAfterDeletion(p);
if (p.getParentNode() != null) {
/**
* p是p的父結點的左子結點
*/
if (p == p.getParentNode().getLeftNode())
p.getParentNode().setLeftNode(null);
/**
* p是p的父結點的右子結點
*/
else if (p == p.getParentNode().getRightNode())
p.getParentNode().setRightNode(null);
/**
* 把p的父結點設爲空
* 刪除OK
*/
p.setParentNode(null);
}
}
}
private void fixAfterDeletion(RBTreeNode<E>node){
/**
* 循環條件:
* node不是根結點
* 而且
* node結點的顏色爲黑色
*/
while(this.getRootNode() != node && colorOf(node) == RBTreeNode.BLACK){
/**
* node是node父結點的左子結點
*/
if( node == leftOf(parentOf(node))){
/**
* sib指向node父結點的右子結點
*/
RBTreeNode<E> sib = rightOf(parentOf(node));
/**
* 參見colorOf(RBTreeNode<E>)方法實現
* 有對空值的判斷
*/
if(colorOf(sib) == RBTreeNode.RED){
/**
* setColor()中也有對空值的判斷
*/
setColor(sib, RBTreeNode.BLACK);
/**
* 把node的父結點設爲紅色
*/
setColor(parentOf(node), RBTreeNode.RED);
/**
* 圍繞node的父結點進行左旋轉
*/
rotateLeft(parentOf(node));
/**
* sib指向node父結點的右子結點
*/
sib = rightOf(parentOf(node));
}
/**
* 如果sib的兩個子結點都是黑色
*/
if(colorOf(leftOf(sib)) == RBTreeNode.BLACK &&
colorOf(rightOf(sib)) == RBTreeNode.BLACK) {
/**
* 把sib設爲紅色
*/
setColor(sib, RBTreeNode.RED);
/**
* node指向它的父結點
*/
node = parentOf(node);
} else {//這個else表示sib的兩個子結點並非都是黑色,一紅一黑或都是紅色
/**
* 如果sib的右子結點是黑色
* 那它的左子結點肯定是紅色
*/
if(colorOf(rightOf(sib)) == RBTreeNode.BLACK) {
/**
* 把sib的左子結點設爲黑色
*/
setColor(leftOf(sib), RBTreeNode.BLACK);
/**
* 把sib的顏色設爲黑色
*/
setColor(sib, RBTreeNode.RED);
/**
* 圍繞sib進行右旋轉
*/
rotateRight(sib);
/**
* sib指向node父結點的右子結點
*/
sib = rightOf(parentOf(node));
}
/**
* 把sib的顏色設爲node父結點的顏色
*/
//setColor(sib, colorOf(parentOf(x)));
setColor(sib, colorOf(parentOf(node)));
/**
* 把node父結點設爲黑色
*/
setColor(parentOf(node), RBTreeNode.BLACK);
/**
* 把sib右子結點的顏色設爲黑色
*/
setColor(rightOf(sib), RBTreeNode.BLACK);
/**
* 圍繞node的父結點進行左旋轉
*/
rotateLeft(parentOf(node));
/**
* node結點指向紅黑樹的根結點
*/
node = rootNode;
}
} else { // symmetric
/**
* sib指向node父結點的左子結點
*/
RBTreeNode<E> sib = leftOf(parentOf(node));
/**
* 如果sib的顏色是紅色
*/
if(colorOf(sib) == RBTreeNode.RED) {
/**
* 把sib的顏色設爲黑色
*/
setColor(sib, RBTreeNode.BLACK);
/**
* 把node父結點的顏色設爲紅色
*/
setColor(parentOf(node), RBTreeNode.RED);
/**
* 圍繞node的父結點進行右旋轉
*/
rotateRight(parentOf(node));
/**
* sib指向node父結點的左子結點
*/
sib = leftOf(parentOf(node));
}
/**
* 如果sib的兩個子結點都是黑色
*/
if (colorOf(rightOf(sib)) == RBTreeNode.BLACK &&
colorOf(leftOf(sib)) == RBTreeNode.BLACK) {
/**
* 把sib的顏色設爲紅色
*/
setColor(sib, RBTreeNode.RED);
/**
* node指向node的父結點
*/
node = parentOf(node);
} else {
/**
* 如果sib的左子結點是黑色
*/
if (colorOf(leftOf(sib)) == RBTreeNode.BLACK) {
/**
* 把sib的右子結點設爲黑色
*/
setColor(rightOf(sib), RBTreeNode.BLACK);
/**
* 把sib的顏色設爲紅色
*/
setColor(sib, RBTreeNode.RED);
/**
* 圍繞sib進行左旋轉
*/
rotateLeft(sib);
/**
* sib指向node父結點的左子結點
*/
sib = leftOf(parentOf(node));
}
/**
* 把sib的顏色設爲node父結點的顏色
*/
setColor(sib, colorOf(parentOf(node)));
/**
* 把node父結點的顏色設爲黑色
*/
setColor(parentOf(node), RBTreeNode.BLACK);
/**
* 把sib左子結點的顏色設爲黑色
*/
//setColor(leftOf(sib), BLACK);
setColor(leftOf(sib), RBTreeNode.BLACK);
/**
* 圍繞node的父結點進行右旋轉
*/
rotateRight(parentOf(node));
node = rootNode;
}
}
}
setColor(node, RBTreeNode.BLACK);
}