1. 程式人生 > >【資料結構】紅-黑樹

【資料結構】紅-黑樹

1.紅-黑樹的特徵

        它主要有兩個特徵:1.節點都有顏色;2.在插入和刪除的過程中,要遵循保持這些顏色的不同排列的規則。首先第一個特徵很好解決,在節點類中店家一個數據欄位,例如boolean型變數,以此來表示節點的顏色資訊。第二個特徵比較複雜,紅-黑樹有它的幾個規則,如果遵循這些規則,那麼樹就是平衡的。紅-黑樹的主要規則如下:

        1.每個節點不是紅色就是黑色的;

        2.根節點總是黑色的;

        3.如果節點是紅色的,則它的子節點必須是黑色的(反之不一定);

        4.對於任意結點而言,其到葉結點樹尾端NIL指標的每條路徑都包含相同數目的黑結點。     

        在紅-黑樹中插入的節點都是紅色的,這不是偶然的,因為插入一個紅色節點比插入一個黑色節點違背紅-黑規則的可能性更小。原因是:插入黑色節點總會改變黑色高度(違背規則4),但是插入紅色節點只有一半的機會會違背規則3。另外違背規則3比違背規則4要更容易修正。當插入一個新的節點時,可能會破壞這種平衡性,那麼紅-黑樹是如何修正的呢?

2.平衡性的修正

        紅-黑樹主要通過三種方式對平衡進行修正,改變節點顏色、左旋和右旋。這看起來有點抽象,我們分別來介紹它們。

1.變色

        改變節點顏色比較容易理解,因為它違背了規則3。假設現在有個節點E,然後插入節點A和節點S,節點A在左子節點,S在右子節點,目前是平衡的。如果此時再插一個節點,那麼就出現了不平衡了,因為紅色節點的子節點必須為黑色,但是新插的節點是紅色的。所以這時候就必須改變節點顏色了。所以我們將根的兩個子節點從紅色變為黑色(至於為什麼都要變,下面插入的時候會詳細介紹),將父節點會從黑色變成紅色。可以用如下示意圖表示一下:

2.左旋

        通常左旋操作用於將一個向右傾斜的紅色連結旋轉為向左連結。示意圖如下:

        左旋有個很萌萌噠的動態示意圖,可以方便理解:

3.右旋

        右旋可左旋剛好相反,這裡不再贅述,直接看示意圖:

 

        當然咯,右旋也有個萌萌的動態圖:

        這裡主要介紹了紅-黑樹對平衡的三種修正方式,大家有個感性的認識,那麼什麼時候該修正呢?什麼時候該用哪種修正呢?這將是接下來我們要探討的問題。

3.紅-黑樹的操作

        紅-黑樹的基本操作是新增、刪除和旋轉。對紅-黑樹進行新增或刪除後,可能會破壞其平衡性,會用到哪種旋轉方式去修正呢?我們首先對紅-黑樹的節點做一介紹,然後分別對左旋和右旋的具體實現做一分析,最後我們探討下紅-黑樹的具體操作。

1.紅-黑樹的節點

        紅-黑樹是對二叉搜尋樹的改進,所以其節點與二叉搜尋樹是差不多的,只不過在它基礎上增加了一個boolean型變數來表示節點的顏色,具體看RBNode<T>類:

public class RBNode<T extends Comparable<T>>{
	boolean color; //顏色
	T key; //關鍵字(鍵值)
	RBNode<T> left; //左子節點
	RBNode<T> right; //右子節點
	RBNode<T> parent; //父節點
	
	public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) {
		this.key = key;
		this.color = color;
		this.parent = parent;
		this.left = left;
		this.right = right;
	}
	
	public T getKey() {
		return key;
	}
	
	public String toString() {
		return "" + key + (this.color == RED? "R" : "B");
	}
}

2.左旋具體實現

        上面對左旋的概念已經有了感性的認識了,這裡就不再贅述了,我們從下面的程式碼中結合上面的示意圖,探討一下左旋的具體實現:

  1. /*************對紅黑樹節點x進行左旋操作 ******************/
    /*
     * 左旋示意圖:對節點x進行左旋
     *     p                       p
     *    /                       /
     *   x                       y
     *  / \                     / \
     * lx  y      ----->       x  ry
     *    / \                 / \
     *   ly ry               lx ly
     * 左旋做了三件事:
     * 1. 將y的左子節點賦給x的右子節點,並將x賦給y左子節點的父節點(y左子節點非空時)
     * 2. 將x的父節點p(非空時)賦給y的父節點,同時更新p的子節點為y(左或右)
     * 3. 將y的左子節點設為x,將x的父節點設為y
     */
    private void leftRotate(RBNode<T> x) {
    	//1. 將y的左子節點賦給x的右子節點,並將x賦給y左子節點的父節點(y左子節點非空時)
    	RBNode<T> y = x.right;
    	x.right = y.left;
    	
    	if(y.left != null) 
    		y.left.parent = x;
    	
    	//2. 將x的父節點p(非空時)賦給y的父節點,同時更新p的子節點為y(左或右)
    	y.parent = x.parent;
    	
    	if(x.parent == null) {
    		this.root = y; //如果x的父節點為空,則將y設為父節點
    	} else {
    		if(x == x.parent.left) //如果x是左子節點
    			x.parent.left = y; //則也將y設為左子節點
    		else
    			x.parent.right = y;//否則將y設為右子節點
    	}
    	
    	//3. 將y的左子節點設為x,將x的父節點設為y
    	y.left = x;
    	x.parent = y;		
    }
    

    3.右旋具體實現

            上面對右旋的概念已經有了感性的認識了,這裡也不再贅述了,我們從下面的程式碼中結合上面的示意圖,探討一下右旋的具體實現:

