紅黑樹是一種自平衡二叉樹,紅黑樹和AVL樹同樣都對插入時間、刪除時間和查找時間提供了最好可能的最壞狀況擔保。算法
紅黑樹須要知足的五條性質: this
性質一:節點是紅色或者是黑色; spa
在樹裏面的節點不是紅色的就是黑色的,沒有其餘顏色,要不怎麼叫紅黑樹呢,是吧。 .net
性質二:根節點是黑色; blog
根節點老是黑色的。它不能爲紅。 遞歸
性質三:每一個葉節點(NIL或空節點)是黑色; 圖片
實現的時候NIL節點是個空節點用null表示,而且是黑色的ip
性質四:每一個紅色節點的兩個子節點都是黑色的(也就是說不存在兩個連續的紅色節點); rem
連續的兩個節點不能是連續的紅色,連續的兩個節點的意思就是父節點與子節點不能是連續的紅色get
性質五:從任一節點到其每一個葉子節點的全部路徑都包含相同數目的黑色節點;
從根節點到每個NIL節點的路徑中,都包含了相同數量的黑色節點。
這五條性質就約束了紅黑樹能夠將查找刪除維持在對數時間內,算法導論中有證實一顆有n個節點的紅黑樹的高度至多爲2lg(n+1)
紅黑樹相對於普通平衡二叉樹一個很重要的操做就是旋轉
假設 : 旋轉節點爲n ,n的左子樹叫作leftChild,n的右子樹叫作rightChild
一、向左旋轉
rightChild的左子樹做爲n的右子樹,
將n的右節點做爲跟,
最後將n做爲rightChild的左節點;
二、向右旋轉
leftChild的右子樹做爲n節點左子樹,
將n的左節點做爲跟,
最後將n做爲leftChild的左節點
由於要知足紅黑樹的這五條性質,若是咱們插入的是黑色節點,那就違反了性質五,須要進行大規模調整,若是咱們插入的是紅色節點,那就只有在要插入節點的父節點也是紅色的時候違反性質四或者是當插入的節點是根節點時,違反性質二,因此,咱們應該把要插入的節點的顏色變成紅色。
1.叔叔節點存在且爲紅色,這時只須要將父,叔節點置爲黑色,將祖父節點(祖父節點一定存在)置爲紅色,而後以祖父節點遞歸上面的調整過程
insertFixup(grandParent), 你能夠想像成祖父節點爲新插入的節點便可;
2.叔節點爲黑色或不存在,就須要進行旋轉調整,這裏講述一種狀況,其餘類同:
假如咱們對上面的初始圖添加一個節點5
明顯如今樹不知足性質4,因此須要進行旋轉,並且經過一次旋轉是不夠的,咱們須要進行兩次旋轉:
第一次旋轉:以新添加的節點5的父節點6爲根進行右旋轉後:
第二次旋轉,先調整顏色,將5置爲黑色,1,6置爲紅色,以節點1進行左旋轉:
首先你要了解普通二叉樹的刪除操做:
1.若是刪除的是葉節點,能夠直接刪除;
2.若是被刪除的元素有一個子節點,能夠將子節點直接移到被刪除元素的位置;
3.若是有兩個子節點,這時候就能夠把被刪除元素的右支的最小節點(被刪除元素右支的最左邊的節點)和被刪除元素互換,咱們把被刪除元素右支的最左邊的節點稱之爲後繼節點(後繼元素),而後在根據狀況1或者狀況2進行操做。
對於刪除操做,咱們須要考慮幾種狀況(來自維基百科):
/** * @author lpf * @create 2018-03-22 20:32 * * 紅黑樹實現: * 性質: * 1.節點要麼紅,要麼黑; * 2.根是黑色; * 3.全部葉子都是黑色;(葉子爲null節點) * 4.每一個紅色節點的兩個子節點都是黑色(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點) * 5.從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點 * **/ public class RedBlackTree<T extends Comparable<T>> { private Node<T> root; // 樹的跟節點 private int size; // 樹元素個數 //標誌葉子節點表示空節點,顏色爲黑色 private Node<T> NIL = new Node<T>(null, null, null, null, Color.BLACK); /** * 節點類 */ private static class Node<E>{ E value; Node<E> parent; Node<E> left; Node<E> right; Color color; public Node(E value, Node<E> parent, Node<E> left, Node<E> right, Color color) { this.value = value; this.parent = parent; this.left = left; this.right = right; this.color = color; } } /** * 節點顏色 */ private static enum Color{ RED, BLACK } /** * 獲取叔叔節點 * @param n 當前節點 * @return 其叔節點 */ private Node<T> uncle(Node<T> n){ Node<T> gp = grandParent(n); if (gp == null){ return null; } if (n.parent == gp.left){ //若其父節點在其祖父節點左邊 return gp.right; } else { return gp.left; } } /** * 獲取祖父節點 * @param n 當前節點 * @return 其祖父節點 */ private Node<T> grandParent(Node<T> n){ if (n.parent == null) return null; return n.parent.parent; } /** * 返回最小元素 * @return 獲取某節點爲根的樹的最小元素 */ public T min(Node<T> n) { Node<T> min = minN(n); return min == NIL ? null : min.value; } /** * 返回最小節點 * @param n 樹根節點 * @return 最小節點 */ private Node<T> minN(Node<T> n) { Node<T> min = n; while (min.left != NIL) { min = min.left; } return min == NIL ? null : min; } /** * 獲取某節點爲根的樹的最大元素 * @return 最大元素, 沒有返回null */ public T max(Node<T> n) { Node<T> max = maxN(n); return max == NIL ? null : max.value; } /** * 獲取某節點爲根的樹的最大節點 * @return 最大節點, 沒有返回null */ public Node<T> maxN(Node<T> n) { Node<T> max = n; while (max.right != NIL) { max = max.right; } return max == NIL ? null : max; } /** * 左旋以n節點爲根的子樹: * 1.將rightChild的左子樹做爲n的右子樹 * 2.將rightChild做爲根 * 3.將n節點做爲rightChild的左孩子 */ private void leftRotate(Node<T> n){ Node<T> rightChild = n.right; //1.將rightChild的左子樹做爲n的右子樹 //將rightChild的左子樹接到n的右邊 n.right = rightChild.left; if(rightChild.left != NIL) rightChild.left.parent = n; //2.將rightChild做爲根 rightChild.parent = n.parent; if (n.parent == null){ //若n爲樹根 root = rightChild; } else if (n.parent.left == n){ //若n爲父親的左孩子 n.parent.left = rightChild; } else { //若n爲父親的右孩子 n.parent.right = rightChild; } //3.將n節點做爲rightChild的左孩子 rightChild.left = n; n.parent = rightChild; } /** * 右旋以n節點爲根的子樹: * 1.將leftChild的右子樹做爲n的左子樹 * 2.將leftChild做爲根 * 3.將n節點做爲leftChild的右孩子 */ private void rightRotate(Node<T> n){ Node<T> leftChild = n.left; //1.將leftChild的右子樹做爲n的左子樹 n.left = leftChild.right; if (leftChild.right != NIL){ leftChild.right.parent = n; } //2.將leftChild做爲根 leftChild.parent = n.parent; if (n.parent == null){ //n爲樹根 root = leftChild; } else if (n == n.parent.left){ //n爲父節點點左孩子 n.parent.left = leftChild; } else{ //n爲父節點右孩子 n.parent.right = leftChild; } //3.將n節點做爲leftChild的右孩子 leftChild.right = n; n.parent = leftChild; } /** * 調整樹以知足紅黑樹性質 * @param n 新添加的節點 */ private void insertFixup(Node<T> n) { //如果樹根 if (n.parent == null){ n.color = Color.BLACK; return; } //父節點爲黑色,無須調整 if (n.parent.color == Color.BLACK){ return; } Node<T> u = uncle(n); Node<T> g = grandParent(n); // 1.父節點及叔節點都爲紅色 if (u != null && u.color == Color.RED){ //將parent和uncle顏色置BLACK n.parent.color = Color.BLACK; u.color = Color.BLACK; //將grand parent置RED g.color = Color.RED; //遞歸調整 grand parent, 這時可想像grand parent爲新添加的紅色節點 insertFixup(g); } else { //父節點P是紅色而叔節點是黑色或缺乏 if (n == n.parent.right && n.parent == g.left){ //n爲父節點右孩子,且父節點爲祖父節點的左孩子 //以父左旋 leftRotate(n.parent); n = n.left; } else if(n == n.parent.left && n.parent == g.right){ //n爲父節點左孩子,且父節點爲祖父節點右孩子 //以父右旋 rightRotate(n.parent); n = n.right; } n.parent.color = Color.BLACK; //parent顏色置爲黑色 g.color = Color.RED; if (n == n.parent.left && n.parent == g.left){ //n節點爲父節點的左孩子,且父節點爲祖父節點的左孩子 //以祖父右旋 rightRotate(g); } else{ //n節點爲父節點的右孩子,且父節點爲祖父節點的右孩子 //以祖父左旋 leftRotate(g); } } } /** * 刪除元素 * 相似二叉查找樹的刪除 * @param t 待刪除節點 * @return 刪除成功返回true, 反之返回false */ public boolean remove(T t) { boolean removed = false; Node<T> n = getN(t); // 獲取要刪除的節點 Node<T> replace = null; // 用於替換節點n Node<T> child = null; // 後繼節點next的孩子節點 if (n != null) { if (n.left == NIL || n.right == NIL) { // 如有最多一個非空孩子 replace = n; } else { // 如有2個非空孩子, 則找其後繼節點 replace = locateNextN(n); } // 獲取替換節點replace的孩子, 有可能爲NIL child = replace.left != NIL ? replace.left : replace.right; // 刪除節點replace, 鏈接replace父節點-->child節點 child.parent = replace.parent; if (replace.parent == null) { // 根節點 root = child; } else if (replace == replace.parent.left) { // replace爲其父節點左孩子 replace.parent.left = child; } else { // replace爲其父節點右孩子 replace.parent.right = child; } // 替換n節點的值爲replace節點 if (replace != n) { n.value = replace.value; } // 若後繼節點爲黑色, 則需作調整, 由於刪除紅色replace節點對紅黑樹性質無影響 if (replace.color == Color.BLACK) { removeFixup(child); } removed = true; } return removed; } /** * 因爲刪除節點而作調整 * @param n 刪除節點的後繼節點的孩子 */ private void removeFixup(Node<T> n) { while (n != root && n.color == Color.BLACK) { if (n == n.parent.left) { // n爲其父節點的左孩子 Node<T> rightBrother = rightBrother(n); if (rightBrother.color == Color.RED) { // 兄弟顏色爲紅 rightBrother.color = Color.BLACK; n.parent.color = Color.RED; leftRotate(n.parent); // 以父左旋 rightBrother = n.parent.right; } // 右兄弟的兩個孩子都爲黑色 if (rightBrother.left.color == Color.BLACK && rightBrother.right.color == Color.BLACK) { rightBrother.color = Color.RED; n = n.parent; } else if (rightBrother.right.color == Color.BLACK) { // 右兄弟右孩子爲黑色 rightBrother.left.color = Color.BLACK; rightBrother.color = Color.RED; rightRotate(rightBrother); rightBrother = n.parent.right; } else { // 右兄弟右孩子爲紅色或其餘狀況,好比爲空葉子節點NIL rightBrother.color = n.parent.color; n.parent.color = Color.BLACK; rightBrother.right.color = Color.BLACK; leftRotate(n.parent); n = root; } } else { // n爲其父節點的右孩子 Node<T> leftBrother = leftBrother(n); if (leftBrother.color == Color.RED) { // 左兄弟爲紅色 leftBrother.color = Color.BLACK; n.parent.color = Color.RED; rightRotate(n.parent); leftBrother = n.parent.left; } if (leftBrother.left.color == Color.BLACK && leftBrother.right.color == Color.BLACK) { // 左兄弟左孩子和右孩子都爲黑色 leftBrother.color = Color.RED; n = n.parent; } else if (leftBrother.left.color == Color.BLACK) { // 僅左兄弟左孩子爲黑色 leftBrother.color = Color.RED; leftBrother.right.color = Color.BLACK; leftRotate(leftBrother); leftBrother = n.parent.left; } else { // 左兄弟左孩子爲紅色 leftBrother.color = n.parent.color; n.parent.color = Color.BLACK; leftBrother.left.color = Color.BLACK; rightRotate(n.parent); n = root; } } } n.color = Color.BLACK; } /** * 獲取節點的右兄弟 * @param n 當前節點 * @return 節點的右兄弟 */ private Node<T> rightBrother(Node<T> n) { return n == null ? null : (n.parent == null ? null : n.parent.right); } /** * 獲取節點的左兄弟 * @param n 當前節點 * @return 節點的右兄弟 */ private Node<T> leftBrother(Node<T> n) { return n == null ? null : (n.parent == null ? null : n.parent.left); } }
參考資料
算法導論第十三章