紅黑樹原理 新增,刪除,打印


模板圖

二叉查找樹

由於紅黑樹本質上就是一棵二叉查找樹,所以在了解紅黑樹之前,咱們先來看下二叉查找樹。

二叉查找樹(Binary Search Tree),也稱有序二叉樹(ordered binary tree),排序二叉樹(sorted binary tree),是指一棵空樹或者具有下列性質的二叉樹:

  • 若任意結點的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
  • 若任意結點的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
  • 任意結點的左、右子樹也分別為二叉查找樹。
  • 沒有鍵值相等的結點(no duplicate nodes)。

因為,一棵由n個結點,隨機構造的二叉查找樹的高度為\(\log_2n\),所以順理成章,一般操作的執行時間為\(O(\log_2n)\)

但二叉樹若退化成了一棵具有n個結點的線性鏈后,則此些操作最壞情況運行時間為O(n)。后面我們會看到一種基於二叉查找樹-紅黑樹,它通過一些性質使得樹相對平衡,使得最終查找、插入、刪除的時間復雜度最壞情況下依然為\(O(\log_2n)\)

紅黑樹

前面我們已經說過,紅黑樹,本質上來說就是一棵二叉查找樹,但它在二叉查找樹的基礎上增加了着色和相關的性質使得紅黑樹相對平衡,從而保證了紅黑樹的查找、插入、刪除的時間復雜度最壞為\(O(\log_2n)\),理論上,極端的情況下可以出現RBTree的高度達到\(2*\log_2n\),但實際上很難遇到

但它是如何保證一棵n個結點的紅黑樹的高度始終保持在h =\(\log_2n\)的呢?這就引出了紅黑樹的5條性質:

1)每個結點要么是紅的,要么是黑的。  
2)根結點是黑的。  
3)每個葉結點(葉結點即指樹尾端NIL指針或NULL結點)是黑的。  
4)如果一個結點是紅的,那么它的兩個子節點都是黑的。  
5)對於任一結點而言,其到葉結點樹尾端NIL指針的每一條路徑都包含相同數目的黑結點。  

性質5保證了紅色節點要么有兩個黑色子節點,要么就是由兩個NIL子節點,不能一個黑色節點,一個NIL節點

性質4和性質5可保證任意節點到其每個葉子節點路徑最長不會超過最短路徑的2倍

正是紅黑樹的這5條性質,使得一棵n個結點是紅黑樹始終保持了\(\log_2n\)的高度,從而也就解釋了上面我們所說的“紅黑樹的查找、插入、刪除的時間復雜度最壞為\(O(\log_2n)\)這一結論的原因。