/*************對紅黑樹節點y進行右旋操作 ******************/
/*
 * 左旋示意圖:對節點y進行右旋
 *        p                   p
 *       /                   /
 *      y                   x
 *     / \                 / \
 *    x  ry   ----->      lx  y
 *   / \                     / \
 * lx  rx                   rx ry
 * 右旋做了三件事:
 * 1. 將x的右子節點賦給y的左子節點,並將y賦給x右子節點的父節點(x右子節點非空時)
 * 2. 將y的父節點p(非空時)賦給x的父節點,同時更新p的子節點為x(左或右)
 * 3. 將x的右子節點設為y,將y的父節點設為x
 */
private void rightRotate(RBNode<T> y) {
	//1. 將y的左子節點賦給x的右子節點,並將x賦給y左子節點的父節點(y左子節點非空時)
	RBNode<T> x = y.left;
	y.left = x.right;
	
	if(x.right != null) 
		x.right.parent = y;
	
	//2. 將x的父節點p(非空時)賦給y的父節點,同時更新p的子節點為y(左或右)
	x.parent = y.parent;
	
	if(y.parent == null) {
		this.root = x; //如果x的父節點為空,則將y設為父節點
	} else {
		if(y == y.parent.right) //如果x是左子節點
			y.parent.right = x; //則也將y設為左子節點
		else
			y.parent.left = x;//否則將y設為右子節點
	}
	
	//3. 將y的左子節點設為x,將x的父節點設為y
	x.right = y;
	y.parent = x;		
}

4.插入操作

        分析完了紅-黑樹中主要的旋轉操作,接下來我們開始分析常見的插入、刪除等操作了。這裡先分析插入操作。 由於紅-黑樹是二叉搜尋樹的改進,所以插入操作的前半工作時相同的,即先找到待插入的位置,再將節點插入,先來看看插入的前半段程式碼:

/*********************** 向紅黑樹中插入節點 **********************/
public void insert(T key) {
	RBNode<T> node = new RBNode<T>(key, RED, null, null, null);
	if(node != null) 
		insert(node);
}
 
//將節點插入到紅黑樹中,這個過程與二叉搜尋樹是一樣的
private void insert(RBNode<T> node) {
	RBNode<T> current = null; //表示最後node的父節點
	RBNode<T> x = this.root; //用來向下搜尋用的
	
	//1. 找到插入的位置
	while(x != null) {
		current = x;
		int cmp = node.key.compareTo(x.key);
		if(cmp < 0) 
			x = x.left;
		else
			x = x.right;
	}
	node.parent = current; //找到了位置,將當前current作為node的父節點
	
	//2. 接下來判斷node是插在左子節點還是右子節點
	if(current != null) {
		int cmp = node.key.compareTo(current.key);
		if(cmp < 0)
			current.left = node;
		else
			current.right = node;
	} else {
		this.root = node;
	}
	
	//3. 將它重新修整為一顆紅黑樹
	insertFixUp(node);
}

  這與二叉搜尋樹中實現的思路一模一樣,這裡不再贅述,主要看看方法裡面最後一步insertFixUp操作。因為插入後可能會導致樹的不平衡,insertFixUp方法裡主要是分情況討論,分析何時變色,何時左旋,何時右旋。我們先從理論上分析具體的情況,然後再看insertFixUp方法的具體實現。

        如果是第一次插入,由於原樹為空,所以只會違反紅-黑樹的規則2,所以只要把根節點塗黑即可;如果插入節點的父節點是黑色的,那不會違背紅-黑樹的規則,什麼也不需要做;但是遇到如下三種情況時,我們就要開始變色和旋轉了:

        1. 插入節點的父節點和其叔叔節點(祖父節點的另一個子節點)均為紅色的;

        2. 插入節點的父節點是紅色,叔叔節點是黑色,且插入節點是其父節點的右子節點;

        3. 插入節點的父節點是紅色,叔叔節點是黑色,且插入節點是其父節點的左子節點。

        下面我們先挨個分析這三種情況都需要如何操作,然後再給出實現程式碼。

        對於情況1:插入節點的父節點和其叔叔節點(祖父節點的另一個子節點)均為紅色的。此時,肯定存在祖父節點,但是不知道父節點是其左子節點還是右子節點,但是由於對稱性,我們只要討論出一邊的情況,另一種情況自然也與之對應。這裡考慮父節點是祖父節點的左子節點的情況,如下左圖所示:

           

        對於這種情況,我們要做的操作有:將當前節點(4)的父節點(5)和叔叔節點(8)塗黑,將祖父節點(7)塗紅,變成上右圖所示的情況。再將當前節點指向其祖父節點,再次從新的當前節點開始演算法(具體等下看下面的程式)。這樣上右圖就變成了情況2了。

        對於情況2:插入節點的父節點是紅色,叔叔節點是黑色,且插入節點是其父節點的右子節點。我們要做的操作有:將當前節點(7)的父節點(2)作為新的節點,以新的當前節點為支點做左旋操作。完成後如左下圖所示,這樣左下圖就變成情況3了。

     

        對於情況3:插入節點的父節點是紅色,叔叔節點是黑色,且插入節點是其父節點的左子節點。我們要做的操作有:將當前節點的父節點(7)塗黑,將祖父節點(11)塗紅,在祖父節點為支點做右旋操作。最後把根節點塗黑,整個紅-黑樹重新恢復了平衡,如右上圖所示。至此,插入操作完成!

        我們可以看出,如果是從情況1開始發生的,必然會走完情況2和3,也就是說這是一整個流程,當然咯,實際中可能不一定會從情況1發生,如果從情況2開始發生,那再走個情況3即可完成調整,如果直接只要調整情況3,那麼前兩種情況均不需要調整了。故變色和旋轉之間的先後關係可以表示為:變色->左旋->右旋。

        至此,我們完成了全部的插入操作。下面我們看看insertFixUp方法中的具體實現(可以結合上面的分析圖,更加利與理解):

