【Java】 大話數據結構(12) 查找算法(3) (平衡二叉樹(AVL樹))

 

本文根據《大話數據結構》一書及網絡資料,實現了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樹的完整代碼

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 
AVLTree

 

 

測試代碼中的兩個AVL樹以下圖所示:

  

圖10 aTree

圖11 bTree

 

 

後記

  若是不用平衡因子BF,而是子樹的高度來進行分析,討論的狀況就比較少,可參考這篇博客:AVL樹(三)之 Java的實現

相關文章
相關標籤/搜索