1. 程式人生 > >紅黑樹 自頂向下插入操作(一)

紅黑樹 自頂向下插入操作(一)

1. 紅黑樹簡介:

   對於紅黑樹 一種變異型的平衡二叉樹,保持最壞的情況下查詢時間的複雜度o(logN),在 紅黑樹的 插入操作過程中有兩種 方式 自頂向下插入 和 自低向上的插入;相比較而言 各自有其優點,不管 採用何種方式 在 Insert 操作中 規定:將插入結點染成紅色主要是滿足 紅黑樹 性質5(從一個節點到NULL任一路徑上有相同的黑色結點數)。

2. 自頂向下插入操作:每一次插入從Heade遍歷開始,讓插入點當成葉子節點進行插入

 根據資料結構與演算法分析Java,在自低向上插入過程中為了滿足性質5 在樹的結構體中 加入parent的父節點指標保留父鏈儲存路徑,同時在 調整樹結構過程同時 需要更新父指標的指向,特別是在刪除採用自低向上操作方式非常的複雜,如果採用自頂向下過程 向下遍歷 樹,不同於自低向上演算法,這裡 自頂向下過程中 發現當前節點有兩個RED節點時候,進行處理,處理方式當前節點設定RED,兩個兒子設定成BLACK,如果 當前節點與其父節點都是RED(違背性質2不能連續出現兩個RED),必須進行 單旋(當前節點parent與祖父結點)或者雙旋(當前節點與祖父結點進行雙旋)--類似與AVL的旋轉。

3. 自頂向下插入 步驟:

    1). 新插入的結點 每一次從根結點起進行遍歷current ,parent,gparent,great,分別表示 當前節點,當前節點父節點,其祖父結點,其曾祖父結點;

    2) 如果 current 結點 有兩個RED 兒子,那麼就將兩個RED 孩子 設定成BLACK,current 設定成RED,同時比較current 結點 與parent 結點的顏色 都是RED 執行步驟3 否則直接執行步驟4;

   3). 目的處理 current 結點與 parent 結點都是RED 情形,那麼其兄弟結點S必定是BLACk,因為從上向下遍歷時候已經處理 兒子全部是RED 情況 對於這種 情形 出現兩種子情況;(只進行左側討論,右側討論映象)

       case 1 . current.color=RED,parent.color=RED, current=parent.left , 形成是一字型,對parent與gparent單旋轉 ,將gparent 變成parent的右孩子。 

        case 2. current.color=RED,parent.color=RED, current=parent.right. , 形成是之字型,需要進行雙旋將 current 移動到 祖父結點上 --AVL 雙旋可以轉化成單旋 singleRightroate(gparent.left) 在之後singLeftRoate(greate.left)

最後記得 更新current= 旋轉之後的結點 明顯發現旋轉之後之前 gparent設定成 RED


 4). 完成 2(3)之後 ,繼續往下前進遍歷,重複2,4 過程,直到到達KEy或者 nullpoint點,至此current=null 從而進行

4. 自頂向下程式設計實現:

     1). 實現 小技巧: ① 建立NULLNode 代表NULL,如果一個節點指向nullNode代表其指向NULl結點

                                     ② 根節點Heade不是真正的 root,Heade.right 才是真正的根節點,這樣實現好處開始時候並不清楚,直到實現 自頂向下刪除操作時候發現其妙處。

  2). 接下來 介紹 處理兩個red 孩子的 程式段(步驟3):

    這裡 程式中判斷做 雙旋還是單旋 小技巧 :判斷 current 是之字形還是一字形:item(要插入結點)  與祖父結點比較結果 和item 與parent 比較結果 一致性,如果是一字形 item 在 parent的左子樹中同時 item在祖父結點的左子樹中, 如果之字型:item 在parent 在右子樹中,在gparent的左子樹中(映象相反),綜合結果 兩者的比較結果一定不是相同 的

    (compare(item,gparent)<0)  == (compare(item,parent)<0) 