private void insertFixUp(RBNode<T> node) {
	RBNode<T> parent, gparent; //定義父節點和祖父節點
	
	//需要修整的條件:父節點存在,且父節點的顏色是紅色
	while(((parent = parentOf(node)) != null) && isRed(parent)) {
		gparent = parentOf(parent);//獲得祖父節點
		
		//若父節點是祖父節點的左子節點,下面else與其相反
		if(parent == gparent.left) {				
			RBNode<T> uncle = gparent.right; //獲得叔叔節點
			
			//case1: 叔叔節點也是紅色
			if(uncle != null && isRed(uncle)) {
				setBlack(parent); //把父節點和叔叔節點塗黑
				setBlack(uncle);
				setRed(gparent); //把祖父節點塗紅
				node = gparent; //將位置放到祖父節點處
				continue; //繼續while,重新判斷
			}
			
			//case2: 叔叔節點是黑色,且當前節點是右子節點
			if(node == parent.right) {
				leftRotate(parent); //從父節點處左旋
				RBNode<T> tmp = parent; //然後將父節點和自己調換一下,為下面右旋做準備
				parent = node;
				node = tmp;
			}
			
			//case3: 叔叔節點是黑色,且當前節點是左子節點
			setBlack(parent);
			setRed(gparent);
			rightRotate(gparent);
		} else { //若父節點是祖父節點的右子節點,與上面的完全相反,本質一樣的
			RBNode<T> uncle = gparent.left;
			
			//case1: 叔叔節點也是紅色
			if(uncle != null & isRed(uncle)) {
				setBlack(parent);
				setBlack(uncle);
				setRed(gparent);
				node = gparent;
				continue;
			}
			
			//case2: 叔叔節點是黑色的,且當前節點是左子節點
			if(node == parent.left) {
				rightRotate(parent);
				RBNode<T> tmp = parent;
				parent = node;
				node = tmp;
			}
			
			//case3: 叔叔節點是黑色的,且當前節點是右子節點
			setBlack(parent);
			setRed(gparent);
			leftRotate(gparent);
		}
	}
	
	//將根節點設定為黑色
	setBlack(this.root);
}

5.刪除操作

        上面探討完了紅-黑樹的插入操作,接下來討論刪除,紅-黑樹的刪除和二叉查詢樹的刪除是一樣的,只不過刪除後多了個平衡的修復而已。我們先來回憶一下二叉搜尋樹的刪除(也可以直接閱讀這篇部落格:二叉搜尋樹):

        1. 如果待刪除節點沒有子節點,那麼直接刪掉即可;

        2. 如果待刪除節點只有一個子節點,那麼直接刪掉,並用其子節點去頂替它;

        3. 如果待刪除節點有兩個子節點,這種情況比較複雜:首選找出它的後繼節點,然後處理“後繼節點”和“被刪除節點的父節點”之間的關係,最後處理“後繼節點的子節點”和“被刪除節點的子節點”之間的關係。每一步中也會有不同的情況,我們結合下面程式碼的分析就能弄清楚,當然了,如果已經弄懂了二叉搜尋樹,那自然自然都能明白,這裡就不贅述了。

        我們來看一下刪除操作的程式碼及註釋:

/*********************** 刪除紅黑樹中的節點 **********************/
public void remove(T key) {
	RBNode<T> node;
	if((node = search(root, key)) != null)
		remove(node);
}
 
