讀書筆記-紅黑樹 頂 原 薦

前言

大家好,頭回寫博客,歡迎批評,以後我會盡量做到一個月2更,最近在重新溫故算法。 今日提供讀書筆記紅黑樹

目的

記錄所學,溫故知新

Java中對應的結構

TreeMap,以下是自己安裝書中實現的原理,工作中應使用TreeMap

紅黑樹的定義

紅黑樹(Red Black Tree) 是一種自平衡二叉查找樹.
紅黑樹和AVL樹類似,都是在進行插入和刪除操作時通過特定操作保持二叉查找樹的平衡

時間界限與特點

紅黑樹的插入,刪除操作在最壞情況下花費

log N

紅黑樹是具有如下着色性質的二叉查找樹:

  1. 每個節點要麼着成紅色,要麼着成黑色
  2. 根是黑色的
  3. 如果一個節點是紅色,那麼他的子節點必須是黑色
  4. 從一個節點到一個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&&current.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.

紅色樹葉刪除簡單,如果要刪除的是黑色分爲如下幾種情:

  1. X與兄弟T的兒子都是黑色 X與兄弟T的兒子都是黑色

  2. X的兒子是黑色,兄弟T有一個左兒子是紅色  X的兒子是黑色,兄弟T有一個左兒子是紅色

  3. X的兒子是黑色,兄弟T有一個右兒子是紅色 X的兒子是黑色,兄弟T有一個右兒子是紅色

  4. 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;
}

總結

  1. 紅黑樹的操作在最壞情況下花費
log N
  1. 插入操作採用自頂向下操作保證要插入的節點的父親是黑色。
  2. 刪除操作採用自頂向下操作保證要刪除的節點爲紅色。

紅黑樹完整代碼地址

https://github.com/floor07/DataStructuresAndAlgorithm/blob/master/src/chapter12/adt/RedBlackTree.java

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章