前言
大家好,頭回寫博客,歡迎批評,以後我會盡量做到一個月2更,最近在重新溫故算法。 今日提供讀書筆記紅黑樹
目的
記錄所學,溫故知新
Java中對應的結構
TreeMap,以下是自己安裝書中實現的原理,工作中應使用TreeMap
紅黑樹的定義
紅黑樹(Red Black Tree) 是一種自平衡二叉查找樹.
紅黑樹和AVL樹類似,都是在進行插入和刪除操作時通過特定操作保持二叉查找樹的平衡
時間界限與特點
紅黑樹的插入,刪除操作在最壞情況下花費
log N
紅黑樹是具有如下着色性質的二叉查找樹:
- 每個節點要麼着成紅色,要麼着成黑色
- 根是黑色的
- 如果一個節點是紅色,那麼他的子節點必須是黑色
- 從一個節點到一個null引用,每一條路徑必須包含相同數目的黑色節點。
使用該着色法則,保證紅黑樹的高度最多爲:
2*log (N+1)
插入操作
自底向上插入及遇到的問題
自底向上插入
如果新插入的項的父節點是黑色,那麼插入結束,默認新插入的節點是紅色的.
如果父親是紅色的,就有幾種情形(每種都有對稱形式,假設父親是曾祖父的左兒子).
- 父親的兄弟是黑色,
如下圖:
-
- 曾祖父爲黑色,當前節點爲父親的右兒子且爲紅色.
- 父親的兄弟是紅色時, 需要進行上濾 如下圖過程:
遇到的問題
上濾需要一個棧或者保持父鏈來實現,並且過程複雜.
自頂向下插入
概念:在向下的過程中如果看到一個節點current有兩個紅兒子,可將該節點呈紅色,兩個兒子變爲黑色。
當current節點的父親parent也是紅色時候,進行適當的選擇,以該方式向下進行插入操作屏蔽了X節點的兄弟節點也是紅色的可能. 代碼:
/**
* 自頂向下插入
*/
public void insert( AnyType item ){
nullNode.element=item;
current=parent=grand=header;
//自頂向下調整,避免插入的父節點和父節點的兄弟節點爲黑色的情況,該情況複雜不利於恢復平衡信心.
while(compare(item,current)!=0){
great=grand;
grand=parent;
parent=current;
current=compare(item,current)<0?current.left:current.right;
if(current.left.color==RED&¤t.right.color==RED){
handleReorientAfterInsert(item);
}
}
if(current!=nullNode){//重複元素跳過
return;
}
//找到位置
//構建新的節點
current=new RedBlackNode<AnyType>(item,nullNode,nullNode);
//維護與父節點的關係
if(compare(item,parent)<0){
parent.left=current;
}else{
parent.right=current;
}
//插入完成後,維護平衡信息
handleReorientAfterInsert(item);
nullNode.element=null;
}
/**
* 插入後維護平衡信息
* @param item
*/
private void handleReorientAfterInsert(AnyType item) {
//初步調整的變換顏色 自己變爲紅色,兩個兒子變爲紅色
current.color=RED;
current.left.color=BLACK;
current.right.color=BLACK;
if(parent.color==RED){
//調整後破壞了紅黑樹性質,需要旋轉
//分兩種類型 一字形和之字形,之字形比一字形調整了多一步
grand.color = RED;
if((compare(item,grand)<0)!=(compare(item,parent)<0)){//之字形
parent=rotate(item,grand);
//調整parent和他的兒子,並將調整後的節點W設置成parent
}
//調整完成,重新設置當前節點
current=rotate(item,great);
//並將當前節點設置爲黑色
current.color=BLACK;
}
//保證根節點是黑色
header.right.color=BLACK;
}
自頂向下刪除
- 刪除操作歸結於可以刪除紅色的樹葉;
- 如果要刪除的節點有右兒子,以右兒子的最小元內容替換要刪除節點內容,之後刪除右兒子最小元來進行刪除。
- 如果只有左兒子,以左兒子最大元內容替換要刪除節點的內容,之後刪除左兒子最大元
- 如果要刪除的節點沒有兒子, 將該節點調整成紅色,將父節點對應的引用設置成nullNode
- 3.如果沒有兒子
-
- 若父節點爲header,將樹變爲空樹
-
- 否則如果當前節點爲黑色,進行調整,保證刪除項爲紅色,之後將要刪除項的父節點的引用設置爲nullNode.
紅色樹葉刪除簡單,如果要刪除的是黑色分爲如下幾種情:
-
X與兄弟T的兒子都是黑色
-
X的兒子是黑色,兄弟T有一個左兒子是紅色
-
X的兒子是黑色,兄弟T有一個右兒子是紅色
-
X的兒子是黑色,兄弟T兒子都是紅色
以上每種情形都有與只對應的對稱類型。如果X節點是紅色,我們生產新的X,P,T向下探索 相關代碼:
/**
* 刪除一個節點,
* 依據可以刪除一個葉子,
* 自頂向下刪除,
* 1如果要刪除項有右兒子,先刪除右兒子最小項,之後使用原右兒子的最小項內容替換要刪除項的內容.
* 2.如果只有左兒子,先刪除左兒子最大,之後使用左兒子的最大項替換要刪除項的內容.
* 3.如果沒有兒子
* 若父節點爲header,將樹變爲空樹
* 否則如果當前節點爲黑色,進行調整,保證刪除項爲紅色,之後將要刪除項的父節點的引用設置爲nullNode.
* @param x
*/
public AnyType remove( AnyType x ){
//需要自己嘗試書寫
//先查找是否存在,存在後刪除
RedBlackNode<AnyType>p=find(x);
RedBlackNode<AnyType>pParent=parent;
if (p == null){
return null;
}
AnyType item=p.element;
//自頂向下刪除
//找到後,如果存在左兒子和右兒子(或 只有右兒子),
//使用右兒子的最小,替換當前 ,之後刪除右兒子最小
//只有左兒子使用左兒子最大替換,
RedBlackNode<AnyType>replacement=findReplaceMent(p);
if(replacement!=null){
//進行替換
p.element=remove(replacement.element);
}else{
//沒有替換者,
if(pParent==header){
makeEmpty();
}else{
if(p.color==BLACK){
//將p地調整爲紅色
fixbeforedelete(p.element) ;
pParent=parent;
}
//調整爲刪除
if(pParent.left==p){
pParent.left=nullNode;
}else if(pParent.right==p){
pParent.right=nullNode;
}
}
}
current=p;
parent=pParent;
return item;
}
/**
* 刪除前調整數的平衡信息,保證要刪除的項是紅色
* @param item
*/
private void fixbeforedelete(AnyType item) {
grand=header;
RedBlackNode<AnyType>p=header.right;
RedBlackNode<AnyType>x=nullNode;
RedBlackNode<AnyType>t=nullNode;
RedBlackNode<AnyType>i=find(item);
//先把p塗成紅色,最後恢復
p.color=RED;
x=item.compareTo(p.element)<=0?p.left:p.right;
t=item.compareTo(p.element)<=0?p.right:p.left;
//保證要刪除的項是紅色
while(i.color!=RED){
if(x.color==RED||
(x.color==BLACK&&(x.left.color==RED&&x.right.color==RED)||
t.color==BLACK&&(x.left.color==RED||x.right.color==RED))
){
//x爲紅色或x兒子爲紅色,x爲黑色&&t爲黑色,x有一個兒子爲紅色,向下探索
grand=p;
p=x;
x=item.compareTo(p.element)<0?p.left:p.right;
t=item.compareTo(p.element)<0?p.right:p.left;
}else if(x.color==BLACK&&t.color==BLACK
&&x.right.color==BLACK&&x.left.color==BLACK){
//3中情況需要,調整的情況
if(t.left.color==BLACK&&t.right.color==BLACK){
//t的兩個兒子,直接變換p和t,x的顏色,重新再該位置下探
p.color=BLACK;
t.color=RED;
x.color=RED;
}else if(t.left.color==RED&&t.right.color==RED){
//t有兩個紅色的兒子,調整後下探
if(p.right==t){
RedBlackNode<AnyType>red=t.left;
p.right=red.left;
t.left=red.right;
red.right=t;
red.left=p;
//更新祖父節點
if(grand.left==p){
grand.left=red;
}else{
grand.right=red;
}
grand=red;
p.color=BLACK;
x.color=RED;
t=p.right;
}else{
RedBlackNode<AnyType>red=t.right;
p.left=red.right;
t.right=red.left;
red.right=p;
red.left=t;
if(grand.left==p){
grand.left=red;
}else{
grand.right=red;
}
grand=red;
p.color=BLACK;
x.color=RED;
t=p.left;
}
}else if(p.right==t&&t.left.color==RED){
//右左,之字調整後繼續下探
RedBlackNode<AnyType>red=t.left;
p.right=red.left;
t.left=red.right;
red.right=t;
red.left=p;
if(grand.left==p){
grand.left=red;
}else{
grand.right=red;
}
grand=red;
p.color=BLACK;
x.color=RED;
t=p.right;
}else if(p.left==t&&(t.right.color==RED)){
//左右,之字調整後繼續下探
RedBlackNode<AnyType>red=t.right;
p.left=red.right;
t.right=red.left;
red.right=p;
red.left=t;
if(grand.left==p){
grand.left=red;
}else{
grand.right=red;
}
grand=red;
p.color=BLACK;
x.color=RED;
t=p.left;
}else if(p.right==t&&t.right.color==RED){
//右右 一字,交換t和p
p.right=t.left;
t.left=p;
if(grand.left==p){
grand.left=t;
}else{
grand.right=t;
}
grand=t;
t.color=RED;
p.color=BLACK;
t=p.right;
}else if(p.left==t&&t.left.color==RED){
//左左 一字 交換t和p
p.left=t.right;
t.right=p;
if(grand.left==p){
grand.left=t;
}else{
grand.right=t;
}
grand=t;
t.color=RED;
p.color=BLACK;
t=p.left;
}
}else if(x.color==BLACK&&p.color==BLACK&&t.color==RED){
//x的兄弟爲黑色,x和x的父節點都是紅色,調整t和p,保證p爲紅色後,繼續下探
if(p.left==x){
p.right=t.left;
t.left=p;
if(grand.left==p){
grand.left=t;
}else{
grand.right=t;
}
grand=t;
t.color=BLACK;
p.color=RED;
t=p.right;
}else{
p.left=t.right;
t.right=p;
if(grand.left==p){
grand.left=t;
}else{
grand.right=t;
}
grand=t;
t.color=BLACK;
p.color=RED;
t=p.left;
}
}else if(header.right==p&&x.color==BLACK
&&p.color==RED&&t.color==RED){
p.color=BLACK;
}
}
header.right.color=BLACK;
parent=p;
}
總結
- 紅黑樹的操作在最壞情況下花費
log N
- 插入操作採用自頂向下操作保證要插入的節點的父親是黑色。
- 刪除操作採用自頂向下操作保證要刪除的節點爲紅色。