private void remove(RBNode<T> node) {
	RBNode<T> child, parent;
	boolean color;
	
	//1. 被刪除的節點“左右子節點都不為空”的情況
	if((node.left != null) && (node.right != null)) {
		//先找到被刪除節點的後繼節點,用它來取代被刪除節點的位置
		RBNode<T> replace = node;
		//  1). 獲取後繼節點
		replace = replace.right;
		while(replace.left != null) 
			replace = replace.left;
		
		//  2). 處理“後繼節點”和“被刪除節點的父節點”之間的關係
		if(parentOf(node) != null) { //要刪除的節點不是根節點
			if(node == parentOf(node).left) 
				parentOf(node).left = replace;
			else
				parentOf(node).right = replace;
		} else { //否則
			this.root = replace;
		}
		
		//  3). 處理“後繼節點的子節點”和“被刪除節點的子節點”之間的關係
		child = replace.right; //後繼節點肯定不存在左子節點!
		parent = parentOf(replace);
		color = colorOf(replace);//儲存後繼節點的顏色
		if(parent == node) { //後繼節點是被刪除節點的子節點
			parent = replace;
		} else { //否則
			if(child != null) 
				setParent(child, parent);
			parent.left = child;
			replace.right = node.right;
			setParent(node.right, replace);
		}
		replace.parent = node.parent;
		replace.color = node.color; //保持原來位置的顏色
		replace.left = node.left;
		node.left.parent = replace;
		
		if(color == BLACK) { //4. 如果移走的後繼節點顏色是黑色,重新修整紅黑樹
			removeFixUp(child, parent);//將後繼節點的child和parent傳進去
		}
		node = null;
		return;
	}
}

下面我們主要看看方法裡面最後的removeFixUp操作。因為remove後可能會導致樹的不平衡,removeFixUp方法裡主要是分情況討論,分析何時變色,何時左旋,何時右旋。我們同樣先從理論上分析具體的情況,然後再看removeFixUp方法的具體實現。

        從上面的程式碼中可以看出,刪除某個節點後,會用它的後繼節點來填上,並且後繼節點會設定為和刪除節點同樣的顏色,所以刪除節點的那個位置是不會破壞平衡的。可能破壞平衡的是後繼節點原來的位置,因為後繼節點拿走了,原來的位置結構改變了,這就會導致不平衡的出現。所以removeFixUp方法中傳入的引數也是後繼節點的子節點和父節點。

        為了方便下文的敘述,我們現在約定:後繼節點的子節點稱為“當前節點”。

        刪除操作後,如果當前節點是黑色的根節點,那麼不用任何操作,因為並沒有破壞樹的平衡性,即沒有違背紅-黑樹的規則,這很好理解。如果當前節點是紅色的,說明剛剛移走的後繼節點是黑色的,那麼不管後繼節點的父節點是啥顏色,我們只要將當前節點塗黑就可以了,紅-黑樹的平衡性就可以恢復。但是如果遇到以下四種情況,我們就需要通過變色或旋轉來恢復紅-黑樹的平衡了。

        1. 當前節點是黑色的,且兄弟節點是紅色的(那麼父節點和兄弟節點的子節點肯定是黑色的);

        2. 當前節點是黑色的,且兄弟節點是黑色的,且兄弟節點的兩個子節點均為黑色的;

        3. 當前節點是黑色的,且兄弟節點是黑色的,且兄弟節點的左子節點是紅色,右子節點時黑色的;

        4. 當前節點是黑色的,且兄弟節點是黑色的,且兄弟節點的右子節點是紅色,左子節點任意顏色。

        以上四種情況中,我們可以看出2,3,4其實是“當前節點是黑色的,且兄弟節點是黑色的”的三種子集,等會在程式中可以體現出來。現在我們假設當前節點是左子節點(當然也可能是右子節點,跟左子節點相反即可,我們討論一邊就可以了),分別解決上面四種情況:

        對於情況1:當前節點是黑色的,且兄弟節點是紅色的(那麼父節點和兄弟節點的子節點肯定是黑色的)。如左下圖所示:A節點表示當前節點。針對這種情況,我們要做的操作有:將父節點(B)塗紅,將兄弟節點(D)塗黑,然後將當前節點(A)的父節點(B)作為支點左旋,然後當前節點的兄弟節點就變成黑色的情況了(自然就轉換成情況2,3,4的公有特徵了),如右下圖所示:

  

        對於情況2:當前節點是黑色的,且兄弟節點是黑色的,且兄弟節點的兩個子節點均為黑色的。如左下圖所示,A表示當前節點。針對這種情況,我們要做的操作有:將兄弟節點(D)塗紅,將當前節點指向其父節點(B),將其父節點指向當前節點的祖父節點,繼續新的演算法(具體見下面的程式),不需要旋轉。這樣變成了右下圖所示的情況:

   

        對於情況3:當前節點是黑色的,且兄弟節點是黑色的,且兄弟節點的左子節點是紅色,右子節點時黑色的。如左下圖所示,A是當前節點。針對這種情況,我們要做的操作有:把當前節點的兄弟節點(D)塗紅,把兄弟節點的左子節點(C)塗黑,然後以兄弟節點作為支點做右旋操作。然後兄弟節點就變成黑色的,且兄弟節點的右子節點變成紅色的情況(情況4)了。如右下圖:

  

        對於情況4:當前節點是黑色的,且兄弟節點是黑色的,且兄弟節點的右子節點是紅色,左子節點任意顏色。如左下圖所示:A為當前節點,針對這種情況,我們要做的操作有:把兄弟節點(D)塗成父節點的顏色,再把父節點(B)塗黑,把兄弟節點的右子節點(E)塗黑,然後以當前節點的父節點為支點做左旋操作。至此,刪除修復演算法就結束了,最後將根節點塗黑即可。

  

        我們可以看出,如果是從情況1開始發生的,可能情況2,3,4中的一種:如果是情況2,就不可能再出現3和4;如果是情況3,必然會導致情況4的出現;如果2和3都不是,那必然是4。當然咯,實際中可能不一定會從情況1發生,這要看具體情況了。

       至此,我們完成了全部的刪除操作。下面我們看看removeFixUp方法中的具體實現(可以結合上面的分析圖,更加利與理解):