如下圖所示,即是一顆紅黑樹(下圖引自wikipedia:http://t.cn/hgvH1l):

img

上文中我們所說的 "葉結點" 或"NULL結點",它不包含數據而只充當樹在此結束的指示,這些結點以及它們的父結點,在繪圖中都會經常被省略。

紅黑樹保證了最壞情形下在 \(O(\log_2n)\) 時間復雜度內完成查找、插入及刪除操作;因此紅黑樹可用於很多場景,比如在 Java 的集合框架 (HashMap、TreeMap、TreeSet)、Nginx 的 Timer 管理、Linux 虛擬內存管理以及 C++ 的 STL 等等都能看到它的應用。

為什么還要紅黑樹?

二叉查找樹並非平衡樹,它只限制了左右子樹與根點之間的大小關系,只有在平衡二叉查找樹時,其時間復雜度才能達到 **\(O(\log_2n)\) **,並且在極端情況下它甚至會退化成鏈表;

如下所示在新創建的二叉查找樹上依次添加數據 1、2、3、4、5、6、7、8、9、10 節點,此二叉查找樹就退化成了鏈表,增刪查性能也退化到了O(n),所以為了避免這種情況,就出現了 AVL 及紅黑樹這種能自平衡的二叉查找樹;

AVL 樹是嚴格的平衡二叉樹,必須滿足所有節點的左右子樹高度差不超過 1;而紅黑樹是相對黑色節點平衡的二叉樹,

AVL樹結構比紅黑樹更加平衡,這也表示其增刪節點時也更容易失衡,失衡就需要糾正,增刪節點時開銷會比紅黑樹大。但不可否認的是AVL樹搜索的效率是非常穩定的。

因此在大量數據需要插入或者刪除時,AVL需要平衡調整的頻率會更高。因此,紅黑樹在需要大量插入和刪除節點的場景下,效率更高。自然,由於AVL高度平衡,因此AVL的Search效率略高。

紅黑樹不是高度平衡樹,但平衡的效果已經很好了。所以,可以認為紅黑樹是一種折中的選擇,其綜合性能較好

平衡性的修正

我們把正在處理(遍歷)的結點叫做當前結點,如圖2中的D,它的父親叫做父結點,它的父親的另外一個子結點叫做兄弟結點,父親的父親叫做祖父結點。

前面講到紅黑樹能自平衡,它靠的是什么?三種操作:左旋、右旋和變色。

  • 左旋:以某個結點作為支點(旋轉結點),其右子結點變為旋轉結點的父結點,右子結點的左子結點變為旋轉結點的右子結點,左子結點保持不變。

    	 * 左旋示意圖(對節點x進行左旋):
    	 *      px                              px
    	 *     /                               /
    	 *    x                               y
    	 *   /  \      --(左旋)-.           / \                
    	 *  lx   y                          x  ry
    	 *     /   \                       /  \
    	 *    ly   ry                     lx  ly
    
  • 右旋:以某個結點作為支點(旋轉結點),其左子結點變為旋轉結點的父結點,左子結點的右子結點變為旋轉結點的左子結點,右子結點保持不變。

    	 * 右旋示意圖(對節點y進行左旋):
    	 *            py                               py
    	 *           /                                /
    	 *          y                                x
    	 *         /  \      --(右旋)-.            /  \                     
    	 *        x   ry                           lx   y
    	 *       / \                                   / \                   
    	 *      lx  rx                                rx  ry
    
  • 變色:結點的顏色由紅變黑或由黑變紅。

假設規定

新增節點用

設根節點為R節點

設祖父節點為G節點

設父節點為P節點

設叔叔節點為S節點 (sibling)

設插入節點為A節點

父子左左關系為:P節點為G節點的左孩子,A節點為P節點左孩子

父子左右關系為:P節點為G節點的左孩子,A節點為P節點右孩子

父子右右關系為:P節點為G節點的右孩子,A節點為P節點右孩子

父子右左關系為:P節點為G節點的右孩子,A節點為P節點左孩子

刪除節點用

設刪除節點為D節點

設刪除節點D的后繼節點為Y節點

設真正被刪除的節點為G節點(如果被刪除節點D有子節點,那么G為D的后繼節點Y,反之就為D本身)

設真正被刪除節點的父節點為R節點

設真正被刪除節點的相鄰節點(叔叔節點)為S節點

設真正被刪除節點的右子節點(如果存在的話)為X節點

插入操作

紅黑樹的插入過程和二叉查找樹插入過程基本類似,不同的地方在於,紅黑樹插入新節點后,需要進行平衡調整,以滿足紅黑樹的性質。

默認插入節點的顏色

性質1規定紅黑樹節點的顏色要么是紅色要么是黑色,那么在插入新節點時,這個節點應該是紅色還是黑色呢?答案是紅色,

因為紅黑樹是完美平衡黑色樹,如果插入節點默認為黑色,那么調整起來比較麻煩。

平衡修正流程

  • 判斷P節點是否為紅色,如果為黑色,只要設置根節點為黑色即可,A節點默認為紅色。
  • 如果P節點為紅色S節點
    • 判斷P節點為G節點的左孩子,還是右孩子,找出對應的S節點
    • 找出對應的S節點,判斷S節點是否為紅色,如果是 ,那么僅需變色(S,P變黑,G變紅),並以G節點為A節點遞歸循環
    • 將父子關系為左右,右左的全部旋轉為左左,右右父子關系。 (旋轉是以P節點旋轉,並且旋轉以后以新的子節點為插入節點)
    • P節點黑色,G節點紅色,並以G節點旋轉(左左關系右旋轉,右右關系左旋轉)
    • 記住每次都要設置根節點為黑色

刪除操作

二叉樹刪除節點

將紅黑樹當成一顆二叉樹,將節點刪除

  • 被刪除節點沒有子節點,直接刪除
  • 被刪除節點只有一個子節點,這個子節點直接替換其父節點
  • 被刪除節點有兩個子節點,查找被刪除節點的后繼節點(后繼節點只能有一個右子節點,或者沒有任何子節點),后繼節點替換被刪除的節點,鏈接關系以及顏色都采用被刪除節點的。(相當於只換了值,但是如果后繼節點有子節點,需要交給后繼節點的替換前的父節點,就替換原先后繼節點的位置,並且不變色)

平衡修正流程

后繼節點就是實際上刪除的節點

  • 判斷真正被刪除節點G的顏色,如果為紅色,直接跳過,不需要修復平衡
  • 如果真正被刪除節點G為黑色
    • 相鄰節點S為紅色
    • 相鄰節點S為黑色(Case1)
      • S節點的兩個子節點為黑色 (Case2)
      • S節點的右節點為黑色,左節點為紅色 (Case3)
      • S節點的右節點為紅色,左節點不管 (Case4)

Case1

在上述步驟中,並沒有更改current之內存位置和顏色,current仍為黑色。不過其sibling必定會變成黑色,因此將進入Case2、Case3或Case4。

Case2

Case3

經過以上修正步驟,sibling之rightchild成為紅色,便進入Case4。

Case4

若sibling為黑色,並且sibling之rightchild為紅色,修正的方法如下

  • 將sibling塗成current之parent的顏色:
    • 若node(C)是紅色,則將node(E)塗成紅色;
    • 若node(C)是黑色,則將node(E)塗成黑色;
  • 將parent塗成黑色:node(C)塗成黑色;
  • 將sibling之rightchild塗成黑色:node(F)塗成黑色;
  • 對parent進行Left Rotation:對node(C)做Left Rotation;
  • 將current移至root,把root塗黑。
    (注意:圖五(d)之node(E)未必是RBT之root。)

完整范例

接着以一個簡單的范例操作上述四種Case的修正方法。

Case3->Case4

若考慮刪除node(19),由於node(19)是黑色,需要修正。
接着判斷,node(19)的child(為黑色的NIL)之sibling:node(27)為黑色,且sibling之rightchild為黑色,符合Case3的描述,因此利用Case3之修正方法

  • siblingleftchild塗成黑色:node(24)塗成黑色;
  • sibling塗成紅色:node(27)塗成紅色;
  • sibling進行Right Rotation:對node(27)進行Right Rotation;
  • sibling移至current->parentrightchild:將sibling移至node(24);

接着進入Case4:subling為黑色,而且siblingrightchild為紅色,進行修正:

  • sibling塗成currentparent的顏色:node(22)是黑色,則將node(24)塗成黑色;
  • parent塗成黑色:node(22)塗成黑色;
  • siblingrightchild塗成黑色:node(27)塗成黑色;
  • parent進行Left Rotation:對node(22)做Left Rotation;
  • current移至root,把root塗黑。

如此一來便再次滿足RBT之特征限制

Case4

再考慮刪除黑色的node(45),判斷:node(45)的child(為黑色的NIL)之sibling:node(52)為黑色,且siblingrightchild:node(55)為紅色,符合Case4的描述,並利用Case4方法修正

  • sibling塗成currentparent的顏色:node(48)是紅色,則將node(52)塗成紅色;
  • parent塗成黑色:node(48)塗成黑色;
  • siblingrightchild塗成黑色:node(55)塗成黑色;
  • parent進行Left Rotation:對node(48)做Left Rotation;
  • current移至root,把root塗黑。

如此一來便再次滿足RBT之特征限制,

Case1->Case4

接着考慮刪除黑色的node(39),判斷:node(39)的child(為黑色的NIL)之sibling:node(52)為紅色,符合Case1之描述,便利用Case1之方法,調整成Case4

Case1調整:

  • sibling塗成黑色:node(52)塗成黑色;
  • currentparent塗成紅色:node(41)塗成紅色;
  • currentparent做Left Rotation:對node(41)做Left Rotation;
  • sibling移動到current->parentrightchild:將sibling移動至node(48);

再利用Case4的方法修正,便能滿足RBT之特征,

Case2

若要刪除黑色的node(7),由於node(7)的child之sibling:node(10)為黑色,且具有兩個黑色的child(都是NIL),符合Case2的情況,便修正如下

  • sibling塗成紅色:node(10)塗成紅色;
  • current移至currnetparentcurrent移至node(9);
  • 若新的current:node(9)為紅色,即跳出回圈,並將current塗黑。

經修正后,便符合RBT之特征

情況0:當前為紅色或當前為根

最后,若要刪除黑色的node(3)呢?由於node(3)的child:node(1)為紅色,並不需要考慮到Case1( sibling為紅色,兩個child為黑色),只要將node(1)塗黑即可

雙黑問題

雙黑是指刪除操作中替換節點與刪除節點均為黑色的情況,雙黑標記表明了當前樹違背了黑色節點數目一致的原則,需要進行修復。修復雙黑就是為了保證紅黑樹滿足上述合法性的操作。

源碼

package com.qhong.dataStructures.RBTreeDemo;

import com.google.common.collect.Lists;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Java 語言: 紅黑樹
 *
 * @author skywang
 * @date 2013/11/07
 */
public class RBTree<T extends Comparable<T>> {

	private static final boolean RED = false;
	private static final boolean BLACK = true;
	private int size;
	private RBTNode<T> mRoot;    // 根結點

	public RBTree() {
		mRoot = null;
		size = 1;
	}

	private RBTNode<T> parentOf(RBTNode<T> node) {
		return node != null ? node.parent : null;
	}

	private boolean colorOf(RBTNode<T> node) {
		return node != null ? node.color : BLACK;
	}

	private boolean isRed(RBTNode<T> node) {
		return (node != null) && (node.color == RED);
	}

	private boolean isBlack(RBTNode<T> node) {
		return !isRed(node);
	}

	private void setBlack(RBTNode<T> node) {
		if (node != null) {
			node.color = BLACK;
		}
	}

	private void setRed(RBTNode<T> node) {
		if (node != null) {
			node.color = RED;
		}
	}

	private void setParent(RBTNode<T> node, RBTNode<T> parent) {
		if (node != null) {
			node.parent = parent;
		}
	}

	private void setColor(RBTNode<T> node, boolean color) {
		if (node != null) {
			node.color = color;
		}
	}

	/*
	 * 前序遍歷"紅黑樹"
	 */
	private void preOrder(RBTNode<T> tree) {
		if (tree != null) {
			System.out.print(tree.key + " ");
			preOrder(tree.left);
			preOrder(tree.right);
		}
	}

	public void preOrder() {
		preOrder(mRoot);
	}

	/*
	 * 中序遍歷"紅黑樹"
	 */
	private void inOrder(RBTNode<T> tree) {
		if (tree != null) {
			inOrder(tree.left);
			System.out.print(tree.key + " ");
			inOrder(tree.right);
		}
	}

	public void inOrder() {
		inOrder(mRoot);
	}

	/*
	 * 后序遍歷"紅黑樹"
	 */
	private void postOrder(RBTNode<T> tree) {
		if (tree != null) {
			postOrder(tree.left);
			postOrder(tree.right);
			System.out.print(tree.key + " ");
		}
	}

	public void postOrder() {
		postOrder(mRoot);
	}

	/*
	 * (遞歸實現)查找"紅黑樹x"中鍵值為key的節點
	 */
	private RBTNode<T> search(RBTNode<T> x, T key) {
		if (x == null) {
			return x;
		}

		int cmp = key.compareTo(x.key);
		if (cmp < 0) {
			return search(x.left, key);
		} else if (cmp > 0) {
			return search(x.right, key);
		} else {
			return x;
		}
	}

	public RBTNode<T> search(T key) {
		return search(mRoot, key);
	}

	/*
	 * (非遞歸實現)查找"紅黑樹x"中鍵值為key的節點
	 */
	private RBTNode<T> iterativeSearch(RBTNode<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;
	}

	public RBTNode<T> iterativeSearch(T key) {
		return iterativeSearch(mRoot, key);
	}

	/*
	 * 查找最小結點:返回tree為根結點的紅黑樹的最小結點。
	 */
	private RBTNode<T> minimum(RBTNode<T> tree) {
		if (tree == null) {
			return null;
		}

		while (tree.left != null) {
			tree = tree.left;
		}
		return tree;
	}

	public T minimum() {
		RBTNode<T> p = minimum(mRoot);
		if (p != null) {
			return p.key;
		}

		return null;
	}

	/*
	 * 查找最大結點:返回tree為根結點的紅黑樹的最大結點。
	 */
	private RBTNode<T> maximum(RBTNode<T> tree) {
		if (tree == null) {
			return null;
		}

		while (tree.right != null) {
			tree = tree.right;
		}
		return tree;
	}

	public T maximum() {
		RBTNode<T> p = maximum(mRoot);
		if (p != null) {
			return p.key;
		}

		return null;
	}

	/*
	 * 找結點(x)的后繼結點。即,查找"紅黑樹中數據值大於該結點"的"最小結點"。
	 */
	public RBTNode<T> successor(RBTNode<T> x) {
		// 如果x存在右孩子,則"x的后繼結點"為 "以其右孩子為根的子樹的最小結點"。
		if (x.right != null) {
			return minimum(x.right);
		}

		// 如果x沒有右孩子。則x有以下兩種可能:
		// (01) x是"一個左孩子",則"x的后繼結點"為 "它的父結點"。
		// (02) x是"一個右孩子",則查找"x的最低的父結點,並且該父結點要具有左孩子",找到的這個"最低的父結點"就是"x的后繼結點"。
		RBTNode<T> y = x.parent;
		while ((y != null) && (x == y.right)) {
			x = y;
			y = y.parent;
		}

		return y;
	}

	/*
	 * 找結點(x)的前驅結點。即,查找"紅黑樹中數據值小於該結點"的"最大結點"。
	 */
	public RBTNode<T> predecessor(RBTNode<T> x) {
		// 如果x存在左孩子,則"x的前驅結點"為 "以其左孩子為根的子樹的最大結點"。
		if (x.left != null) {
			return maximum(x.left);
		}

		// 如果x沒有左孩子。則x有以下兩種可能:
		// (01) x是"一個右孩子",則"x的前驅結點"為 "它的父結點"。
		// (01) x是"一個左孩子",則查找"x的最低的父結點,並且該父結點要具有右孩子",找到的這個"最低的父結點"就是"x的前驅結點"。
		RBTNode<T> y = x.parent;
		while ((y != null) && (x == y.left)) {
			x = y;
			y = y.parent;
		}

		return y;
	}

	/*
	 * 對紅黑樹的節點(x)進行左旋轉
	 *
	 * 左旋示意圖(對節點x進行左旋):
	 *      px                              px
	 *     /                               /
	 *    x                               y
	 *   /  \      --(左旋)-.           / \                #
	 *  lx   y                          x  ry
	 *     /   \                       /  \
	 *    ly   ry                     lx  ly
	 *
	 *
	 */
	private void leftRotate(RBTNode<T> x) {
		// 設置x的右孩子為y
		RBTNode<T> y = x.right;

		// 將 “y的左孩子” 設為 “x的右孩子”;
		// 如果y的左孩子非空,將 “x” 設為 “y的左孩子的父親”
		x.right = y.left;
		if (y.left != null) {
			y.left.parent = x;
		}

		// 將 “x的父親” 設為 “y的父親”
		y.parent = x.parent;

		if (x.parent == null) {
			this.mRoot = y;            // 如果 “x的父親” 是空節點,則將y設為根節點
		} else {
			if (x.parent.left == x) {
				x.parent.left = y;    // 如果 x是它父節點的左孩子,則將y設為“x的父節點的左孩子”
			} else {
				x.parent.right = y;    // 如果 x是它父節點的左孩子,則將y設為“x的父節點的左孩子”
			}
		}

		// 將 “x” 設為 “y的左孩子”
		y.left = x;
		// 將 “x的父節點” 設為 “y”
		x.parent = y;
	}

	/*
	 * 對紅黑樹的節點(y)進行右旋轉
	 *
	 * 右旋示意圖(對節點y進行左旋):
	 *            py                               py
	 *           /                                /
	 *          y                                x
	 *         /  \      --(右旋)-.            /  \                     #
	 *        x   ry                           lx   y
	 *       / \                                   / \                   #
	 *      lx  rx                                rx  ry
	 *
	 */
	private void rightRotate(RBTNode<T> y) {
		// 設置x是當前節點的左孩子。
		RBTNode<T> x = y.left;

		// 將 “x的右孩子” 設為 “y的左孩子”;
		// 如果"x的右孩子"不為空的話,將 “y” 設為 “x的右孩子的父親”
		y.left = x.right;
		if (x.right != null) {
			x.right.parent = y;
		}

		// 將 “y的父親” 設為 “x的父親”
		x.parent = y.parent;

		if (y.parent == null) {
			this.mRoot = x;            // 如果 “y的父親” 是空節點,則將x設為根節點
		} else {
			if (y == y.parent.right) {
				y.parent.right = x;    // 如果 y是它父節點的右孩子,則將x設為“y的父節點的右孩子”
			} else {
				y.parent.left = x;    // (y是它父節點的左孩子) 將x設為“x的父節點的左孩子”
			}
		}

		// 將 “y” 設為 “x的右孩子”
		x.right = y;

		// 將 “y的父節點” 設為 “x”
		y.parent = x;
	}

	/*
	 * 紅黑樹插入修正函數
	 *
	 * 在向紅黑樹中插入節點之后(失去平衡),再調用該函數;
	 * 目的是將它重新塑造成一顆紅黑樹。
	 *
	 * 參數說明:
	 *     node 插入的結點        // 對應《算法導論》中的z
	 */
	private void insertFixUp(RBTNode<T> node) {
		RBTNode<T> parent, gparent;

		// 若“父節點存在,並且父節點的顏色是紅色”
		while (((parent = parentOf(node)) != null) && isRed(parent)) {
			gparent = parentOf(parent);

			//若“父節點”是“祖父節點的左孩子”
			if (parent == gparent.left) {
				// Case 1條件:叔叔節點是紅色
				RBTNode<T> uncle = gparent.right;
				if ((uncle != null) && isRed(uncle)) {
					setBlack(uncle);
					setBlack(parent);
					setRed(gparent);
					node = gparent;
					continue;
				}

				// Case 2條件:父子節點左右關系改成左左關系
				if (parent.right == node) {
					RBTNode<T> tmp;
					leftRotate(parent);
					tmp = parent;
					parent = node;
					node = tmp;
				}

				// Case 3條件:變色並對祖父節點進行右旋
				setBlack(parent);
				setRed(gparent);
				rightRotate(gparent);
			} else {
				// 父節點為祖父節點的右孩子
				// Case 1條件:叔叔節點是紅色
				RBTNode<T> uncle = gparent.left;
				if ((uncle != null) && isRed(uncle)) {
					setBlack(uncle);
					setBlack(parent);
					setRed(gparent);
					node = gparent;
					continue;
				}

				// Case 2條件:父子節點右左關系改成右右關系
				if (parent.left == node) {
					RBTNode<T> tmp;
					rightRotate(parent);
					tmp = parent;
					parent = node;
					node = tmp;
				}

				// Case 3條件:變色並對祖父節點進行左旋
				setBlack(parent);
				setRed(gparent);
				leftRotate(gparent);
			}
		}

		// 將根節點設為黑色
		setBlack(this.mRoot);
	}

	/*
	 * 將結點插入到紅黑樹中
	 *
	 * 參數說明:
	 *     node 插入的結點        // 對應《算法導論》中的node
	 */
	private void insert(RBTNode<T> node) {
		int cmp;
		RBTNode<T> y = null;
		RBTNode<T> x = this.mRoot;

		// 1. 將紅黑樹當作一顆二叉查找樹,將節點添加到二叉查找樹中。
		while (x != null) {
			y = x;
			cmp = node.key.compareTo(x.key);
			if (cmp < 0) {
				x = x.left;
			} else {
				x = x.right;
			}
		}

		node.parent = y;
		if (y != null) {
			cmp = node.key.compareTo(y.key);
			if (cmp < 0) {
				y.left = node;
			} else {
				y.right = node;
			}
		} else {
			this.mRoot = node;
		}

		// 2. 設置節點的顏色為紅色
		node.color = RED;

		// 3. 將它重新修正為一顆二叉查找樹
		insertFixUp(node);
	}

	/*
	 * 新建結點(key),並將其插入到紅黑樹中
	 *
	 * 參數說明:
	 *     key 插入結點的鍵值
	 */
	public void insert(T key) {
		RBTNode<T> node = new RBTNode<T>(key, BLACK, null, null, null);

		// 如果新建結點失敗,則返回。
		if (node != null) {
			insert(node);
		}
		size++;
	}

	/*
	 * 紅黑樹刪除修正函數
	 *
	 * 在從紅黑樹中刪除插入節點之后(紅黑樹失去平衡),再調用該函數;
	 * 目的是將它重新塑造成一顆紅黑樹。
	 *
	 * 參數說明:
	 *     node 待修正的節點,平衡點,remove刪除操作,
	 *     這里的node就是真正的被刪除節點的原始位置,二叉樹刪除操作后該位置新的節點
	 *     如果被刪除節點沒有子節點,node為null
	 *     如果被刪除節點的后繼節點沒有右子節點,node為null
	 *     反之node九尾被刪除節點的后繼節點的右子節點
	 *     嗯,也可以理解為,真正被刪除節點的位置,在二叉樹刪除操作后,該位置的節點
	 *
	 *     parent為真正被刪除節點(被刪除節點有子節點,就為后繼節點)的父節點
	 *     但有一種特殊情況,就是后繼節點為被刪除節點的父節點,此時,定義parent為后繼節點
	 *     嗯,也可以理解為,真正被刪除節點的父節點位置,在二叉樹刪除操作后,該位置的節點。
	 */
	private void removeFixUp(RBTNode<T> node, RBTNode<T> parent) {
		RBTNode<T> other;

		//node節點不是紅色節點
		//下面注釋中,x為真正被刪除節點位置的當前節點
		//s節點為x的相鄰節點
		while ((node == null || isBlack(node)) && (node != this.mRoot)) {

			System.out.println("removeFixUp方法:node:"+node+",parent:"+parent);
			System.out.println("二叉樹刪除后");
			if(mRoot!=null) {
				mRoot.print();
			}

			//除了后繼節點是被刪除節點的子節點這種情況
			if (parent.left == node) {
				other = parent.right;
				if (isRed(other)) {
					// Case 1: x的兄弟w是紅色的
					setBlack(other);
					setRed(parent);
					leftRotate(parent);
					other = parent.right;
				}

				if ((other.left == null || isBlack(other.left)) &&
						(other.right == null || isBlack(other.right))) {
					// Case 2: x的兄弟w是黑色,且w的倆個孩子也都是黑色的
					setRed(other);
					node = parent;
					parent = parentOf(node);
				} else {
					if (other.right == null || isBlack(other.right)) {
						// Case 3: x的兄弟w是黑色的,並且w的左孩子是紅色,右孩子為黑色。
						setBlack(other.left);
						setRed(other);
						rightRotate(other);
						other = parent.right;
					}
					// Case 4: x的兄弟w是黑色的;並且w的右孩子是紅色的,左孩子任意顏色。
					setColor(other, colorOf(parent));
					setBlack(parent);
					setBlack(other.right);
					leftRotate(parent);
					node = this.mRoot;
					break;
				}
			} else {
				// 被刪除節點沒有子節點,parent為被刪除節點的父節點
				// 被刪除節點就是后繼節點的父節點,那么parent為后繼節點
				other = parent.left;
				if (isRed(other)) {
					// Case 1: x的兄弟w是紅色的
					setBlack(other);
					setRed(parent);
					rightRotate(parent);
					other = parent.left;
				}

				if ((other.left == null || isBlack(other.left)) &&
						(other.right == null || isBlack(other.right))) {
					// Case 2: x的兄弟w是黑色,且w的倆個孩子也都是黑色的
					setRed(other);
					node = parent;
					parent = parentOf(node);
				} else {
					if (other.left == null || isBlack(other.left)) {
						// Case 3: x的兄弟w是黑色的,並且w的左孩子是紅色,右孩子為黑色。
						setBlack(other.right);
						setRed(other);
						leftRotate(other);
						other = parent.left;
					}

					// Case 4: x的兄弟w是黑色的;並且w的右孩子是紅色的,左孩子任意顏色。
					setColor(other, colorOf(parent));
					setBlack(parent);
					setBlack(other.left);
					rightRotate(parent);
					node = this.mRoot;
					break;
				}
			}
		}

		if (node != null) {
			setBlack(node);
		}
	}

	/*
	 * 刪除結點(node),並返回被刪除的結點
	 *
	 * 參數說明:
	 *     node 刪除的結點
	 */
	private void remove(RBTNode<T> node) {
		RBTNode<T> child, parent;
		boolean color;

		// 被刪除節點的"左右孩子都不為空"的情況。
		if ((node.left != null) && (node.right != null)) {
			// 被刪節點的后繼節點。(稱為"取代節點")
			// 用它來取代"被刪節點"的位置,然后再將"被刪節點"去掉。
			RBTNode<T> replace = node;

			// 獲取后繼節點
			replace = replace.right;
			while (replace.left != null) {
				replace = replace.left;
			}

			// "node節點"不是根節點(只有根節點不存在父節點)
			// 更新被刪除的節點,替換為其后繼節點
			if (parentOf(node) != null) {
				if (parentOf(node).left == node) {
					parentOf(node).left = replace;
				} else {
					parentOf(node).right = replace;
				}
			} else {
				// "node節點"是根節點,更新根節點。
				this.mRoot = replace;
			}

			// child是"取代節點"的右孩子,也是需要"調整的節點"。
			// "取代節點"肯定不存在左孩子!因為它是一個后繼節點。
			child = replace.right;
			// parent為后繼節點的父節點
			parent = parentOf(replace);
			// 保存"取代節點"的顏色
			color = colorOf(replace);

			// "被刪除節點"是"它的后繼節點的父節點"
			// 此時會將parent設置為后繼節點,因為父節點會被刪掉
			if (parent == node) {
				parent = replace;
			} else {
				// child不為空
				if (child != null) {
					child.parent = 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) {
				removeFixUp(child, parent);
			}

			node = null;
			return;
		}
        // 被刪除節點node只有一個子節點child
		// 或者沒有任何子節點,child為null
		if (node.left != null) {
			child = node.left;
		} else {
			child = node.right;
		}

		parent = node.parent;
		// 保存"取代節點"的顏色
		color = node.color;

		if (child != null) {
			child.parent = parent;
		}

		// "node節點"不是根節點
		if (parent != null) {
			if (parent.left == node) {
				parent.left = child;
			} else {
				parent.right = child;
			}
		} else {
			this.mRoot = child;
		}

		if (color == BLACK) {
			removeFixUp(child, parent);
		}
		node = null;
	}

	/*
	 * 刪除結點(z),並返回被刪除的結點
	 *
	 * 參數說明:
	 *     tree 紅黑樹的根結點
	 *     z 刪除的結點
	 */
	public void remove(T key) {
		RBTNode<T> node;

		if ((node = search(mRoot, key)) != null) {
			remove(node);
		}
		size--;
	}

	/*
	 * 銷毀紅黑樹
	 */
	private void destroy(RBTNode<T> tree) {
		if (tree == null) {
			return;
		}

		if (tree.left != null) {
			destroy(tree.left);
		}
		if (tree.right != null) {
			destroy(tree.right);
		}
		tree = null;
	}

	public void clear() {
		destroy(mRoot);
		mRoot = null;
	}

	public int size() {
		return size;
	}

	public void print() {
		if (mRoot != null) {
			mRoot.print();
		}
	}

	private class RBTNode<T extends Comparable<T>> {

		/**
		 * 寬度間隔(不能小於5,如果key大於1000,請加寬度)
		 */
		private final int interval = 5;
		boolean color;        // 顏色
		T key;                // 關鍵字(鍵值)
		RBTNode<T> left;    // 左孩子
		RBTNode<T> right;    // 右孩子
		RBTNode<T> parent;    // 父結點
		private int maxHeight = 0;
		private Map<Integer, List<String>> allFieldMap = new HashMap<>();

		public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {
			this.key = key;
			this.color = color;
			this.parent = parent;
			this.left = left;
			this.right = right;
		}

		public T getKey() {
			return key;
		}

		/**
		 * 當前節點的高度,根節點高度為1
		 */
		public int getHight() {
			int i = 1;
			RBTNode<T> node = this;
			while (node.parent != null) {
				node = node.parent;
				i++;
			}
			return i;
		}

		public String toString() {
			if (key == null) {
				return "NIL";
			}
			if (this.color == RED) {
				return "(" + key + ")";
			} else {
				return "" + key;
			}
		}

		private void getMaxHeight(RBTNode<T> tree) {
			if (tree != null) {
				if (tree.getHight() > maxHeight) {
					maxHeight = tree.getHight();
				}
				getMaxHeight(tree.left);
				getMaxHeight(tree.right);
			}
		}

		/**
		 * 獲取最深的高度,根節點高度為1
		 */
		public int getMaxHeight() {
			if (mRoot != null) {
				getMaxHeight(this);
				return maxHeight;
			}
			return 0;
		}

		/**
		 *
		 */
		private void show(RBTNode<T> tree, int leftWidth, int fixWidth) {
			if (tree != null) {
				//System.out.println("leftWidth:" + leftWidth+"   fixWidth:"+fixWidth+"   value:"+tree.key);
				String showContent = "";
				int ahead = 0;
				if (tree.color == RED) {
					ahead++;
				}

				for (int i = 0; i < leftWidth - ahead; i++) {
					showContent += " ";
				}
				showContent += tree;
				getFieldList(tree).add(showContent);
				show(tree.left, leftWidth - (int) (0.5 * fixWidth), fixWidth / 2);
				show(tree.right, leftWidth + (int) (0.5 * fixWidth), fixWidth / 2);
			}
		}

		/**
		 * 獲取對應節點對應高度的數組
		 */
		private List<String> getFieldList(RBTNode<T> tree) {
			if (allFieldMap.get(tree.getHight()) == null) {
				allFieldMap.put(tree.getHight(), Lists.newArrayList());
			}
			return allFieldMap.get(tree.getHight());
		}

		/**
		 * 打印節點
		 */
		public void print() {
			if (mRoot != null) {
				allFieldMap = new HashMap<>();
				int fieldWidth = (2 << (mRoot.getMaxHeight() - 1) - 1);
				int allWidth = fieldWidth * interval;
				int fixWidth = (allWidth - interval) / 2;
				show(this, fixWidth, fixWidth);

				allFieldMap.entrySet().forEach(x -> {
					AtomicReference<String> str = new AtomicReference<>("");
					List<String> list = x.getValue();
					list.stream().sorted(Comparator.comparing(y -> y.length())).forEach(y -> {
						str.set(str + y.substring(str.get().length()));
					});
					System.out.println(str.get());
				});
			}
		}
	}
}

運行調試

public class RBTreeTest {

	private static final int[] a = {10, 40, 30, 60, 90, 70, 20, 50, 80, 75,88,87,98,23,44,55,66,77,1,2,3,4,35,36,33,32,31,89,81,82,83,84,85,86,79};
	private static final boolean mDebugInsert = false;    // "插入"動作的檢測開關(false,關閉;true,打開)
	private static final boolean mDebugDelete = false;    // "刪除"動作的檢測開關(false,關閉;true,打開)

	public static void main(String[] args) {
		int i, ilen = a.length;
		RBTree<Integer> tree = new RBTree<Integer>();

		System.out.printf("== 原始數據: ");
		for (i = 0; i < ilen; i++) {
			System.out.printf("%d ", a[i]);
		}
		System.out.printf("\n");

		for (i = 0; i < ilen; i++) {
			tree.insert(a[i]);
			// 設置mDebugInsert=true,測試"添加函數"
			if (mDebugInsert) {
				System.out.printf("== 添加節點: %d\n", a[i]);
				System.out.printf("== 樹的詳細信息: \n");
				tree.print();
				System.out.printf("\n");
			}
		}
		System.out.println("== 遍歷  ");
		tree.print();

		System.out.printf("== 前序遍歷: ");
		tree.preOrder();

		System.out.printf("\n== 中序遍歷: ");
		tree.inOrder();

		System.out.printf("\n== 后序遍歷: ");
		tree.postOrder();
		System.out.printf("\n");

		System.out.printf("== 最小值: %s\n", tree.minimum());
		System.out.printf("== 最大值: %s\n", tree.maximum());

		System.out.printf("\n");
//        tree.remove(60);
//        tree.print();
		// 設置mDebugDelete=true,測試"刪除函數"
		if (mDebugDelete) {
			for (i = 0; i < ilen; i++) {
				tree.remove(a[i]);

				System.out.printf("== 刪除節點: %d\n", a[i]);
				System.out.printf("== 樹的詳細信息: \n");
				tree.print();
				System.out.printf("\n");
			}
		}

		// 銷毀二叉樹
		tree.clear();
	}
}

被刪除節點沒有子節點,不存在后繼節點

== 原始數據: 1 4 5 3 6 7 8 9 10 
== 遍歷  
                 6
        (4)             (8)
     1       5       7       9
      (3)                     (10)
二叉樹刪除 5
                 6
        (4)             (8)
     1               7       9
      (3)                     (10)
平衡修正后:
                 6
        (3)             (8)
     1       4       7       9
                              (10)

removeFixUp方法的node為null,parent為4

后繼節點為被刪除節點子節點:

== 原始數據: 1 4 5 3 6 7 8 9 10 
== 遍歷  
                 6
        (4)             (8)
     1       5       7       9
      (3)                     (10)

二叉樹刪除 8
                 6
        (4)             (9)
     1       5       7      (10)
      (3)

平衡修正后:
                 6
        (4)             (9)
     1       5       7       10
      (3)

removeFixUp方法的node為10,parent為9

后繼節點不是被刪除節點子節點:

== 原始數據: 1 4 5 3 6 7 8 9 10 
== 遍歷  
                 6
        (4)             (8)
     1       5       7       9
      (3)                     (10)
二叉樹刪除 6 
                 7
        (4)             (8)
     1       5               9
      (3)                     (10)
平衡修正后:
                 7
        (4)             (9)
     1       5       8       10
      (3)

removeFixUp方法的node為null,parent為8

特殊場景,平衡點存在左子樹

下面的第二次平衡操作,平衡點為86,而86存在左子樹

== 遍歷                               
                 84
         79              86
     45     (82)     85      89
           81  83

== 刪除節點: 89
== 樹的詳細信息: 
removeFixUp方法:node:null,parent:86
平衡操作
                 84
         79              86
     45     (82)     85
           81  83
removeFixUp方法:node:86,parent:84

平衡操作
                 84
         79              86
     45     (82)    (85)
           81  83
           
== 平衡調整后
                 82
         79              84
     45      81      83      86
                          (85)

各種樹的操作時間復雜度

參考:

漫畫:什么是紅黑樹?

JAVA學習-紅黑樹詳解

【集合系列】- 紅黑樹實現分析

Java紅黑樹

紅黑樹(五)之 Java的實現

紅黑樹(一)之 原理和算法詳細介紹

30張圖帶你徹底理解紅黑樹

教你透徹了解紅黑樹

紅黑樹深入剖析及Java實現

紅黑樹詳細分析,看了都說好

面試還在被紅-黑樹虐?看完這篇動圖文章輕松反虐面試官

紅黑樹

細說紅黑樹(1)-核心定理

通俗易懂的紅黑樹圖解(上)

左偏紅黑樹

數據結構:紅黑樹的刪除

紅黑樹的刪除

親自動手畫紅黑樹

紅黑樹的刪除(算法導論)

Red Black Tree: Delete(刪除資料)與Fixup(修正)

數據結構之紅黑樹-動圖演示(下)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM