資料結構——紅黑樹的資料插入操作
1. 紅黑樹
1. 紅黑樹是二叉搜尋樹的優化版,應為如果二叉搜尋樹的節點大部分全集中在左子樹或右子樹上,就會導致樹的高度非常高,繼而導致在進行集合操作時與連結串列差距不大。紅黑樹是一種平衡二叉樹結構,其保證了在最壞情況下的集合操作時間複雜度為O(lgn),保證了紅黑樹中任意一條從根節點到葉子節點的路徑長度不會超過其他路徑的2倍。
2. 樹中的每個節點包含5個屬性:key(該值是整數,用來進行比較搜尋)、value(節點儲存的資料物件)、color(節點顏色)、right(右子節點)、left(左子節點)以及p(父節點)。用color表示節點顏色,只能是紅色或黑色。如果某個節點沒有左子節點或右子節點(子節點為null),那麼就將指向該左子節點或右子節點的指標值置為null,null(或者說NIL)可以看做二叉樹的外部節點,也是葉子節點,而帶有value的節點是為樹的內部節點。實現程式碼如下
private static final class Node{
private int key;
private Object value;
private boolean red;//true為紅色,false為黑色
private Node left;//左子節點
private Node right;//右子節點
private Node parent;//父節點
}
3. 紅黑樹性質:
(1)每個節點都是黑色或者紅色的;
(2)根節點都是黑色的;
(3)每個葉子節點(NIL)都是黑色的;
(4)如果一個節點是紅色的,則它的兩個子節點都是黑色的;
(5)從根節點到其所有後代的葉子節點(NIL)的簡單路徑上,均包含相同數量的黑色節點;
(6)從性質5又可以推出:如果一個結點存在黑子結點,那麼該結點肯定有兩個子結點;
4. 紅黑樹中的左旋與右旋操作:
(1)左旋:左旋指的是將當前節點X的右子節點Y提升至當前節點X的位置,並且將X節點與Y節點的左子樹進行交換,將Y節點的原左子樹轉移到X節點的柚子樹上
(2)右旋:右旋與左旋剛好相反,將首先轉移Y節點父節點的連線到X節點上,然後將Y節點為根節點的子樹連線到X節點的右子樹上,並且讓X節點原來的右子樹轉移到Y節點的左子樹上
(3)實現程式碼:
//左自旋方法
private Node rotateLeft(Node node, Node parent) {
Node temp = parent.parent;
if (temp == null) {
parent.right = node.left;
node.left.parent = parent;
node.left = parent;
parent.parent = node;
node.parent = null;
root = node;
return node;
}
if (temp.left == parent) {
temp.left = node;
node.parent = temp;
parent.right = node.left;
node.left.parent = parent;
node.left = parent;
parent.parent = node;
} else {
temp.right = node;
node.parent = temp;
parent.right = node.left;
node.left.parent = parent;
node.left = parent;
parent.parent = node;
}
return node;
}
//右自旋方法
private Node rotateRight(Node node, Node parent) {
Node temp = parent.parent;
if (temp == null) {
parent.left = node.right;
node.right.parent = parent;
node.right = parent;
parent.parent = node;
node.parent = null;
root = node;
return node;
}
if (temp.left == parent) {
temp.left = node;
node.parent = temp;
parent.left = node.right;
node.right.parent = parent;
node.right = parent;
parent.parent = node;
} else {
temp.right = node;
node.parent = temp;
parent.left = node.right;
node.right.parent = parent;
node.right = parent;
parent.parent = node;
}
return node;
}
2. 紅黑樹的插入操作實現
與普通二叉樹的增長不同,普通搜尋二叉樹的增長是自頂向下增長,而紅黑樹是自頂向下增長,但同時要自底向上平衡,在看下面的實現思路時要考慮這句話的意思。
1. 插入操作包括兩部分工作:一查詢插入的位置;二插入後自平衡。查詢插入節點的父結點很簡單,跟查詢操作區別不大
實現程式碼
//尋找要被插入的節點在紅黑樹中應在的位置,並插入
Node n = root;
while (true) {
if (node.key > n.key) {
if (n.right == null) {
n.right = node;
node.parent = n;
break;
} else {
n = n.right;
}
} else if (node.key == n.key) {
n.value = node.value;
break;
} else {
if (n.left == null) {
n.left = node;
node.parent = n;
break;
} else {
n = n.left;
}
}
}
2. 平衡二叉樹:這一步是核心步驟,首先插入的節點要將其顏色設定為紅色,因為紅色在父結點(如果存在)為黑色結點時,紅黑樹的黑色平衡沒被破壞,不需要做自平衡操作。但如果插入結點是黑色,那麼插入位置所在的子樹黑色結點總是多1,必須做自平衡。然後我們需要對插入節點所面臨的的各種情況進行分析
(1)情景1:如果紅黑樹為空樹,那麼就直接將插入節點作為根節點。
(2)情景2:如果插入節點的父節點為黑色,那麼就直接插入,無需進行二叉樹平衡操作。
(3)情景3:如果插入節點的key已經存在,那麼就替換該節點下的物件元素並且不更換原節點的顏色,只需要將原節點的值進行更新即可。(因為紅黑樹基本都是用來進行搜尋的,每個節點除了要帶一個儲存的物件,還需要依據物件生成一個整數key來標示,key用來找到插入元素在紅黑樹中的位置,有些紅黑樹實現策略可能並不是將節點儲存的物件進行更新,而是將新節點插入到key相等的父節點的左側或右側,不過要保證儲存的物件實現了equals方法用來進行比較是否相等,不相等則插入,相等則不插入。)。
(4)情景4:當前被插入節點的叔叔節點存在,且父節點與叔叔節點都為紅色;此情景下可以確定父節點的父節點(祖父節點)一定是黑色的,處理時比較簡單,只需要將父節點與叔叔節點置為黑色,但此時會打破紅黑樹性質第5條,所以還需要將祖父節點置為紅色即可。
置為紅色節點後,祖父節點還有可能與其父節點衝突(都為紅色),所以要將祖父節點視為新插入的節點再次進行平衡。
(5)叔叔結點不存在或為黑結點,並且插入結點的父親結點是祖父結點的左子結點,而且插入節點是父節點的左子節點;該情況下需要將插入節點的父節點置為黑色,祖父節點置為紅色,然後對父節點和祖父節點進行右旋處理。處理完成後就發現紅黑樹重新處於平衡狀態
(6)叔叔結點不存在或為黑結點,並且插入結點的父親結點是祖父結點的左子結點,而且插入節點是父節點的右子節點;該情況下需要先將插入節點與父節點進行一次左旋操作,轉為情景(5),然後按照情景5進行處理即可。
(7)叔叔結點不存在或為黑結點,並且插入結點的父親結點是祖父結點的右子結點,而且插入節點是父節點的右子節點;與情景5正好相反,該情況下需要將插入節點的父節點置為黑色,祖父節點置為紅色,然後對父節點和祖父節點進行左旋處理。處理完成後就發現紅黑樹重新處於平衡狀態
(8)叔叔結點不存在或為黑結點,並且插入結點的父親結點是祖父結點的右子結點,而且插入節點是父節點的左子節點;將插入節點與父節點進行右旋操作,就會變為情景7,在執行情景7操作即可。處理完成後紅黑樹重新處於平衡狀態
3. 平衡紅黑樹邏輯實現程式碼:
//平衡紅黑樹方法,傳入新插入的節點作為引數
protected Node balanceTree(Node node) {
//如果插入節點的父節點為黑色節點,則表示插入完成無需平衡
if (!node.parent.red) {
return node;
}
Node pr = node.parent.parent.right;//祖父節點的右子節點
Node pl = node.parent.parent.left;//祖父節點的左子節點
Node gf = node.parent.parent;//祖父節點
//如果被插入節點的父節點是紅色的,而且其兄弟節點也是紅色的
if (pr != null && pl != null && pr.red && pl.red) {
//將左右子節點顏色置為黑色,祖父節點置為紅色,並將新插入節點指標指向祖父節點
pr.red = false;
pl.red = false;
pl.parent.red = true;
node = pl.parent;
//將祖父節點作為新插入節點平衡操作
return balanceTree(node);
}
//如果被插入節點的父節點是紅色的,而且兄弟節點為黑色或null,那麼就對新插入節點的父節點與祖父節點進行左旋或右旋處理
//如果父節點為上一級節點的左子節點
if (node.parent == pl && (pr == null || !pr.red)) {
//當前插入節點為父節點的左子節點
if (node == pl.left) {
//右旋處理前需要將父節點與祖父節點的顏色變換
pl.red = false;
pl.parent.red = true;
//右旋處理父節點與祖父節點
rotateRight(pl, pl.parent);
return node;
}
//當前插入節點為父節點的右子節點
if (node == pr.right) {
//先左旋,將新插入節點與父節交換位置
rotateLeft(node, node.parent);
//右旋處理前需要將上面左旋處理後的新插入節點與父節點的顏色變換
node.red = false;
node.parent.red = true;
//然後右旋新節點與新父節點
rotateRight(node, node.parent);
return node;
}
}
//如果父節點為上一級節點的右子節點
if (node.parent == pr && (pl == null || !pl.red)) {
//當前插入節點為父節點的右子節點
if (node == pr.right) {
//左旋處理前需要將父節點與祖父節點的顏色變換
pr.red = false;
pr.parent.red = true;
//左旋處理
rotateLeft(pr, pr.parent);
return node;
}
//當前插入節點為父節點的左子節點
if (node == pr.left) {
//先右旋,將當前節點與父節點位置交換
rotateRight(node, node.parent);
//左旋處理前需要將上面右旋後的新插入節點與父節點的顏色變換
node.red = false;
node.parent.red = true;
//然後左旋
rotateLeft(node, node.parent);
return node;
}
}
return node;
}
4. 插入方法的實現:
public class RBTree {
private Node root=null;
public Node insert(Node node){
node.red = true;
if (root == null) {
root = node;
root.red = false;
return root;
}
//尋找要被插入的節點在紅黑樹中應在的位置,並插入
Node n = root;
while (true) {
if (node.key > n.key) {
if (n.right == null) {
n.right = node;
node.parent = n;
break;
} else {
n = n.right;
}
} else if (node.key == n.key) {
n.value = node.value;
return node;
} else {
if (n.left == null) {
n.left = node;
node.parent = n;
break;
} else {
n = n.left;
}
}
}
//插入後進行平衡
balanceTree(node);
return node;
}
//左自旋方法
private Node rotateLeft(Node node, Node parent) {
Node temp = parent.parent;
if (temp == null) {
parent.right = node.left;
node.left.parent = parent;
node.left = parent;
parent.parent = node;
node.parent = null;
root = node;
return node;
}
if (temp.left == parent) {
temp.left = node;
node.parent = temp;
parent.right = node.left;
node.left.parent = parent;
node.left = parent;
parent.parent = node;
} else {
temp.right = node;
node.parent = temp;
parent.right = node.left;
node.left.parent = parent;
node.left = parent;
parent.parent = node;
}
return node;
}
//右自旋方法
private Node rotateRight(Node node, Node parent) {
Node temp = parent.parent;
if (temp == null) {
parent.left = node.right;
node.right.parent = parent;
node.right = parent;
parent.parent = node;
node.parent = null;
root = node;
return node;
}
if (temp.left == parent) {
temp.left = node;
node.parent = temp;
parent.left = node.right;
node.right.parent = parent;
node.right = parent;
parent.parent = node;
} else {
temp.right = node;
node.parent = temp;
parent.left = node.right;
node.right.parent = parent;
node.right = parent;
parent.parent = node;
}
return node;
}
//平衡紅黑樹方法,傳入新插入的節點作為引數
protected Node balanceTree(Node node) {
//如果插入節點的父節點為黑色節點,則表示插入完成無需平衡
if (!node.parent.red) {
return node;
}
Node pr = node.parent.parent.right;//祖父節點的右子節點
Node pl = node.parent.parent.left;//祖父節點的左子節點
//如果被插入節點的父節點是紅色的,而且其兄弟節點也是紅色的
if (pr != null && pl != null && pr.red && pl.red) {
//將左右子節點顏色置為黑色,祖父節點置為紅色,並將新插入節點指標指向祖父節點
pr.red = false;
pl.red = false;
pl.parent.red = true;
node = pl.parent;
//將祖父節點作為新插入節點平衡操作
return balanceTree(node);
}
//如果被插入節點的父節點是紅色的,而且兄弟節點為黑色或null,那麼就對新插入節點的父節點與祖父節點進行左旋或右旋處理
//如果父節點為上一級節點的左子節點
if (node.parent == pl && (pr == null || !pr.red)) {
//當前插入節點為父節點的左子節點
if (node == pl.left) {
//右旋處理前需要將父節點與祖父節點的顏色變換
pl.red = false;
pl.parent.red = true;
//右旋處理父節點與祖父節點
rotateRight(pl, pl.parent);
return node;
}
//當前插入節點為父節點的右子節點
if (node == pr.right) {
//先左旋,將新插入節點與父節交換位置
rotateLeft(node, node.parent);
//右旋處理前需要將上面左旋處理後的新插入節點與父節點的顏色變換
node.red = false;
node.parent.red = true;
//然後右旋新節點與新父節點
rotateRight(node, node.parent);
return node;
}
}
//如果父節點為上一級節點的右子節點
if (node.parent == pr && (pl == null || !pl.red)) {
//當前插入節點為父節點的右子節點
if (node == pr.right) {
//左旋處理前需要將父節點與祖父節點的顏色變換
pr.red = false;
pr.parent.red = true;
//左旋處理
rotateLeft(pr, pr.parent);
return node;
}
//當前插入節點為父節點的左子節點
if (node == pr.left) {
//先右旋,將當前節點與父節點位置交換
rotateRight(node, node.parent);
//左旋處理前需要將上面右旋後的新插入節點與父節點的顏色變換
node.red = false;
node.parent.red = true;
//然後左旋
rotateLeft(node, node.parent);
return node;
}
}
return node;
}
private static final class Node{
private int key;
private Object value;
private boolean red;//true為紅色,false為黑色
private Node left;//左子節點
private Node right;//右子節點
private Node parent;//父節點
}
}