平衡查找树:AVL树 AvlTree<T extends Comparable<? super T>>

平衡查找树:AVL树 AvlTree< T extends Comparable<? super T> >

  请先参看: 二叉查找树 BinarySearchTree<>
  本文是对《数据结构与算法分析(Java语言描述)》的学习,关键代码和思想均源于教材,后面代码是自己的整理,和对教材中出于精简考虑没给出的代码的补全。


  没有类型参数化的泛型类,好像不能继承,所以这里的AVL树就单独写了一个泛型类。

  和二叉查找树相比,主要是多了平衡条件,简单理解就是,以前insert、remove后,不需要考虑任何平衡的问题,只需要依然满足二叉查找关系:左子树上所有点小于节点,右子树反之。平衡条件就是左右子树除了基本的查找关系,还需要满足平衡条件:任意节点的左右子树的高相差不超过1。

  进一步,把上面的过程具体化,就是在插入、删除后,返回新的子树的节点前,先进行平衡,再返回平衡后的新子树根节点。把它们抽象成一个private的方法,叫做平衡(根节点),参数根节点是将进行平衡的子树的根节点,该balance(Node)方法具体要做:1根据平衡规则更新链,2更新平衡后的各节点高度,3返回平衡后子树的新的根节点。

  那么上面balance方法中,最核心的第一个要求,根据平衡规则更新链,这就比较不好讲了……一些关键点如下:

  首先,是二叉树,左右实际上是对称的,很多情况实质相同只是编程要对称考虑。
  然后,插入和删除其实算是互逆的操作,左子树删除后的平衡情况可以看成某些右子树插入后的情况。
  所以,简而言之,我们只讨论左子树插入的情况。

  插入前平衡,插入后失衡,那么必然有左子树插入前左比右高1,将左子树比右高的1单独用个左子树的根节点表示(该根节点是失衡节点的左链),然后左子树根节点的左、右子树和失衡节点的右子树一定高度相同(若左子树根节点的左、右子树高度不同那么失衡的就是该左子树根节点了)。这里是很重要的一个思想,文字很绕,我能怎么办,我已经尽量提取关键点了。图、视频请网上搜……

  所以最后一个关键点,失衡节点的左子树根节点的左、右子树的插入情况,左侧插入实为外侧插入,一次单旋转即可解决,单旋转自己查资料,本质其实就是换根、交换势能;右侧插入实为内侧插入,是双旋转,可以由两次单旋转实现。就这两种情况。

  2021/7/21修订,验证了自己仿写的2个基础数据结构的4种实现,仿写了数据存储和层序打印输出。

PS:文末有 彩蛋

package myTree;

import myList.MyArrayList;
import myList.MyLinkedList;
import myList.myQueue.MyCircularQueue;
import myList.myQueue.MyLinkedQueue;

/**
 * An AVL tree is a binary search tree with a balance condition that the height
 * difference between the left and right subtrees of each node is no more than
 * one.
 * Revised on 2021/7/21.
 * 
 * @author CatInCoffee.
 */
public class AvlTree<T extends Comparable<? super T>> {

	private static class AvlNode<T> {
		T element;
		public AvlNode<T> left;
		private AvlNode<T> right;
		int height;

		public AvlNode(T paraElement) {
			this(paraElement, null, null);
		}// of the first Constructor

		public AvlNode(T paraElement, AvlNode<T> paraLeft, AvlNode<T> paraRight) {
			element = paraElement;
			left = paraLeft;
			right = paraRight;
			height = 0;
		}// of the second Constructor
	}// of class AvlNode<T>

	private AvlNode<T> root;

	public AvlTree() {
		root = null;
	}// of the first Constructor

	public void makeEmpty() {
		root = null;
	}// of makeEmpty

	public boolean isEmpty() {
		return root == null;
	}// of isEmpty

	public boolean contains(T x) {
		return contains(x, root);
	}// of contains(T)