//node表示待修正的節點,即後繼節點的子節點(因為後繼節點被挪到刪除節點的位置去了)
private void removeFixUp(RBNode<T> node, RBNode<T> parent) {
	RBNode<T> other;
	
	while((node == null || isBlack(node)) && (node != this.root)) {
		if(parent.left == node) { //node是左子節點,下面else與這裡的剛好相反
			other = parent.right; //node的兄弟節點
			if(isRed(other)) { //case1: node的兄弟節點other是紅色的
				setBlack(other);
				setRed(parent);
				leftRotate(parent);
				other = parent.right;
			}
			
			//case2: node的兄弟節點other是黑色的,且other的兩個子節點也都是黑色的
			if((other.left == null || isBlack(other.left)) && 
					(other.right == null || isBlack(other.right))) {
				setRed(other);
				node = parent;
				parent = parentOf(node);
			} else {
				//case3: node的兄弟節點other是黑色的,且other的左子節點是紅色,右子節點是黑色
				if(other.right == null || isBlack(other.right)) {
					setBlack(other.left);
					setRed(other);
					rightRotate(other);
					other = parent.right;
				}
				
				//case4: node的兄弟節點other是黑色的,且other的右子節點是紅色,左子節點任意顏色
				setColor(other, colorOf(parent));
				setBlack(parent);
				setBlack(other.right);
				leftRotate(parent);
				node = this.root;
				break;
			}
		} else { //與上面的對稱
			other = parent.left;
			
            if (isRed(other)) {
                // Case 1: node的兄弟other是紅色的  
                setBlack(other);
                setRed(parent);
                rightRotate(parent);
                other = parent.left;
            }
 
            if ((other.left==null || isBlack(other.left)) &&
                (other.right==null || isBlack(other.right))) {
                // Case 2: node的兄弟other是黑色,且other的倆個子節點都是黑色的  
                setRed(other);
                node = parent;
                parent = parentOf(node);
            } else {
 
                if (other.left==null || isBlack(other.left)) {
                    // Case 3: node的兄弟other是黑色的,並且other的左子節點是紅色,右子節點為黑色。  
                    setBlack(other.right);
                    setRed(other);
                    leftRotate(other);
                    other = parent.left;
                }
 
                // Case 4: node的兄弟other是黑色的;並且other的左子節點是紅色的,右子節點任意顏色
                setColor(other, colorOf(parent));
                setBlack(parent);
                setBlack(other.left);
                rightRotate(parent);
                node = this.root;
                break;
            }
		}
	}
	if (node!=null)
        setBlack(node);
}

4.完整原始碼

        終於分析完了插入和刪除操作的所有東西。另外,紅-黑樹還有一些其他操作,比如:查詢特定值、遍歷、返回最值、銷燬樹等操作我將放到原始碼中給大家呈現出來,詳見下面紅-黑樹的完整程式碼。

package tree;
/**
 * @description implementation of Red-Black Tree by Java
 * @author eson_15
 * @param <T>
 * @date 2016-4-18 19:27:28
 */
public class RBTree<T extends Comparable<T>> {
	private RBNode<T> root; //根節點
	private static final boolean RED = false; //定義紅黑樹標誌
	private static final boolean BLACK = true;
	
	//內部類:節點類
	public class RBNode<T extends Comparable<T>>{
		boolean color; //顏色
		T key; //關鍵字(鍵值)
		RBNode<T> left; //左子節點
		RBNode<T> right; //右子節點
		RBNode<T> parent; //父節點
		
		public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) {
			this.key = key;
			this.color = color;
			this.parent = parent;
			this.left = left;
			this.right = right;
		}
		
		public T getKey() {
			return key;
		}
		
