本文根據《大話數據結構》一書及網絡資料,實現了Java版的平衡二叉樹(AVL樹)。html
在上篇博客中所實現的二叉排序樹(二叉搜索樹),其查找性能取決於二叉排序樹的形狀,當二叉排序樹比較平衡時(深度與徹底二叉樹相同,[log2n]+1),時間複雜度爲O(logn);但也有可能出現極端的斜樹,如依照{35,37,47,51,58,62,73,88,91,99}的順序,構建的二叉排序樹就以下圖所示,查找時間複雜度爲O(n)。java
圖1 斜樹node
爲提升查找複雜度,在二叉排序樹的基礎上,提出了二叉平衡樹:一種二叉排序樹,其中每一個結點的左右子樹的高度差至多等於1。算法
圖2 平衡二叉樹與非平衡二叉樹網絡
定義二叉樹結點的左子樹深度減去右子樹深度的值爲平衡因子BF(Balance Factor),平衡樹全部結點的BF只能是-1,0,1。數據結構
距離新插入結點最近,且平衡因子的絕對值大於1的結點爲根的子樹,稱爲最小不平衡子樹。ide
構建平衡二叉樹的基本思想就是:在構建過程當中,每當插入一個結點時,檢查是否破壞了樹的平衡性,如果,則找出最小不平衡樹,進行相應的調整。函數
具體實現步驟不少地方都有介紹,本文再也不贅述。post
二叉樹的結點結構定義:性能
private class AVLnode { int data; // 結點數據 int bf; // 平衡因子,左高記爲1,右高記爲-1,平衡記爲0 AVLnode lChild, rChild; // 左右孩子 public AVLnode(int data) { this.data = data; bf = 0; lChild = null; rChild = null; } }
根據以前提到的基本思想,爲調整最小不平衡樹,首先要了解兩種最基本的操做:左旋操做和右旋操做。
(1)右旋
以下圖中左邊的最小不平衡二叉樹,進行右旋操做便可變爲右邊中的平衡二叉樹。
圖3 右旋操做(狀況1)
根據上圖,容易編寫右旋操做的代碼以下:
/* * 右旋 * 返回新的根結點 */ public AVLnode rRotate(AVLnode p) { AVLnode l = p.lChild; p.lChild = l.rChild; l.rChild = p; return l; }
(2)左旋操做
同上所述,左旋操做的圖示及代碼,以下所示。
圖4 左旋操做
/* * 左旋 * 返回新的根結點 */ public AVLnode lRotate(AVLnode p) { AVLnode r = p.rChild; p.rChild = r.lChild; r.lChild = p; return r; }
對於最小不平衡子樹,若其左子樹深度比右子樹大2(下面稱爲左斜的不平衡樹),需進行左平衡旋轉操做。若右子樹深度大,則需進行右平衡旋轉操做。
(1)左平衡旋轉:
左斜的不平衡樹有幾種形式,下面分開討論
>> L結點的BF值爲1時
直接對根結點P右旋便可
狀況(1):以下圖所示,右旋根結點P。平衡後,P結點的BF值爲0,其左結點L的BF值也爲0。
圖5 狀況(1)
>> L結點的BF值爲-1時
都是先對L結點左旋,再對P結點右旋。根據平衡後P結點和L結點的BF值不一樣,能夠分出下面三種狀況:
狀況(2):以下圖所示,先左旋L結點,再右旋P結點。平衡後,P結點的BF值爲-1,L結點的BF值爲0,LR結點的BF值爲0。
圖6 狀況(2)
(注:示意圖中,小三角形表示的子樹比大三角形表示的子樹深度少1,下同)
狀況(3):以下圖所示,先左旋L結點,再右旋P結點。平衡後,P結點的BF值爲0,L結點的BF值爲1,LR結點的BF值爲0。
圖7 狀況(3)
狀況(4):以下圖所示,先左旋L結點,再右旋P結點。平衡後,P結點的BF值爲0,L結點的BF值爲0,LR結點的BF值爲0。
圖8 狀況(4)
>> L結點的BF值爲0時
最小不平衡子樹也可能出現下面這種狀況(插入時不會出現,但刪除操做過程當中可能出現),《大話》一書中沒有討論到這種狀況。
狀況(5):以下圖所示,直接右旋P結點。平衡後,L結點的BF值爲-1,LR結點的BF值爲1。
圖9 狀況(5)
綜上所述,左平衡旋轉一共可能出現5種狀況,如下爲左平衡旋轉操做的代碼:
/* * 左平衡旋轉(左子樹高度比右子樹高2時(左斜)執行的操做) * 返回值爲新的根結點 */ public AVLnode leftBalance(AVLnode p) { AVLnode l = p.lChild; switch (l.bf) { case 1: // 情況(1) p.bf = 0; l.bf = 0; return rRotate(p); case -1: AVLnode lr = l.rChild; switch (lr.bf) { case 1: // 情況(2) p.bf = -1; l.bf = 0; break; // break別漏寫了 case -1: // 情況(3) p.bf = 0; l.bf = 1; break; case 0: // 情況(4) p.bf = 0; l.bf = 0; break; } lr.bf = 0; // 設置好平衡因子bf後,先左旋 p.lChild = lRotate(l);// 不能用l=leftBalance(l); // 再右旋 return rRotate(p); case 0: // 這種狀況書中沒有考慮到,狀況(5) l.bf = -1; p.bf = 1; return rRotate(p); } // 如下狀況應該是不會出現的,全部狀況都已經包括,除非程序還有問題 System.out.println("bf超出範圍,請檢查程序!"); return p; }
(2)右平衡旋轉:
與左平衡的分析相似,也能夠分爲五種狀況,再也不贅述,下面直接給出代碼:
/* * 右平衡旋轉(右子樹高度比左子樹高2時執行的操做) * 返回值爲新的根結點 */ public AVLnode rightBalance(AVLnode p) { AVLnode r = p.rChild; switch (r.bf) { case -1: p.bf = 0; r.bf = 0; return lRotate(p); case 1: AVLnode rl = r.lChild; switch (rl.bf) { case 1: r.bf = -1; p.bf = 0; break; case -1: r.bf = 0; p.bf = 1; break; case 0: r.bf = 0; p.bf = 0; break; } rl.bf = 0; p.rChild = rRotate(r); return lRotate(p); case 0: p.bf = -1; r.bf = 1; return lRotate(p); } // 如下狀況應該是不會出現的,全部狀況都已經包括,除非程序還有問題 System.out.println("bf超出範圍,請檢查程序!"); return p; }
二叉平衡樹是一種二叉排序樹,因此其操做與二叉排序樹相同,但爲了保持平衡,須要對平衡度進行分析。
引入一個變量taller來衡量子樹是否長高,若子樹長高了,就必須對平衡度進行分析:若是不平衡,就進行上面所說的左右平衡旋轉操做。
具體的Java實現代碼以下:
/* * 插入操做 * 要多定義一個taller變量 */ boolean taller;// 樹是否長高 public void insert(int key) { root = insert(root, key); } private AVLnode insert(AVLnode tree, int key) {// 二叉查找樹的插入操做同樣,但多了樹是否長高的判斷(樹沒長高就徹底相似BST二叉樹),要記得每次對taller賦值 if (tree == null) { taller = true; return new AVLnode(key); } if (key == tree.data) { System.out.println("數據重複,沒法插入!"); taller = false; return tree; } else if (key < tree.data) { tree.lChild = insert(tree.lChild, key); if (taller == true) { // 左子樹長高了,要對tree的平衡度分析 switch (tree.bf) { case 1: // 本來左子樹比右子樹高,須要左平衡處理 taller = false; // 左平衡處理,高度沒有增長 return leftBalance(tree); case 0: // 本來左右子樹等高,現因左子樹增高而增高 tree.bf = 1; taller = true; return tree; case -1: // 本來右子樹比左子樹高,現左右子樹相等 tree.bf = 0; taller = false; return tree; } } } else if (key > tree.data) { tree.rChild = insert(tree.rChild, key); if (taller == true) { // 右子樹長高了,要對tree的平衡度分析 switch (tree.bf) { case 1: // 本來左子樹高,現等高 tree.bf = 0; taller = false; return tree; case 0: // 本來等高,現右邊增高了 tree.bf = -1; taller = true; return tree; case -1: // 本來右子樹高,需右平衡處理 taller = false; return rightBalance(tree); } } } return tree; }
AVL樹的完整代碼以下(含測試代碼):
package AVLTree; /** * AVL樹 * @author Yongh * */ public class AVLTree { private AVLnode root; private class AVLnode { int data; // 結點數據 int bf; // 平衡因子,左高記爲1,右高記爲-1,平衡記爲0 AVLnode lChild, rChild; // 左右孩子 public AVLnode(int data) { this.data = data; bf = 0; lChild = null; rChild = null; } } /* * 右旋 * 返回新的根結點 */ public AVLnode rRotate(AVLnode p) { AVLnode l = p.lChild; p.lChild = l.rChild; l.rChild = p; return l; } /* * 左旋 * 返回新的根結點 */ public AVLnode lRotate(AVLnode p) { AVLnode r = p.rChild; p.rChild = r.lChild; r.lChild = p; return r; } /* * 左平衡旋轉(左子樹高度比右子樹高2時(左斜)執行的操做) * 返回值爲新的根結點 */ public AVLnode leftBalance(AVLnode p) { AVLnode l = p.lChild; switch (l.bf) { case 1: // 情況(1) p.bf = 0; l.bf = 0; return rRotate(p); case -1: AVLnode lr = l.rChild; switch (lr.bf) { case 1: // 情況(2) p.bf = -1; l.bf = 0; break; // break別漏寫了 case -1: // 情況(3) p.bf = 0; l.bf = 1; break; case 0: // 情況(4) p.bf = 0; l.bf = 0; break; } lr.bf = 0; // 設置好平衡因子bf後,先左旋 p.lChild = lRotate(l);// 不能用l=leftBalance(l); // 再右旋 return rRotate(p); case 0: // 這種狀況書中沒有考慮到,狀況(5) l.bf = -1; p.bf = 1; return rRotate(p); } // 如下狀況應該是不會出現的,全部狀況都已經包括,除非程序還有問題 System.out.println("bf超出範圍,請檢查程序!"); return p; } /* * 右平衡旋轉(右子樹高度比左子樹高2時執行的操做) * 返回值爲新的根結點 */ public AVLnode rightBalance(AVLnode p) { AVLnode r = p.rChild; switch (r.bf) { case -1: p.bf = 0; r.bf = 0; return lRotate(p); case 1: AVLnode rl = r.lChild; switch (rl.bf) { case 1: r.bf = -1; p.bf = 0; break; case -1: r.bf = 0; p.bf = 1; break; case 0: r.bf = 0; p.bf = 0; break; } rl.bf = 0; p.rChild = rRotate(r); return lRotate(p); case 0: p.bf = -1; r.bf = 1; return lRotate(p); } // 如下狀況應該是不會出現的,全部狀況都已經包括,除非程序還有問題 System.out.println("bf超出範圍,請檢查程序!"); return p; } /* * 插入操做 * 要多定義一個taller變量 */ boolean taller;// 樹是否長高 public void insert(int key) { root = insert(root, key); } private AVLnode insert(AVLnode tree, int key) {// 二叉查找樹的插入操做同樣,但多了樹是否長高的判斷(樹沒長高就徹底相似BST二叉樹),要記得每次對taller賦值 if (tree == null) { taller = true; return new AVLnode(key); } if (key == tree.data) { System.out.println("數據重複,沒法插入!"); taller = false; return tree; } else if (key < tree.data) { tree.lChild = insert(tree.lChild, key); if (taller == true) { // 左子樹長高了,要對tree的平衡度分析 switch (tree.bf) { case 1: // 本來左子樹比右子樹高,須要左平衡處理 taller = false; // 左平衡處理,高度沒有增長 return leftBalance(tree); case 0: // 本來左右子樹等高,現因左子樹增高而增高 tree.bf = 1; taller = true; return tree; case -1: // 本來右子樹比左子樹高,現左右子樹相等 tree.bf = 0; taller = false; return tree; } } } else if (key > tree.data) { tree.rChild = insert(tree.rChild, key); if (taller == true) { // 右子樹長高了,要對tree的平衡度分析 switch (tree.bf) { case 1: // 本來左子樹高,現等高 tree.bf = 0; taller = false; return tree; case 0: // 本來等高,現右邊增高了 tree.bf = -1; taller = true; return tree; case -1: // 本來右子樹高,需右平衡處理 taller = false; return rightBalance(tree); } } } return tree; } /* * 前序遍歷 */ public void preOrder() { preOrderTraverse(root); System.out.println(); } private void preOrderTraverse(AVLnode node) { if (node == null) return; System.out.print(node.data+" "); preOrderTraverse(node.lChild); preOrderTraverse(node.rChild); } /* * 中序遍歷 */ public void inOrder() { inOrderTraverse(root); System.out.println(); } private void inOrderTraverse(AVLnode node) { if (node == null) return; inOrderTraverse(node.lChild); System.out.print(node.data+" "); inOrderTraverse(node.rChild); } /* * 測試代碼 */ public static void main(String[] args) { AVLTree aTree = new AVLTree(); int[] arr = { 3, 2, 1, 4, 5, 6, 7, 10, 9, 8 }; for (int i : arr) { aTree.insert(i); } System.out.print("前序遍歷結果:"); aTree.preOrder(); System.out.print("中序遍歷結果:"); aTree.inOrder(); AVLTree bTree = new AVLTree(); int[] arr2 = { 3,2,1,4,5,6,7,16,15,14,13,12,11,10,8,9 }; for (int i : arr2) { bTree.insert(i); } System.out.print("前序遍歷結果:"); bTree.preOrder(); System.out.print("中序遍歷結果:"); bTree.inOrder(); } }
前序遍歷結果:4 2 1 3 7 6 5 9 8 10 中序遍歷結果:1 2 3 4 5 6 7 8 9 10 前序遍歷結果:7 4 2 1 3 6 5 13 11 9 8 10 12 15 14 16 中序遍歷結果:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
測試代碼中的兩個AVL樹以下圖所示:
圖10 aTree
圖11 bTree
後記
若是不用平衡因子BF,而是子樹的高度來進行分析,討論的狀況就比較少,可參考這篇博客:AVL樹(三)之 Java的實現