/**
	 * 從頂向下 遍歷 時候 current 結點 有 兩個紅色 兒子 --- 目的 保證 current的兄弟 結點 永遠是黑色 不同於自低向上刪除
	 * 設定 讓 紅色兒子成為 BLACk,當前結點 變成 RED 同時 與 其父節點 進行顏色 比較 
	 * parent 是 RED 必須 進行旋轉(旋轉中判斷 是進行 單旋 還是 雙旋)
	 * */
	private void handReorient(T item) {
		
		current.color=RED;
		current.left.color=BLACK;
		current.right.color=BLACK;
		
		//發現 current 與父節點 都是 
		if(parent.color==RED)
		{
			gparent.color=RED;
			
			/**判斷 是對 current 的 父結點 還是祖父結點 進行 旋轉
			 * 插入 結點 比當前節點 父節點 小 一字型 旋轉 祖父結點---單旋轉 對 祖父結點
			 * 插入結點 比 當前 結點 父節點 大 之字型---雙旋轉 --先對 父節點 在對 祖父結點 */
			if((compare(item, gparent)<0)!=(compare(item, parent)<0))
			{
				parent=roate(item,gparent);// 祖父 --的 兒子 (當前節點的 父節點 之字形) 執行之後還會//執行 下一行程式 相當於雙旋
			}
			current=roate(item,great);// 旋轉曾祖父的兒子 只進行單一 單旋
			
			current.color=BLACK;// 調整 樹結構 重新遍歷 新插入點
					
					
		}
		heade.right.color=BLACK;// make the root BLACk
	}

Roate 函式: 藉助 AVL中 roateWithLeft 、roateWithRight:

                  翻轉的 輸入結點的兒子結點 ,因此為什麼要其曾祖結點 great

/**
	 *  roate 旋轉 只是 輸入結點的 兒子結點 ,更新 樹的結構
	 *  */
	private RedBlackNode<T> roate(T item, RedBlackNode<T> parent) {
		if(compare(item, parent)<0)
		{
			return parent.left=compare(item, parent.left)<0?
					rotateWithLeftChild(parent.left):  // LL
				   rotateWithRightChild(parent.left)	;//LR
			
		}
		else
            return parent.right = compare( item, parent.right ) < 0 ?
                rotateWithLeftChild( parent.right ) :  // RL
                rotateWithRightChild( parent.right );  // RR
  
	}
	
package com.Tree;

import java.util.LinkedList;
import java.util.Queue;

public class RedBlackTree<T extends Comparable<? super T>> {

	private static final boolean RED = true;
	private static final boolean BLACK = false;
	
	public RedBlackNode<T> heade;
	public RedBlackNode<T> nullNode;
	
	private RedBlackNode<T>current;
	private RedBlackNode<T> parent;
	private RedBlackNode<T>gparent;
	private RedBlackNode<T> great;
	public RedBlackTree()
	{
		heade=new RedBlackNode<T>(null);
		nullNode=new RedBlackNode<T>(null);
		nullNode.left=nullNode.right=nullNode;
		heade.left=heade.right=nullNode;
	}
	private static class RedBlackNode<T>{
		
		T element;
		RedBlackNode<T> left;
		RedBlackNode<T> right;
		boolean color;
		
		public RedBlackNode(T e)
		{
			this(e,BLACK,null,null);
		}

		public RedBlackNode(T e, boolean c, RedBlackNode<T> object, RedBlackNode<T>object2) {
			this.element=e;
			
			this.color=c;
			
			this.left=object;
			
			this.right=object2;
		}
	}
	
	
	/**
	 * Insert into the tree 
	 *    自頂向下的插入方法
	 * */
	
	public void insert(T item)
	{
		insert(item,heade);
	}
	
	/**
	 * s輸出 結點 層次遍歷*/
	public void printTree(RedBlackNode<T> heade)
	{
		DoPrint(heade.right,0);
	}
	// 利用 層次遍歷
	private void DoPrint(RedBlackNode<T> r,int depth)
	{
		if(r!=nullNode)
		{
			DoPrint(r.left, depth+1);
			for(int i=0;i<depth;i++)
				System.out.print("     ");
			System.out.println(r.element+(r.color==RED?"RED":"BLACK"));
			DoPrint(r.right,depth+1);
		}
	}
	public static void main(String[] args)
	{
		int [] array={10,85,15,70,20,60,30,50,65,80,90,40,5,55};
		RedBlackTree<Integer> redBlackTree=new RedBlackTree<>();
		for(int i=0;i<array.length;i++)
			redBlackTree.insert(array[i]);
		
		redBlackTree.printTree(redBlackTree.heade);
	}
	public void LevelPrint(RedBlackNode<T> head)
	{
		RedBlackNode<T> root=head.right;
		
		Queue<RedBlackNode<T>> queue=new LinkedList<RedBlackTree.RedBlackNode<T>>();
		
		queue.add(root);
		
	}
	private void insert(T item, RedBlackNode<T> node) 
	{
		// 自頂向下 的 插入 邊 查詢 一邊進行 樹的調整
		current=parent=gparent=heade;
		nullNode.element=item;
		while(compare(item,current)!=0)
		
		{
			/*頂 往下 遍歷 定位到 其 插入位置**/
			great=gparent;gparent=parent;parent=current;
			
			 current=compare(item, current)<0? current.left:current.right;
			 
			 /**左右 兒子 是 RED 自頂向下的 插入過程中 主要遇到情形--目的 讓兄弟結點 U 永遠是 黑色*/
			 if(current.left.color==RED && current.right.color==RED)
				 handReorient(item);
		}
		
		// Insert fails if already present
		if(current!=nullNode)
			return ;
		// 遍歷 直接 遍歷到其葉子節點上
		current=new RedBlackNode<T>(item,BLACK,nullNode,nullNode);
		
		if(compare(item, parent)<0)
			parent.left=current;
		else
			parent.right=current;
		handReorient(item);
	}

