自動平衡二叉樹的構建-AVL樹
AVL 樹是基於二叉搜尋樹的。但是它是自動平衡的,意思是,它左子樹的深度和右子樹的深度差要麼是 0,±1 。沒有其他可能。這就是 AVL 樹,這棵樹長的比較對稱,不會出現極端的一邊倒的情況。這也就意味著 AVL 在建立過程中,根節點也會不斷的變換。 AVL 樹的目的就是為了解決搜尋二叉搜尋樹的時候可能出現最壞複雜度的情況。 AVL 樹極端情況下複雜度也就 log(n) 。
但是 AVL 樹的建立有點複雜。網上查詢了部分資料,維基百科的 AVL 定義是我主要的參考來源。具體定義可以自行檢視。
在實現過程中,最讓我頭疼的是平衡因子的計算,資料中也沒有說明具體的演算法,或者說了也沒看懂。網上搜尋了一些資料,也是極盡坑爹,要麼含糊不說,要麼一筆帶過。始終不知道如何計算。因此本人在程式中使用的是計算出左右子樹的最大深度,做減法。由於計算左右子樹是一個遞迴演算法,也是相對有些複雜。因此演算法的整體複雜度也隨之增加。
為了降低程式的複雜度,對於 AVL 樹節點的定義資訊,也有所增加,比如增加了指向父節點的指標。當指明孩子的時候,自動的也確定父親。程式碼如下:
1. public node leftchild
2. {
3. set
4. {
5. _leftchild = value ;
6. if (_leftchild != null )
7. {
8. _leftchild.father = this;// 確定父結點
9.
10. }
11. }
12. get
13. {
14. return _leftchild;
15. }
16. }
17. public node rightchild
18. {
19. set
20. {
21. _rightchild = value ;
22. if (_rightchild != null )
23. {
24. _rightchild.father = this;// 確定父結點
25.
26. }
27. }
28. get
29. {
30. return _rightchild;
31. }
32. }
33.
其中 balance 是平衡因子。實現方式如下:
1. public int balance
2. {
3. get
4. {
5. return maxDepth( this .leftchild) - maxDepth( this .rightchild);
6. }
7. }
AVL
樹構建中的關鍵步驟叫做轉換。分成
4
種情況的轉換。具體的內容可以參考維基百科。本人參照維基百科的圖例寫出
4
個轉換方式。這四種分別是
LR
,
LL
,
RL
,
RR
。
此處簡述一下 LR 轉換。首先要匹配哪個結點是哪個結點。要找準了,不然就容易出錯。 LR 中,結點 4 轉到結點 5 和 3 中間,並且結點 B 成為結點 3 右孩子了。其中 LR 到 LL 的時候結點 5 依然是根結點,沒變。那麼程式就如下寫法,其中 root 是根結點引數,但此處的轉換不需要有根結點參與。程式的寫法儘量照圖,把要轉動的點作為 leaf 結點傳進來。即 node3 就是 leaf ,然後依次匹配好哪個是 node4 ,哪個是 node5, 哪個是 B ,全都匹配好之後,把圖上的動作翻譯成程式碼。此處需要對連結串列操作有一定的基礎。懂的自然懂。當 LR 經過圖上的轉換後,變成 LL 型了,那麼直接呼叫 LL 的轉換方式就可以了。
1. static void LL_To_Balanced( ref node root , node leaf )
2. {
3.
4. node node4 = leaf;
5. node node3 = node4.leftchild;
6. node node5 = node4.father;
7. node C = node4.rightchild;
8. // 以上程式碼根據圖把結點給找準了,下面就開始動作了。
9.
10. if (node5.father != null)// 此處涉及到node4變成根結點了。那麼如果node5的father存在的話,要指向node4的。
11. {
12. if (node5.isleftchild)
13. node5.father.leftchild = node4;
14. else if (node5.isrightchild)
15. node5.father.rightchild = node4;
16. }
17. else
18. {
19. root = node4;// 如果node5是整棵樹的根結點,那麼現在就變成node4了
20. node4.father = null;
21. }
22. node4.rightchild = node5;
23. node5.leftchild = C;
24. }
再看 LL 型的轉換,首先,同樣的找準結點,匹配好哪個是結點 4 ,哪個是結點 5 ,哪個是結點 3 ,那個結點 C ,因為 LL 的轉換也就只涉及到結點 3,4,5,C 這幾個結點。程式碼中,把結點 4 當做 leaf ,因此傳值的時候要傳對了。由於 LL 轉換涉及到樹的根結點的變換,根據圖中的示例,假設 node5 有個 father ,那麼這個 father 現在是把 node4 作為孩子了。至於是左孩子還是右孩子,就看 node5 是左孩子還是右孩子。假設 node5 沒有 father ,也就是說 node5 是根節點,那麼 node4 就成為根結點了。
1. static void LL_To_Balanced( ref node root , node leaf )
2. {
3.
4. node node4 = leaf;
5. node node3 = node4.leftchild;
6. node node5 = node4.father;
7. node C = node4.rightchild;
8. // 以上程式碼根據圖把結點給找準了,下面就開始動作了。
9.
10. if (node5.father != null)// 此處涉及到node4變成根結點了。那麼如果node5的father存在的話,要指向node4的。
11. {
12. if (node5.isleftchild)
13. node5.father.leftchild = node4;
14. else if (node5.isrightchild)
15. node5.father.rightchild = node4;
16. }
17. else
18. {
19. root = node4;// 如果node5是整棵樹的根結點,那麼現在就變成node4了
20. node4.father = null;
21. }
22. node4.rightchild = node5;
23. node5.leftchild = C;
24. }
RL 和 RR 的轉換也是如上面所述的演算法。關鍵要匹配好那些結點。
本人之前寫過二叉搜尋樹的插入,可以參考前面的文章。 AVL 的建立和二叉搜尋樹是類似的,根據結點的大小插入位子。但是多了一步,就是 要做轉型,也就是上面說的 4 種轉換方式。
那麼程式碼可以先借用構建二叉搜尋樹的程式碼了。插完結點後,呼叫轉型方法不就 OK 了嘛。如下的方法,就是生成了普通的二叉搜尋樹。然後在對其轉換。
1. // 此處演算法就是插入二叉搜尋樹
2. static void InsertIntoAVL( node root, node leaf)
3. {
4. if (root == null)
5. {
6. root = leaf;
7. return ;
8. }
9. if (root.nodevalue == leaf.nodevalue)
10. {
11. return ;
12. }
13. else if (root.nodevalue > leaf.nodevalue)
14. {
15. if (root.hasleftchild == false)
16. {
17. root.leftchild = leaf;
18. }
19. else
20. {
21. InsertIntoAVL(root.leftchild, leaf);
22. }
23. }
24. else if (root.nodevalue < leaf.nodevalue)
25. {
26. if (root.hasrightchild == false)
27. {
28. root.rightchild = leaf;
29. }
30. else
31. {
32. InsertIntoAVL(root.rightchild, leaf);
33. }
34. }
35. }
以上透過程式碼插入結點,演算法同二叉搜尋樹的結點插入,接下來確定轉換問題。轉換的時候,要確定,對誰轉換,哪種型別的轉換。
先第一個問題,對誰轉換。那當然是插入了哪個結點,這個結點有可能打破了平衡,因此轉換肯定和該節點有關。在我的程式碼中,以新插入的結點作為參照,依次找它的父節點。(此處就知道為什麼要做 node 的定義中增加 father 屬性了),一旦找到某個父節點的平衡因子是 ±2 ,可以開始處理第二個問題了。此處再強調的是,平衡因子的演算法是左 - 右,因此如果是 +2 ,那麼就是說明左邊更深,反之亦反。
第二個問題,哪種型別的轉換,維基百科上也有現成的說法,拿來照搬就行。簡單的說就是如果一個結點的平衡因子是 ±2 了,那麼就看它的孩子的平衡因子,是 ±1 ,不同的值代表了不同的型別。具體的邏輯,可以看我的程式碼反推。此處要注意的是,傳進去的 leaf 結點,我都是把平衡因子是 ±2 的子結點作為 leaf 結點的。當然你也可以直接就用平衡因子是 ±2 結點作為引數傳遞。
程式碼實現如下,此方法的引數 root 就是表示這顆 AVL 樹的根結點,因為很有可能在構建這棵樹的時候根結點會變化,所以要時刻記錄下,列印樹的時候可以從根結點開始。 leaf 引數就是之前你新插入的結點,根據這個 leaf 為基準找 father ,你可以根據程式碼反推一些邏輯:
1. // 做旋轉
2. static void Revolve(ref node root, node leaf)
3. {
4. Console.ForegroundColor = ConsoleColor.Yellow;
5. if (root == null )// 如果一棵樹是空樹,即沒有根結點的情況下,插入一個葉子,那麼根結點就是這樣葉子,也不需要做旋轉了。
6. {
7. root = leaf;
8. Console.ForegroundColor = ConsoleColor.Gray;
9. return;
10. }
11. node itsfather = leaf.father;
12. while (itsfather != null )
13. {
14. if (itsfather.balance == 2 )//LR 或者LL
15. {
16. if (itsfather.leftchild.balance == 1 )
17. {
18. Console.WriteLine( "LL" );
19. LL_To_Balanced(ref root, itsfather.leftchild);
20. }
21. else if (itsfather.leftchild.balance == - 1 )
22. {
23. Console.WriteLine( "LR" );
24. LR_To_Balanced(ref root, itsfather.leftchild);
25.
26. }
27. Console.ForegroundColor = ConsoleColor.Gray;
28. return;
29. }
30. else if (itsfather.balance == - 2 )
31. {
32. if (itsfather.rightchild.balance == - 1 )
33. {
34. Console.WriteLine( "RR" );
35. RR_To_Balanced(ref root, itsfather.rightchild);
36. }
37. else if (itsfather.rightchild.balance == 1 )
38. {
39. RL_To_Balanced(ref root, itsfather.rightchild);
40. Console.WriteLine( "RL" );
41. }
42. Console.ForegroundColor = ConsoleColor.Gray;
43. return;
44. }
45. itsfather = itsfather.father;
46. }
47.
48. Console.WriteLine( "no need to revolve" );
49. Console.ForegroundColor = ConsoleColor.Gray;
50. }
最後,就是呼叫插入結點的方法。為了讓結果直觀,本人寫了個插入方法,主要列印樹的結構。可自行反推邏輯。
1. // 插入結點,列印一些資訊
2. private static node InsertNode ( ref node root , node thenode )
3. {
4. InsertIntoAVL(root, thenode);
5. Console.WriteLine( " 插入了" + thenode.nodevalue);
6. printtree(root);
7. Console.WriteLine( " 開始對其旋轉" );
8. Revolve( ref root, thenode);
9. Console.ForegroundColor = ConsoleColor.Green;
10. Console.WriteLine( " 旋轉後的結構如下" );
11. Console.ForegroundColor = ConsoleColor.Gray;
12. printtree(root);
13. Console.WriteLine( "******END*******" );
14. return root;
15. }
Main 方法中,呼叫就如下:
1. node node_1 = new node (1 );
2. node node_2 = new node (2 );
3. node node_3 = new node (3 );
4. node node_4 = new node (4 );
5. node node_5 = new node (5 );
6. node node_6 = new node (6 );
7. node node_7 = new node (7 );
8. node node_8 = new node (8 );
9. node node_9 = new node (9 );
10. node node_10 = new node (10 );
11. node node_11 = new node (11 );
12. node node_12 = new node (12 );
13. node node_13 = new node (13 );
14. node node_14 = new node (14 );
15. node node_15 = new node (15 );
16. node node_16 = new node (16 );
17. node node_17 = new node (17 );
18. node node_18 = new node (18 );
19. node node_19 = new node (19 );
20. node node_20 = new node (20 );
21. node node_21 = new node (21 );
22. node node_22 = new node (22 );
23.
24.
25. node root = null;
26. InsertNode( ref root, node_10);
27. InsertNode( ref root, node_8);
28. InsertNode( ref root, node_5);
29. InsertNode( ref root, node_12);
30. InsertNode( ref root, node_17);
31. InsertNode( ref root, node_9);
32. InsertNode( ref root, node_1);
33. InsertNode( ref root, node_4);
34.
結果如下:
具體程式碼,看附件。
後記:
AVL 樹的構建,看起來簡單,但是真的寫起程式碼來還是很麻煩。思路清晰是關鍵。還有很多要改進的地方,比如,平衡因子的計算,本人演算法實在不算高明。還有最佳化的餘地,比如回溯法。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69992957/viewspace-2754794/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 平衡二叉樹(AVL)二叉樹
- 手擼二叉樹——AVL平衡二叉樹二叉樹
- 平衡二叉樹(AVL樹),原來如此!!!二叉樹
- 十三、Mysql之平衡二叉樹(AVL樹)MySql二叉樹
- Java 樹結構實際應用 四(平衡二叉樹/AVL樹)Java二叉樹
- 平衡二叉樹(AVL樹)和 二叉排序樹轉化為平衡二叉樹 及C語言實現二叉樹排序C語言
- 手寫AVL平衡二叉搜尋樹
- 演算法與資料結構——AVL樹(平衡二叉搜尋樹)演算法資料結構
- 資料結構中的樹(二叉樹、二叉搜尋樹、AVL樹)資料結構二叉樹
- 資料結構高階--AVL(平衡二叉樹)(圖解+實現)資料結構二叉樹圖解
- 資料結構與演算法-二叉查詢樹平衡(AVL)資料結構演算法
- 看動畫學演算法之:平衡二叉搜尋樹AVL Tree動畫演算法
- 平衡二叉樹,B樹,B+樹二叉樹
- 平衡二叉樹二叉樹
- 排序二叉樹和平衡二叉樹排序二叉樹
- 平衡樹和二叉樹的區別二叉樹
- 資料結構-平衡二叉樹資料結構二叉樹
- 資料結構之樹結構概述(含滿二叉樹、完全二叉樹、平衡二叉樹、二叉搜尋樹、紅黑樹、B-樹、B+樹、B*樹)資料結構二叉樹
- 資料結構和演算法-二叉樹,AVL,紅黑樹資料結構演算法二叉樹
- Java集合原始碼分析之基礎(五):平衡二叉樹(AVL Tree)Java原始碼二叉樹
- 詳解什麼是平衡二叉樹(AVL)(修訂補充版)二叉樹
- 5分鐘瞭解二叉樹之AVL樹二叉樹
- 資料結構之MySQL獨愛B+樹(二叉樹、AVL樹、紅黑樹、B樹對比)資料結構MySql二叉樹
- 漫話:什麼是平衡(AVL)樹?這應該是把AVL樹講的最好的文章了
- 平衡二叉查詢樹:紅黑樹
- 滿二叉樹、完全二叉樹、平衡二叉樹、二叉搜尋樹(二叉查詢樹)和最優二叉樹二叉樹
- 110. 平衡二叉樹二叉樹
- 資料結構 - AVL 樹資料結構
- Java實現紅黑樹(平衡二叉樹)Java二叉樹
- 二叉樹的深度、寬度遍歷及平衡樹二叉樹
- BST(二叉搜尋樹)、AVL樹、紅黑樹、2-3樹、B樹、B+樹、LSM樹、Radix樹比較
- 二叉堆、BST 與平衡樹
- LeetCode-110-平衡二叉樹LeetCode二叉樹
- JZ-039-平衡二叉樹二叉樹
- 資料結構之「AVL樹」資料結構
- 程式碼隨想錄——二叉樹-12.平衡二叉樹二叉樹
- 夜刷:平衡二叉樹的基本操作二叉樹
- python實現非平衡二叉樹Python二叉樹