紅黑樹插入時的自平衡
紅黑樹實質上是一棵自平衡的二叉查找樹,引入帶顏色的節點也是為了方便在進行插入或刪除操作時,如果破壞了二叉查找樹的平衡性能通過一系列變換保持平衡。
紅黑樹的性質
- 每個節點要么是紅色,要么是黑色
- 根節點必須是黑色
- 兩個紅色節點不能相連
- 從根節點出發到達任意葉子節點經過的黑色節點個數相同
紅黑樹的數據結構
紅黑樹實質上是一顆二叉查找樹,左子樹的值小于根節點的值,右子樹的值大于根節點的值。
public class RedBlackTree {private static int BLACK = 1;private static final int RED = 0;private static Node root;private static class Node {private int color = RED;private int data;private Node left;private Node right;private Node parent;Node(int data) {this.data = data;}}
}
紅黑樹的插入
插入的節點默認是紅色的(要不然全是黑色節點它也滿足紅黑樹的定義,不過就沒意義了);
由于紅黑樹是一顆二叉查找樹,所以它的插入可以使用遞歸(先不考慮破壞紅黑樹的結構)
/*** 通過遞歸往紅黑樹中插入一個新節點* @param root 要插入的樹的根節點* @param data 新節點的值*/private void insert(Node root, int data) {if(root.data > data) {if(root.left == null) {Node node = new Node(data);root.left = node;node.parent = root;} else {insert(root.left, data);}}else {if(root.right == null) {Node node = new Node(data);root.right = node;node.parent = root;} else {insert(root.right, data);}}}
調整結構
新插入節點后,可能破壞紅黑樹的定義,雖然紅黑樹的定義有四條,前兩條都是確定了的,不會因為新添加節點而被破壞,只需要關注第三條就可以了(滿足前三條第四條就會自然滿足)
/*** 判斷插入新節點后紅黑樹結構是否需要變化* 根據紅黑樹的定義,兩個紅色節點不能連接* @param root 插入的新節點* @return 返回true表示插入新節點后破壞了紅黑樹的結構,* 需要通過旋轉變色來糾正,否則不需要修改。*/private boolean checkStruct(Node root) {return root.color == RED && root.parent.color == RED;}
所以只要新插入的節點的父節點是紅色,就需要調整結構。調整結構的辦法有三種:
1. 變色
就是把紅色變為黑色,黑色變為紅色
2. 左旋
以節點C為軸左旋的步驟:
- 將C的父節點A沉下來,C升上去作為新的父節點
- 將原來C的左子樹掛到A的右子樹上
- 其他不變
/*** 左旋* - 旋轉前的右子節點變成旋轉后的父節點* - 旋轉前的父節點(軸)變為旋轉后父節點的左子節點* - 旋轉前軸的右子節點的右子節點旋轉后變為軸的右子節點* - 旋轉前右子節點的左子樹變成旋轉后左子節點的右子樹* - 其他不變* @param node 以該節點為軸旋轉*/private static void leftSpin(Node node) {Node nextFather = node.right;nextFather.parent = node.parent;node.right = node.right.left;nextFather.left = node;connectParent(node, nextFather);}
3. 右旋
右旋和左旋正好相反
以B為軸右旋的步驟:
- 將B的父節點A沉下來,B升上去作為新的父節點
- 將原來B的右子樹接到新的A的左子樹的位置
/*** 將變換后的樹和它上面的節點連接* @param node 變換前的軸* @param nextFather 變換后的子樹*/private static void connectParent(Node node, Node nextFather) {// 如果變換的是根節點,就把root賦值成變換后的節點if(node.parent != null) {if(node.parent.data > node.data) {node.parent.left = nextFather;} else {node.parent.right = nextFather;}} else {RedBlackTree.root = nextFather;}node.parent = nextFather;} /*** 右旋* - 旋轉前的左子節點變成旋轉后的父節點* - 旋轉前左子節點的右子樹變成旋轉后右子節點的左子樹* @param node 旋轉軸。*/private static void rightSpin(Node node) {Node nextFather = node.left;nextFather.parent = node.parent;node.left = node.left.right;nextFather.right = node;connectParent(node, nextFather);}
根據新插入節點位置的不同情況,節點調整有五種不同的方案:
1. 父節點和叔叔節點均為紅色
如果新插入節點的父節點和叔叔節點都是紅色,只需要將父節點和叔叔節點變為黑色,祖父節點變為紅色即可。
如果祖父節點是根節點,祖父節點保持黑色。
ONLY_CHANGE_COLOR {/*** 適用于:* - 父節點和叔叔節點都為紅色的情況;* 具體方法:* - 把父節點和叔叔節點的顏色變為黑色,* - 爺爺節點的顏色變為紅色* - 如果爺爺節點為根節點,爺爺節點顏色恢復黑色* @param node 當前新修改的節點*/@Overridepublic void way(Node node) {node.parent.parent.left.color = BLACK;node.parent.parent.right.color = BLACK;if(node.parent.parent.parent != null){node.parent.parent.color = RED;}}},
2. 叔叔節點不存在或為黑色,父節點位于祖父節點的左子樹
2.1 當前節點位于左子樹
調整辦法:
- 將父節點設置為黑色
- 經祖父節點設置為紅色
- 對祖父節點進行右旋
RIGHT_SPIN_CHANGE_COLOR {/*** 適用于:* - 無叔叔節點或叔叔節點為黑色* - 父節點位于祖父節點的左子樹* - 新節點位于父節點左子樹的情況* 具體方法:* - 將當前節點的父節點設置為黑色* - 將當前節點的祖父節點設置為紅色* - 對祖父節點進行右旋* @param node 當前節點*/@Overridepublic void way(Node node) {node.parent.color = BLACK;node.parent.parent.color = RED;Solution.rightSpin(node.parent.parent);}},
2.2 當前節點位于右子樹
如果當前節點位于右子樹,需要先對它的父節點進行左旋得到2.1的情況,再按2.1進行變換
LEFT_SPIN_WITH_RIGHT_SPIN {/*** 適用于:* - 無叔叔節點或叔叔節點為黑色* - 父節點位于祖父節點的左子樹* - 新節點位于父節點右子樹的情況* 具體方法:* - 對當前節點的父節點進行左旋* - 執行 RIGHT_SPIN_CHANGE_COLOR* @param node 當前節點*/@Overridepublic void way(Node node) {Solution.leftSpin(node.parent);RIGHT_SPIN_CHANGE_COLOR.way(node.left);}},
3.叔叔節點不存在或為黑色,父節點在祖父節點的右子樹
與上面的情況正好相反
3.1 當前節點位于父節點的右子樹上
調整步驟:
- 將父節點變為黑色
- 將祖父節點變為紅色
- 對祖父節點左旋
LEFT_SPIN_CHANGE_COLOR {/*** 適用于:* - 無叔叔節點或叔叔節點為黑色* - 父節點位于祖父節點的右子樹* - 新節點位于父節點右子樹的情況* 具體方法:* - 將當前節點的父節點設置為黑色* - 將當前節點的祖父節點設置為紅色* - 對祖父節點進行左旋* @param node 當前節點*/@Overridepublic void way(Node node) {node.parent.color = BLACK;node.parent.parent.color = RED;Solution.leftSpin(node.parent.parent);}},
3.2 當前節點位于左子樹
調整步驟:
- 對當前節點的父節點進行右旋
- 執行3.1的步驟
RIGHT_SPIN_LEFT_SPIN {/*** 適用于:* - 無叔叔節點或叔叔節點為黑色* - 父節點位于祖父節點的右子樹* - 新節點位于父節點左子樹的情況* 具體方法:* - 對當前節點的父節點進行右旋* - 執行 LEFT_SPIN_CHANGE_COLOR* @param node 當前節點*/@Overridepublic void way(Node node) {Solution.rightSpin(node.parent);LEFT_SPIN_CHANGE_COLOR.way(node.right);}},
完整代碼
package Note.cistern;public class RedBlackTree {private static int BLACK = 1;private static final int RED = 0;private static Node root;private static class Node {private int color = RED;private int data;private Node left;private Node right;private Node parent;Node(int data) {this.data = data;}@Overridepublic String toString() {return "Node{" +"data=" + data +", color=" + color +", left=" + left +", right=" + right +'}';}}interface SolutionInterface {void way(Node node);}enum Solution implements SolutionInterface{ONLY_CHANGE_COLOR {/*** 適用于:* - 父節點和叔叔節點都為紅色的情況;* 具體方法:* - 把父節點和叔叔節點的顏色變為黑色,* - 爺爺節點的顏色變為紅色* - 如果爺爺節點為根節點,爺爺節點顏色恢復黑色* @param node 當前新修改的節點*/@Overridepublic void way(Node node) {node.parent.parent.left.color = BLACK;node.parent.parent.right.color = BLACK;if(node.parent.parent.parent != null){node.parent.parent.color = RED;}}},RIGHT_SPIN_LEFT_SPIN {/*** 適用于:* - 無叔叔節點或叔叔節點為黑色* - 父節點位于祖父節點的右子樹* - 新節點位于父節點左子樹的情況* 具體方法:* - 對當前節點的父節點進行右旋* - 執行 LEFT_SPIN_CHANGE_COLOR* @param node 當前節點*/@Overridepublic void way(Node node) {Solution.rightSpin(node.parent);LEFT_SPIN_CHANGE_COLOR.way(node.right);}},LEFT_SPIN_CHANGE_COLOR {/*** 適用于:* - 無叔叔節點或叔叔節點為黑色* - 父節點位于祖父節點的右子樹* - 新節點位于父節點右子樹的情況* 具體方法:* - 將當前節點的父節點設置為黑色* - 將當前節點的祖父節點設置為紅色* - 對祖父節點進行左旋* @param node 當前節點*/@Overridepublic void way(Node node) {node.parent.color = BLACK;node.parent.parent.color = RED;Solution.leftSpin(node.parent.parent);}},LEFT_SPIN_WITH_RIGHT_SPIN {/*** 適用于:* - 無叔叔節點或叔叔節點為黑色* - 父節點位于祖父節點的左子樹* - 新節點位于父節點右子樹的情況* 具體方法:* - 對當前節點的父節點進行左旋* - 執行 RIGHT_SPIN_CHANGE_COLOR* @param node 當前節點*/@Overridepublic void way(Node node) {Solution.leftSpin(node.parent);RIGHT_SPIN_CHANGE_COLOR.way(node.left);}},RIGHT_SPIN_CHANGE_COLOR {/*** 適用于:* - 無叔叔節點或叔叔節點為黑色* - 父節點位于祖父節點的左子樹* - 新節點位于父節點左子樹的情況* 具體方法:* - 將當前節點的父節點設置為黑色* - 將當前節點的祖父節點設置為紅色* - 對祖父節點進行右旋* @param node 當前節點*/@Overridepublic void way(Node node) {node.parent.color = BLACK;node.parent.parent.color = RED;Solution.rightSpin(node.parent.parent);}},PASS {/*** 異常情況* @param node 當前節點*/@Overridepublic void way(Node node) {}};/*** 左旋* - 旋轉前的右子節點變成旋轉后的父節點* - 旋轉前的父節點(軸)變為旋轉后父節點的左子節點* - 旋轉前軸的右子節點的右子節點旋轉后變為軸的右子節點* - 旋轉前右子節點的左子樹變成旋轉后左子節點的右子樹* - 其他不變* @param node 以該節點為軸旋轉*/private static void leftSpin(Node node) {Node nextFather = node.right;nextFather.parent = node.parent;node.right = node.right.left;nextFather.left = node;connectParent(node, nextFather);}/*** 將變換后的樹和它上面的節點連接* @param node 變換前的軸* @param nextFather 變換后的子樹*/private static void connectParent(Node node, Node nextFather) {// 如果變換的是根節點,就把root賦值成變換后的節點if(node.parent != null) {if(node.parent.data > node.data) {node.parent.left = nextFather;} else {node.parent.right = nextFather;}} else {RedBlackTree.root = nextFather;}node.parent = nextFather;}/*** 右旋* - 旋轉前的左子節點變成旋轉后的父節點* - 旋轉前左子節點的右子樹變成旋轉后右子節點的左子樹* @param node 旋轉軸。*/private static void rightSpin(Node node) {Node nextFather = node.left;nextFather.parent = node.parent;node.left = node.left.right;nextFather.right = node;connectParent(node, nextFather);}}public RedBlackTree(int rootData){root = new Node(rootData);root.color = BLACK;}/*** 通過一個整形數組快速構建紅黑樹* @param data 要創建樹的元素*/public RedBlackTree(int [] data) {assert data.length >= 1: "data的長度至少為1";root = new Node(data[0]);root.color = BLACK;for (int i = 1; i < data.length; i++) {this.append(data[i]);}}/*** 判斷插入新節點后紅黑樹結構是否需要變化* 根據紅黑樹的定義,兩個紅色節點不能連接* @param root 插入的新節點* @return 返回true表示插入新節點后破壞了紅黑樹的結構,* 需要通過旋轉變色來糾正,否則不需要修改。*/private boolean checkStruct(Node root) {return root.color == RED && root.parent.color == RED;}/*** 確定該以哪種方式變換當前樹,使之滿足紅黑樹的條件* @param node 當前新加入的,使紅黑樹結構錯誤的節點* @return 返回解決辦法*/private Solution determineSolution(Node node) {int fatherColor = node.parent.color;int uncleColor;// 如果父親是爺爺的右子樹if(node.parent.data > node.parent.parent.data){uncleColor = node.parent.parent.left == null? BLACK: node.parent.parent.left.color;if(fatherColor == RED && uncleColor == BLACK){if(node.data < node.parent.data){return Solution.RIGHT_SPIN_LEFT_SPIN;} else {return Solution.LEFT_SPIN_CHANGE_COLOR;}}} else {uncleColor = node.parent.parent.right == null? BLACK: node.parent.parent.right.color;if(fatherColor == RED && uncleColor == BLACK){// 插入的節點是父節點的左子節點if(node.data < node.parent.data){return Solution.RIGHT_SPIN_CHANGE_COLOR;} else {return Solution.LEFT_SPIN_WITH_RIGHT_SPIN;}}}// 如果父節點和叔叔節點都是紅色if(fatherColor == RED && uncleColor == RED){return Solution.ONLY_CHANGE_COLOR;}return Solution.PASS;}private void changeTree(Node node) {Solution solution = determineSolution(node);solution.way(node);if(node.parent != null && node.parent.parent != null && checkStruct(node.parent.parent)) {changeTree(node.parent.parent);}}/*** 通過遞歸往紅黑樹中插入一個新節點* @param root 要插入的樹的根節點* @param data 新節點的值*/private void insert(Node root, int data) {if(root.data > data) {if(root.left == null) {Node node = new Node(data);root.left = node;node.parent = root;if(checkStruct(node)) {changeTree(node);}} else {insert(root.left, data);}}else {if(root.right == null) {Node node = new Node(data);root.right = node;node.parent = root;if(checkStruct(node)) {changeTree(node);}} else {insert(root.right, data);}}}public Node append(int data) {insert(root, data);return root;}public Node append(int[] data) {for (int datum : data) {append(datum);}return root;}public Node getRoot(){return root;}public static void main(String[] args) {int[] data = {16, 8, 11, 13, 15, 17, 22, 25, 27};RedBlackTree redBlackTree = new RedBlackTree(data);System.out.println(redBlackTree.getRoot());}}