1. 程式人生 > >平衡二叉樹各種演算法詳解一:紅黑樹

平衡二叉樹各種演算法詳解一:紅黑樹

平衡二叉樹(Balanced Binary Tree)具有以下性質:它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。平衡二叉樹的常用演算法有紅黑樹、AVL、Treap、伸展樹、SBT等。最小二叉平衡樹的節點的公式如下 F(n)=F(n-1)+F(n-2)+1 這個類似於一個遞迴的數列,可以參考Fibonacci數列,1是根節點,F(n-1)是左子樹的節點數量,F(n-2)是右子樹的節點數量。平衡二叉樹的時間複雜度為o(h),h為樹高,保持樹矮胖的形態有利於提高演算法的效率。

平衡二叉樹中常見操作有插入,刪除,旋轉等。

旋轉是維持平衡二叉樹必要的操作,包括左旋和右旋,二者成映象對稱。

左旋演算法如下(T.ROOT表示樹根,NIL表示空結點)

def Left-Rotate(x):
    y=x.right
    if(y.left!=NIL):
        y.left.parent=x
        x.right=y.left
    if(x.parent==NIL):
        T.ROOT=y
    else if(x.parent.left==x):
        x.parent.left=y
    else x.parent.right=y
    y.parent=x.parent
    x.parent=y
    y.left=x

右旋與之類似,故不再貼出程式碼。

插入演算法如下:

def Tree-Insert(T,z):
    y=NIL         //use y as the parent of x
    x=T.ROOT
    while(x!=NIL):
        y=x
        if(z.value<x.value):
            x=x.left
        else x=x.right
    z.parent=y
    if(y==NIL):    //tree is empty
        T.ROOT=z
    else if(z.value<y.value):
        y.left=z
    else y.right=z

刪除演算法需要考慮三種情況:

1. z沒有子結點,直接刪除,修改z的父結點,再用NIL替代z即可

2. z只有一個子結點,將其提升到z的位置,修改z的父結點,再用z的子結點代替z

3. z有兩個子結點,這時稍麻煩一點,需要尋找z的後繼,即z的右子樹中最小的一個結點,還要考慮後繼正好為z的右孩子的情況

首先定義一個子樹的移植演算法:

def Transplant(T,u,v):      //用以v為根的子樹替代以U為根的子樹
    if(u.parent==NIL)
        T.ROOT=v
    else if(u==u.parent.left)
        u.parent.left=v
    else u.parent.right=v
    if(v!=NIL)
        v.parent=u.parent

再定義一個尋找後繼的方法:
def Tree-min(z):
    if(z.left!=NIL)
        z=z.left
    return z

利用現成的移植演算法進行刪除:
def Tree-delete(T,z)
    if(z.left==NIL)
        Transplant(T,z,z.right)
    else if(z.right==NIL)
        Transplant(T,z,z.left)
    else y=Tree-min(z.right)       //y is the successor of z
        if(y.parent!=z)
            Transplant(T,y,y.right)
            y.right=z.right
            y.right.parent=y
        Transplant(T,z,y)
        z.left.parent=y
        y.left=z.left

這就是平衡二叉樹中常見的旋轉,插入,刪除演算法。由於插入,刪除有可能改變樹的平衡性,所以需呼叫旋轉方法以保持樹的平衡性。

下面舉例詳解各種平衡二叉樹。

紅黑樹:

紅黑樹(Red Black Tree) 是一種自平衡二叉查詢樹,是在計算機科學中用到的一種資料結構,典型的用途是實現關聯陣列。紅黑樹在很多地方都有應用。在C++ STL中,很多部分(包括set, multiset, map, multimap)應用了紅黑樹的變體(SGI STL中的紅黑樹有一些變化,這些修改提供了更好的效能,以及對set操作的支援)。