	/**
	 * Internal method to find an item in a subtree.
	 * 
	 * @param x the item to search for.
	 * @param t the node that roots the subtree. // root在这里是“是……的根(节点)”的意思
	 * @return true if the item is found; false otherwise.
	 */
	private boolean contains(T x, AvlNode<T> t) {
		if (t == null) {
			return false;
		} // of if

		int compareResult = x.compareTo(t.element);

		if (compareResult < 0) {
			return contains(x, t.left);
		} else if (compareResult > 0) {
			return contains(x, t.right);
		} else {
			return true;
		} // of if-elseIf-else
	}// of contains(T, AvlNode<T>)

	public T findMin() {
		if (isEmpty()) {
			System.out.println("No minimum element, it's an empty tree.");
			return null;
//			throw new BufferUnderflowException();
		} // of if

		return findMin(root).element;
	}// of findMin()

	/**
	 * Internal method to find the smallest item in a subtree.
	 * 
	 * @param t the node that roots the subtree.
	 * @return the node containing the smallest item.
	 */
	private AvlNode<T> findMin(AvlNode<T> t) {
		if (t == null) {
			return null;
		} else if (t.left == null) {
			return t;
		} // of if-elseIf

		return findMin(t.left);

//		if (t != null) {
//			while (t.left != null) {
//				t = t.left;
//			} // of while
//		} // of if
//
//		return t;
	}// of findMin(AvlNode<T>)

	public T findMax() {
		if (isEmpty()) {
			System.out.println("No maximum element, it's an empty tree.");
			return null;
//			throw new BufferUnderflowException();			
		} // of if

		return findMax(root).element;
	}// of findMax()

	/**
	 * Internal method to find the largest item in a subtree.
	 * 
	 * @param t the node that roots the subtree.
	 * @return the node containing the largest item.
	 */
	private AvlNode<T> findMax(AvlNode<T> t) {
		if (t != null) {
			while (t.right != null) {
				t = t.right;
			} // of while
		} // of if

		return t;
	}// of findMax(AvlNode<T>)

	public void insert(T x) {
		root = insert(x, root);
	}// of insert

	/**
	 * Internal method to insert into a subtree. Balance condition is under
	 * consideration here.
	 * 
	 * @param x the item to insert.
	 * @param t the node that roots the subtree.
	 * @return the new root of the subtree after being inserted and balanced.
	 */
	private AvlNode<T> insert(T x, AvlNode<T> t) {
		if (t == null) {
			return new AvlNode<>(x);
		} // of if

		int compareResult = x.compareTo(t.element);

		if (compareResult < 0) {
			t.left = insert(x, t.left);
		} else if (compareResult > 0) {
			t.right = insert(x, t.right);
		} else {
			; // Duplicate; here we do nothing.
		} // of if-elseIf-else

		// After these judgments above, we actually insert x into the nonempty subtrees.
		// But we haven't linked the new tree to its parent(could be root), as we need
		// balance the inserted tree and return the new root of the balanced subtree.
		return balance(t);
	}// of insert(T, AvlNode<T>)

	/**
	 * Internal method to balance the inserted tree (update the corresponding links
	 * and heights), and return the new root of the balanced subtree.
	 * 
	 * @param t the node that roots the subtree. Assume that the tree corresponding
	 *          to t is either balanced or within one of being balanced.
	 * @return the new root of the subtree after being balanced.
	 */
	private AvlNode<T> balance(AvlNode<T> t) {
		if (t == null) {
			return t;
		} // of if

		// goal 1: Update the links.
		// 4 conditions divided by a nested if statements.
		if (height(t.left) - height(t.right) > ALLOW_IMBALANCE) {
			if (height(t.left.left) >= height(t.left.right)) { // ">=": "=" is for method remove(T)
				t = rotateWithLeftChild(t);
			} else {
				t = doubleWithLeftChild(t);
			} // of if-else
		} else if (height(t.right) - height(t.left) > ALLOW_IMBALANCE) {
			if (height(t.right.right) >= height(t.right.left)) {
				t = rotateWithRightChild(t);
			} else {
				t = doubleWithRightChild(t);
			} // of if-else
		} // of if-elseIf

		// goal 2: Update the heights.
		t.height = Math.max(height(t.left), height(t.right)) + 1;

		// goal 3: Return the new root of the subtree after being balanced.
		return t;
	}// of balance(AvlNode<T>)

	private static final int ALLOW_IMBALANCE = 1;

