平衡查找树: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=bf0p−qpn−1−qn−1+f1p−qpn−qn−(1−p)(1−q)c(bp−qpn−1−qn−1+p−qpn−qn−1)
其中,p、q是特征方程 x 2 − a x − b = 0 x^2-ax-b=0 x2−ax−b=0 的根。
唉,写起来好麻烦,推导不想写了,大概就是高中数学中的递推公式的推导。
为什么有这个彩蛋呢?因为AVL树的极端情况,可以验证N个节点最多有多少层,即复杂度分析要用到。