	/**
	 * 從頂向下 遍歷 時候 current 結點 有 兩個紅色 兒子 --- 目的 保證 current的兄弟 結點 永遠是黑色 不同於自低向上刪除
	 * 設定 讓 紅色兒子成為 BLACk,當前結點 變成 RED 同時 與 其父節點 進行顏色 比較 
	 * parent 是 RED 必須 進行旋轉(旋轉中判斷 是進行 單旋 還是 雙旋)
	 * */
	private void handReorient(T item) {
		
		current.color=RED;
		current.left.color=BLACK;
		current.right.color=BLACK;
		
		//發現 current 與父節點 都是 
		if(parent.color==RED)
		{
			gparent.color=RED;
			
			/**判斷 是對 current 的 父結點 還是祖父結點 進行 旋轉
			 * 插入 結點 比當前節點 父節點 小 一字型 旋轉 祖父結點---單旋轉 對 祖父結點
			 * 插入結點 比 當前 結點 父節點 大 之字型---雙旋轉 --先對 父節點 在對 祖父結點 */
			if((compare(item, gparent)<0)!=(compare(item, parent)<0))
			{
				parent=roate(item,gparent);// 祖父 --的 兒子 (當前節點的 父節點 之字形) 執行之後還會執行 下一行程式 相當於雙旋
			}
			current=roate(item,great);// 旋轉曾祖父的兒子 只進行單一 單旋
			
			current.color=BLACK;// 調整 樹結構 重新遍歷 新插入點
					
					
		}
		heade.right.color=BLACK;
	}

	/**
	 *  roate 旋轉 只是 輸入結點的 兒子結點 ,更新 樹的結構
	 *  */
	private RedBlackNode<T> roate(T item, RedBlackNode<T> parent) {
		if(compare(item, parent)<0)
		{
			return parent.left=compare(item, parent.left)<0?
					rotateWithLeftChild(parent.left):  // LL
				   rotateWithRightChild(parent.left)	;//LR
			
		}
		else
            return parent.right = compare( item, parent.right ) < 0 ?
                rotateWithLeftChild( parent.right ) :  // RL
                rotateWithRightChild( parent.right );  // RR
  
	}
	
	/**
     * Rotate binary tree node with left child.
     */
    private RedBlackNode<T> rotateWithLeftChild( RedBlackNode<T> k2 )
    {
        RedBlackNode<T> k1 = k2.left;
        k2.left = k1.right;
        k1.right = k2;
        return k1;
    }

    /**
     * Rotate binary tree node with right child.
     */
    private RedBlackNode<T> rotateWithRightChild( RedBlackNode<T> k1 )
    {
        RedBlackNode<T> k2 = k1.right;
        k1.right = k2.left;
        k2.left = k1;
        return k2;
    }

	private int compare(T item, RedBlackNode<T> current) {
		if (current==heade)
			return 1;
		else
			return item.compareTo(current.element);
		
	}
public void LevelPrint(RedBlackNode<T> head)
{
RedBlackNode<T> root=head.right;

Queue<RedBlackNode<T>> queue=new LinkedList<RedBlackTree.RedBlackNode<T>>();

queue.add(root);
RedBlackNode<T> current;
while(!queue.isEmpty())
{
/**按層次輸出*/
int L=queue.size();// 記錄上一層 結點數
for(int i=0;i<L;i++)
{
current=queue.poll();
System.out.print(current.element +(current.color==RED?"RED":"BLACK")+"   ");
if(current.left!=nullNode) queue.add(current.left);

if(current.right!=nullNode ) queue.add(current.right);


}
System.out.println();
}

}
}