最近看到好多紅黑樹的東西,英文好的童鞋能夠直接點擊http://www.cs.princeton.edu/~rs/talks/LLRB/RedBlack.pdf這裏查看我以前學習的材料,對理解下面講的東西確定也有點幫助(可是不徹底同樣),英文通常的同窗就直接看個人文采飛揚把哈哈。還有你們能夠去coursera上學習一些國外比較好的資料。感受比國內一些學習網站作的好不少。html
前面一篇隨筆寫的binarysearchtree(http://www.cnblogs.com/robsann/p/7567596.html)說了有一個缺點就是不平衡,意思就是插入已經排好序的對象的時候會變成一條鏈表,鏈表固然比二叉樹要慢不少啦,隨機插入的話二叉樹的各類方法須要的時間和LgN的成正比。因此紅黑樹實際上是在解決二叉樹不平衡的問題的。java
2 - 3 - 4 Tree 我以爲算是一種模型,一種樹模型保證了樹是平衡的,所謂平衡就是樹不會一個枝頭長得很高,另一個枝頭長得很矮,那保證平衡有什麼用?平衡的狀況下,全部操做須要的時間都是和LgN成正比的,你說膩害不膩害。node
2 - 3 - 4 樹,容許一個節點是 2-nodes 或者是 3 nodes 或者是 4 nodes, 具體的意思就是說git
這是一個2-nodes, 有2個觸手,可以指向不一樣的2個子元素,左邊的子元素小於A,右邊的子元素大於Agithub
3-nodes, 3 分叉,最左邊的子元素小於C,中間的子元素between c和e, 最右邊的子元素大於Eide
4-nodes, 3分叉。同上學習
看看2-3-4tree是如何保證平衡的
網站
假設有 10 7 6 3 8 11 15 要插入 2 -3 4 樹中ui
這裏插入還有另一種選擇叫Bottom-up solution,之下而上的一種解法(能夠忽略)。就是先找到這個節點會被插入的位置,若是插入後變成了5-nodes,就把其中一個節點往父節點拋出,讓父節點和其中的一個子節點結合。this
刪除同理,爲了要保證樹的平衡,當刪除的時候,若是被刪除的節點只有一個元素的話,必需要把父親元素拉下來造成一個3-nodes而後刪除後變成2-nodes(後面還會說)
紅黑樹和2-3-4樹的關係,紅黑樹是2-3-4樹的一種實現方式。2-3-4樹只是一個模型。
先介紹一下節點對象Node,看代碼,以後會用到這個對象
private class Node { private K k; // 這個節點的key private V v; //節點的value private Node left, right; //左右節點 private int size; //節點爲根的樹的大小 private boolean color; //節點的顏色,分黑和紅 Node(K k, V v, int size, boolean color) { this.k = k; this.v = v; this.size = size; this.color = color; } @Override public String toString() { if (color) return "red " + v; else return "black " + v; } }
左傾紅黑樹和紅黑樹的區別以下
這是通常的紅黑樹,3-node 的時候紅節點能夠左傾或者右傾
左邊是2-3-4樹的模型,右邊是紅黑樹的實現
這是左傾紅黑樹, 3-nodes的時候紅色的節點必須在左邊。
用常量來表示紅黑
首先左傾紅黑樹的性質必需要獲得保證(就是上圖中說的Require that 3 -node be left leaning),可是不少時候可能由於插入或者刪除的操做破壞了這個性質,因此咱們必需要修正。
這裏介紹3個修正的方法
第一個方法的使用場景是這樣子的,(爲了避免破壞平衡,每次插入的都是紅節點),下圖插入的節點在右邊,破壞了左傾的性質,因此必須rotateLeft。rotateLeft是指把紅色節點左移
方法以下
//右樹是紅link的時候,turn this red link to left private Node rotateLeft(Node h) { assert(isRed(h.right)); Node x = h.right; //change the pointers h.right = x.left; x.left = h; x.color = h.color; //change the colors h.color = RED; x.size = h.size; //change the sizes h.size = size(h.left) + size(h.right) + 1; return x; }
另一種狀況是最新插入的節點在最左邊,把中間節點rotateRight,從新平衡
方法以下
//左樹是紅link的時候,turn this red link to left private Node rotateRight(Node h) { assert(isRed(h.left)); Node x = h.left; h.left = x.right; x.right = h; x.color = h.color; h.color = RED; x.size = h.size; //size is the same h.size = size(h.left) + size(h.right) + 1; return x; }
這張圖是破壞了性質以後,修正的辦法
還有一個方法是flipColors(),代碼以下,就是可能插入的時候須要知足當前節點不是4-nodes,可能就會使用這個方法
有了上面這些輔助的方法後就能夠開始下面的學習了
在2-3-4tree中的介紹中也知道了,put的時候,當前節點若是是4-nodes的話就沒有位置留給須要插入的對象了。
因此咱們在put的時候,必定要保證當前的節點(currentNode)之後用cn來表示。cn必須不是4-nodes, 若是是4-nodes的話就用flipColor把4-node變成3個2-node
假設咱們要插入10 7 6 3 這4個對象, 大片動態圖,燃燒的經費。
附上代碼
public void put(K k, V v) { root = put(root, k, v); root.color = BLACK; } private Node put(Node cn, K k, V v) { if (cn == null) return new Node(k, v, 1, RED); if(isRed(cn.left) && isRed(cn.right)) split4Node(cn);//是4節點的話 就split int cmp = k.compareTo(cn.k); if (cmp > 0) cn.right = put(cn.right, k, v); // k > node.k go right else if (cmp < 0) cn.left = put(cn.left, k, v); else cn.v = v; //hit //following code is to fix the tree on the way up if (isRed(cn.right) && !isRed(cn.left)) cn = rotateLeft(cn); // right leaning 3nodes的時候 須要變成 left leaning if (isRed(cn.left) && isRed(cn.left.left)) cn = rotateRight(cn); //變成了一個4節點 cn.size = size(cn.left) + size(cn.right) + 1; return cn; }
樹的get方法其實很簡單,就是判斷key是否是相等,若是相等就return 這個值。
public V get(K k) { return get(root, k); } //cn means currentNode private V get(Node cn, K k) { if (cn == null) return null; // not find the key int cmp = k.compareTo(cn.k); if(cmp > 0) return get(cn.right, k); else if (cmp < 0) return get(cn.left, k); else return cn.v; // hit }
刪除能夠說是最難的了把,基本的思想就是,cn節點(當前節點)不會是2-node,(若是root節點是2-nodes,咱們須要把root節點變成紅節點)
保證其中一個子節點不是2節點(這個保證須要看刪除的節點位於當前節點的哪裏,好比刪除的節點比cn節點小,因此接下來咱們會往left走,因此要保證left節點不是2-node)。由於2節點若是刪除了的話就不會平衡。因此必需要把紅節點從root一步一步carry下去。
先實現一個deleteMin方法,咱們要把紅節點帶向左邊。想想,紅節點帶向左邊後,若是左邊有節點刪除了可能沒辦法保持平衡,紅節點能夠變成黑節點,代替剛纔被刪除的節點。經過這樣子能夠保證左邊的樹是必定會平衡的。
deleteMin的幾種狀況
1. , 如今能夠直接刪除掉。也不會影響平衡。刪除了後3節點變成了2節點。
2., 須要繼續向左走,可是左子節點是2-node,必需要想辦法變成不是2-node。因此須要把父親節點和兄弟節點和cn節點。整合在一塊兒變成4-node
3. , 須要繼續向左走,可是左子節點是2-node,必需要想辦法變成不是2-node。發現兄弟節點是否是2-nodes。因此把兄弟節點借一個node過來
2和3這2種狀況總結在一塊兒就是moveRedLeft的代碼
public void deleteMin() { //保證了root節點不是2nodes if (!isRed(root.left) && !isRed(root.right)) root.color = RED; root = deleteMin(root); root.color = BLACK; } public Node deleteMin(Node cn) { if (cn.left == null) return null; if (!isRed(cn.left) && !isRed(cn.left.left)) //判斷左邊子節點是否是2node,是的話就須要把Red帶下去 cn = moveRedLeft(cn); cn.left = deleteMin(cn.left); return fixup(cn); } private Node fixup(Node h) { if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h); //右傾 if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h); h.size = size(h.left) + size(h.right) + 1; //right the size return h; }
隨意的delete方法。按照下面的圖說一下,基本的思想。
首先刪除D。從H開始,H不是2-node(右邊有一個紅節點),D小於H,左往H的左邊找
找到了D。D不是2-node且是紅節點。找到了D,處理辦法是找到右樹中最小的值,發現是E, 把最小的值賦值給當前的node
因此咱們要往右邊走。可是發現右邊節點F是2-node。因此咱們要把紅色的鏈接往右邊帶。
flipColor(D), B D 和 F就變成了一個4-node(能夠本身作一下圖看看是否是這樣子的)。 這時候紅連接也往右邊帶了。
如今到了F 節點。發現F 的左節點是 2-node。把紅色的連接往左邊帶。
flipColor(F),這個時候F 和 E 和 G ,變成了一個4-node。 瓜熟蒂落刪除左邊的節點。沒有影響平衡。
接着原路返回,修復節點。
public void delete(K k) { if (k == null) throw new IllegalArgumentException("argument to delete() is null"); if (!contains(k)) return; if (!isRed(root.left) && !isRed(root.right)) root.color = RED; root = delete(root, k); if (root != null) root.color = BLACK; } public boolean contains(K k) { return get(k) != null; } private Node delete(Node cn, K k) { if (cn == null) return null; int cmp = k.compareTo(cn.k); if (cmp < 0) { // k < node.k go left if (!isRed(cn.left) && !isRed(cn.left.left)) //保證了下一個左元素不是2nodes cn = moveRedLeft(cn); cn.left = delete(cn.left, k); } else if (cmp > 0) { // k > node.k go right if (isRed(cn.left) && !isRed(cn.right)) //若是是3節點的話須要 rotate 把red轉到右邊 cn = rotateRight(cn); if (!isRed(cn.right) && !isRed(cn.right.left)) //保證下一個右節點不是2nodes cn = moveRedRight(cn); cn.right = delete(cn.right, k); } else { //hit if (isRed(cn.left) && !isRed(cn.right)) cn = rotateRight(cn); if (k.compareTo(cn.k) == 0 && (cn.right == null)) //find null just return null return null; if (!isRed(cn.right) && !isRed(cn.right.left)) //保證下一個右節點不是2nodes cn = moveRedRight(cn); if (k.compareTo(cn.k) == 0) { Node x = min(cn.right); cn.k = x.k; cn.v = x.v; cn.right = deleteMin(cn.right); } else cn.right = delete(cn.right, k); } return fixup(cn); }
具體的實現能夠參考一下https://github.com/Cheemion/algorithms/blob/master/src/com/algorithms/tree/LeftLeaningRedBlackTree.java
可能有地方說的不清楚哈。見諒,能夠留言我有不清楚的地方