紅黑樹是每個節點都帶有顏色屬性的二叉查詢樹,顏色或紅色或黑色。在二叉查詢樹強制一般要求以外,對於任何有效的紅黑樹我們增加了如下的額外要求: 性質1. 節點是紅色或黑色。 性質2. 根節點是黑色。 性質3 每個葉節點(NIL節點,空節點)是黑色的。 性質4 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點) 性質5. 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。 這些約束強制了紅黑樹的關鍵性質: 從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長。結果是這個樹大致上是平衡的。因為操作比如插入、刪除和查詢某個值的最壞情況時間都要求與樹的高度成比例,這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的,而不同於普通的二叉查詢樹。

紅黑樹的插入:

def RB-Insert(T,z):
    y=T.nil         //use y as the parent of x
    x=T.ROOT
    while(x!=T.nil):
        y=x
        if(z.value<x.value):
            x=x.left
        else x=x.right
    z.parent=y
    if(y==T.nil):    //tree is empty
        T.ROOT=z
    else if(z.value<y.value):
        y.left=z
    else y.right=z
    z.left=T.nil
    z.right=T.nil
    z.color=RED       //z is red 
    RB-Insert-fixup(T,z)   //make the tree follow red-black rule
插入後修復樹的顏色:

def RB-Insert-fixup(T,z)
    while(z.parent.color==RED)
        if(z.parent==z.parent.parent.left)
            y=z.parent.parent.right            //y is the uncle node of z 
            if(y.color==RED)                   //case 1
                z.p.color=BLACK
                y.color=BLACK
                z.parent.parent.color=RED
                z=z.parent.parent
            else if(z==z.parent.right)          //case 2
                z=z.parent
                Left-Rotate(T,z)
            z.parent.color=BLACK                //case 3
            z.parent.parent.color=RED
            Right-Rotate(T,z.parent.parent)     <pre name="code" class="python">        else (same as "then" clause with right and left exchanged)  //if a.parent==z.parent.parent.left,case 1,2,3with right and left exchanged
 T.ROOT.color=BLACK

紅黑樹的刪除:

同樣先定義一個移植演算法:

def RB-Transplant(T,u,v):      //用以v為根的子樹替代以U為根的子樹
    if(u.parent==T.nil)
        T.ROOT=v
    else if(u==u.parent.left)
        u.parent.left=v
    else u.parent.right=v
    v.parent=u.parent

刪除演算法:

def RB-delete(T,z)
	y=z 
	y-original-color=y.color 
	if(z.left==T.nil)
		x=z.right
		RB-Transplant(T,z,z.right)
	else if(z.right==T.nil)
		x=z.left
		RB-Transplant(T,z,z.left)
	else y=Tree-min(z.right)
		y-original-color=y.color
		x=y.right
		if(y.parent==z)
			x.parent=y 
		else RB-Transplant(T,y,y.right)
			y.right=z.right
			y.right.parent=y 
		RB-Transplant(T,z,y)
		y.left=z.left
		y.left.parent=y 
		y.color=z.color
	if(y-original-color==BLACK)
		RB-delete-fixup(T,x)

刪除後的修復顏色屬性操作

RB-delete-fixup(T,x):

def RB-delete-fixup(T,x)
    while(x!=T.ROOT&&x.color==BLACK)
        if(x==x.parent.left)
            w=x.parent.right
            if(w.color==RED)                     //case 1
                w.color=BlACK
                x.parent.color=RED
                Left-Rotate(T,x.parent)
                w=x.parent.right
            if(w.left.color==BLACK&&w.right.color==BLACK)       //case 2
                w.color=RED
                x=x.parent
            else if(w.right.color==BLACK)        //case 3
                w.left.color=BLACK
                w.color=RED
                Right-Rotate(T,w)
                w=x.parent.right
            w.color=x.parent.color               //case 4
            x.parent.color=BLACK
            w.right.color=BLACK
            Left-Rotate(T,x.parent)
            x=T.ROOT
        else (same as "then" clause with right and left exchanged)
    x.color=BLACK


(ps:紅黑樹這塊真是要把我弄暈了再見