數據結構之AVL樹

1. 什麼AVL樹

AVL樹由兩位科學家在1962年發表的論文《An algorithm for the organization of information》當中提出,其命名來自於它的發明者G.M. Adelson-Velsky和E.M. Landis的名字縮寫。
AVL樹是最先發明的自平衡二叉查找樹,也被稱爲高度平衡樹。相比於二叉查找樹,它的特點是:任何節點的兩個子樹的最大高度差爲1。
在這裏插入圖片描述
上面的兩張圖片,左邊的是AVL樹,它的任何節點的兩個子樹的高度差別都<=1;而右邊的不是AVL樹,因爲7的兩顆子樹的高度相差爲2(以2爲根節點的樹的高度是3,而以8爲根節點的樹的高度是1)。

2. AVL樹的作用

對於一般的二叉搜索樹,其期望高度(即爲一棵平衡樹時)爲log2n,其各操作的時間複雜度O(log2n)同時也由此而決定。但是,在某些極端的情況下(如在插入的序列是有序的時),二叉搜索樹將退化成近似鏈或鏈,此時,其操作的時間複雜度將退化成線性的,即O(n)。我們可以通過隨機化建立二叉搜索樹來儘量的避免這種情況,但是在進行了多次的操作之後,由於在刪除時,我們總是選擇將待刪除節點的後繼代替它本身,這樣就會造成總是右邊的節點數目減少,以至於樹向左偏沉。這同時也會造成樹的平衡性受到破壞,提高它的操作的時間複雜度。
例如:我們按順序將一組數據1,2,3,4,5,6分別插入到一顆空二叉查找樹和AVL樹中,插入的結果如下圖:
在這裏插入圖片描述
由上圖可知,同樣的結點,由於插入方式不同導致樹的高度也有所不同。特別是在帶插入結點個數很多且正序的情況下,會導致二叉樹的高度是O(N),而AVL樹就不會出現這種情況,樹的高度始終是O(lgN)。高度越小,對樹的一些基本操作的時間複雜度就會越小。
AVL樹的操作基本和二叉查找樹一樣,我們關注的是兩個變化很大的操作:插入和刪除。
AVL樹不僅是一顆二叉查找樹,它還有其他的性質。如果我們按照一般的二叉查找樹的插入方式可能會破壞AVL樹的平衡性。同理,在刪除的時候也有可能會破壞樹的平衡性,所以我們要做一些特殊的旋轉處理來重新恢復平衡。

3. 旋轉

如果在AVL樹中進行插入或刪除節點後,可能導致AVL樹失去平衡。這種失去平衡的可以概括爲4種姿態:LL(左左)LR(左右)RR(右右)RL(右左)。下面給出它們的示意圖:
在這裏插入圖片描述

上圖中的4棵樹都是"失去平衡的AVL樹",從左往右的情況依次是:LL、LR、RL、RR。除了上面的情況之外,還有其它的失去平衡的AVL樹,如下圖:
在這裏插入圖片描述
上面的兩張圖都是爲了便於理解,而列舉的關於"失去平衡的AVL樹"的例子。總的來說,AVL樹失去平衡時的情況一定是LL、LR、RL、RR這4種之一,它們都由各自的定義:

  • (1)LL:LeftLeft,也稱爲"左左"。插入或刪除一個節點後,根節點的左子樹的左子樹還有非空子節點,導致"根的左子樹的高度"比"根的右子樹的高度"大2,導致AVL樹失去了平衡。
    例如,在上面LL情況中,由於"根節點(8)的左子樹(4)的左子樹(2)還有非空子節點",而"根節點(8)的右子樹(12)沒有子節點";導致"根節點(8)的左子樹(4)高度"比"根節點(8)的右子樹(12)"高2。
  • (2) LR:LeftRight,也稱爲"左右"。插入或刪除一個節點後,根節點的左子樹的右子樹還有非空子節點,導致"根的左子樹的高度"比"根的右子樹的高度"大2,導致AVL樹失去了平衡。
    例如,在上面LR情況中,由於"根節點(8)的左子樹(4)的左子樹(6)還有非空子節點",而"根節點(8)的右子樹(12)沒有子節點";導致"根節點(8)的左子樹(4)高度"比"根節點(8)的右子樹(12)"高2。
  • (3) RL:RightLeft,稱爲"右左"。插入或刪除一個節點後,根節點的右子樹的左子樹還有非空子節點,導致"根的右子樹的高度"比"根的左子樹的高度"大2,導致AVL樹失去了平衡。
    例如,在上面RL情況中,由於"根節點(8)的右子樹(12)的左子樹(10)還有非空子節點",而"根節點(8)的左子樹(4)沒有子節點";導致"根節點(8)的右子樹(12)高度"比"根節點(8)的左子樹(4)"高2。
  • (4) RR:RightRight,稱爲"右右"。插入或刪除一個節點後,根節點的右子樹的右子樹還有非空子節點,導致"根的右子樹的高度"比"根的左子樹的高度"大2,導致AVL樹失去了平衡。
    例如,在上面RR情況中,由於"根節點(8)的右子樹(12)的右子樹(14)還有非空子節點",而"根節點(8)的左子樹(4)沒有子節點";導致"根節點(8)的右子樹(12)高度"比"根節點(8)的左子樹(4)"高2。

