介紹AVL樹以前先簡單瞭解平衡二叉搜索樹。平衡二叉搜索樹具備如下性質:它是一顆空樹或者它的左右兩個子樹的高度的絕對值不超過1,而且左右兩個子樹都是一個平衡二叉樹。AVL是最早發明的自平衡二叉樹算法。在AVL中任何節點的兩個子樹的高度最大差異爲1,因此它也被稱爲高度平衡樹。查找,插入和刪除在平均和最壞狀況下都是O(log n)。增長和刪除可能須要經過一次或者屢次旋轉來從新平衡這個樹。 java
package test.algorithm.FastSlowPointer; import test.algorithm.FastSlowPointer.BinarySortTree.BiTNode; public class AVLTree { class BiTNode{ static final int LH = 1; static final int EH = 0; static final int RH = -1; private int data; private BiTNode parent; private BiTNode lChild; private BiTNode rChild; //平衡因子(值爲-一、0、1,非這三個值必需要旋轉,計算公式爲左子樹層次數減去右子樹層次數) private int bf; public BiTNode(int data,int bf,BiTNode parent){ this.data = data; this.bf = bf; this.parent = parent; } } //樹的根節點 private BiTNode root; /** * 平衡而二叉樹的插入操做 * 基本原理爲: * 1.首先如同二叉排序樹通常,找到其要插入的節點的位置,並把元素插入其中; * 2.自下向上進行回溯,回溯作兩個事情: * (1)其一是修改祖先節點的平衡因子,當插入 一個節點時只有根節點到插入節點 * 的路徑中的節點的平衡因子會被改變,並且改變的原則是當插入節點在某節點(稱爲A) * 的左子樹 中時,A的平衡因子(稱爲BF)爲BF+1,當插入節點在A的右子樹中時A的BF-1, * 而判斷插入節點在左子樹中仍是右子樹中只要簡單的比較它與A的大小 * (2)在改變祖先節點的平衡因子的同時,找到最近一個平衡因子大於2或小於-2的節點, * 從這個節點開始調整最小不平衡樹進行旋轉調整,關於如何調整見下文。 * 因爲調整後,最小不平衡子樹的高度與插入節點前的高度相同,故不需繼續要調整祖先節點。 * 這裏還有一個特殊狀況,若是調整BF時,發現某個節點的BF變爲0了,則中止向上繼續調整, * 由於這代表此節點中高度小的子樹增長了新節點,高度不變,那麼祖先節點的BF天然不變。 * */ public boolean insert(int key){ if(root==null){ //樹爲空的時候,添加到根節點 root = new BiTNode(key,0,null); return true; } //當前遍歷的節點 BiTNode curr = root; //要插入節點的父節點 BiTNode parent = null; do{ parent = curr; if(key<curr.data){ // 往左子樹找 curr = curr.lChild; }else if(key>curr.data){ // 往右子樹找 curr = curr.rChild; }else{ // 找到,插入失敗 return false; } }while(curr!=null); //插入節點 if(key<parent.data){ //插入左孩子 parent.lChild = new BiTNode(key,BiTNode.EH,parent); }else{ //插入右孩子 parent.rChild = new BiTNode(key,BiTNode.EH,parent); } //自下向上回溯,查找最近不平衡節點 while(parent!=null){ if(key<parent.data){ //插入節點在parent的左子樹中 parent.bf++; }else{ //插入節點在parent的右子樹中 parent.bf--; } if(parent.bf == BiTNode.EH){ //此節點的balance爲0,再也不向上調整BF值,且不須要旋轉 break; } if(Math.abs(parent.bf) == 2){ //找到最小不平衡子樹根節點 ,旋轉修復 fixAfterInsertion(parent); break; } parent = parent.parent; } return true; } /** * 調整的方法: * 1.當最小不平衡子樹的根(如下簡稱R)爲2時,即左子樹高於右子樹: * 若是R的左子樹的根節點的BF爲1時,作右旋; * 若是R的左子樹的根節點的BF爲-1時,先左旋而後再右旋 * * 2.R爲-2時,即右子樹高於左子樹: * 若是R的右子樹的根節點的BF爲1時,先右旋後左旋 * 若是R的右子樹的根節點的BF爲-1時,作左旋 * * 至於調整以後,各節點的BF變化見代碼 * * 左旋:兩節點順時針旋轉 (右孩子作根左旋) * 右旋:兩節點逆時針旋轉 (左孩子作根右旋) */ private void fixAfterInsertion(BiTNode parent){ //左子樹層次深 if(parent.bf == 2){ leftBalance(parent); } //右子樹層次深 if(parent.bf == -2){ rightBalance(parent); } } /** * 作左平衡處理 * 平衡因子的調整如圖: * * * 狀況1(rd的BF爲1) * t rd * / \ / \ * l tr 左旋後右旋 l t * / \ -------> / \ \ * ll rd ll rdl tr * / * rdl * * * 狀況2(rd的BF爲0) * t rd * / \ / \ * l tr 左旋後右旋 l t * / \ -------> / \ / \ * ll rd ll rdl rdr tr * / \ * rdl rdr * * * 狀況3(rd的BF爲-1) * t rd * / \ / \ * l tr 左旋後右旋 l t * / \ -------> / / \ * ll rd ll rdr tr * \ * rdr * * * 狀況4(L等高) * t l * / 右旋處理 / \ * l ------> ll t * / \ / * ll rd rd * */ private boolean leftBalance(BiTNode t){ // 標記樹的高度是否改變 boolean taller = true; BiTNode l = t.lChild; switch (l.bf) { //左高,右旋調整,旋轉後樹的高度減少(左左狀況,見圖) case BiTNode.LH : t.bf = BiTNode.EH; l.bf = BiTNode.EH; rotateRight(t); break; //右高,分狀況調整 (左右狀況,見圖) case BiTNode.RH : BiTNode rd = l.rChild; //調整各個節點的BF switch(rd.bf){ //參見方法註釋狀況1 case BiTNode.LH : t.bf = BiTNode.RH; l.bf = BiTNode.EH; break; //參見方法註釋狀況2 case BiTNode.EH : t.bf = BiTNode.EH; l.bf = BiTNode.EH; break; //參見方法註釋狀況3 case BiTNode.RH : t.bf = BiTNode.EH; l.bf = BiTNode.LH; break; } //先左旋再右旋 rd.bf = BiTNode.EH; rotateLeft(t.lChild); rotateRight(t); break; //特殊狀況4,這種狀況在添加時不可能出現, //只在移除時可能出現,旋轉以後總體樹高不變 //刪除root的右孩子 case BiTNode.EH : t.bf = BiTNode.LH; l.bf = BiTNode.RH; rotateRight(t); taller = false; break; } return taller; } /** * 最小旋轉子樹的根節點 * 向右旋轉以後,p移到p的右子節點處,p的左子樹l變爲最小旋轉子樹的根節點 * l的右子節點變爲p的左節點、 * 例如: p(2) l(-1) * / 右旋轉 / \ * l(0) ------> / p(0) * / \ / / * lL(0) lR(0) lL(0) lR(0) */ private void rotateRight(BiTNode p){ System.out.println("繞"+p.data+"右旋"); if(p!=null){ BiTNode l = p.lChild; //p的父節點賦給l的父節點 l.parent = p.parent; //把l的右節點lR做爲p的左節點 p.lChild = l.rChild; if(l.rChild!=null){ l.rChild.parent = p; } if(p.parent==null){ //p是根節點,從新設置根節點 root = l; }else if(p.parent.rChild==p){ //p是父節點右子樹的根節點,從新設置左子樹的根節點 p.parent.rChild = l; }else{ //p是父節點左子樹的根節點,從新設置左子樹的根節點 p.parent.rChild = l; } //p爲l的右子樹 l.rChild = p; //設置p的父節點爲l p.parent = l; } } /** * 最小旋轉子樹的根節點 * 向左旋轉以後p移到p的左子樹處,p的右子樹B變爲此最小子樹根節點, * B的左子樹變爲p的右子樹 * 好比: p(-2) r(1) * \ 左旋轉 / \ * r(0) ----> p(0) \ * / \ \ \ * rL(0) rR(0) rL(0) rR(0) * 旋轉以後樹的深度之差不超過1 */ private void rotateLeft(BiTNode p){ System.out.println("繞"+p.data+"左旋"); if(p!=null){ BiTNode r = p.rChild; //p的父節點賦給r的父節點 r.parent = p.parent; //把r的左節點rR做爲p的右節點 p.rChild = r.lChild; if(r.lChild!=null){ r.lChild.parent = p; } if (p.parent == null) //p是根節點 ,r變爲父節點,即B爲父節點 root = r; else if (p.parent.lChild == p) //p是左子節點 ,p的父節點的左子樹爲r p.parent.lChild = r; else //若是p是右子節點 p.parent.rChild = r; //p爲r的左子樹 r.lChild = p; //設置p的父節點爲r p.parent = r; } } /** * 作右平衡處理 * 平衡因子的調整如圖: * 狀況1(ld的BF爲1) * t ld * / \ / \ * tl r 先右旋再左旋 t r * / \ --------> / \ \ * ld rr tl ldl rr * / * ldl * * 狀況2(ld的BF爲0) * t ld * / \ / \ * tl r 先右旋再左旋 t r * / \ --------> / \ / \ * ld rr tl ldl ldr rr * / \ * ldl ldr * * 狀況3(ld的BF爲-1) * t ld * / \ / \ * tl r 先右旋再左旋 t r * / \ --------> / / \ * ld rr tl ldr rr * \ * ldr * * 狀況4(r的BF爲0) * t r * \ 左旋 / \ * r -------> t rr * / \ \ * ld rr ld * */ private boolean rightBalance(BiTNode t){ //記錄樹的層次變化 boolean heightLower = true; BiTNode r = t.rChild; switch(r.bf){ //左高,分狀況調整(右左狀況) case BiTNode.LH: BiTNode ld = r.lChild; //調整各個節點的BF switch(ld.bf){ //參見方法註釋狀況1 case BiTNode.LH : t.bf = BiTNode.EH; r.bf = BiTNode.RH; break; //參見方法註釋狀況2 case BiTNode.EH : t.bf = BiTNode.EH; r.bf = BiTNode.EH; break; //參見方法註釋狀況3 case BiTNode.RH : t.bf = BiTNode.LH; r.bf = BiTNode.EH; break; } ld.bf = BiTNode.EH; rotateRight(t.rChild); rotateLeft(t); break; //右高,左旋調整(右右狀況) case BiTNode.RH: t.bf = BiTNode.EH; r.bf = BiTNode.EH; rotateLeft(t); break; //特殊狀況4 case BiTNode.EH: r.bf = BiTNode.LH; t.bf = BiTNode.RH; rotateLeft(t); heightLower = false; break; } return heightLower; } /** * 查找指定元素,若是找到返回其BiTNode對象,不然返回null */ public BiTNode search(int key){ BiTNode node = root; while(node!=null){ if(key==node.data){ return node; }else if(key<node.data){ node = node.lChild; }else{ node = node.rChild; } } return null; } /** * 平衡二叉樹的移除元素操做 * */ public boolean remove(int key){ BiTNode e = search(key); if(e!=null){ delete(e); return true; } return false; } /** * 獲取中序遍歷節點node的直接後繼節點 * @param node * @return */ public BiTNode successor(BiTNode node){ if(node==null){ return null; }else if(node.rChild!=null){ //有右子樹,那麼右子樹最小的節點是node的直接後繼節點 BiTNode p = node.rChild; while(p.lChild!=null){ p = p.lChild; } return p; }else{ //沒有右子樹,且node的父節點左孩子,則node的父節點是直接後繼節點 BiTNode p = node.parent; //若是t是p的右子樹,則繼續向上搜索其直接後繼 //(node和其父節點均可能是右孩子,所以循環查找) BiTNode ch = node; while(p != null && ch == p.rChild){ ch = p; p = p.parent; } return p; } } private void delete(BiTNode p){ //若是p左右子樹都不爲空,找到其直接後繼,替換p, //以後p指向s,刪除p實際上是刪除s //(左右子樹都不爲空,用直接後繼節點替換之) if (p.lChild != null && p.rChild != null) { // 找直接後繼節點(右子樹最左節點) BiTNode s = successor(p); p.data = s.data; p = s; } BiTNode replacement = (p.lChild != null ? p.lChild : p.rChild); if (replacement != null){ //要刪除的節點有一個孩子(replacement不爲空) //(兩個孩子的狀況用直接後繼節點替換了,後繼節點沒有左孩) replacement.parent = p.parent; if(p.parent==null){ //要刪除的p是根節點,修改root值 root = replacement; }else if (p == p.parent.lChild){ //刪除節點p是左孩子 p.parent.lChild = replacement; }else{ //刪除節點p是右孩子 p.parent.rChild = replacement; } //p的指針清空,防止內存泄露 p.lChild = p.rChild = p.parent = null; //這裏更改了replacement的父節點,因此能夠直接從它開始向上回溯 修復到平衡 fixAfterDeletion(replacement); }else if(p.parent == null){ //全樹只有一個節點 root = null; }else{ //修復 fixAfterDeletion(p); //刪除p if (p.parent != null) { if (p == p.parent.lChild){ p.parent.lChild = null; }else if (p == p.parent.rChild){ p.parent.rChild = null; } p.parent = null; } } } /** * 刪除某節點p後的調整方法: * 1.從p開始向上回溯,修改祖先的BF值,這裏只要調整從p的父節點到根節點的BF值, * 調整原則爲,當p位於某祖先節點(簡稱A)的左子樹中時,A的BF減1,當p位於A的 * 右子樹中時A的BF加1。當某個祖先節點BF變爲1或-1時中止回溯,這裏與插入是相反的, * 由於本來這個節點是平衡的,刪除它的子樹的某個節點並不會改變它的高度 * * 2.檢查每一個節點的BF值,若是爲2或-2須要進行旋轉調整,調整方法以下文, * 若是調整以後這個最小子樹的高度下降了,那麼必須繼續從這個最小子樹的根節點(假設爲B)繼續 * 向上回溯,這裏和插入不同,由於B的父節點的平衡性由於其子樹B的高度的改變而發生了改變, * 那麼就可能須要調整,因此刪除可能進行屢次的調整。 * */ public void fixAfterDeletion(BiTNode p){ //看最小子樹調整後,它的高度是否發生變化,若是減少,繼續回溯 boolean heightLower = true; BiTNode t = p.parent; //自下向上回溯,查找不平衡的節點進行調整 while(t!=null && heightLower){ /** * 刪除的節點是右子樹,等於的話,必然是刪除的某個節點的左右子樹不爲空的狀況 * 例如: 10 * / \ * 5 15 * / \ * 3 6 * 這裏刪除5,是把6的值賦給5,而後刪除6,這裏6是p,p的父節點的值也是6。 * 而這也是右子樹的一種 (刪除節點必然引發改節點的父bf變化,) */ if(p.data<t.data){ //刪除左子樹節點 t.bf--; }else{ t.bf++; } //父節點通過調整平衡因子後,若是爲1或-1, //說明調整以前是0,如今刪除一個後代,平衡因子爲1或-1, //不影響該樹的總體平衡,中止回溯。 if(Math.abs(t.bf) == 1){ break; } BiTNode r = t; //這裏的調整跟插入同樣 if(t.bf == 2){ //左旋 heightLower = leftBalance(r); }else if(t.bf==-2){ //右旋 heightLower = rightBalance(r); } t = t.parent; } } /** * 中序遍歷二叉排序樹(排序) */ private void inOrderTraverse(BiTNode root){ if(root!=null){ //遍歷左子樹 inOrderTraverse(root.lChild); System.out.print(root.data+" "); //遍歷右子樹 inOrderTraverse(root.rChild); } } /** * 中序遍歷二叉排序樹(排序) */ public void inOrderTraverse(){ inOrderTraverse(root); } public static void main(String[] args) { AVLTree tree = new AVLTree(); System.out.println("------添加------"); tree.insert(50); System.out.print(50+" "); tree.insert(66); System.out.print(66+" "); for(int i=0;i<10;i++){ tree.insert(i); } System.out.print("平衡二叉樹中序遍歷:"); tree.inOrderTraverse(); System.out.println(); System.out.println("------刪除------"); tree.remove(8); tree.remove(50); tree.remove(66); System.out.print("平衡二叉樹中序遍歷:"); tree.inOrderTraverse(); System.out.println(); } }
PS:下圖表以四列表示四種操做,每行表示在該種狀況下要進行的操做。在左左和右右的狀況下,只需進行一次旋轉操做;在左右和右左的狀況下,須要進行兩次操做。 node