平衡二叉搜索樹之AVL樹

什麼是AVL樹

      介紹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

相關文章
相關標籤/搜索