如果在AVL樹中進行插入或刪除節點後,可能導致AVL樹失去平衡。AVL失去平衡之後,可以通過旋轉使其恢復平衡,下面分別介紹"LL(左左),LR(左右),RR(右右)和RL(右左)"這4種情況對應的旋轉方法。

3.1 LL的旋轉

LL失去平衡的情況,可以通過一次旋轉讓AVL樹恢復平衡。如下圖:
在這裏插入圖片描述
圖中左邊是旋轉之前的樹,右邊是旋轉之後的樹。從中可以發現,旋轉之後的樹又變成了AVL樹,而且該旋轉只需要一次即可完成。
對於LL旋轉,你可以這樣理解爲:LL旋轉是圍繞"失去平衡的AVL根節點"進行的,也就是節點k2;而且由於是LL情況,即左左情況,就用手抓着"左孩子,即k1"使勁搖。將k1變成根節點,k2變成k1的右子樹,“k1的右子樹"變成"k2的左子樹”。

3.2 RR的旋轉

理解了LL之後,RR就相當容易理解了。RR是與LL對稱的情況!RR恢復平衡的旋轉方法如下:
在這裏插入圖片描述
圖中左邊是旋轉之前的樹,右邊是旋轉之後的樹。RR旋轉也只需要一次即可完成。

3.3 LR的旋轉

LR失去平衡的情況,需要經過兩次旋轉才能讓AVL樹恢復平衡。如下圖:
在這裏插入圖片描述
第一次旋轉是圍繞"k1"進行的"RR旋轉",第二次是圍繞"k3"進行的"LL旋轉"。

3.4 RL的旋轉

RL是與LR的對稱情況!RL恢復平衡的旋轉方法如下:
在這裏插入圖片描述
第一次旋轉是圍繞"k3"進行的"LL旋轉",第二次是圍繞"k1"進行的"RR旋轉"。

4. java實現

/**
 * @author chenlongfei
 */
public class AVLTree {

	private AVLTreeNode root; // 根結點
	
	/**
	 * 插入操作的入口
	 * @author chenlongfei
	 * @param insertValue
	 */
	public void insert(long insertValue) {
		root = insert(root, insertValue);
	}

	/**
	 * 插入的地遞歸實現
	 * @author chenlongfei
	 * @param subTree
	 * @param insertValue
	 * @return
	 */
	private AVLTreeNode insert(AVLTreeNode subTree, long insertValue) {
		if (subTree == null) {
			return new AVLTreeNode(insertValue, null, null);
		}

		if (insertValue < subTree.value) { // 插入左子樹

			subTree.left = insert(subTree.left, insertValue);
			if (unbalanceTest(subTree)) { // 插入後造成失衡
				if (insertValue < subTree.left.value) { // LL型失衡
					subTree = leftLeftRotation(subTree);
				} else { // LR型失衡
					subTree = leftRightRotation(subTree);
				}
			}

		} else if (insertValue > subTree.value) { // 插入右子樹

			subTree.right = insert(subTree.right, insertValue);
			if (unbalanceTest(subTree)) { // 插入後造成失衡
				if (insertValue < subTree.right.value) { // RL型失衡
					subTree = rightLeftRotation(subTree);
				} else { // RR型失衡
					subTree = rightRightRotation(subTree);
				}
			}

		} else {
			throw new RuntimeException("duplicate value: " + insertValue);
		}

		return subTree;
	}

	/**
	 * RL型旋轉
	 * @author chenlongfei
	 * @param k1 子樹根節點
	 * @return
	 */
	private AVLTreeNode rightLeftRotation(AVLTreeNode k1) {
		k1.right = leftLeftRotation(k1.right);

		return rightRightRotation(k1);
	}

	/**
	 * RR型旋轉
	 * @author chenlongfei
	 * @param k1 k1 子樹根節點
	 * @return
	 */
	private AVLTreeNode rightRightRotation(AVLTreeNode k1) {
		AVLTreeNode k2;

		k2 = k1.right;
		k1.right = k2.left;
		k2.left = k1;

		return k2;
	}

	/**
	 * LR型旋轉
	 * @author chenlongfei
	 * @param k3
	 * @return
	 */
	private AVLTreeNode leftRightRotation(AVLTreeNode k3) {
		k3.left = rightRightRotation(k3.left);

		return leftLeftRotation(k3);
	}

	/**
	 * LL型旋轉
	 * @author chenlongfei
	 * @param k2
	 * @return
	 */
	private AVLTreeNode leftLeftRotation(AVLTreeNode k2) {
		AVLTreeNode k1;

		k1 = k2.left;
		k2.left = k1.right;
		k1.right = k2;

		return k1;
	}