	// Return the height of node t, or -1 if null.
	private int height(AvlNode<T> t) {
		return t == null ? -1 : t.height;
	}// of height(AvlNode<T>)

	// Rotate binary tree node with left child.
	// A sketch map is needed here to help understand.
	// k1.element < k2.element
	// Update heights, then return new root.
	private AvlNode<T> rotateWithLeftChild(AvlNode<T> k2) {
		AvlNode<T> k1 = k2.left;
		// Update the links.
		k2.left = k1.right;
		k1.right = k2;
		// Update the heights.
		k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
		k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
		// Return the new root.
		return k1;
	}// of rotateWithLeftChild(AvlNode<T>)

	// Similar to left condition, and two condition could use the same sketch map.
	private AvlNode<T> rotateWithRightChild(AvlNode<T> k1) {
		AvlNode<T> k2 = k1.right;
		// Update the links.
		k1.right = k2.left;
		k2.left = k1;
		// Update the heights.
		k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
		k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
		// Return the new root.
		return k2;
	}// of rotateWithRightChild(AvlNode<T>)

	// Double rotate binary tree node: first left child with its right child; then
	// node k3 with new left child.
	// A sketch map is needed here to help understand.
	// k1.element < k2.element < k3.element
	// Update heights, then return new root.
	private AvlNode<T> doubleWithLeftChild(AvlNode<T> k3) {
		k3.left = rotateWithRightChild(k3.left);
		return rotateWithLeftChild(k3);
	}// of doubleWithLeftChild(AvlNode<T>)

	// Double rotate binary tree node: first right child with its left child; then
	// node k1 with new left child.
	private AvlNode<T> doubleWithRightChild(AvlNode<T> k1) {
		k1.right = rotateWithLeftChild(k1.right);
		return rotateWithRightChild(k1);
	}// of doubleWithRightChild(AvlNode<T>)

	public void remove(T x) {
		root = remove(x, root);
	}// of remove(T)

	/**
	 * Internal method to remove from a subtree. This private method doesn't finish
	 * the whole remove operation.
	 * 
	 * @param x the item to remove.
	 * @param t the node that roots the subtree.
	 * @return the new root of the subtree after being removed.
	 */
	private AvlNode<T> remove(T x, AvlNode<T> t) {
		if (t == null) {
			return t; // item not found; do nothing
		} // of if

		int compareResult = x.compareTo(t.element);

		if (compareResult < 0) {
			t.left = remove(x, t.left);
		} else if (compareResult > 0) {
			t.right = remove(x, t.right);
		} else if (t.left != null && t.right != null) { // two children
			t.element = findMin(t.right).element;
			t.right = remove(t.element, t.right);
		} else { // one or zero child
			t = (t.left != null) ? t.left : t.right;
		} // of if-elseIf-else

		return balance(t);
	}// of remove(T, AvlNode<T>)

	public void printTree() {
		if (root == null) {
			System.out.println("An empty tree.");
			return;
		} // of if

//		preOrderPrint(root);
//		inOrderPrint(root);
//		postOrderPrint(root);
		levelOrderPrint(root);
	}// of printTree()

	private void preOrderPrint(AvlNode<T> t) { // Printing empty tree does nothing.
		if (t != null) {
			System.out.print(t.element + " ");
			preOrderPrint(t.left);
			preOrderPrint(t.right);
		} // of if
	}// of preOrderPrint(AvlNode<T>)

	private void inOrderPrint(AvlNode<T> t) {
		if (t != null) {
			inOrderPrint(t.left);
//			System.out.print("" + t.element + "(" + t.height + ")" + " ");
			inOrderPrint(t.right);
		} // of if
	}// of inOrderPrint(AvlNode<T>)

	private void postOrderPrint(AvlNode<T> t) {
		if (t != null) {
			postOrderPrint(t.left);
			postOrderPrint(t.right);
			System.out.print(t.element + " ");
		} // of if
	}// of postOrderPrint(AvlNode<T>)

