AVL Tree是最早的自平衡二分搜索树结构。平衡二叉树对于任意结点左子树与右子树高度差不超过1,平衡二叉树高度与结点数量为O(log n)关系。每一个结点需要记录每一个结点的高度,计算平衡因子,平衡因子等于左子树高度减去右子树高度。对于一个二分搜索树,当添加元素时会破坏平衡,这时需要维护,删除元素时也会造成平衡破坏,需要维护平衡。主要有四种情况,分别如下
1.LL,平衡因子大于1,左子树平衡因子大于等于0,需要将y进行顺时针向下右旋转,x作为父结点
右旋转,首先保存x右子树T3,将y作为x的右子树,T3作为y的左孩子,需要更新x,y的高度值,完成旋转。
2.RR ,平衡因子小于-1,右子树平衡因子小于等于0,需要将y进行顺时针向下左旋转,x作为父结点
左旋转,首先保存x左子树T2,将y作为x的左子树,T2作为y的右孩子,需要更新x,y的高度值,完成旋转。
3.LR,平衡因子大于1,左子树平衡因子小于0,首先将x进行左旋转,在将y进行右旋转
4.RL,平衡因子大于-1,右子树平衡因子大于0,首先将x进行右旋转,在将y进行左旋转
对于删除过程也是会产生上面四种情况,删除完元素也是 一样的。以下是实现过程
import java.util.ArrayList;
public class AVLTree<K extends Comparable<K>, V> {
private class Node { //声明私有结点内部类
public K key; //声明键
public V value; //声明值
public Node left, right; //声明左右结点
public int height; //结点高度
//有参构造函数,根据键值对构造相应结点,初始高度为1
public Node(K key, V value) {
this.key = key;
this.value = value;
left = null;
right = null;
height = 1;
}
}
private Node root; //声明根结点
private int size; //声明尺寸大小
public AVLTree() { //无参构造函数
root = null;
size = 0;
}
public int getSize() { //获取树大小
return size;
}
public boolean isEmpty() { //判断树是否为空
return size == 0;
}
//判断树是否为二分搜索树,利用二分搜索树中序遍历后是有序的性质
public boolean isBST() {
ArrayList<K> keys = new ArrayList<>();
inOrder(root, keys);
for (int i = 1; i < keys.size(); i++) {
if (keys.get(i - 1).compareTo(keys.get(i)) > 0)
return false;
}
return true;
}
//中序遍历
private void inOrder(Node node, ArrayList<K> keys) {
if (node == null)
return;
inOrder(node.left, keys);
keys.add(node.key);
inOrder(node.right, keys);
}
//判断树是否平衡,调用私有判断函数
public boolean isBalanced() {
return isBalanced(root);
}
//判断树是否平衡,采用递归思想,计算每一个结点的平衡因子
private boolean isBalanced(Node node) {
if (node == null)
return true;
int balanceFactor = getBalanceFactor(node);
if (Math.abs(balanceFactor) > 1)
return false;
return isBalanced(node.left) && isBalanced(node.right);
}
//获取结点高度
private int getHeight(Node node) {
if (node == null)
return 0;
return node.height;
}
//获取结点平衡因子,采用递归思想
private int getBalanceFactor(Node node) {
if (node == null)
return 0;
return getHeight(node.left) - getHeight(node.right);
}
//右旋转函数
private Node rightRotate(Node y) {
Node x = y.left;
Node T3 = x.right;
x.right = y;
y.left = T3;
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
//左旋转函数
private Node leftRotate(Node y) {
Node x = y.right;
Node T2 = x.left;
x.left = y;
y.right = T2;
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
//添加元素,调用私有添加函数
public void add(K key, V value) {
root = add(root, key, value);
}
//添加函数,采用递归思想添加元素,在添加完元素后,维护高度,计算平衡因子,维护平衡
private Node add(Node node, K key, V value) {
if (node == null) {
size++;
return new Node(key, value);
}
if (key.compareTo(node.key) < 0)
node.left = add(node.left, key, value);
else if (key.compareTo(node.key) > 0)
node.right = add(node.right, key, value);
else
node.value = value;
//当前结点高度等于左右子树最大高度加1
node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
int balanceFactor = getBalanceFactor(node);
// 平衡维护
// LL
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
return rightRotate(node);
// RR
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
return leftRotate(node);
// LR
if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
// RL
if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node; //返回维护好的结点
}
//获取相应结点
private Node getNode(Node node, K key) {
if (node == null)
return null;
if (key.equals(node.key))
return node;
else if (key.compareTo(node.key) < 0)
return getNode(node.left, key);
else
return getNode(node.right, key);
}
//根据键查看是否包含相应结点
public boolean contain(K key) {
return getNode(root, key) != null;
}
//根据键获取该结点值
public V get(K key) {
Node node = getNode(root, key);
return node == null ? null : node.value;
}
//根据键和值更新相应结点
public void set(K key, V newValue) {
Node node = getNode(root, key);
if (node == null)
throw new IllegalArgumentException(key + " doesn't exist!");
node.value = newValue;
}
//获取该结点子树中最小结点,一直往左边递归遍历
private Node minimum(Node node) {
if (node.left == null)
return node;
return minimum(node.left);
}
//根据键删除相应结点,并返回相应值,调用相应私有递归函数
public V remove(K key) {
Node node = getNode(root, key);
if (node != null) {
root = remove(root, key);
return node.value;
}
return null;
}
//采用递归思想删除相应元素,保存相应返回结点,维护高度,计算平衡因子,维护平衡
private Node remove(Node node, K key) {
if (node == null)
return null;
Node retNode;
if (key.compareTo(node.key) < 0) {
node.left = remove(node.left, key);
retNode = node;
} else if (key.compareTo(node.key) > 0) {
node.right = remove(node.right, key);
retNode = node;
} else {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
retNode = rightNode;
} else if (node.right == null) {
Node leftNode = node.left;
node.left = null;
size--;
retNode = leftNode;
} else {
Node successor = minimum(node.right);
successor.right = remove(node.right, successor.key);
successor.left = node.left;
node.left = node.right = null;
retNode = successor;
}
}
if(retNode == null)
return null;
retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right));
int balanceFactor = getBalanceFactor(retNode);
// 平衡维护
// LL
if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0)
return rightRotate(retNode);
// RR
if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0)
return leftRotate(retNode);
// LR
if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) {
retNode.left = leftRotate(retNode.left);
return rightRotate(retNode);
}
// RL
if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
retNode.right = rightRotate(retNode.right);
return leftRotate(retNode);
}
return retNode;
}
}
以上整个过程就是实现AVL Tree,能够保证二分搜索树自平衡。与前几篇博客一样通过读取文本来比较AVL Tree 与BST的数度,这次让数据按照顺序进入到二者中,测试二者消耗时间。如果按照顺序进入,BST应该退化为链表,速度非常慢,AVL Tree能够自平衡,速度应该很快。下面是测试程序和测试结果
1.文件操作类
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.Locale;
import java.io.File;
import java.io.BufferedInputStream;
import java.io.IOException;
public class FileOperation {
public static boolean readFile(String filename, ArrayList<String> words){
if (filename == null || words == null){
System.out.println("filename is null or words is null");
return false;
}
Scanner scanner;
try {
File file = new File(filename);
if(file.exists()){
FileInputStream fis = new FileInputStream(file);
scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
scanner.useLocale(Locale.ENGLISH);
}
else
return false;
}
catch(IOException ioe){
System.out.println("Cannot open " + filename);
return false;
}
if (scanner.hasNextLine()) {
String contents = scanner.useDelimiter("\\A").next();
int start = firstCharacterIndex(contents, 0);
for (int i = start + 1; i <= contents.length(); )
if (i == contents.length() || !Character.isLetter(contents.charAt(i))) {
String word = contents.substring(start, i).toLowerCase();
words.add(word);
start = firstCharacterIndex(contents, i);
i = start + 1;
} else
i++;
}
return true;
}
private static int firstCharacterIndex(String s, int start){
for( int i = start ; i < s.length() ; i ++ )
if( Character.isLetter(s.charAt(i)) )
return i;
return s.length();
}
}
2.测试类
import java.util.ArrayList;
import java.util.Collections;
public class Main {
public static void main(String[] args) {
System.out.println("Pride and Prejudice");
ArrayList<String> words = new ArrayList<>();
if (FileOperation.readFile("pride-and-prejudice.txt", words)) {
System.out.println("Total words: " + words.size());
Collections.sort(words); //对数据进行排序
long startTime = System.nanoTime();
BST<String, Integer> bst = new BST<>();
for (String word : words) {
if (bst.contain(word))
bst.set(word, bst.get(word) + 1);
else
bst.add(word, 1);
}
for (String word : words)
bst.contain(word);
long endTime = System.nanoTime();
double time = (endTime - startTime) / 1000000000.0;
System.out.println("BST: " + time + " s");
startTime = System.nanoTime();
AVLTree<String, Integer> avl = new AVLTree<>();
for (String word : words) {
if (avl.contain(word))
avl.set(word, avl.get(word) + 1);
else
avl.add(word, 1);
}
for (String word : words)
avl.contain(word);
endTime = System.nanoTime();
time = (endTime - startTime) / 1000000000.0;
System.out.println("AVL: " + time + " s");
}
System.out.println();
}
}
3.测试结果
测试结果符合预期,BST非常慢,AVL Tree真对这种有序的数据依然很快。如果对于分布很随机的数据,BST效果好一些,非常随机,BST就不会退化和极度倾斜,此时的AVL Tree反而因为各种判断整体效果会差一些。以上就是对于AVL Tree有实现过程。