java實現平衡二叉樹(詳細分析)
阿新 • • 發佈:2018-12-25
package com.utils; import java.util.Iterator; import java.util.NoSuchElementException; /** * 平衡二叉樹 * 定義:首先它是一種特殊的二叉排序樹,其次它的左子樹和右子樹都是平衡二叉樹, * 且左子樹和右子樹的深度之差不超過1 * 平衡因子:可以定義為左子樹的深度減去右子樹的深度 * * 平衡二叉樹是對二叉排序樹的優化,防止二叉排序樹在最壞情況下平均查詢時間為n, * 二叉排序樹在此時形如一個單鏈表 * 平衡二叉樹查詢元素的次數不超過樹的深度,時間複雜度為logN */ public class AVLTree<E> { /** * 根節點 */ private Entry<E> root = null; /** * 樹中元素個數 */ private int size = 0; public AVLTree(){ } public int size(){ return size; } /** * @param p 最小旋轉子樹的根節點 * 向左旋轉之後p移到p的左子樹處,p的右子樹B變為此最小子樹根節點, * B的左子樹變為p的右子樹 * 比如: A(-2) B(1) * \ 左旋轉 / \ * B(0) ----> A(0) \ * / \ \ \ * BL(0) BR(0) BL(0) BR(0) * 旋轉之後樹的深度之差不超過1 */ private void rotateLeft(Entry<E> p) { System.out.println("繞"+p.element+"左旋"); if(p!=null){ Entry<E> r = p.right; p.right = r.left; //把p右子樹的左節點嫁接到p的右節點,如上圖,把BL作為A的右子節點 if (r.left != null) //如果B的左節點BL不為空,把BL的父節點設為A r.left.parent = p; r.parent = p.parent; //A的父節點設為B的父節點 if (p.parent == null) //如果p是根節點 root = r; //r變為父節點,即B為父節點 else if (p.parent.left == p) //如果p是左子節點 p.parent.left = r; //p的父節點的左子樹為r else //如果p是右子節點 p.parent.right = r; //p的父節點的右子樹為r r.left = p; //p變為r的左子樹,即A為B的左子樹 p.parent = r; //同時更改p的父節點為r,即A的父節點為B } } /** * @param p 最小旋轉子樹的根節點 * 向右旋轉之後,p移到p的右子節點處,p的左子樹B變為最小旋轉子樹的根節點 * B的右子節點變為p的左節點、 * 例如: A(2) B(-1) * / 右旋轉 / \ * B(0) ------> / A(0) * / \ / / * BL(0) BR(0) BL(0) BR(0) */ private void rotateRight(Entry<E> p){ System.out.println("繞"+p.element+"右旋"); if(p!=null){ Entry<E> l = p.left; p.left = l.right; //把B的右節點BR作為A的左節點 if (l.right != null) //如果BR不為null,設定BR的父節點為A l.right.parent = p; l.parent = p.parent; //A的父節點賦給B的父節點 if (p.parent == null) //如果p是根節點 root = l; //B為根節點 else if (p.parent.right == p) //如果A是其父節點的左子節點 p.parent.right = l; //B為A的父節點的左子樹 else //如果A是其父節點的右子節點 p.parent.left = l; //B為A的父節點的右子樹 l.right = p; //A為B的右子樹 p.parent = l; //設定A的父節點為B } } /** * 平衡而二叉樹的插入操作 * 基本原理為: * 1.首先如同二叉排序樹一般,找到其要插入的節點的位置,並把元素插入其中; * 2.自下向上進行回溯,回溯做兩個事情: * (1)其一是修改祖先節點的平衡因子,當插入 一個節點時只有根節點到插入節點 * 的路徑中的節點的平衡因子會被改變,而且改變的原則是當插入節點在某節點(稱為A) * 的左子樹 中時,A的平衡因子(稱為BF)為BF+1,當插入節點在A的右子樹中時A的BF-1, * 而判斷插入節點在左子樹中還是右子樹中只要簡單的比較它與A的大小 * (2)在改變祖先節點的平衡因子的同時,找到最近一個平衡因子大於2或小於-2的節點, * 從這個節點開始調整最小不平衡樹進行旋轉調整,關於如何調整見下文。 * 由於調整後,最小不平衡子樹的高度與插入節點前的高度相同,故不需繼續要調整祖先節點。 * 這裡還有一個特殊情況,如果調整BF時,發現某個節點的BF變為0了,則停止向上繼續調整, * 因為這表明此節點中高度小的子樹增加了新節點,高度不變,那麼祖先節點的BF自然不變。 * * */ public boolean add(E element){ Entry<E> t = root; if(t == null){ root = new Entry<E>(element,null); size = 1; return true; } int cmp; Entry<E> parent; //儲存t的父節點 Comparable<? super E> e = (Comparable<? super E>) element; //從根節點向下搜尋,找到插入位置 do { parent = t; cmp = e.compareTo(t.element); if(cmp < 0){ t = t.left; }else if(cmp > 0){ t = t.right; }else{ return false; } } while (t!=null); Entry<E> child = new Entry(element,parent); if(cmp < 0){ parent.left = child; }else{ parent.right = child; } //自下向上回溯,查詢最近不平衡節點 while(parent!=null){ cmp = e.compareTo(parent.element); if(cmp < 0){ //插入節點在parent的左子樹中 parent.balance++; }else{ //插入節點在parent的右子樹中 parent.balance--; } if(parent.balance == 0){ //此節點的balance為0,不再向上調整BF值,且不需要旋轉 break; } if(Math.abs(parent.balance) == 2){ //找到最小不平衡子樹根節點 fixAfterInsertion(parent); break; //不用繼續向上回溯 } parent = parent.parent; } size ++; return true; } /** * 調整的方法: * 1.當最小不平衡子樹的根(以下簡稱R)為2時,即左子樹高於右子樹: * 如果R的左子樹的根節點的BF為1時,做右旋; * 如果R的左子樹的根節點的BF為-1時,先左旋然後再右旋 * * 2.R為-2時,即右子樹高於左子樹: * 如果R的右子樹的根節點的BF為1時,先右旋後左旋 * 如果R的右子樹的根節點的BF為-1時,做左旋 * * 至於調整之後,各節點的BF變化見程式碼 */ private void fixAfterInsertion(Entry<E> p){ if(p.balance == 2){ leftBalance(p); } if(p.balance == -2){ rightBalance(p); } } /** * 做左平衡處理 * 平衡因子的調整如圖: * t rd * / \ / \ * l tr 左旋後右旋 l t * / \ -------> / \ / \ * ll rd ll rdl rdr tr * / \ * rdl rdr * * 情況2(rd的BF為0) * * * t rd * / \ / \ * l tr 左旋後右旋 l t * / \ -------> / \ \ * ll rd ll rdl tr * / * rdl * * 情況1(rd的BF為1) * * * t rd * / \ / \ * l tr 左旋後右旋 l t * / \ -------> / / \ * ll rd ll rdr tr * \ * rdr * * 情況3(rd的BF為-1) * * * t l * / 右旋處理 / \ * l ------> ll t * / \ / * ll rd rd * 情況4(L等高) */ private boolean leftBalance(Entry<E> t){ boolean heightLower = true; Entry<E> l = t.left; switch (l.balance) { case LH: //左高,右旋調整,旋轉後樹的高度減小 t.balance = l.balance = EH; rotateRight(t); break; case RH: //右高,分情況調整 Entry<E> rd = l.right; switch (rd.balance) { //調整各個節點的BF case LH: //情況1 t.balance = RH; l.balance = EH; break; case EH: //情況2 t.balance = l.balance = EH; break; case RH: //情況3 t.balance = EH; l.balance = LH; break; } rd.balance = EH; rotateLeft(t.left); rotateRight(t); break; case EH: //特殊情況4,這種情況在新增時不可能出現,只在移除時可能出現,旋轉之後整體樹高不變 l.balance = RH; t.balance = LH; rotateRight(t); heightLower = false; break; } return heightLower; } /** * 做右平衡處理 * 平衡因子的調整如圖: * t ld * / \ / \ * tl r 先右旋再左旋 t r * / \ --------> / \ / \ * ld rr tl ldl ldr rr * / \ * ldl ldr * 情況2(ld的BF為0) * * t ld * / \ / \ * tl r 先右旋再左旋 t r * / \ --------> / \ \ * ld rr tl ldl rr * / * ldl * 情況1(ld的BF為1) * * t ld * / \ / \ * tl r 先右旋再左旋 t r * / \ --------> / / \ * ld rr tl ldr rr * \ * ldr * 情況3(ld的BF為-1) * * t r * \ 左旋 / \ * r -------> t rr * / \ \ * ld rr ld * 情況4(r的BF為0) */ private boolean rightBalance(Entry<E> t){ boolean heightLower = true; Entry<E> r = t.right; switch (r.balance) { case LH: //左高,分情況調整 Entry<E> ld = r.left; switch (ld.balance) { //調整各個節點的BF case LH: //情況1 t.balance = EH; r.balance = RH; break; case EH: //情況2 t.balance = r.balance = EH; break; case RH: //情況3 t.balance = LH; r.balance = EH; break; } ld.balance = EH; rotateRight(t.right); rotateLeft(t); break; case RH: //右高,左旋調整 t.balance = r.balance = EH; rotateLeft(t); break; case EH: //特殊情況4 r.balance = LH; t.balance = RH; rotateLeft(t); heightLower = false; break; } return heightLower; } /** * 查詢指定元素,如果找到返回其Entry物件,否則返回null */ private Entry<E> getEntry(Object element) { Entry<E> tmp = root; Comparable<? super E> e = (Comparable<? super E>) element; int c; while (tmp != null) { c = e.compareTo(tmp.element); if (c == 0) { return tmp; } else if (c < 0) { tmp = tmp.left; } else { tmp = tmp.right; } } return null; } /** * 平衡二叉樹的移除元素操作 * */ public boolean remove(Object o){ Entry<E> e = getEntry(o); if(e!=null){ deleteEntry(e); return true; } return false; } private void deleteEntry(Entry<E> p){ size --; //如果p左右子樹都不為空,找到其直接後繼,替換p,之後p指向s,刪除p其實是刪除s //所有的刪除左右子樹不為空的節點都可以調整為刪除左右子樹有其一不為空,或都為空的情況。 if (p.left != null && p.right != null) { Entry<E> s = successor(p); p.element = s.element; p = s; } Entry<E> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { //如果其左右子樹有其一不為空 replacement.parent = p.parent; if (p.parent == null) //如果p為root節點 root = replacement; else if (p == p.parent.left) //如果p是左孩子 p.parent.left = replacement; else //如果p是右孩子 p.parent.right = replacement; p.left = p.right = p.parent = null; //p的指標清空 //這裡更改了replacement的父節點,所以可以直接從它開始向上回溯 fixAfterDeletion(replacement); } else if (p.parent == null) { // 如果全樹只有一個節點 root = null; } else { //左右子樹都為空 fixAfterDeletion(p); //這裡從p開始回溯 if (p.parent != null) { if (p == p.parent.left) p.parent.left = null; else if (p == p.parent.right) p.parent.right = null; p.parent = null; } } } /** * 返回以中序遍歷方式遍歷樹時,t的直接後繼 */ static <E> Entry<E> successor(Entry<E> t) { if (t == null) return null; else if (t.right != null) { //往右,然後向左直到盡頭 Entry<E> p = t.right; while (p.left != null) p = p.left; return p; } else { //right為空,如果t是p的左子樹,則p為t的直接後繼 Entry<E> p = t.parent; Entry<E> ch = t; while (p != null && ch == p.right) { //如果t是p的右子樹,則繼續向上搜尋其直接後繼 ch = p; p = p.parent; } return p; } } /** * 刪除某節點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的高度的改變而發生了改變, * 那麼就可能需要調整,所以刪除可能進行多次的調整。 * */ private void fixAfterDeletion(Entry<E> p){ boolean heightLower = true; //看最小子樹調整後,它的高度是否發生變化,如果減小,繼續回溯 Entry<E> t = p.parent; Comparable<? super E> e = (Comparable<? super E>)p.element; int cmp; //自下向上回溯,查詢不平衡的節點進行調整 while(t!=null && heightLower){ cmp = e.compareTo(t.element); /** * 刪除的節點是右子樹,等於的話,必然是刪除的某個節點的左右子樹不為空的情況 * 例如: 10 * / \ * 5 15 * / \ * 3 6 * 這裡刪除5,是把6的值賦給5,然後刪除6,這裡6是p,p的父節點的值也是6。 * 而這也是右子樹的一種 */ if(cmp >= 0 ){ t.balance ++; }else{ t.balance --; } if(Math.abs(t.balance) == 1){ //父節點經過調整平衡因子後,如果為1或-1,說明調整之前是0,停止回溯。 break; } Entry<E> r = t; //這裡的調整跟插入一樣 if(t.balance == 2){ heightLower = leftBalance(r); }else if(t.balance==-2){ heightLower = rightBalance(r); } t = t.parent; } } private static final int LH = 1; //左高 private static final int EH = 0; //等高 private static final int RH = -1; //右高 /** * 樹節點,為方便起見不寫get,Set方法 */ static class Entry<E>{ E element; Entry<E> parent; Entry<E> left; Entry<E> right; int balance = EH; //平衡因子,只能為1或0或-1 public Entry(E element,Entry<E> parent){ this.element = element; this.parent = parent; } public Entry(){} @Override public String toString() { return element+" BF="+balance; } } //返回中序遍歷此樹的迭代器,返回的是一個有序列表 private class BinarySortIterator implements Iterator<E>{ Entry<E> next; Entry<E> lastReturned; public BinarySortIterator(){ Entry<E> s = root; if(s !=null){ while(s.left != null){ //找到中序遍歷的第一個元素 s = s.left; } } next = s; //賦給next } @Override public boolean hasNext() { return next!=null; } @Override public E next() { Entry<E> e = next; if (e == null) throw new NoSuchElementException(); next = successor(e); lastReturned = e; return e.element; } @Override public void remove() { if (lastReturned == null) throw new IllegalStateException(); // deleted entries are replaced by their successors if (lastReturned.left != null && lastReturned.right != null) next = lastReturned; deleteEntry(lastReturned); lastReturned = null; } } public Iterator<E> itrator(){ return new BinarySortIterator(); } private int treeHeight(Entry<E> p){ if(p == null){ return -1; } return 1 + Math.max(treeHeight(p.left), treeHeight(p.right)); } //測試,你也可以任意測試 public static void main(String[] args) { AVLTree<Integer> tree = new AVLTree<Integer>(); System.out.println("------新增------"); tree.add(50); System.out.print(50+" "); tree.add(66); System.out.print(66+" "); for(int i=0;i<10;i++){ int ran = (int)(Math.random() * 100); System.out.print(ran+" "); tree.add(ran); } System.out.println("------刪除------"); tree.remove(50); tree.remove(66); System.out.println(); Iterator<Integer> it = tree.itrator(); while(it.hasNext()){ System.out.print(it.next()+" "); } } }