	private void levelOrderPrint(AvlNode<T> t) {
		saveTree(t);

//		System.out.print("\n\t{ ");
//		for (T eleOfNode : listOfData) {
//			System.out.print("" + eleOfNode + " ");
//		} // of for node
//		System.out.print("}");

		int n = listOfData.size();
		System.out.print("\n\t{");
		for (int i = 0; i < n - 1; i++) {
			// Actually, another data structure HashMap could be better.
			System.out.print("" + listOfData.get(i) + "(" + listOfPositions.get(i) + "),");
		} // of for i
		System.out.println("" + listOfData.get(n - 1) + "(" + listOfPositions.get(n - 1) + ")" + "}");

	}// of levelOrderPrint(AvlNode<T>)

	private MyLinkedList<T> listOfData;
	private MyArrayList<Integer> listOfPositions;

	private void saveTree(AvlNode<T> root) {
		// Two lists to save the data of the tree.
		listOfData = new MyLinkedList<>();
		listOfPositions = new MyArrayList<>();

		// breadth first search is enough here.
		MyCircularQueue<AvlNode<T>> tempNodeQueue = new MyCircularQueue<>();
		MyLinkedQueue<Integer> tempPositonQueue = new MyLinkedQueue<>();

		tempNodeQueue.enqueue(root);
		tempPositonQueue.enqueue(Integer.valueOf(0)); // The position of root is 0.

		AvlNode<T> dequeuedNode;
		Integer dequeuedPosition;
		while (!tempNodeQueue.isEmpty()) {
			dequeuedNode = tempNodeQueue.dequeue();
			dequeuedPosition = tempPositonQueue.dequeue();

			listOfData.add(dequeuedNode.element);
			listOfPositions.add(dequeuedPosition);

			if (dequeuedNode.left != null) {
				tempNodeQueue.enqueue(dequeuedNode.left);
				tempPositonQueue.enqueue(dequeuedPosition * 2 + 1);
			} // of if

			if (dequeuedNode.right != null) {
				tempNodeQueue.enqueue(dequeuedNode.right);
				tempPositonQueue.enqueue(dequeuedPosition * 2 + 2);
			} // of if
		} // of while

//		// The  frame of level order traversal.
//		MyCircularQueue<AvlNode<T>> tempQueue = new MyCircularQueue<>();
//		tempQueue.enqueue(t);
//
//		AvlNode<T> dequeuedNode;
//		int sizeOfLevel;
//		while (!tempQueue.isEmpty()) {
//			sizeOfLevel = tempQueue.size();
//
//			for (int i = 0; i < sizeOfLevel; i++) {
//				dequeuedNode = tempQueue.dequeue();
//
//				if (dequeuedNode.left != null) {
//					tempQueue.enqueue(dequeuedNode.left);
//				} // of if
//				if (dequeuedNode.right != null) {
//					tempQueue.enqueue(dequeuedNode.right);
//				} // of if
//			} // of for i
//		} // of while

	}// of saveTree(AvlNode<T>)

	public static void main(String[] args) {
		AvlTree<String> tempTree = initialTree();

		for (char c = 'P'; c > 'I'; c--) {
			tempTree.insert(String.valueOf(c));
			System.out.print("After inserting " + c + ", the tree is: ");
			tempTree.printTree();
		} // of for i

		for (char c = 'H'; c < 'J'; c++) {
			tempTree.insert(String.valueOf(c));
			System.out.print("After inserting " + c + ", the tree is: ");
			tempTree.printTree();
		} // of for i
		System.out.println();

		for (char c = 'E'; c < 'G'; c++) {
			tempTree.remove(String.valueOf(c));
			System.out.print("After removing " + c + ", the tree is: ");
			tempTree.printTree();
		} // of for i

		tempTree.remove("C");
		System.out.print("After removing \"C\", the tree is: ");
		tempTree.printTree();
	}// of main

	public static AvlTree<String> initialTree() {
		int i = 0;
		for (char c = 'A'; c <= 'Z'; c++) {
			i++;
			System.out.print("" + i + "-" + String.valueOf(c) + "  ");
		} // of for i

		System.out.println("\nFor instance, 'D(0)' indicates that node.element is \"D = 4\", and its position is 0.");
		System.out.println("      This instance is used to compare with the example at page 90 in the book.");
		
		AvlTree<String> paraTree = new AvlTree<>();
		System.out.println();
		paraTree.insert("D");
		paraTree.insert("B");
		paraTree.insert("F");
		paraTree.insert("A");
		paraTree.insert("C");
		paraTree.insert("E");
		paraTree.insert("G");
		System.out.print("The initial tree is: ");
		paraTree.printTree();
		System.out.println();

		return paraTree;
	}// of initialTree()

}// of class AVLTree<T extends Comparable<? super T>>

  结果:

