前言:java
記得在大一懵懵懂懂的時候就接觸了紅黑樹的算法。但因爲當時內功尚淺,沒法將其內化,只是以爲它很神奇,是個好算法,設計它的人很牛!現今重拾起這個算法,不得再也不次被它的精妙所折服!編寫本文,是但願以鄙人的理解將紅黑樹算法的精髓向博客園的園友陳述一番,也但願對其有獨特看法的朋友能不吝賜教。準備好了的話,咱們就開始吧~算法
--------------------------------------------this
Part I:BSTspa
做爲開始,咱們得先談談二叉樹(Binary Search Tree)。設計
1.假設存在一個以下簡單的鍵值字符表:code
Key Valueblog
A 2遞歸
C 1ip
B 6get
B 11
H 1
J 3
要求你按照讀入順序創建這樣一棵二叉查找樹,建好以後要求可以進行對於的查詢操做。
源於二分查找的思想,二叉查找樹有這樣一個特色:
對於樹上的任意一個結點,若是它有左右子結點的話,其結點大小一定大於其左子結點且小於其右子結點。
2.查找get(key)
因爲單獨創建一個二叉查找樹起初很差分析,咱們就假設如今有一棵已經構造好二叉查找樹。咱們僅須要思考如何在其上面進行查找操做。
根據二分查找的思想,咱們能夠按照下面步驟進行查找:
Step1:將須要查找的key與二叉查找樹的當前根節點的key做比較,獲得比較結果後進行下面的step2;
Step2:若查找的key比根節點的key小,則遞歸從根節點的左子樹進行一樣的查找key操做;若比根節點的key大,則遞歸地從根節點的右子樹進行一樣的查找key操做;
若,查找的key恰好等於當前根節點的key,則返回當前key對應的value,結束!
3.插入put(key,value)
假設如今已經有了一個二叉查找樹,咱們要插入一對鍵值(key-value)。源於查找過程的經驗,咱們知道插入操做其實近似於查找操做。由於,咱們插入的時候一樣是拿key跟當前根節點的key比較,以後再肯定是往左走仍是右走,或者是更新當前值(key=root.key時)。
Code:
1 package com.gdufe.binarysearchtree; 2 3 import java.io.File; 4 import java.util.Scanner; 5 6 public class BST<Key extends Comparable<Key>, Value> { 7 8 Node root; // 維護根節點 9 10 class Node { // 二叉樹的結點 11 Key key; 12 Value value; 13 Node left, right; 14 15 public Node(Key key, Value value) { // 初始化結點 16 this.key = key; 17 this.value = value; 18 } 19 } 20 21 public Value get(Key key) { 22 return get(root, key); 23 } 24 25 //查找操做 26 public Value get(Node x, Key key) { 27 if (x == null) 28 return null; 29 int cmp = key.compareTo(x.key); 30 if (cmp < 0) 31 return get(x.left, key); 32 else if (cmp > 0) 33 return get(x.right, key); 34 else 35 return x.value; 36 } 37 38 public void put(Key key, Value value) { 39 root = put(root, key, value); 40 } 41 //插入操做 42 public Node put(Node x, Key key, Value value) { 43 if (x == null) 44 return new Node(key, value); 45 int cmp = key.compareTo(x.key); 46 if (cmp < 0) 47 x.left = put(x.left, key, value); 48 else if (cmp > 0) 49 x.right = put(x.right, key, value); 50 else 51 x.value = value; 52 return x; 53 } 54 55 public static void main(String[] args) throws Exception { 56 Scanner input = new Scanner(new File("data_BST.txt")); 57 BST<String, Integer> bst = new BST<String, Integer>(); 58 while (input.hasNext()) { 59 String key = input.next(); 60 int value = input.nextInt(); 61 bst.put(key, value); 62 } 63 System.out.println(bst.get("H")); 64 System.out.println(bst.get("B")); 65 } 66 67 }
輸出結果:
1 11
分析:
插入或查找時,有可能最壞狀況樹不斷惡意生長(垂直生長),此時的時間複雜度爲:O(N),平均的時間複雜度爲:O(lgN)
----------------------------------------
Part II:RedBlackBST
1. 2-3樹
在二叉樹的基礎之上,咱們引入了平衡2-3樹。簡單地說,二叉樹每一個結點至多隻能有2個子結點(稱爲「2結點」),而如今咱們能夠經過將2個結點「綁」在一塊兒造成一個有3個子結點的「3結點」。見下圖:
因爲查找操做較簡單,咱們重點討論它的插入操做。一樣基於上面所給的數據,見圖:
------------------------------------------------
2.紅黑二叉查找樹(簡稱「紅黑樹」)
那麼問題來了,咱們該如何實現這樣一棵2-3樹呢?正常的思惟固然是但願在原先的Node結構中進行重構,再構造一個嵌套的BIGNode。但巧妙的地方就在這裏,咱們能夠以以前的二叉查找樹爲基礎,把結點之間的連接分爲「紅連接」和「黑連接」。其中,紅鏈接經過鏈接兩個2結點組成3結點,黑鏈接是以前二叉查找樹的普通鏈接。爲了方便,咱們不妨把3結點統一表示爲一條左斜的紅色連接。如圖:
上面經過定義紅黑樹的規則實現咱們等價的2-3樹結構,因而紅黑樹也就有了下面等價的定義。
含有紅黑連接而且知足下列條件的二叉查找樹:
1)紅連接均爲左連接
2)沒有任何結點同時和2條紅連接相連
3)任意空連接到根節點路徑上的黑連接數相同
---------------------------------------------
既然從上面的闡述中,咱們得出 了「紅黑樹≈2-3樹",咱們咱們緊接着用上面的數據構建咱們的紅黑樹,見圖:
其中,存在着3個關鍵操做:
左旋:當結點出現左子結點爲黑,右子結點爲紅時,進行左旋轉;
右旋:當結點出現左子結點以及左子結點的左結點均爲紅時,進行右旋轉;
變色:當結點出現左右子結點均爲紅時,進行變色操做(2個子連接均變黑色,並將紅連接向上傳遞!)
具體,見下圖:
Code:
1 package com.gdufe.binarysearchtree; 2 3 import java.io.File; 4 import java.util.Scanner; 5 6 public class RedBlackTree<Key extends Comparable<Key>, Value> { 7 8 Node root; // 維護根節點 9 10 final static boolean RED = true; 11 final static boolean BLACK = false; 12 13 class Node { // 二叉樹的結點 14 Key key; 15 Value value; 16 boolean color; 17 Node left, right; 18 19 public Node(Key key, Value value, boolean color) { // 初始化結點 20 this.key = key; 21 this.value = value; 22 this.color = color; 23 } 24 } 25 26 public Value get(Key key) { 27 return get(root, key); 28 } 29 30 // 右旋 31 public Node rotateRight(Node h) { 32 Node x = h.left; 33 h.left = x.right; 34 x.right = h; 35 x.color = h.color; 36 h.color = RED; 37 return x; 38 } 39 40 // 左旋 41 public Node rotateLeft(Node h) { 42 Node x = h.right; 43 h.right = x.left; 44 x.left = h; 45 x.color = h.color; 46 h.color = RED; 47 return x; 48 } 49 50 // 變色處理 51 public void flipColors(Node h) { 52 h.left.color = BLACK; 53 h.right.color = BLACK; 54 h.color = RED; 55 } 56 public boolean isRed(Node x){ 57 if(x==null) return false; 58 else return x.color; 59 } 60 public Value get(Node x, Key key) { 61 if (x == null) 62 return null; 63 int cmp = key.compareTo(x.key); 64 if (cmp < 0) 65 return get(x.left, key); 66 else if (cmp > 0) 67 return get(x.right, key); 68 else 69 return x.value; 70 } 71 72 public void put(Key key, Value value) { 73 root = put(root, key, value); 74 root.color = BLACK; 75 } 76 77 public Node put(Node x, Key key, Value value) { 78 if (x == null) 79 return new Node(key, value, RED); // 添加的結點連接爲紅色 80 int cmp = key.compareTo(x.key); 81 if (cmp < 0) 82 x.left = put(x.left, key, value); 83 else if (cmp > 0) 84 x.right = put(x.right, key, value); 85 else { 86 x.value = value; 87 } 88 // 判斷是否須要左旋,右旋,變色操做 89 if (x != null) { 90 if (!isRed(x.left) && isRed(x.right)) 91 x = rotateLeft(x); 92 if (isRed(x.left) && isRed(x.left.left)) 93 x = rotateRight(x); 94 if (isRed(x.left ) && isRed(x.right)) 95 flipColors(x); 96 } 97 98 return x; 99 } 100 101 public static void main(String[] args) throws Exception { 102 Scanner input = new Scanner(new File("data_BST.txt")); 103 RedBlackTree<String, Integer> bst = new RedBlackTree<String, Integer>(); 104 while (input.hasNext()) { 105 String key = input.next(); 106 int value = input.nextInt(); 107 bst.put(key, value); 108 } 109 System.out.println(bst.get("H")); 110 System.out.println(bst.get("B")); 111 } 112 113 }
輸出結果:
1 11
分析:
有了上面3個關鍵操做以後,咱們保證了樹的平衡性,即樹不會再惡意生長。插入N個結點後,樹的高度爲:O(lgN)~O(2*lgN) (思考一下?)。因此,咱們獲得插入和查找的總體時間複雜度均降爲:O(lgN)。
--------------------------
結語:
不得不認可,紅黑樹算法堪稱算法研究領域的非凡之做。在現今的汪洋信息時代,存在着上億的數據。可是,當咱們用紅黑樹算法對其進行動態的增長和查找時,僅僅須要幾十次操做便可完事兒,怎能不讓人拍案叫絕!!