紅黑樹是在實際工程中被普遍應用的一種數據結構,好比Linux中的線程調度就是使用的紅黑樹來管理進程控制塊,而Nginx中也是使用紅黑樹來管理的timer,Java中的TreeMap和TreeSet也是基於紅黑樹來實現的。紅黑樹相比普通二叉查找樹的一個優點就是它的樹高爲~lgN,因此無論是查找/插入/刪除操做它均能保證可以在對數時間以內完成。本文咱們就先來了解一下紅黑樹插入算法的實現。java
紅黑樹能夠定義成含有紅黑連接而且知足下列條件的二叉查找樹:node
好比下圖就是一棵典型的紅黑樹,若是以前瞭解過2-3樹的話(不瞭解也沒有關係,咱們下面的內容並不會涉及到2-3樹),能夠發現若是將紅黑樹中的紅色結點畫平,實際上它就是2-3樹的一種變形,不過相比2-3樹,紅黑樹的查找操做要簡單的多,但它同時也結合了2-3樹中高效的插入操做,因此說紅黑樹實際上是普通的二叉查找樹和2-3樹兩種數據結構優勢的結合。算法
在實現紅黑樹以前咱們先來定義一棵紅黑樹:數據結構
public class RedBlackLiteBST<Key extends Comparable<Key>, Value> { private static final boolean RED = true; private static final boolean BLACK = false; private Node root; // root of the BST private int n; // number of key-value pairs in BST // 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 public Node(Key key, Value val, boolean color) { this.key = key; this.val = val; this.color = color; } } }
在上面的代碼中,咱們將連接的顏色保存在表示該結點的Node
對象中的color
變量中。若是指向它的連接是紅色的,那麼該變量爲true,黑色則爲false,咱們規定空連接都爲黑色。以下圖所示,指向結點C
的連接是紅色,那麼咱們就將h.left.color
設置爲紅色,指向結點J
的連接是黑色的,咱們就將h.right.color
設置爲黑色。this
紅黑樹相比普通的二叉查找樹的一個重要優點就是插入的高效性,可是正由於如此紅黑樹的插入操做的算法實現相比普通的二叉樹要複雜一些。在正式實現插入算法以前,咱們有必要先了解一下對於紅黑樹的幾種基本操做。spa
以下圖所示,如今咱們有一條紅色的右連接,如今咱們想要將這條紅色右連接轉換爲左連接,這個操做過程就叫作左旋轉:線程
咱們要作的就是在保持紅黑樹平衡性的同時,將上圖的結構變爲下面這樣:code
仔細觀察能夠發現,咱們要實現的其實就是將紅色連接關聯的兩個節點中由較小的節點E
做爲根節點轉換成由較大的節點S
做爲根節點。同時在這個過程當中爲了保持二叉樹中左子樹都要小於根節點,右子樹都要大於根節點,因此若是S
節點還存在的話左連接咱們還須要將它變成E
節點的右連接。具體的實現代碼以下:對象
private Node rotateLeft(Node h) { assert (h != null) && isRed(h.right); Node x = h.right; h.right = x.left; x.left = h; x.color = h.color; h.color = RED; return x; }
右旋轉的實現和左旋轉的實現是相似的,就是將一個紅色左連接轉換成一個紅色右連接:進程
與左旋轉的時候相反,咱們要作的其實就是紅色連接關聯的兩個節點中較大的節點S
做爲根節點轉換成由較小的節點E
做爲根節點:
因此轉換成具體的代碼,咱們只須要將left和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 = h.color; h.color = RED; return x; }
上面咱們提到紅黑樹的一個重要特性就是紅連接均爲作左連接,因此對於下面這種情形,若是一個結點的兩個子結點均爲紅色連接,咱們就將子結點的顏色所有由紅色轉換成黑色,同時將父結點的顏色由黑變紅。
具體的實現代碼很是簡單:
private void flipColors(Node h) { assert !isRed(h) && isRed(h.left) && isRed(h.right); h.color = RED; h.left.color = BLACK; h.right.color = BLACK; }
上面說了這麼多,其實都是爲接下來的插入操做作的預熱。接下來結合下圖,咱們來分析紅黑樹插入操做過程咱們會遇到的三種情形:
c
大於樹中現存的兩個鍵,因此咱們要將它鏈接到b
結點的右連接。由於這個時候b
的兩條連接都是紅連接,因此咱們要進行flipColors
。接下來能夠發現,咱們下面的兩種狀況都會轉換成這種情形。a
要比樹中現存的兩個鍵都要小,因此咱們先將它鏈接到結點b
的左連接,前面咱們提到紅黑樹的一個特性就是沒有任何一個節點能夠同時和兩個紅色連接相連,而如今b
結點卻違背了這一原則,因此咱們要進行右旋轉操做,接下來情形1是如出一轍的了。b
在樹中現存的兩個鍵之間,因此咱們先將它鏈接到結點a
的右連接,前面咱們提到紅黑樹中紅色連接都是左連接,因此咱們首先要進行左旋轉操做,接下來就和情形2如出一轍了。插入操做的具體實現代碼以下,下面的代碼直到// fix-up any right-leaning right
以前咱們作的都是爲了找到合適的插入位置,而以後的3個if
語句實際上就是對上圖中情形3的一種總結。
public void put(Key key, Value val) { root = insert(root, key, val); root.color = BLACK; // assert check(); // Check integrity of red-black tree data structure. } private Node insert(Node h, Key key, Value val) { if (h == null) { n++; return new Node(key, val, RED); } int cmp = key.compareTo(h.key); if (cmp < 0) h.left = insert(h.left, key, val); else if (cmp > 0) h.right = insert(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); return h; }
isRed
的實現很是簡單,我就不解釋了:
// is node x red (and non-null) ? private boolean isRed(Node x) { if (x == null) return false; return x.color == RED; }
本文爲做者原創,轉載請於開頭明顯處聲明博客出處:)