[转]C#与数据结构--树论--平衡二叉树(AVL Tree)
??
C#與數據結構--樹論--平衡二叉樹(AVL Tree)
http://www.cnblogs.com/abatei/archive/2008/11/17/1335031.html
介紹
我們知道在二叉查找樹中,如果插入元素的順序接近有序,那么二叉查找樹將退化為鏈表,從而導致二叉查找樹的查找效率大為降低。如何使得二叉查找樹無論在什么樣情況下都能使它的形態最大限度地接近滿二叉樹以保證它的查找效率呢?
前蘇聯科學家G.M. Adelson-Velskii 和 E.M. Landis給出了答案。他們在1962年發表的一篇名為 《An algorithm for the organization of information》的文章中提出了一種自平衡二叉查找樹(self-balancing binary search tree)。這種二叉查找樹在插入和刪除操作中,可以通過一系列的旋轉操作來保持平衡,從而保證了二叉查找樹的查找效率。最終這種二叉查找樹以他們的名字命名為“AVL-Tree”,它也被稱為平衡二叉樹(Balanced Binary Tree)。這里所說的平衡使我們想到了中庸之道,但有句話說得好,“中不偏,庸不易”。學會這種平衡術是一個相當痛苦的過程。
什么是平衡
為了保證平衡,AVL樹中的每個結點都有一個平衡因子(balance factor,以下用BF表示),它表示這個結點的左、右子樹的高度差,也就是左子樹的高度減去右子樹的高度的結果值。AVL樹上所有結點的BF值只能是-1、0、1。反之,只要二叉樹上一個結點的BF的絕對值大于1,則該二叉樹就不是平衡二叉樹。圖1演示了平衡二叉樹和非平衡二叉樹。
AVL樹的構造
如何構造一棵平衡二叉樹呢?動態地調整二叉查找樹平衡的方法為:每插入一個結點后,首先檢查是否破壞了樹的平衡性,如果因插入結點而破壞了二叉查找樹的平衡,則找出離插入點最近的不平衡結點,然后將該不平衡結點為根的子樹進行旋轉操作,我們稱該不平衡結點為旋轉根,以該旋轉根為根的子樹稱為最小不平衡子樹。失衡狀態可歸納為4種,它們對應著4種旋轉類型。下面使用了Flash動畫演示了這四種旋轉類型,請確保你的電腦安裝了Flash8.0以上版本的播放器,并且瀏覽器允許使用Flash。做這幾個動畫純屬好玩,希望有一天可以使用Silverlight做這些的動畫。不過好象現在還沒什么博客支持。
l???????? LL型旋轉
?
?
如以上動畫所示,插入結點5后,結點50的BF值由1變為2,此時結點50為旋轉根。這種插入結點50的左孩子的左子樹而導致失衡的情況需要進行LL旋轉(LL意為左左)??梢杂^察到,雖然結點50的BF值由1變為了0,但最小不平衡子樹在插入結點5前和旋轉后的高度不變。
l???????? RR型旋轉
如以上動畫所示,插入結點90后,結點25的BF值由-1變為-2,此時結點25為旋轉根。這種插入結點25的右孩子的右子樹而導致失衡的情況需要進行RR旋轉。最小不平衡子樹在插入結點90前和旋轉后的高度不變。
l???????? LR型旋轉
?
插入旋轉根的左孩子的右子樹而導致失衡的情況需要進行LR旋轉。這里演示了LR(L)和LR(R) 兩種情況。插入結點前和旋轉后的最小不平衡子樹高度不變。
l???????? RL型旋轉
插入旋轉根的右孩子的左子樹而導致失衡的情況需要進行RL旋轉。這里演示了RL(L)和RL(R) 兩種情況。插入結點前和旋轉后的最小不平衡子樹高度不變。
以上動畫只演示了幾種旋轉類型的較復雜的情況,并沒有全部演示,比如旋轉根的左子樹或右子樹為空的情況,具體算法請參見稍后的代碼。
AVL樹上結點的插入
AVL算法的思想理解起來還是不太困難的,但如果真要使用代碼實現就沒那么簡單了,它擁有超高的算法實現復雜度。我查了很多資料,大部分只給出主要算法代碼,對于如何回溯修改BF值,如何處理不需要旋轉的情況絕口不提,甚至對刪除算法直接忽略。上網找資料,中文的,英文的全找了,大部分寫代碼不加注釋,狂汗....,實在看不下去。大部分代碼使用遞歸算法,C#實現更是少得可憐,在國外網站找到一個,但使用了三叉鏈表實現,多加了一個parent指針,總之無法找到讓人滿意的代碼。最后一咬牙一跺腳,自己實現。最讓人頭痛的莫過于如何處理插入和刪除后的回溯和修改BF值,慶幸的是最終還是按照我最初的想法比較漂亮地實現了AVL樹。優點是:無遞歸;無parent指針;插入和刪除操作使用同一旋轉方法,使代碼更為簡化。缺點是:為了兼顧效率,有些地方的處理比較特殊,代碼很難完全讀懂。
下面對本算法做原理上的介紹:
1、 如何回溯修改祖先結點的平衡因子
我們知道,在AVL樹上插入一個新結點后,有可能導致其他結點BF值的改變,哪些結點的BF值會被改變?如何計算新的BF值呢?要解決這些問題,我們必須理解以下幾個要點:
l???????? 只有根結點到插入結(橙色結點)點路徑(稱為插入路徑)上的結點的BF值會被改變。如圖2所示,只有插入路徑上結點(灰色結點)的BF值被改變,其他非插入路徑上結點的BF值不變。
?
?
?
?
l???????? 當一個結點插入到某個結點的左子樹時,該結點的BF值加1(如圖2的結點50、43);當一個結點插入到某個結點的右子樹時,該結點的BF值減1(如圖2的結點25、30)。如何在程序中判斷一個結點是插入到左子樹還是右子樹呢?很簡單,根據二叉查找樹的特性可以得出結論:如果插入結點小于某個結點,則必定是插入到這個結點的左子樹中;如果如果插入結點大于某個結點,則必定插入到這個結點的右子樹中。
l???????? 修改BF值的操作需從插入點開始向上回溯至根結點依次進行,當路徑上某個結點BF值修改后變為0,則修改停止。如圖3所示,插入結點30后,首先由于30<43,將結點43的BF值加1,使得結點43的BF值由0變為 1;接下來由于30>25,結點25的BF值由1改為0;此時結點25的BF值為0,停止回溯,不需要再修改插入路徑上結點50的平衡因子。道理很簡單:當結點的BF值由1或-1變為0,表明高度小的子樹添加了新結點,樹的高度沒有增加,所以不必修改祖先結點的平衡因子;當結點的BF值由0變為1或-1時,表明原本等高左右子樹由于一邊變高而導致失衡,整棵子樹的高度變高,所以必須向上修改祖先結點的BF值。
?
?
?
2、 何時進行旋轉操作?如何判斷作什么類型的旋轉?
在回溯修改祖先結點的平衡因子時,如果碰到某個結點的平衡因子變為2或-2,表明AVL樹失衡,這時需要以該結點為旋轉根,對最小不平衡子樹進行旋轉操作。由于是從插入點開始回溯,所以最先碰到的BF值變為2或-2的結點必定為最小不平衡子樹的根結點。如圖4所示,插入39后,43和50兩個結點的BF值都會變為2,而必定先訪問到結點43,所以43是最小不平衡子樹的根。根據以上Flash動畫演示所示,旋轉操作完成后,最小不平衡子樹插入結點前和旋轉完成后的高度不變,所以可以得出結論:旋轉操作完成后,無需再回溯修改祖先的BF值。這樣,圖4中的結點25和50的平衡因子實際上在插入結點操作完成后的BF值不變(對比圖2)。
?
?
可以通過旋轉根及其孩子的BF值來決定作什么類型的旋轉操作:
l???????? 當旋轉根的BF值為2時:
如果旋轉根的左孩子的BF值為1,則進行LL型旋轉;
如果旋轉根的左孩子的BF值為-1,則進行LR型旋轉。
l???????? 當旋轉根的BF值為-2時:
如果旋轉根的右孩子的BF值為1,則進行RL型旋轉;
如果旋轉根的右孩子的BF值為-1,則進行RR型旋轉。
可通過觀察之前的Flash動畫檢驗以上結論。
3、 如何保存插入路徑?
可以使用棧來保存插入路徑上的各個結點,但由于棧是由數組抽象而來,為了進一步加快AVL樹的運行速度,我直接使用數組存放插入路徑,這樣可以減少方法的調用,盡量避免一些不必要的操作。
如果實現AVL樹實現索引器,而在索引器中使用int32,那么AVL樹元素的長度不會超過一個32位整數的最大值。一個深度為32的滿二叉樹可以存放結點數為:2^32-1=4294967295,這個值已經遠遠超出32位的整數范圍,所以我將數組的長度定為32。這樣就不必如ArrayList那樣進行擴容操作了。另外本程序還使用了一個成員變量p用于指示當前訪問結點,由于p指針的存在可以不必在每次進行插入和刪除操作后清空數組中的元素,進一步增加了AVL樹的運行速度。
使用數組的另一個好處是可以隨時訪問旋轉根的雙親結點,以方便進行旋轉操作時修改根結點。
AVL樹上結點的刪除
AVL樹的刪除操作與插入操作有許多相似之處,它的大體步驟如下:
⑴用二叉查找樹的刪除算法找到并刪除結點(這里簡稱為刪除點);
⑵沿刪除點向上回溯,必要時,修改祖先結點的BF值;
⑶回溯途中,一旦發現某個祖先的BF值失衡,如插入操作那樣旋轉不平衡子樹使之變為平衡,跟插入操作不同的是,旋轉完成后,回溯不能停止,也就是說在AVL樹上刪除一個結點有可能引起多次旋轉。
AVL樹上的刪除和插入操作雖然大體相似,但還是有一些不同之處,大家需要注意以下幾點:
1、?回溯方式的不同
在刪除結點的回溯過程中,當某個結點的BF值變為1或-1時,則停止回溯。這一點同插入操作正好相反,因為BF值由0變為1或-1,表明原本平衡的子樹由于某個結點的刪除導致了不平衡,子樹的總體高度不變,所以不再需要向上回溯。
2、 旋轉方式的不同
如圖5所示:刪除AVL樹中的結點25導致結點50的BF值由原來的-1變為-2,但旋轉根50的右孩子的BF值為0,這種情況在前面所講的旋轉操作中并不存在,那么是需要對它進行RR旋轉還是RL旋轉呢?正確方法是使用RR旋轉,所不同之處是旋轉后的BF值不同,需要單獨處理。需要注意,這種情況在插入操作時不可能發生,LL旋轉也存在類型的情況。另外旋轉完成后樹的整體高度沒有改變,所以大部分情況下旋轉操作完成后,子樹的高度降低,需要繼續向上回溯修改祖先的BF值,而只有這種情況由于子樹的高度未改變,所以停止回溯。
?
?
?
?
3、 刪除點的選擇特例
在二叉查找樹中,我們知道當刪除點p既有左子樹,又有右子樹,此時可以令p的中序遍歷直接前驅結點代替p,然后再從二叉查找樹中刪除它的直接前驅。如圖7.13所示,結點5既有左子樹,又有右子樹,它的直接前驅結點為4。在刪除結點5時,首先用結點4代替結點5,然后再刪除結點4完成刪除操作。這里需要注意的是此時必須將刪除前的結點4作為刪除點來進行向上回溯操作,而不是結點5。
?
?
?
?
AVL樹的代碼實現
這里沒有給出AVL樹的泛型實現,它只存放整數。因為如果使用泛型實現并按照微軟慣例,使用鍵/值對實現,那么代碼真的就很難讀懂了。以這個代碼為基礎,改為泛型實現是很容易的事。另外C#中沒AVL樹的實現,而實現了紅黑樹,說明紅黑樹更有效率,所以也不必將AVL泛型化,代碼忽略了部分出錯可能。紅黑樹將在后面講解。
?
?
?
public?class?BinarySearchTree?:?IBinaryTree?//實現畫樹接口
????{????//成員變量
????????private?Node?_head;?//頭指針
????????private?Node[]?path?=?new?Node[32];?//記錄訪問路徑上的結點
????????private?int?p;?//表示當前訪問到的結點在_path上的索引
????????INode?IBinaryTree.Head?//顯式接口實現
????????{
????????????get?{?return?(INode)_head;?}
????????}
????????public?bool?Add(int?value)?//添加一個元素
????????{???//如果是空樹,則新結點成為二叉排序樹的根
????????????if?(_head?==?null)
????????????{
????????????????_head?=?new?Node(value);
????????????????_head.BF?=?0;
????????????????return?true;
????????????}
????????????p?=?0;
????????????//prev為上一次訪問的結點,current為當前訪問結點
????????????Node?prev?=?null,?current?=?_head;
????????????while?(current?!=?null)
????????????{
????????????????path[p++]?=?current;?//將路徑上的結點插入數組
????????????????//如果插入值已存在,則插入失敗
????????????????if?(current.Data?==?value)
????????????????{
????????????????????return?false;
????????????????}
????????????????prev?=?current;
????????????????//當插入值小于當前結點,則繼續訪問左子樹,否則訪問右子樹
????????????????current?=?(value?<?prev.Data)???prev.Left?:?prev.Right;
????????????}
????????????current?=?new?Node(value);?//創建新結點
????????????current.BF?=?0;
????????????if?(value?<?prev.Data)?//如果插入值小于雙親結點的值
????????????{
????????????????prev.Left?=?current;?//成為左孩子
????????????}
????????????else?//如果插入值大于雙親結點的值
????????????{
????????????????prev.Right?=?current;?//成為右孩子
????????????}
????????????path[p]?=?current;?//將新元素插入數組path的最后
????????????//修改插入點至根結點路徑上各結點的平衡因子
????????????int?bf?=?0;
????????????while?(p?>?0)
????????????{???//bf表示平衡因子的改變量,當新結點插入左子樹,則平衡因子+1
????????????????//當新結點插入右子樹,則平衡因子-1
????????????????bf?=?(value?<?path[p?-?1].Data)???1?:?-1;
????????????????path[--p].BF?+=?bf;?//改變當父結點的平衡因子
????????????????bf?=?path[p].BF;?//獲取當前結點的平衡因子
????????????????//判斷當前結點平衡因子,如果為0表示該子樹已平衡,不需再回溯
????????????????//而改變祖先結點平衡因子,此時添加成功,直接返回
????????????????if?(bf?==?0)
????????????????{
????????????????????return?true;
????????????????}
????????????????else?if?(bf?==?2?||?bf?==?-2)?//需要旋轉的情況
????????????????{
????????????????????RotateSubTree(bf);
????????????????????return?true;
????????????????}
????????????}
????????????return?true;
????????}
????????//刪除指定值
????????public?bool?Remove(int?value)?
????????{
????????????p?=?-1;
????????????//parent表示雙親結點,node表示當前結點
????????????Node?node?=?_head;
????????????//尋找指定值所在的結點
????????????while?(node?!=?null)
????????????{
????????????????path[++p]?=?node;
????????????????//如果找到,則調用RemoveNode方法刪除結點
????????????????if?(value?==?node.Data)
????????????????{
????????????????????RemoveNode(node);//現在p指向被刪除結點
????????????????????return?true;?//返回true表示刪除成功
????????????????}
????????????????if?(value?<?node.Data)
????????????????{???//如果刪除值小于當前結點,則向左子樹繼續尋找
????????????????????node?=?node.Left;
????????????????}
????????????????else
????????????????{???//如果刪除值大于當前結點,則向右子樹繼續尋找
????????????????????node?=?node.Right;
????????????????}
????????????}
????????????return?false;?//返回false表示刪除失敗
????????}
????????//刪除指定結點
????????private?void?RemoveNode(Node?node)
????????{
????????????Node?tmp?=?null;
????????????//當被刪除結點存在左右子樹時
????????????if?(node.Left?!=?null?&&?node.Right?!=?null)
????????????{
????????????????tmp?=?node.Left;?//獲取左子樹
????????????????path[++p]?=?tmp;
????????????????while?(tmp.Right?!=?null)?//獲取node的中序遍歷前驅結點,并存放于tmp中
????????????????{???//找到左子樹中的最右下結點
????????????????????tmp?=?tmp.Right;
????????????????????path[++p]?=?tmp;
????????????????}
????????????????//用中序遍歷前驅結點的值代替被刪除結點的值
????????????????node.Data?=?tmp.Data;
????????????????if?(path[p?-?1]?==?node)
????????????????{
????????????????????path[p?-?1].Left?=?tmp.Left;
????????????????}
????????????????else
????????????????{
????????????????????path[p?-?1].Right?=?tmp.Left;
????????????????}
????????????}
????????????else?//當只有左子樹或右子樹或為葉子結點時
????????????{???//首先找到惟一的孩子結點
????????????????tmp?=?node.Left;
????????????????if?(tmp?==?null)?//如果只有右孩子或沒孩子
????????????????{
????????????????????tmp?=?node.Right;
????????????????}
????????????????if?(p?>?0)
????????????????{
????????????????????if?(path[p?-?1].Left?==?node)
????????????????????{???//如果被刪結點是左孩子
????????????????????????path[p?-?1].Left?=?tmp;
????????????????????}
????????????????????else
????????????????????{???//如果被刪結點是右孩子
????????????????????????path[p?-?1].Right?=?tmp;
????????????????????}
????????????????}
????????????????else??//當刪除的是根結點時
????????????????{
????????????????????_head?=?tmp;
????????????????}
????????????}
????????????//刪除完后進行旋轉,現在p指向實際被刪除的結點
????????????int?data?=?node.Data;
????????????while?(p?>?0)
????????????{???//bf表示平衡因子的改變量,當刪除的是左子樹中的結點時,平衡因子-1
????????????????//當刪除的是右子樹的孩子時,平衡因子+1
????????????????int?bf?=?(data?<=?path[p?-?1].Data)???-1?:?1;
????????????????path[--p].BF?+=?bf;?//改變當父結點的平衡因子
????????????????bf?=?path[p].BF;?//獲取當前結點的平衡因子
????????????????if?(bf?!=?0)?//如果bf==0,表明高度降低,繼續后上回溯
????????????????{
????????????????????//如果bf為1或-1則說明高度未變,停止回溯,如果為2或-2,則進行旋轉
????????????????????//當旋轉后高度不變,則停止回溯
????????????????????if?(bf?==?1?||?bf?==?-1?||?!RotateSubTree(bf))
????????????????????{
????????????????????????break;
????????????????????}
????????????????}
????????????}
????????}
????????//旋轉以root為根的子樹,當高度改變,則返回true;高度未變則返回false
????????private?bool?RotateSubTree(int?bf)?
????????{
????????????bool?tallChange?=?true;
????????????Node?root?=?path[p],?newRoot?=?null;
????????????if?(bf?==?2)?//當平衡因子為2時需要進行旋轉操作
????????????{
????????????????int?leftBF?=?root.Left.BF;
????????????????if?(leftBF?==?-1)?//LR型旋轉
????????????????{
????????????????????newRoot?=?LR(root);
????????????????}
????????????????else?if?(leftBF?==?1)
????????????????{
????????????????????newRoot?=?LL(root);?//LL型旋轉
????????????????}
????????????????else?//當旋轉根左孩子的bf為0時,只有刪除時才會出現
????????????????{
????????????????????newRoot?=?LL(root);
????????????????????tallChange?=?false;
????????????????}
????????????}
????????????if?(bf?==?-2)?//當平衡因子為-2時需要進行旋轉操作
????????????{
????????????????int?rightBF?=?root.Right.BF;?//獲取旋轉根右孩子的平衡因子
????????????????if?(rightBF?==?1)?
????????????????{
????????????????????newRoot?=?RL(root);?//RL型旋轉
????????????????}
????????????????else?if?(rightBF?==?-1)
????????????????{
????????????????????newRoot?=?RR(root);?//RR型旋轉
????????????????}
????????????????else?//當旋轉根左孩子的bf為0時,只有刪除時才會出現
????????????????{
????????????????????newRoot?=?RR(root);
????????????????????tallChange?=?false;
????????????????}
????????????}
????????????//更改新的子樹根
????????????if?(p?>?0)
????????????{
????????????????if?(root.Data?<?path[p?-?1].Data)
????????????????{
????????????????????path[p?-?1].Left?=?newRoot;
????????????????}
????????????????else
????????????????{
????????????????????path[p?-?1].Right?=?newRoot;
????????????????}
????????????}
????????????else
????????????{
????????????????_head?=?newRoot;?//如果旋轉根為AVL樹的根,則指定新AVL樹根結點
????????????}
????????????return?tallChange;
????????}
????????//root為旋轉根,rootPrev為旋轉根雙親結點
????????private?Node?LL(Node?root)?//LL型旋轉,返回旋轉后的新子樹根
????????{
????????????Node?rootNext?=?root.Left;
????????????root.Left?=?rootNext.Right;
????????????rootNext.Right?=?root;
????????????if?(rootNext.BF?==?1)
????????????{
????????????????root.BF?=?0;
????????????????rootNext.BF?=?0;
????????????}
????????????else?//rootNext.BF==0的情況,刪除時用
????????????{
????????????????root.BF?=?1;
????????????????rootNext.BF?=?-1;
????????????}
????????????return?rootNext;?//rootNext為新子樹的根
????????}
????????private?Node?LR(Node?root)?//LR型旋轉,返回旋轉后的新子樹根
????????{
????????????Node?rootNext?=?root.Left;
????????????Node?newRoot?=?rootNext.Right;
????????????root.Left?=?newRoot.Right;
????????????rootNext.Right?=?newRoot.Left;
????????????newRoot.Left?=?rootNext;
????????????newRoot.Right?=?root;
????????????switch?(newRoot.BF)?//改變平衡因子
????????????{
????????????????case?0:
????????????????????root.BF?=?0;
????????????????????rootNext.BF?=?0;
????????????????????break;
????????????????case?1:
????????????????????root.BF?=?-1;
????????????????????rootNext.BF?=?0;
????????????????????break;
????????????????case?-1:
????????????????????root.BF?=?0;
????????????????????rootNext.BF?=?1;
????????????????????break;
????????????}
????????????newRoot.BF?=?0;
????????????return?newRoot;?//newRoot為新子樹的根
????????}
????????private?Node?RR(Node?root)?//RR型旋轉,返回旋轉后的新子樹根
????????{
????????????Node?rootNext?=?root.Right;
????????????root.Right?=?rootNext.Left;
????????????rootNext.Left?=?root;
????????????if?(rootNext.BF?==?-1)
????????????{
????????????????root.BF?=?0;
????????????????rootNext.BF?=?0;
????????????}
????????????else?//rootNext.BF==0的情況,刪除時用
????????????{
????????????????root.BF?=?-1;
????????????????rootNext.BF?=?1;
????????????}
????????????return?rootNext;?//rootNext為新子樹的根
????????}
????????private?Node?RL(Node?root)?//RL型旋轉,返回旋轉后的新子樹根
????????{
????????????Node?rootNext?=?root.Right;
????????????Node?newRoot?=?rootNext.Left;
????????????root.Right?=?newRoot.Left;
????????????rootNext.Left?=?newRoot.Right;
????????????newRoot.Right?=?rootNext;
????????????newRoot.Left?=?root;
????????????switch?(newRoot.BF)?//改變平衡因子
????????????{
????????????????case?0:
????????????????????root.BF?=?0;
????????????????????rootNext.BF?=?0;
????????????????????break;
????????????????case?1:
????????????????????root.BF?=?0;
????????????????????rootNext.BF?=?-1;
????????????????????break;
????????????????case?-1:
????????????????????root.BF?=?1;
????????????????????rootNext.BF?=?0;
????????????????????break;
????????????}
????????????newRoot.BF?=?0;
????????????return?newRoot;?//newRoot為新子樹的根
????????}
????}
?
轉載于:https://www.cnblogs.com/freebird92/archive/2010/10/08/1845962.html
總結
以上是生活随笔為你收集整理的[转]C#与数据结构--树论--平衡二叉树(AVL Tree)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Stuxnet病毒全球肆虐 将影响我国众
- 下一篇: 坚信。。。。