樹篇3-平衡二叉查詢樹之紅黑樹
一、概述
紅黑樹是一種自平衡樹在電腦科學中。二叉樹的每個節點都有一個額外的位,該位通常被解釋為節點的顏色(紅色或黑色)。這些顏色位用於確保樹在插入和刪除期間保持近似平衡。通過以滿足某些屬性的方式用兩種顏色之一繪製樹的每個節點來保留平衡,這些屬性共同限制樹在最壞情況下的不平衡程度。修改樹時,隨後重新排列新樹並重新繪製以恢復著色屬性。這些屬性的設計使得可以有效地執行這種重新排列和重新著色。樹的平衡並不完美,但它足以保證在O(log n)時間內進行搜尋,其中n是樹中元素的總數。插入和刪除操作以及樹重新排列和重新著色也在O(log n)時間內執行。跟蹤每個節點的顏色每個節點僅需要1位資訊,因為只有兩種顏色。該樹不包含任何其他特定於紅黑樹的資料,因此其記憶體佔用量幾乎與經典(無色)二叉搜尋樹相同。在許多情況下,可以儲存額外的資訊,而無需額外的儲存器成本。
二、屬性
1.每個節點不是紅色就是黑色。
2.根節點是黑色。(由根節點始終可以從紅色變成黑色,所以這條規則對分析沒影響)
3.所有葉子(NIL)都是黑的。
4.如果該節點是紅色,則其兩個孩子節點必須是黑色。(黑節點不要求兩個孩子節點一定都是紅)
5.從任意節點到任意可到達的葉子結點(NIL),路徑中所包含的黑節點數目相同。(極其重要,這保證了紅黑樹是自平衡二叉樹)
註釋:
1)屬性4和屬性5保證了紅黑樹為自平衡二叉樹。因此,保證紅黑樹滿足4、5即可保證自平衡。
2)紅黑樹中從根節點到所有葉子節點的路徑中,最長路徑長度小於最短路徑長度的二倍。
3)葉子節點NIL: 以往二叉樹的根節點父指標域、節點的左右孩子域;有時到達樹葉了,就會為空。在紅黑樹種,把空著的指標 域指向NIL節點,作為葉子節點。
三、紅黑樹的節點及整體
/** * 紅黑樹 */ public class RedBlackTree<T extends Comparable<T>> { /** * 紅黑樹節點 */ static class RedBlackNode<T extends Comparable<T>> { T key; // 節點值 boolean red; // 是否是紅色節點 RedBlackNode<T> parent; // 父節點 RedBlackNode<T> leftChild; // 父節點 RedBlackNode<T> rightChild; // 父節點 public RedBlackNode() { } public RedBlackNode(T key) { this(); this.key = key; } public boolean isRed() { return red; } @Override public String toString() { return key + ""; } } private final RedBlackNode<T> NIL = new RedBlackNode<>(); // 哨兵節點(葉子結點) private RedBlackNode<T> root = NIL; // 根節點 private int size; // 元素數量 public RedBlackTree() { // 構造方法 root.parent = NIL; root.leftChild = NIL; root.rightChild = NIL; } // 插入資料 public void insert(T key){} private void insert(RedBlackNode<T> node) {} // 插入修正 private void insertFixup(RedBlackNode<T> node) {} // 右旋操作 private void rightRotate(RedBlackNode<T> node) {} // 左旋操作 private void leftRotate(RedBlackNode<T> node) {} // 判斷節點是否是Nil(哨兵) private boolean isNil(RedBlackNode<T> node) {} // 遍歷 public void preOrderErgodic(){} public void preOrderErgodic(RedBlackNode node){} // 返回樹節點數 public int getSize() {} // 移除節點 public void remove(T t) {} // 移除節點 private void removeNode(RedBlackNode<T> v) {} // 移除節點後修正 private void removeFixup(RedBlackNode<T> node) {} // 獲取後繼 private RedBlackNode<T> treeSuccessor(RedBlackNode<T> waitRemoveNode) {} // 獲取以node為根節點的子樹的最小節點 private RedBlackNode<T> treeMinimum(RedBlackNode<T> node) {} // 根據值查詢節點 private RedBlackNode<T> search(T key) {} }
四、插入操作
向紅黑樹中插入節點分兩步完成:插入資料、根據紅黑樹性質對樹進行修整。插入資料的方式和普通二叉樹插入資料的方式相同,新插入的節點一定是紅色節點;根據紅黑樹性質對樹進行修整,是對紅黑樹進行顏色變換和旋轉,是插入資料後,紅黑樹仍然滿足上面的五條性質。
插入資料:
// 1.資料插入
private void insert(RedBlackNode<T> node) {
RedBlackNode<T> parent = NIL; // index的父節點
RedBlackNode<T> index = root; // 索引
while (!isNil(index)) { // 迴圈遍歷,找到插入位置和父節點
parent = index;
if (node.key.compareTo(index.key) < 0) {
index = index.leftChild;
} else if (node.key.compareTo(index.key) > 0) {
index = index.rightChild;
} else { // 找到相等節點,結束程式
return;
}
}
// 將孩子指向父親
node.parent = parent;
size++;
// 將父親指向孩子,分情況討論:
// 1.父節點是NIL(當前樹為空,沒有根節點)
if (isNil(parent))
root = node;
// 2.父親的左孩子
else if (node.key.compareTo(parent.key) < 0)
parent.leftChild = node;
// 3.父親的右孩子
else
parent.rightChild = node;
// 初始化孩子和顏色(新新增的節點一定是紅色)
node.leftChild = NIL;
node.rightChild = NIL;
node.red = true;
// 2.紅黑樹插入修正
insertFixup(node);
}
紅黑樹修正分一下四種情況:
情況1:新插入節點N為根節點
// 顏色根部始終為黑色
// 情況1:新插入節點N為根節點
root.red = false;
情況2:新插入節點N的父節點為黑色
如果N節點的父親是黑節點,此時插入新節點N對樹沒有任何影響,此時樹依舊滿足紅黑樹的五條性質。因此不要做任何操作。
情況3:新插入節點N的父節點為紅色,叔節點(父親的兄弟節點)為紅色
// 情況3.cousinNode(叔叔節點)是紅節點
if (cousinNode.isRed()) {
node.parent.red = false; // node父節點置為黑
cousinNode.red = false; // node叔節點置為黑
node.parent.parent.red = true; // node的爺爺節點置紅
node = node.parent.parent;
}
情況4:新插入節點N的父節點為紅色,叔節點(父親的兄弟節點)為黑色
// 情況4.cousinNode節點為[黑節點]且為父節點[左孩子]
else if (node == node.parent.leftChild) {
// 重置顏色、以node的爺爺旋轉
node.parent.red = false;
node.parent.parent.red = true;
rightRotate(node.parent.parent);
}
// 情況4.cousinNode節點為[黑節點]且為父節點[右孩子]
else {
// 以node的父親左旋
node = node.parent;
leftRotate(node);
}
完整的插入後修正程式碼如下:
/**
* 插入修正
*
* @param node 插入的節點
*/
private void insertFixup(RedBlackNode<T> node) {
RedBlackNode<T> cousinNode = NIL; //叔節點
// 迴圈修正節點
// 新插入節點的父親為紅色,則不滿足性質4(如果該節點是紅色,則其兩個孩子節點必須是黑色)
while (node.parent.isRed()) {
// node節點的 [父親] 是 [爺爺] 節點的 [左孩子]
if (node.parent == node.parent.parent.leftChild) {
// 初始化node的叔叔(父節點的兄弟)
cousinNode = node.parent.parent.rightChild;
// 情況3.cousinNode(叔叔節點)是紅節點
if (cousinNode.isRed()) {
node.parent.red = false; // node父節點置為黑
cousinNode.red = false; // node叔節點置為黑
node.parent.parent.red = true; // node的爺爺節點置紅
node = node.parent.parent;
}
// 情況4.cousinNode節點為[黑節點]且為父節點[左孩子]
else if (node == node.parent.leftChild) {
// 重置顏色、以node的爺爺旋轉
node.parent.red = false;
node.parent.parent.red = true;
rightRotate(node.parent.parent);
}
// 情況4.cousinNode節點為[黑節點]且為父節點[右孩子]
else {
// 以node的父親左旋
node = node.parent;
leftRotate(node);
}
}
// node節點的 [父親] 是 [爺爺] 節點的 [右孩子]
else {
// 初始化node的叔叔(父節點的兄弟)
cousinNode = node.parent.parent.leftChild;
// 情況1.cousinNode(叔叔節點)是紅節點
if (cousinNode.isRed()) {
node.parent.red = false; // node父節點置為黑
cousinNode.red = false; // node叔節點置為黑
node.parent.parent.red = true; // node的爺爺節點置紅
node = node.parent.parent;
}
// 情況2.cousinNode節點為[黑節點]且為父節點[左孩子]
else if (node == node.parent.leftChild) {
node = node.parent;
// 以node的父親右旋
rightRotate(node);
}
// 情況3.cousinNode節點為[黑節點]且為父節點[右孩子]
else {
// 重置顏色、以node的爺爺旋轉
node.parent.red = false;
node.parent.parent.red = true;
leftRotate(node.parent.parent);
}
}
}
// 顏色根部始終為黑色
// 情況1:新插入節點N為根節點
root.red = false;
}
五、左旋和右旋操作
當插入資料後,需要對樹進行修復使其滿足紅黑樹的五條性質;在修復的過程中,需要對節點的位置進行調整,這是就要使用左旋和右旋操作。
/**
* 左旋操作
*
* @param node 旋轉的軸節點
*/
private void leftRotate(RedBlackNode<T> node) {
RedBlackNode<T> nodeRightChild = node.rightChild; // node的右孩子
node.rightChild = nodeRightChild.leftChild; // 將node的右孩子的左孩子過繼給node,作為它的右孩子
if (!isNil(nodeRightChild.leftChild)) // node的右孩子的左孩子不為NIL
nodeRightChild.leftChild.parent = node; // 將node作為它的父親
nodeRightChild.parent = node.parent; // 將nodeRightChild的父域指向node的父親
// 如果node的父親NIL
if (isNil(node.parent))
root = nodeRightChild;
// node是其父節點的左孩子
else if (node.parent.leftChild == node)
node.parent.leftChild = nodeRightChild;
// node是其父節點的右孩子
else
node.parent.rightChild = nodeRightChild;
// 最後的旋轉
nodeRightChild.leftChild = node;
node.parent = nodeRightChild;
}
/**
* 右旋操作
*
* @param node 旋轉的軸節點
*/
private void rightRotate(RedBlackNode<T> node) {
RedBlackNode<T> nodeLeftChild = node.leftChild; // node左孩子
node.leftChild = nodeLeftChild.rightChild; // 將node的左孩子的右孩子過繼給node,作為它的左孩子
if (!isNil(nodeLeftChild.rightChild)) // node的左孩子的右孩子不為NIL
nodeLeftChild.rightChild.parent = node; // 將node作為它的父親
nodeLeftChild.parent = node.parent; // 將nodeLeftChild的父域指向node的父親
// 如果node的父親NIL
if (isNil(node.parent))
root = nodeLeftChild;
// node是其父節點的左孩子
else if (node.parent.leftChild == node)
node.parent.leftChild = nodeLeftChild;
// node是其父節點的右孩子
else
node.parent.rightChild = nodeLeftChild;
// 最後的旋轉
nodeLeftChild.rightChild = node;
node.parent = nodeLeftChild;
}
六、刪除節點
刪除節點的操作與插入相似,直接上程式碼
// 移除節點
private void removeNode(RedBlackNode<T> v) {
RedBlackNode<T> waitRemoveNode = search(v.key); // 查詢待刪除節點
RedBlackNode<T> successorNode = NIL; // 後繼節點
RedBlackNode<T> successorChild = NIL; // 後繼的子節點
// waitRemoveNode沒有孩子節點,或者有一個孩子節點的情況
if (isNil(waitRemoveNode.leftChild) || isNil(waitRemoveNode.rightChild))
successorNode = waitRemoveNode;
else // waitRemoveNode有兩個孩子節點的情況
successorNode = treeSuccessor(waitRemoveNode);
if (!isNil(successorNode.leftChild)) // 獲取後繼的孩子
successorChild = successorNode.leftChild;
else
successorChild = successorNode.rightChild;
successorChild.parent = successorNode.parent; //successorChild父域指向successorNode父親
if (isNil(successorNode.parent)) // 如果successorNode父親為空
root = successorChild;
// 左孩子
else if (!isNil(successorNode.parent.leftChild) && successorNode.parent.leftChild == successorNode)
successorNode.parent.leftChild = successorChild;
// 右孩子
else if (!isNil(successorNode.parent.rightChild) && successorNode.parent.rightChild == successorNode)
successorNode.parent.rightChild = successorChild;
// 待刪除節點已從樹中移除,複製值
if (successorNode != waitRemoveNode)
waitRemoveNode.key = successorNode.key;
if (!successorNode.red) // 如果後繼為黑節點,需要平衡紅黑樹
removeFixup(successorChild);
}
/**
* 刪除節點後修復紅黑樹
*
* @param node 為黑色的後繼節點
*/
private void removeFixup(RedBlackNode<T> node) {
RedBlackNode<T> nodeBrothers; // node的兄弟節點
// node不是根節點,且顏色為黑色
while (node != root && node.red == false) {
// 如果node是其父親的左孩子
if (node.parent.leftChild == node) {
nodeBrothers = node.parent.rightChild; // node父親的右孩子即為node的兄弟
// 1.node兄弟節點為紅色
if (nodeBrothers.red == true) {
nodeBrothers.red = false;
nodeBrothers.parent.red = true;
leftRotate(nodeBrothers.parent);
nodeBrothers = node.parent.rightChild;
}
// 2.node兄弟節點的左右孩子都為黑色
if (nodeBrothers.leftChild.red == false && nodeBrothers.rightChild.red == false) {
nodeBrothers.red = true;
node = node.parent;
} else {
// 3.node兄弟節點的右孩子為黑色
if (nodeBrothers.rightChild.red == false) {
nodeBrothers.leftChild.red = false;
nodeBrothers.red = true;
rightRotate(nodeBrothers);
nodeBrothers = node.parent.rightChild;
}
// 4.node兄弟節點為黑色, node兄弟節點的右孩子為紅色
nodeBrothers.red = node.parent.red;
node.parent.red = false;
nodeBrothers.rightChild.red = false;
leftRotate(node.parent);
node = root;
}
}
// node是父親的右孩子
else {
nodeBrothers = node.parent.leftChild; // node父親的左孩子即為node的兄弟
// 1.nodeBrothers為紅色
if (nodeBrothers.red == true) {
nodeBrothers.red = false;
nodeBrothers.parent.red = true;
rightRotate(nodeBrothers.parent);
nodeBrothers = node.parent.leftChild;
}
// 2.nodeBrothers的孩子節點都是黑色
if (nodeBrothers.leftChild.red == false && nodeBrothers.rightChild.red == false) {
nodeBrothers.red = true;
node = node.parent;
} else {
// 3.nodeBrothers的左孩子是黑色
if (nodeBrothers.leftChild.red == false) {
nodeBrothers.rightChild.red = false;
nodeBrothers.red = true;
leftRotate(nodeBrothers);
nodeBrothers = node.parent.leftChild;
}
// 4.nodeBrothers是黑色,nodeBrothers的左孩子是紅色
nodeBrothers.red = node.parent.red;
node.parent.red = false;
nodeBrothers.leftChild.red = false;
rightRotate(node.parent);
node = root;
}
}
}
// 設定節點為黑色,保證滿足紅黑樹的特性
node.red = false;
}
private RedBlackNode<T> treeSuccessor(RedBlackNode<T> waitRemoveNode) {
if (!isNil(waitRemoveNode.leftChild))
return treeMinimum(waitRemoveNode.rightChild);
return null;
}
// 獲取以node為根節點的子樹的最小節點
private RedBlackNode<T> treeMinimum(RedBlackNode<T> node) {
while (!isNil(node.leftChild))
node = node.leftChild;
return node;
}
七、完整版程式碼如下
import com.sun.org.apache.xalan.internal.xsltc.dom.NodeIteratorBase;
import org.hamcrest.core.IsNull;
/**
* 紅黑樹
*/
public class RedBlackTree<T extends Comparable<T>> {
/**
* 紅黑樹節點
*/
static class RedBlackNode<T extends Comparable<T>> {
T key; // 節點值
boolean red; // 是否是紅色節點
RedBlackNode<T> parent; // 父節點
RedBlackNode<T> leftChild; // 父節點
RedBlackNode<T> rightChild; // 父節點
public RedBlackNode() {
}
public RedBlackNode(T key) {
this();
this.key = key;
}
public boolean isRed() {
return red;
}
@Override
public String toString() {
return key + "";
}
}
private final RedBlackNode<T> NIL = new RedBlackNode<>(); // 哨兵節點(葉子結點)
private RedBlackNode<T> root = NIL; // 根節點
private int size; // 元素數量
public RedBlackTree() { // 構造方法
root.parent = NIL;
root.leftChild = NIL;
root.rightChild = NIL;
}
// 插入資料分兩步:1.資料插入 2.根據紅黑樹性質修正樹
public void insert(T key) {
if (key == null || key == "") // 資料檢查
return;
insert(new RedBlackNode<T>(key));
}
// 1.資料插入
private void insert(RedBlackNode<T> node) {
RedBlackNode<T> parent = NIL; // index的父節點
RedBlackNode<T> index = root; // 索引
while (!isNil(index)) { // 迴圈遍歷,找到插入位置和父節點
parent = index;
if (node.key.compareTo(index.key) < 0) {
index = index.leftChild;
} else if (node.key.compareTo(index.key) > 0) {
index = index.rightChild;
} else { // 找到相等節點,結束程式
return;
}
}
// 將孩子指向父親
node.parent = parent;
size++;
// 將父親指向孩子,分情況討論:
// 1.父節點是NIL(當前樹為空,沒有根節點)
if (isNil(parent))
root = node;
// 2.父親的左孩子
else if (node.key.compareTo(parent.key) < 0)
parent.leftChild = node;
// 3.父親的右孩子
else
parent.rightChild = node;
// 初始化孩子和顏色(新新增的節點一定是紅色)
node.leftChild = NIL;
node.rightChild = NIL;
node.red = true;
// 2.紅黑樹插入修正
insertFixup(node);
}
/**
* 插入修正
*
* @param node 插入的節點
*/
private void insertFixup(RedBlackNode<T> node) {
RedBlackNode<T> cousinNode = NIL; //叔節點
// 迴圈修正節點
// 新插入節點的父親為紅色,則不滿足性質4(如果該節點是紅色,則其兩個孩子節點必須是黑色)
while (node.parent.isRed()) {
// node節點的 [父親] 是 [爺爺] 節點的 [左孩子]
if (node.parent == node.parent.parent.leftChild) {
// 初始化node的叔叔(父節點的兄弟)
cousinNode = node.parent.parent.rightChild;
// 情況3.cousinNode(叔叔節點)是紅節點
if (cousinNode.isRed()) {
node.parent.red = false; // node父節點置為黑
cousinNode.red = false; // node叔節點置為黑
node.parent.parent.red = true; // node的爺爺節點置紅
node = node.parent.parent;
}
// 情況4.cousinNode節點為[黑節點]且為父節點[左孩子]
else if (node == node.parent.leftChild) {
// 重置顏色、以node的爺爺旋轉
node.parent.red = false;
node.parent.parent.red = true;
rightRotate(node.parent.parent);
}
// 情況4.cousinNode節點為[黑節點]且為父節點[右孩子]
else {
// 以node的父親左旋
node = node.parent;
leftRotate(node);
}
}
// node節點的 [父親] 是 [爺爺] 節點的 [右孩子]
else {
// 初始化node的叔叔(父節點的兄弟)
cousinNode = node.parent.parent.leftChild;
// 情況1.cousinNode(叔叔節點)是紅節點
if (cousinNode.isRed()) {
node.parent.red = false; // node父節點置為黑
cousinNode.red = false; // node叔節點置為黑
node.parent.parent.red = true; // node的爺爺節點置紅
node = node.parent.parent;
}
// 情況2.cousinNode節點為[黑節點]且為父節點[左孩子]
else if (node == node.parent.leftChild) {
node = node.parent;
// 以node的父親右旋
rightRotate(node);
}
// 情況3.cousinNode節點為[黑節點]且為父節點[右孩子]
else {
// 重置顏色、以node的爺爺旋轉
node.parent.red = false;
node.parent.parent.red = true;
leftRotate(node.parent.parent);
}
}
}
// 顏色根部始終為黑色
// 情況1:新插入節點N為根節點
root.red = false;
}
/**
* 右旋操作
*
* @param node 旋轉的軸節點
*/
private void rightRotate(RedBlackNode<T> node) {
RedBlackNode<T> nodeLeftChild = node.leftChild; // node左孩子
node.leftChild = nodeLeftChild.rightChild; // 將node的左孩子的右孩子過繼給node,作為它的左孩子
if (!isNil(nodeLeftChild.rightChild)) // node的左孩子的右孩子不為NIL
nodeLeftChild.rightChild.parent = node; // 將node作為它的父親
nodeLeftChild.parent = node.parent; // 將nodeLeftChild的父域指向node的父親
// 如果node的父親NIL
if (isNil(node.parent))
root = nodeLeftChild;
// node是其父節點的左孩子
else if (node.parent.leftChild == node)
node.parent.leftChild = nodeLeftChild;
// node是其父節點的右孩子
else
node.parent.rightChild = nodeLeftChild;
// 最後的旋轉
nodeLeftChild.rightChild = node;
node.parent = nodeLeftChild;
}
/**
* 左旋操作
*
* @param node 旋轉的軸節點
*/
private void leftRotate(RedBlackNode<T> node) {
RedBlackNode<T> nodeRightChild = node.rightChild; // node的右孩子
node.rightChild = nodeRightChild.leftChild; // 將node的右孩子的左孩子過繼給node,作為它的右孩子
if (!isNil(nodeRightChild.leftChild)) // node的右孩子的左孩子不為NIL
nodeRightChild.leftChild.parent = node; // 將node作為它的父親
nodeRightChild.parent = node.parent; // 將nodeRightChild的父域指向node的父親
// 如果node的父親NIL
if (isNil(node.parent))
root = nodeRightChild;
// node是其父節點的左孩子
else if (node.parent.leftChild == node)
node.parent.leftChild = nodeRightChild;
// node是其父節點的右孩子
else
node.parent.rightChild = nodeRightChild;
// 最後的旋轉
nodeRightChild.leftChild = node;
node.parent = nodeRightChild;
}
// 判斷節點是否是Nil(哨兵)
private boolean isNil(RedBlackNode<T> node) {
return node == this.NIL;
}
public void preOrderErgodic(){
preOrderErgodic(this.root);
}
/**
* 前序遍歷紅黑樹
* @param node
*/
public void preOrderErgodic(RedBlackNode node){
if (node != this.NIL) {
System.out.print(node.key + "\tC:" + (node.red ? "Red" : "Black") + "\t\t");
if (node.leftChild != this.NIL)
System.out.print( "(L)" + node.leftChild.key + "\t\t");
else
System.out.print("(L)Null\t\t");
if (node.rightChild != this.NIL)
System.out.print("(R)" + node.rightChild.key + "\t\t");
else
System.out.print("(R)Null\t\t");
System.out.println();
preOrderErgodic(node.leftChild);
preOrderErgodic(node.rightChild);
}
}
// 返回樹節點數
public int getSize() {
return size;
}
/**
* 刪除節點
*
* @param t 要刪除的節點
*/
public void remove(T t) {
if (t == null || t == "") // 簡單的錯誤檢查
return;
removeNode(new RedBlackNode<T>(t));
size--;
}
// 移除節點
private void removeNode(RedBlackNode<T> v) {
RedBlackNode<T> waitRemoveNode = search(v.key); // 查詢待刪除節點
RedBlackNode<T> successorNode = NIL; // 後繼節點
RedBlackNode<T> successorChild = NIL; // 後繼的子節點
// waitRemoveNode沒有孩子節點,或者有一個孩子節點的情況
if (isNil(waitRemoveNode.leftChild) || isNil(waitRemoveNode.rightChild))
successorNode = waitRemoveNode;
else // waitRemoveNode有兩個孩子節點的情況
successorNode = treeSuccessor(waitRemoveNode);
if (!isNil(successorNode.leftChild)) // 獲取後繼的孩子
successorChild = successorNode.leftChild;
else
successorChild = successorNode.rightChild;
successorChild.parent = successorNode.parent; //successorChild父域指向successorNode父親
if (isNil(successorNode.parent)) // 如果successorNode父親為空
root = successorChild;
// 左孩子
else if (!isNil(successorNode.parent.leftChild) && successorNode.parent.leftChild == successorNode)
successorNode.parent.leftChild = successorChild;
// 右孩子
else if (!isNil(successorNode.parent.rightChild) && successorNode.parent.rightChild == successorNode)
successorNode.parent.rightChild = successorChild;
// 待刪除節點已從樹中移除,複製值
if (successorNode != waitRemoveNode)
waitRemoveNode.key = successorNode.key;
if (!successorNode.red) // 如果後繼為黑節點,需要平衡紅黑樹
removeFixup(successorChild);
}
/**
* 刪除節點後修復紅黑樹
*
* @param node 為黑色的後繼節點
*/
private void removeFixup(RedBlackNode<T> node) {
RedBlackNode<T> nodeBrothers; // node的兄弟節點
// node不是根節點,且顏色為黑色
while (node != root && node.red == false) {
// 如果node是其父親的左孩子
if (node.parent.leftChild == node) {
nodeBrothers = node.parent.rightChild; // node父親的右孩子即為node的兄弟
// 1.node兄弟節點為紅色
if (nodeBrothers.red == true) {
nodeBrothers.red = false;
nodeBrothers.parent.red = true;
leftRotate(nodeBrothers.parent);
nodeBrothers = node.parent.rightChild;
}
// 2.node兄弟節點的左右孩子都為黑色
if (nodeBrothers.leftChild.red == false && nodeBrothers.rightChild.red == false) {
nodeBrothers.red = true;
node = node.parent;
} else {
// 3.node兄弟節點的右孩子為黑色
if (nodeBrothers.rightChild.red == false) {
nodeBrothers.leftChild.red = false;
nodeBrothers.red = true;
rightRotate(nodeBrothers);
nodeBrothers = node.parent.rightChild;
}
// 4.node兄弟節點為黑色, node兄弟節點的右孩子為紅色
nodeBrothers.red = node.parent.red;
node.parent.red = false;
nodeBrothers.rightChild.red = false;
leftRotate(node.parent);
node = root;
}
}
// node是父親的右孩子
else {
nodeBrothers = node.parent.leftChild; // node父親的左孩子即為node的兄弟
// 1.nodeBrothers為紅色
if (nodeBrothers.red == true) {
nodeBrothers.red = false;
nodeBrothers.parent.red = true;
rightRotate(nodeBrothers.parent);
nodeBrothers = node.parent.leftChild;
}
// 2.nodeBrothers的孩子節點都是黑色
if (nodeBrothers.leftChild.red == false && nodeBrothers.rightChild.red == false) {
nodeBrothers.red = true;
node = node.parent;
} else {
// 3.nodeBrothers的左孩子是黑色
if (nodeBrothers.leftChild.red == false) {
nodeBrothers.rightChild.red = false;
nodeBrothers.red = true;
leftRotate(nodeBrothers);
nodeBrothers = node.parent.leftChild;
}
// 4.nodeBrothers是黑色,nodeBrothers的左孩子是紅色
nodeBrothers.red = node.parent.red;
node.parent.red = false;
nodeBrothers.leftChild.red = false;
rightRotate(node.parent);
node = root;
}
}
}
// 設定節點為黑色,保證滿足紅黑樹的特性
node.red = false;
}
private RedBlackNode<T> treeSuccessor(RedBlackNode<T> waitRemoveNode) {
if (!isNil(waitRemoveNode.leftChild))
return treeMinimum(waitRemoveNode.rightChild);
return null;
}
// 獲取以node為根節點的子樹的最小節點
private RedBlackNode<T> treeMinimum(RedBlackNode<T> node) {
while (!isNil(node.leftChild))
node = node.leftChild;
return node;
}
/**
* 根據值尋找節點
*
* @param key 指定的值
* @return 節點
*/
private RedBlackNode<T> search(T key) {
RedBlackNode<T> index = root; // 指標
while (!isNil(index)) { // 迴圈遍歷
if (key.equals(index.key)) // 找到了直接返回
return index;
else if (key.compareTo(index.key) < 0) // 小於取左
index = index.leftChild;
else // 大於取右
index = index.rightChild;
}
return null; // 未找到節點返回null
}
}
測試程式碼如下:
public class RedBlackTreeDemo {
public static void main(String[] args) {
RedBlackTree tree = new RedBlackTree();
// tree.insert(10);
// tree.insert(9);
// tree.insert(8);
// tree.insert(7);
// tree.insert(6);
// tree.insert(5);
// tree.insert(4);
// tree.insert(3);
// tree.insert(2);
// tree.insert(1);
tree.insert(40);
tree.insert(30);
tree.insert(50);
tree.insert(25);
tree.insert(51);
tree.insert(66);
tree.insert(66);
System.out.println("The number of node in the tree: " + tree.getSize());
tree.preOrderErgodic();
// tree.remove(7);
tree.remove(40);
System.out.println("The number of node in the tree: " + tree.getSize());
tree.preOrderErgodic();
}
}
八、參考
https://github.com/Arsenalist/Red-Black-Tree-Java-Implementation/blob/master/README