基於《算法》一書的紅黑樹的插入和刪除。看過不一樣的教材,也有不一樣的實現方式,可是最終的結果也大體相同,感受這個比較容易理解,就採用這種的方式來進行簡單實現。java
private static final boolean RED = true; private static final boolean BLACK = false; /** * 紅黑樹的節點結構 * 保存的值,左節點,右節點以及顏色(true爲紅色,false爲黑色) * 默認添加一個紅節點 * * @param <E> */ static final class RedBlackTreeNode<E extends Comparable<E>> { E val; RedBlackTreeNode<E> left; RedBlackTreeNode<E> right; boolean color = RED; RedBlackTreeNode(E val) { this.val = val; } }
這裏簡單的定義了一下紅黑樹,而且只有節點,並非map這樣的k-v結構。若是定義k-v結構到時比較的時候比較k便可。node
用了泛型,而且要支持比較(繼承自Comparable),否則沒法比較大小進行插入。算法
而後定義了一個值,左節點和右節點,而後顏色默認爲紅色。函數
再增長一個構造函數便可優化
主要作的就是插入和刪除節點,爲了方便查看是否符合添加了一箇中序遍歷的打印方法this
public class RedBlackTree<E extends Comparable<E>> { RedBlackTreeNode<E> head; public void add(E e) { ... } public void remove(E e){ ... } public void printTree(){ ... } }
定義這些公共方法來對外部調用,具體實現能夠放到私有方法中。debug
在紅黑樹的變換中主要有三個:左旋,右旋,變色。接下來咱們就來實現這三個方法。3d
旋轉操做能夠保持紅黑樹的兩個重要性質:有序性和完美平衡性code
private RedBlackTreeNode<E> rotateLeft(RedBlackTreeNode<E> node) { //變換位置 RedBlackTreeNode<E> result = node.right; node.right = result.left; result.left = node; //換色 result.color = node.color; node.color = RED; return result; }
當右節點爲紅色, 左節點爲空或者黑色時,須要進行左旋操做。blog
首先定義一個變量存儲右節點,而後將右節點的左節點做爲父節點(傳入參數)的右節點。這時與右節點(定義的變量)斷開了關聯。
而後將定義的變量(右節點)的左節點設置爲參數節點(左節點以前已經賦值到參數節點的右節點上)。
還需進行一步換色,將定義變量的顏色設置爲父節點的顏色(不影響上一級的操做),而後將父節點設置爲紅色。
將定義的變量做爲父節點返回。
private RedBlackTreeNode<E> rotateRight(RedBlackTreeNode<E> node) { //變換位置 RedBlackTreeNode<E> result = node.left; node.left = result.right; result.right = node; //變色 result.color = node.color; node.color = RED; return result; }
當左節點爲紅色,左節點的左節點也爲紅色時,須要進行右旋操做。
這個與左旋基本相似,將左節點做爲父節點返回,而後對其餘節點也要確保不丟失,還有換色操做不能影響紅黑樹的特性。
private void flipColor(RedBlackTreeNode<E> node) { node.left.color = BLACK; node.right.color = BLACK; node.color = RED; }
當兩個子節點都爲紅色時,須要進行換色
讓兩個子節點變爲黑色,父節點變爲紅色
剛纔咱們在上面有提到,須要判斷節點的顏色,雖然咱們在節點的類型中定義了color
屬性,可是考慮到其餘狀況仍是寫一個方法來完成判斷顏色的功能:
private boolean isRed(RedBlackTreeNode<E> node) { if (node == null) { return false; } return node.color; }
當節點爲空時返回false即爲黑色,否則判斷節點的color屬性是否爲紅色。
還有一箇中序打印的方法
public void printTree() { print(head); } private void print(RedBlackTreeNode<E> node) { if (node == null) { return; } print(node.left); System.out.print(node.val + " "); print(node.right); }
在對外部的方法中調用了內部方法,傳入了頭結點。
因爲是中序遍歷,因此須要先遍歷左節點,而後打印本身,而後遍歷右節點。這是一個遞歸操做,因此須要定義終止條件:當節點爲空時就返回。
public void add(E val) throws IllegalAccessException { if (val == null) { throw new IllegalAccessException("不能添加null值"); } head = addVal(val, head); //最終將根節點設置爲黑色 head.color = BLACK; } private RedBlackTreeNode<E> addVal(E val, RedBlackTreeNode<E> node) { //達到最終的節點,若是爲空則新建一個紅色的節點 if (node == null) { return new RedBlackTreeNode<E>(val); } if (val.compareTo(node.val) < 0) { //若是小,則左節點爲 新建節點返回的節點(可能會通過調整) node.left = addVal(val, node.left); } else if (val.compareTo(node.val) > 0) { //若是大,則右節點爲 新建節點後返回的節點(可能會通過調整) node.right = addVal(val, node.right); } else { //值相等 return node; } //判斷平衡等操做 if (isRed(node.right) && !isRed(node.left)) { //右節點爲紅色,左節點爲空或者黑色時須要進行左旋 node = rotateLeft(node); } if (isRed(node.left) && isRed(node.left.left)) { //左節點爲紅色,左節點的左節點也爲紅色時,須要進行右旋 node = rotateRight(node); } if (isRed(node.left) && isRed(node.right)) { //當兩個子節點都爲紅色時,須要進行變色 flipColor(node); } return node; }
在公共方法中首先進行了一個參數校驗,若是爲空則沒法比較因此就拋出一個異常。
而後調用私有方法進行添加節點:傳入的參數爲要添加的值,樹的頭結點。
在私有方法中首先判斷了傳入的節點是否爲空,若是爲空則新建一個紅色節點返回。
當不爲空時進行大小判斷,判斷是添加在左子樹仍是右子樹上,而後遞歸調用當前方法,傳入要添加的值和左節點或右節點,若是相等則直接返回當期節點便可(不是map不用從新改變value)。而且添加後可能會進行調整,因此須要從新賦值。
接下來就是判斷是否符合紅黑樹的規定,而後進行左旋,右旋,變色等操做。這時也會進行從新調整,因此須要從新賦值。
操做完成後返回到公共方法中。
在公共方法中將頭結點的顏色設置爲黑色,保證紅黑樹的特性。
public void remove(E val) throws IllegalAccessException { if (val == null) { throw new IllegalAccessException("不容許null值操做"); } if (head == null) { throw new IllegalAccessException("樹爲空"); } head = removeVal(val, head); } private RedBlackTreeNode<E> removeVal(E val, RedBlackTreeNode<E> node) throws IllegalAccessException { if (node == null) { throw new IllegalAccessException("val not exist!"); } if (val.compareTo(node.val) < 0) { node.left = removeVal(val, node.left); } else if (val.compareTo(node.val) > 0) { node.right = removeVal(val, node.right); } else { if (node.right != null) { node = getRightMinNode(node); } else if (node.left != null) { node = getLeftMaxNode(node); } else { node = null; } } //判斷平衡等操做 if (node != null) { //判斷平衡等操做 if (isRed(node.right) && !isRed(node.left)) { //右節點爲紅色,左節點爲空或者黑色時須要進行左旋 node = rotateLeft(node); } if (isRed(node.left) && isRed(node.left.left)) { //左節點爲紅色,左節點的左節點也爲紅色時,須要進行右旋 node = rotateRight(node); } if (isRed(node.left) && isRed(node.right)) { //當兩個子節點都爲紅色時,須要進行變色 flipColor(node); } } return node; }
在公共方法中進行參數校驗,若是刪除的是null,則拋出異常。
而後當樹爲空時也不能進行刪除操做。刪除操做也可能會進行結構修改,因此也須要進行從新賦值。
用參數與當前節點比較,若是小則遞歸傳入左節點,若是大則遞歸傳入右節點,當節點爲空時表示要刪除的節點再也不樹中,我在這裏是拋出了異常,可能有些不太穩當。
若是與當前節點相同,則刪除當前節點。這時就暴露了一個問題,噹噹前節點有子節點時若是進行刪除。其實這也分爲幾種狀況即上面代碼中的第20-26行:
當前節點無子節點,刪除當前節點即置爲null便可。
將右子節點的最小節點做爲當前節點的替代,而後刪除這個最小節點。
/** * 獲取右側樹的最小節點 * * @param node * @return */ private RedBlackTreeNode<E> getRightMinNode(RedBlackTreeNode<E> node) { RedBlackTreeNode<E> parent = node.right; if (parent.left == null) { node.right = parent.right; return parent; } RedBlackTreeNode<E> result = parent.left; //可能有優化的地方 while (result.left != null) { parent = parent.left; result = parent.left; } parent.left = null; return result; }
當右節點爲空時,找到左節點的最大值做爲當前節點的替代,而後刪除這個最大節點。
private RedBlackTreeNode<E> getLeftMaxNode(RedBlackTreeNode<E> node) { RedBlackTreeNode<E> parent = node.left; if (parent.right == null) { node.right = parent.left; return parent; } RedBlackTreeNode<E> result = parent.right; while (result.right != null) { parent = parent.right; result = parent.right; } parent.right = null; return result; }
進行替換後,須要檢查是否符合紅黑樹的特性是否須要左旋,右旋,變色等操做。
public static void main(String[] args) throws IllegalAccessException { RedBlackTree<Integer> redBlackTree = new RedBlackTree<Integer>(); redBlackTree.add(1); redBlackTree.add(3); redBlackTree.add(5); redBlackTree.add(7); redBlackTree.add(9); redBlackTree.add(2); redBlackTree.add(4); redBlackTree.printTree(); System.out.println(); redBlackTree.remove(2); redBlackTree.printTree(); System.out.println(); redBlackTree.remove(11); }
首先咱們依次添加[1,3,5,7,9,2,4]。而後將樹打印,按照預期結果打印出的結果應該是順序的1~9。而後咱們刪除2節點,若是咱們將插入過程畫出來會發現若是刪除2,則會形成1,3兩個紅節點的鏈接,這不符合紅黑樹的規定,因此須要進行調整。而後再次進行打印查看結果是否爲有誤。
最後咱們刪除一個不存在的值,看它是否會報錯。
1 2 3 4 5 7 9 1 3 4 5 7 9 Exception in thread "main" java.lang.IllegalAccessException: val not exist! at RedBlackTree.removeVal(RedBlackTree.java:33) at RedBlackTree.removeVal(RedBlackTree.java:38) at RedBlackTree.removeVal(RedBlackTree.java:38) at RedBlackTree.removeVal(RedBlackTree.java:38) at RedBlackTree.remove(RedBlackTree.java:28) at Test.main(Test.java:21)
經過輸出能夠看出結果符合咱們的要求,而後也能夠經過debug的方法查看刪除2節點後的節點狀況發現與在草稿上手畫版一致。
給出一個剛纔插入的圖畫過程。
刪除2節點後的狀況
本文由博客一文多發平臺 OpenWrite 發佈! 博主郵箱:liunaijie1996@163.com,有問題能夠郵箱交流。