1-A  2-B  3-C  4-D  5-E  6-F  7-G  8-H  9-I  10-J  11-K  12-L  13-M  14-N  15-O  16-P  17-Q  18-R  19-S  20-T  21-U  22-V  23-W  24-X  25-Y  26-Z  
For instance, 'D(0)' indicates that node.element is "D = 4", and its position is 0.
      This instance is used to compare with the example at page 90 in the book.

The initial tree is: 
	{D(0),B(1),F(2),A(3),C(4),E(5),G(6)}

After inserting P, the tree is: 
	{D(0),B(1),F(2),A(3),C(4),E(5),G(6),P(14)}
After inserting O, the tree is: 
	{D(0),B(1),F(2),A(3),C(4),E(5),O(6),G(13),P(14)}
After inserting N, the tree is: 
	{D(0),B(1),G(2),A(3),C(4),F(5),O(6),E(11),N(13),P(14)}
After inserting M, the tree is: 
	{G(0),D(1),O(2),B(3),F(4),N(5),P(6),A(7),C(8),E(9),M(11)}
After inserting L, the tree is: 
	{G(0),D(1),O(2),B(3),F(4),M(5),P(6),A(7),C(8),E(9),L(11),N(12)}
After inserting K, the tree is: 
	{G(0),D(1),M(2),B(3),F(4),L(5),O(6),A(7),C(8),E(9),K(11),N(13),P(14)}
After inserting J, the tree is: 
	{G(0),D(1),M(2),B(3),F(4),K(5),O(6),A(7),C(8),E(9),J(11),L(12),N(13),P(14)}
After inserting H, the tree is: 
	{G(0),D(1),M(2),B(3),F(4),K(5),O(6),A(7),C(8),E(9),J(11),L(12),N(13),P(14),H(23)}
After inserting I, the tree is: 
	{G(0),D(1),M(2),B(3),F(4),K(5),O(6),A(7),C(8),E(9),I(11),L(12),N(13),P(14),H(23),J(24)}

After removing E, the tree is: 
	{G(0),D(1),M(2),B(3),F(4),K(5),O(6),A(7),C(8),I(11),L(12),N(13),P(14),H(23),J(24)}
After removing F, the tree is: 
	{G(0),B(1),M(2),A(3),D(4),K(5),O(6),C(9),I(11),L(12),N(13),P(14),H(23),J(24)}
After removing "C", the tree is: 
	{K(0),G(1),M(2),B(3),I(4),L(5),O(6),A(7),D(8),H(9),J(10),N(13),P(14)}

  彩蛋:
  Fibonacci数列无敌扩展版:
   f n + 2 = a f n + 1 + b f n + c f_{n+2}=af_{n+1}+bf_{n}+c fn+2=afn+1+bfn+c,其中 a , b , c , f 0 , f 1 a,b,c,f_{0},f_{1} a,b,c,f0,f1均已知且为实数,则

         f n = b f 0 p n − 1 − q n − 1 p − q + f 1 p n − q n p − q − c ( 1 − p ) ( 1 − q ) ( b p n − 1 − q n − 1 p − q + p n − q n p − q − 1 ) f_{n}=bf_{0}\frac{p^{n-1}-q^{n-1}}{p-q}+f_{1}\frac{p^{n}-q^{n}}{p-q}-\frac{c}{(1-p)(1-q)}(b\frac{p^{n-1}-q^{n-1}}{p-q}+\frac{p^{n}-q^{n}}{p-q}-1) fn=bf0pqpn1qn1+f1pqpnqn(1p)(1q)c(bpqpn1qn1+pqpnqn1)

其中,p、q是特征方程 x 2 − a x − b = 0 x^2-ax-b=0 x2axb=0 的根。

  唉,写起来好麻烦,推导不想写了,大概就是高中数学中的递推公式的推导。
  为什么有这个彩蛋呢?因为AVL树的极端情况,可以验证N个节点最多有多少层,即复杂度分析要用到。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值