		public String toString() {
			return "" + key + (this.color == RED? "R" : "B");
		}
	}
	
	public RBTree() {
		root = null;
	}
	
	public RBNode<T> parentOf(RBNode<T> node) { //獲得父節點
		return node != null? node.parent : null;
	}
	
	public void setParent(RBNode<T> node, RBNode<T> parent) { //設定父節點
		if(node != null) 
			node.parent = parent;
	}
	
	public boolean colorOf(RBNode<T> node) { //獲得節點的顏色
		return node != null? node.color : BLACK;
	}
	
	public boolean isRed(RBNode<T> node) { //判斷節點的顏色
		return (node != null)&&(node.color == RED)? true : false;
	}
	
	public boolean isBlack(RBNode<T> node) {
		return !isRed(node);
	}
	
	public void setRed(RBNode<T> node) { //設定節點的顏色
		if(node != null) 
			node.color = RED;
	}
	
	public void setBlack(RBNode<T> node) {
		if(node != null) {
			node.color = BLACK;
		}
	}
	 
	public void setColor(RBNode<T> node, boolean color) {
		if(node != null)
			node.color = color;
	}
	 
	/***************** 前序遍歷紅黑樹 *********************/
	public void preOrder() {
		preOrder(root);
	}
 
	private void preOrder(RBNode<T> tree) {
		if(tree != null) {
			System.out.print(tree.key + " ");
			preOrder(tree.left);
			preOrder(tree.right);
		}
	}
	 
	/***************** 中序遍歷紅黑樹 *********************/
	public void inOrder() {
		inOrder(root);
	}
 
	private void inOrder(RBNode<T> tree) {
		if(tree != null) {
			 preOrder(tree.left);
			 System.out.print(tree.key + " ");
			 preOrder(tree.right);
		 }
	}
	
	/***************** 後序遍歷紅黑樹 *********************/
	public void postOrder() {
		postOrder(root);
	}
 
	private void postOrder(RBNode<T> tree) {
		if(tree != null) {
			 preOrder(tree.left);
			 preOrder(tree.right);
			 System.out.print(tree.key + " ");
		 }
	}
	
	/**************** 查詢紅黑樹中鍵值為key的節點 ***************/
	public RBNode<T> search(T key) {
		return search(root, key);
//		return search2(root, key); //使用遞迴的方法,本質一樣的
	}
 
	private RBNode<T> search(RBNode<T> x, T key) {
		while(x != null) {
			int cmp = key.compareTo(x.key);
			if(cmp < 0) 
				x = x.left;
			else if(cmp > 0) 
				x = x.right;
			else 
				return x;
		}
		return x;
	}
	//使用遞迴
	private RBNode<T> search2(RBNode<T> x, T key) {
		if(x == null)
			return x;
		int cmp = key.compareTo(x.key);
		if(cmp < 0)
			return search2(x.left, key);
		else if(cmp > 0) 
			return search2(x.right, key);
		else
			return x;
	}
	
	/**************** 查詢最小節點的值  **********************/
	public T minValue() {
		RBNode<T> node = minNode(root);
		if(node != null)
			return node.key;
		return null;
	}
 
	private RBNode<T> minNode(RBNode<T> tree) {
		if(tree == null)
			return null;
		while(tree.left != null) {
			tree = tree.left;
		}
		return tree;
	}
	
	/******************** 查詢最大節點的值 *******************/
	public T maxValue() {
		RBNode<T> node = maxNode(root);
		if(node != null)
			return node.key;
		return null;
	}
 
	private RBNode<T> maxNode(RBNode<T> tree) {
		if(tree == null)
			return null;
		while(tree.right != null)
			tree = tree.right;
		return tree;
	}
	
	/********* 查詢節點x的後繼節點,即大於節點x的最小節點 ***********/
	public RBNode<T> successor(RBNode<T> x) {
		//如果x有右子節點,那麼後繼節點為“以右子節點為根的子樹的最小節點”
		if(x.right != null) 
			return minNode(x.right);
		//如果x沒有右子節點,會出現以下兩種情況:
		//1. x是其父節點的左子節點,則x的後繼節點為它的父節點
		//2. x是其父節點的右子節點,則先查詢x的父節點p,然後對p再次進行這兩個條件的判斷
		RBNode<T> p = x.parent;
		while((p != null) && (x == p.right)) { //對應情況2
			x = p;
			p = x.parent;
		}
		return p; //對應情況1
		
	}
	
	/********* 查詢節點x的前驅節點,即小於節點x的最大節點 ************/
	public RBNode<T> predecessor(RBNode<T> x) {
		//如果x有左子節點,那麼前驅結點為“左子節點為根的子樹的最大節點”
		if(x.left != null) 
			return maxNode(x.left);
		//如果x沒有左子節點,會出現以下兩種情況:
		//1. x是其父節點的右子節點,則x的前驅節點是它的父節點
		//2. x是其父節點的左子節點,則先查詢x的父節點p,然後對p再次進行這兩個條件的判斷
		RBNode<T> p = x.parent;
		while((p != null) && (x == p.left)) { //對應情況2
			x = p;
			p = x.parent;
		}
		return p; //對應情況1
	}
	
	/*************對紅黑樹節點x進行左旋操作 ******************/
	/*
	 * 左旋示意圖:對節點x進行左旋
	 *     p                       p
	 *    /                       /
	 *   x                       y
	 *  / \                     / \
	 * lx  y      ----->       x  ry
	 *    / \                 / \
	 *   ly ry               lx ly
	 * 左旋做了三件事:
	 * 1. 將y的左子節點賦給x的右子節點,並將x賦給y左子節點的父節點(y左子節點非空時)
	 * 2. 將x的父節點p(非空時)賦給y的父節點,同時更新p的子節點為y(左或右)
	 * 3. 將y的左子節點設為x,將x的父節點設為y
	 */
	private void leftRotate(RBNode<T> x) {
		//1. 將y的左子節點賦給x的右子節點,並將x賦給y左子節點的父節點(y左子節點非空時)
		RBNode<T> y = x.right;
		x.right = y.left;
		
		if(y.left != null) 
			y.left.parent = x;
		
		//2. 將x的父節點p(非空時)賦給y的父節點,同時更新p的子節點為y(左或右)
		y.parent = x.parent;
		
		if(x.parent == null) {
			this.root = y; //如果x的父節點為空,則將y設為父節點
		} else {
			if(x == x.parent.left) //如果x是左子節點
				x.parent.left = y; //則也將y設為左子節點
			else
				x.parent.right = y;//否則將y設為右子節點
		}
		
		//3. 將y的左子節點設為x,將x的父節點設為y
		y.left = x;
		x.parent = y;		
	}
	
	/*************對紅黑樹節點y進行右旋操作 ******************/
	/*
	 * 左旋示意圖:對節點y進行右旋
	 *        p                   p
	 *       /                   /
	 *      y                   x
	 *     / \                 / \
	 *    x  ry   ----->      lx  y
	 *   / \                     / \
	 * lx  rx                   rx ry
	 * 右旋做了三件事:
	 * 1. 將x的右子節點賦給y的左子節點,並將y賦給x右子節點的父節點(x右子節點非空時)
	 * 2. 將y的父節點p(非空時)賦給x的父節點,同時更新p的子節點為x(左或右)
	 * 3. 將x的右子節點設為y,將y的父節點設為x
	 */
	private void rightRotate(RBNode<T> y) {
		//1. 將y的左子節點賦給x的右子節點,並將x賦給y左子節點的父節點(y左子節點非空時)
		RBNode<T> x = y.left;
		y.left = x.right;
		
		if(x.right != null) 
			x.right.parent = y;
		
		//2. 將x的父節點p(非空時)賦給y的父節點,同時更新p的子節點為y(左或右)
		x.parent = y.parent;
		
		if(y.parent == null) {
			this.root = x; //如果x的父節點為空,則將y設為父節點
		} else {
			if(y == y.parent.right) //如果x是左子節點
				y.parent.right = x; //則也將y設為左子節點
			else
				y.parent.left = x;//否則將y設為右子節點
		}
		
		//3. 將y的左子節點設為x,將x的父節點設為y
		x.right = y;
		y.parent = x;		
	}
	
	/*********************** 向紅黑樹中插入節點 **********************/
	public void insert(T key) {
		RBNode<T> node = new RBNode<T>(key, RED, null, null, null);
		if(node != null) 
			insert(node);
	}
	
	//將節點插入到紅黑樹中,這個過程與二叉搜尋樹是一樣的
	private void insert(RBNode<T> node) {
		RBNode<T> current = null; //表示最後node的父節點
		RBNode<T> x = this.root; //用來向下搜尋用的
		
		//1. 找到插入的位置
		while(x != null) {
			current = x;
			int cmp = node.key.compareTo(x.key);
			if(cmp < 0) 
				x = x.left;
			else
				x = x.right;
		}
		node.parent = current; //找到了位置,將當前current作為node的父節點
		
		//2. 接下來判斷node是插在左子節點還是右子節點
		if(current != null) {
			int cmp = node.key.compareTo(current.key);
			if(cmp < 0)
				current.left = node;
			else
				current.right = node;
		} else {
			this.root = node;
		}
		
		//3. 將它重新修整為一顆紅黑樹
		insertFixUp(node);
	}
 
	private void insertFixUp(RBNode<T> node) {
		RBNode<T> parent, gparent; //定義父節點和祖父節點
		
		//需要修整的條件:父節點存在,且父節點的顏色是紅色
		while(((parent = parentOf(node)) != null) && isRed(parent)) {
			gparent = parentOf(parent);//獲得祖父節點
			
			//若父節點是祖父節點的左子節點,下面else與其相反
			if(parent == gparent.left) {				
				RBNode<T> uncle = gparent.right; //獲得叔叔節點
				
				//case1: 叔叔節點也是紅色
				if(uncle != null && isRed(uncle)) {
					setBlack(parent); //把父節點和叔叔節點塗黑
					setBlack(uncle);
					setRed(gparent); //把祖父節點塗紅
					node = gparent; //將位置放到祖父節點處
					continue; //繼續while,重新判斷
				}
				
				//case2: 叔叔節點是黑色,且當前節點是右子節點
				if(node == parent.right) {
					leftRotate(parent); //從父節點處左旋
					RBNode<T> tmp = parent; //然後將父節點和自己調換一下,為下面右旋做準備
					parent = node;
					node = tmp;
				}
				
				//case3: 叔叔節點是黑色,且當前節點是左子節點
				setBlack(parent);
				setRed(gparent);
				rightRotate(gparent);
			} else { //若父節點是祖父節點的右子節點,與上面的完全相反,本質一樣的
				RBNode<T> uncle = gparent.left;
				
				//case1: 叔叔節點也是紅色
				if(uncle != null & isRed(uncle)) {
					setBlack(parent);
					setBlack(uncle);
					setRed(gparent);
					node = gparent;
					continue;
				}
				
				//case2: 叔叔節點是黑色的,且當前節點是左子節點
				if(node == parent.left) {
					rightRotate(parent);
					RBNode<T> tmp = parent;
					parent = node;
					node = tmp;
				}
				
				//case3: 叔叔節點是黑色的,且當前節點是右子節點
				setBlack(parent);
				setRed(gparent);
				leftRotate(gparent);
			}
		}
		
		//將根節點設定為黑色
		setBlack(this.root);
	}
	
	/*********************** 刪除紅黑樹中的節點 **********************/
	public void remove(T key) {
		RBNode<T> node;
		if((node = search(root, key)) != null)
			remove(node);
	}
	
	private void remove(RBNode<T> node) {
		RBNode<T> child, parent;
		boolean color;
		
		//1. 被刪除的節點“左右子節點都不為空”的情況
		if((node.left != null) && (node.right != null)) {
			//先找到被刪除節點的後繼節點,用它來取代被刪除節點的位置
			RBNode<T> replace = node;
			//  1). 獲取後繼節點
			replace = replace.right;
			while(replace.left != null) 
				replace = replace.left;
			
			//  2). 處理“後繼節點”和“被刪除節點的父節點”之間的關係
			if(parentOf(node) != null) { //要刪除的節點不是根節點
				if(node == parentOf(node).left) 
					parentOf(node).left = replace;
				else
					parentOf(node).right = replace;
			} else { //否則
				this.root = replace;
			}
			
			//  3). 處理“後繼節點的子節點”和“被刪除節點的子節點”之間的關係
			child = replace.right; //後繼節點肯定不存在左子節點!
			parent = parentOf(replace);
			color = colorOf(replace);//儲存後繼節點的顏色
			if(parent == node) { //後繼節點是被刪除節點的子節點
				parent = replace;
			} else { //否則
				if(child != null) 
					setParent(child, parent);
				parent.left = child;
				replace.right = node.right;
				setParent(node.right, replace);
			}
			replace.parent = node.parent;
			replace.color = node.color; //保持原來位置的顏色
			replace.left = node.left;
			node.left.parent = replace;
			
			if(color == BLACK) { //4. 如果移走的後繼節點顏色是黑色,重新修整紅黑樹
				removeFixUp(child, parent);//將後繼節點的child和parent傳進去
			}
			node = null;
			return;
		}
	}
	//node表示待修正的節點,即後繼節點的子節點(因為後繼節點被挪到刪除節點的位置去了)
	private void removeFixUp(RBNode<T> node, RBNode<T> parent) {
		RBNode<T> other;
		
		while((node == null || isBlack(node)) && (node != this.root)) {
			if(parent.left == node) { //node是左子節點,下面else與這裡的剛好相反
				other = parent.right; //node的兄弟節點
				if(isRed(other)) { //case1: node的兄弟節點other是紅色的
					setBlack(other);
					setRed(parent);
					leftRotate(parent);
					other = parent.right;
				}
				
				//case2: node的兄弟節點other是黑色的,且other的兩個子節點也都是黑色的
				if((other.left == null || isBlack(other.left)) && 
						(other.right == null || isBlack(other.right))) {
					setRed(other);
					node = parent;
					parent = parentOf(node);
				} else {
					//case3: node的兄弟節點other是黑色的,且other的左子節點是紅色,右子節點是黑色
					if(other.right == null || isBlack(other.right)) {
						setBlack(other.left);
						setRed(other);
						rightRotate(other);
						other = parent.right;
					}
					
					//case4: node的兄弟節點other是黑色的,且other的右子節點是紅色,左子節點任意顏色
					setColor(other, colorOf(parent));
					setBlack(parent);
					setBlack(other.right);
					leftRotate(parent);
					node = this.root;
					break;
				}
			} else { //與上面的對稱
				other = parent.left;
				
	            if (isRed(other)) {
	                // Case 1: node的兄弟other是紅色的  
	                setBlack(other);
	                setRed(parent);
	                rightRotate(parent);
	                other = parent.left;
	            }
 
	            if ((other.left==null || isBlack(other.left)) &&
	                (other.right==null || isBlack(other.right))) {
	                // Case 2: node的兄弟other是黑色,且other的倆個子節點都是黑色的  
	                setRed(other);
	                node = parent;
	                parent = parentOf(node);
	            } else {
 
	                if (other.left==null || isBlack(other.left)) {
	                    // Case 3: node的兄弟other是黑色的,並且other的左子節點是紅色,右子節點為黑色。  
	                    setBlack(other.right);
	                    setRed(other);
	                    leftRotate(other);
	                    other = parent.left;
	                }
 
	                // Case 4: node的兄弟other是黑色的;並且other的左子節點是紅色的,右子節點任意顏色
	                setColor(other, colorOf(parent));
	                setBlack(parent);
	                setBlack(other.left);
	                rightRotate(parent);
	                node = this.root;
	                break;
	            }
			}
		}
		if (node!=null)
	        setBlack(node);
	}
	
	/****************** 銷燬紅黑樹 *********************/
	public void clear() {
		destroy(root);
		root = null;
	}
 
	private void destroy(RBNode<T> tree) {
		if(tree == null) 
			return;
		if(tree.left != null) 
			destroy(tree.left);
		if(tree.right != null) 
			destroy(tree.right);
		tree = null;
	}
 
	/******************* 列印紅黑樹 *********************/
	public void print() {
		if(root != null) {
			print(root, root.key, 0);
		}
	}
	/*
	 * key---節點的鍵值
	 * direction--- 0:表示該節點是根節點
	 *              1:表示該節點是它的父節點的左子節點
	 *              2:表示該節點是它的父節點的右子節點
	 */
	private void print(RBNode<T> tree, T key, int direction) {
		if(tree != null) {
			if(0 == direction) 
				System.out.printf("%2d(B) is root\n", tree.key);
			else
				System.out.printf("%2d(%s) is %2d's %6s child\n", 
						tree.key, isRed(tree)?"R":"b", key, direction == 1?"right":"left");
			print(tree.left, tree.key, -1);
			print(tree.right, tree.key, 1);
		}
	}
}
 

轉載地址: