紅黑樹 自頂向下插入操作(一)
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();
}
}
}