	/**
	 * 獲取樹的深度
	 * @author chenlongfei
	 * @param treeRoot 根節點
	 * @param initDeep 初始深度
	 * @return
	 */
	private static int getDepth(AVLTreeNode treeRoot, int initDeep) {
		if (treeRoot == null) {
			return initDeep;
		}
		int leftDeep = initDeep;
		int rightDeep = initDeep;
		if (treeRoot.left != null) {
			leftDeep = getDepth(treeRoot.left, initDeep++);
		}
		if (treeRoot.right != null) {
			rightDeep = getDepth(treeRoot.right, initDeep++);
		}

		return Math.max(leftDeep, rightDeep);
	}

	/**
	 * 判斷是否失衡
	 * @author chenlongfei
	 * @param treeRoot
	 * @return
	 */
	private boolean unbalanceTest(AVLTreeNode treeRoot) {
		int leftHeight = getDepth(treeRoot.left, 1);
		int righHeight = getDepth(treeRoot.right, 1);
		int diff = Math.abs(leftHeight - righHeight);
		return diff > 1;
	}
	
	/**
	 * 刪除操作的入口
	 * @param value
	 */
	public void remove(long value) {
		root = remove(root, value);
	}
	
	/**
	 * 刪除操作的遞歸實現
	 * @param tree
	 * @param value
	 * @return
	 */
	private AVLTreeNode remove(AVLTreeNode tree, long value) {
		if (tree == null) {
			return tree;
		}
		
		if (value < tree.value) { //要刪除的節點在左子樹
			
			tree.left = remove(tree.left, value);
			
		} else if (value > tree.value){  //要刪除的節點在右子樹
			
			tree.right = remove(tree.right, value);
			
		} else if (tree.value == value) {  //要刪除的節點就是本身

			if (tree.left != null && tree.right != null) { // 左右子樹都存在
				
				if (getDepth(tree.left, 1) > getDepth(tree.right, 1)) {
					/**
					 * 如果tree的左子樹比右子樹高: 
					 * 1. 找出tree的左子樹中的最大節點 
					 * 2. 將該最大節點的值賦值給tree。 
					 * 3. 刪除該最大節點。
					 * 這類似於用"tree的左子樹中最大節點"做"tree"的替身 
					 * 採用這種方式的好處是:刪除"tree的左子樹中最大節點"之後,AVL樹仍然是平衡的
					 */
					AVLTreeNode max = getMaxNode(tree.left);
	                tree.value = max.value;
	                tree.left = remove(tree.left, max.value);
				} else {
					/**
					 * 如果tree的左子樹不高於右子樹: 
					 * 1. 找出tree的右子樹中的最小節點 
					 * 2. 將該最小節點的值賦值給tree。 
					 * 3. 刪除該最小節點。
					 * 這類似於用"tree的右子樹中最小節點"做"tree"的替身
					 * 採用這種方式的好處是:刪除"tree的左子樹中最大節點"之後,AVL樹仍然是平衡的
					 */
					AVLTreeNode min = getMinNode(tree.right);
	                tree.value = min.value;
	                tree.right = remove(tree.right, min.value);

				}

			} else {

				tree = tree.left == null ? tree.right : tree.left;

			}
		} else {
			System.out.println("no node matched value: " + value);
		}

		return tree;
	}
	
	
	/**
	 * 獲取值最大的節點
	 * @param node
	 * @return
	 */
	private AVLTreeNode getMaxNode(AVLTreeNode node) {
		if (node == null) {
			return null;
		}

		if (node.right != null) {
			return getMaxNode(node.right);
		} else {
			return node;
		}
	}
	
	/**
	 * 獲取值最小的節點
	 * @param node
	 * @return
	 */
	private AVLTreeNode getMinNode(AVLTreeNode node) {
		if (node == null) {
			return null;
		}

		if (node.left != null) {
			return getMinNode(node.left);
		} else {
			return node;
		}
	}
	
	// AVL樹的節點
	class AVLTreeNode {
		long value; // 節點存儲的數值
		AVLTreeNode left; // 左孩子
		AVLTreeNode right; // 右孩子

		public AVLTreeNode(long value, AVLTreeNode left, AVLTreeNode right) {
			this.value = value;
			this.left = left;
			this.right = right;
		}

		public long getValue() {
			return this.value;
		}

		public void setValue(long value) {
			this.value = value;
		}

		public AVLTreeNode getLeft() {
			return this.left;
		}

		public void setLeft(AVLTreeNode left) {
			this.left = left;
		}

		public AVLTreeNode getRight() {
			return this.right;
		}

		public void setRight(AVLTreeNode right) {
			this.right = right;
		}
		
	}
}

測試代碼:

		/**
	 * 前序遍歷
	 * @param currentRoot
	 */
	public static void preorder(AVLTreeNode currentRoot) {
		if (currentRoot != null) {
			System.out.print(currentRoot.value + "\t");
			preorder(currentRoot.left);
			preorder(currentRoot.right);
		}
	}
	
	public static void main(String [] args) {
		AVLTree tree = new AVLTree();
		int arr[]= {3,2,1,4,5,6,7,16,15,14,13,12,11,10,8,9};
		for (int a : arr) {
			tree.insert(a);
			
		}
		preorder(tree.root);
		
	}

打印結果如下:

3 2 1 4 5 6 7 16 15 14 13 12 11 10 8 9

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章