前言
如果让我们在一堆带有标识的数据中,去高效查询一个特定标识的数据,我们有几种方法?通常我们都会知道二分查找法,二分查找法确实是一种比较高效的查找算法,但是也有弊端就是使用二分查找的数据必须要先经过排序而且针对链表,树这种数据结构并不能像数组一样根据坐标去直接访问。
原理
二叉查找树
那么根据上面所说的情况对树型结构的查找就有人提出了下图的形式即子节点和自己的父节点进行比较,比父节点大的放到父节点右边,比父节点小的放到父节点左边这样我们在查找的时候就不用遍历整个树而且可以做到有方向的查找,效率较高。
看了上图大家可能就会有发现,此种方法的查找的效率和树的高度有关,这种结构叫做二叉查找树这种算法确实可以将我们查找效率提高不少但是查找二叉树可能会由于数据的不同查询的时间又有较大的变化,因为查找二叉树会有以下的情况:
如上图我们可以发现如果我们的数据是经过排序的,那么查找树的结构其实就是链表结构,查找的效率也会退化成链表结构的查找效率。
平衡查找二叉树(AVL树)
出于二叉查找树的最坏情况考虑,提出了平衡查找二叉树,特点如下:在平衡查找二叉树中,任何一个根节点的左右子树高度差都不能大于1。
那面我们如何保证平衡查找二叉树的特点呢?这就需要我们在插入节点和删除节点时,做一些节点位置上的平衡和处理来维持这个特性。
维持这个特性的操作我们称之为左旋和右旋操作(我们只会对最小范围的子树进行操作),下面我们会有实际的讲解:
-
左旋: 根节点以右子树为轴进行旋转。
-
右旋: 根节点以左子树为轴进行旋转。
一般来说查找二叉树插入一个新节点后会出现以下四种类型:LL型,LR型,RR型,RL型以下是这几种类型的介绍与保持平衡的方法。 -
LL型
对于插入节点后最小二叉树是LL型我们采用右旋操作: -
LR型
对于插入节点后最小二叉树是LR型我们采用先左旋再右旋操作: -
RR型
对于插入节点后最小二叉树是LL型我们采用左旋操作: -
RL型
对于插入节点后最小二叉树是LR型我们采用先右旋再左旋操作:
实现
AVLTree
import java.util.Comparator;
public class AVLTree<K, V> {
//根节点
private AVLNode<K, V> root;
//平衡二叉树左树大于右树的最大差值
private static final int MAX_AVL_ALLOW_LIMIT = 1;
//平衡二叉树右树大于左树的最大差值
private static final int MIN_AVL_ALLOW_LIMIT = -1;
//Key比较器
private Comparator<K> comparator;
//树当前大小
private int size = 1;
public AVLTree(Comparator<K> comparator) {
this.comparator = comparator;
}
public void add(K key, V value) {
AVLNode<K, V> node = new AVLNode<>(key, value);
//如果root为空,初始化
if (root == null) {
root = node;
return;
}
//插入
insert(root, node);
//平衡二叉树
balance(node);
}
public void remove(K key) {
AVLNode<K, V> node = find(root, key);
if (node == null) return;
//最上层节点删除
if(root == node) {
//左子树为空的情况
if(node.left == null) root = root.right;
//右子树为空的情况
else if(node.right == null) root = root.left;
else {
AVLNode<K,V> tNode = node.left.right;
node.left.setNode(node.right, AVLNode.RIGHT_NODE_INDEX);
node = node.left;
node.setNode(null, AVLNode.PARENT_NODE_INDEX); //父节点职null
//左子树挂到右子树的左子树下面
leftNodeMoveRightNode(node, tNode);
root = node;
}
}
//叶子结点删除
else if(node.left == null && node.right == null) {
node.parent.removeLeaf(node.parent.isChildNode(node));
}
//根节点被删除,优先左节点替换
else {
AVLNode<K,V> pNode = node.parent;
int index = pNode.isChildNode(node);
pNode.removeLeaf(index);
if (node.left != null) {
pNode.setNode(node.left, index);
//如果左子树根节点的右子树不为空
AVLNode<K, V> rNode = node.left.right;
if(rNode != null) {
if(node.right != null) {
leftNodeMoveRightNode(node, rNode);
}
node.left.setNode(node.right, AVLNode.RIGHT_NODE_INDEX); //设置右节点
}
} else {
//直接设置右节点即可
pNode.setNode(node.right, index);
}
}
size--;
//获取root树的最下层叶子结点并进行平衡检验
balance(findLowestLeafNode(root));
}
//左子树挂到右子树的左子树下面
private void leftNodeMoveRightNode(AVLNode<K, V> node, AVLNode<K, V> tNode) {
AVLNode<K, V> rrlNode = node.right;
do {
rrlNode = rrlNode.left;
} while (rrlNode.left != null);//获得右子树的最左侧节点
rrlNode.setNode(tNode, AVLNode.LEFT_NODE_INDEX); //将左节点的右节点转换到右节点上
}
public V get(K key) {
AVLNode<K, V> node = find(root, key);
return node == null ? null : node.value;
}
//递归方式查询,如果数据量过大,可能会有方法栈溢出的风险
private AVLNode<K, V> find(AVLNode<K, V> node, K key) {
if (node == null) return null;
int val = comparator.compare(node.key, key);
if (val == 0) return node;
if (val > 0) return find(node.left, key);
return find(node.right, key);
}
//打印root节点(包括子树)
public void printTree() {
//获得二叉树高度
int height = height(root);
//申请数组
Object[] arr = new Object[2 << height - 1];
//链表转二叉树数组
linkedTransferArray(arr, root, 1);
//长度
int maxSpaceCount = 2 << height;
//遍历数组
for (int i = 0; i < height; i++) {
StringBuffer sb = new StringBuffer();
int nodes = (((2 << (i - 1)) + 1) == 1 ? 2 : ((2 << (i - 1)) + 1));
if (i == height - 1) nodes -= 1;
int spaceCount = (maxSpaceCount - nodes) / nodes;
//打印前面空格
for (int i1 = 0; i1 < spaceCount; i1++) {
sb.append(" ");
}
//打印数组内容
int indexLimit = (2 << i) - 1;
int i3 = ((2 << (i - 1)) - 1) == -1 ? 0 : (2 << (i - 1)) - 1;
for (int i1 = i3; i1 < indexLimit; i1++) {
if (arr[i1] != null) sb.append(arr[i1]);
for (int i2 = 0; i2 < spaceCount; i2++) sb.append(" ");
}
System.out.println(sb);
sb = new StringBuffer();
//打印前面空格
nodes = (((2 << i) + 1) == 1 ? 2 : ((2 << i) + 1));
if (i == height - 2) nodes -= 1;
spaceCount = (maxSpaceCount - nodes) / nodes;
for (int i1 = 0; i1 < spaceCount; i1++) {
sb.append(" ");
}
//打印树枝
for (int i1 = i3; i1 < indexLimit; i1++) {
if (arr.length > i1 * 2 + 1 && arr[i1 * 2 + 1] != null) sb.append("/");
//树枝前面间隔
for (int i2 = 0; i2 < spaceCount; i2++) sb.append(" ");
if (arr.length > (i1 * 2 + 2) && arr[i1 * 2 + 2] != null) sb.append("\\");
//树枝后面间隔
for (int i2 = 0; i2 < spaceCount; i2++) sb.append(" ");
}
System.out.println(sb);
}
}
public int size() {
return size;
}
private void insert(AVLNode<K, V> tNode, AVLNode<K, V> sNode) {
if (comparator.compare(tNode.key, sNode.key) < 0) {
//root小于的情况,右节点
if (tNode.right == null) {
tNode.right = sNode;
sNode.parent = tNode;
size++;
return;
}
insert(tNode.right, sNode);
} else if (comparator.compare(tNode.key, sNode.key) > 0) {
//root大于的情况,左节点
if (tNode.left == null) {
tNode.left = sNode;
sNode.parent = tNode;
size++;
return;
}
insert(tNode.left, sNode);
} else {
//key相等的情况,更新value
tNode.value = sNode.value;
}
}
//将树结构转换成数组结构,方便打印(非正常排序)
private void linkedTransferArray(Object[] arr, AVLNode<K, V> node, int index) {
if (node == null) return;
arr[index - 1] = node;
if (node.left != null)
linkedTransferArray(arr, node.left, index * 2);
if (node.right != null)
linkedTransferArray(arr, node.right, index * 2 + 1);
}
//计算树的最大高度
private int height(AVLNode<K, V> node) {
if (node == null) return 0;
return Math.max(height(node.left), height(node.right)) + 1;
}
//获取最下层叶子结点
private AVLNode<K, V> findLowestLeafNode(AVLNode<K, V> node) {
if (node == null) return null;
if (node.left == null && node.right == null) return node;
int maxDeep = height(node);
AVLNode<K, V> cNode = node;
int cHeight = 0;
int direction = 0; //0代表向下,1代表向上
do {
//叶子结点
if (cNode.left == null && cNode.right == null) {
if (cHeight + 1 == maxDeep) {
break;
}
else {
//向上查找
//获得上一个根节点
AVLNode<K, V> root = cNode;
//如果是左节点,那么直接回退
if (cNode.parent.isChildNode(cNode) == AVLNode.LEFT_NODE_INDEX) {
do {
cHeight--;//当前层回溯
root = root.parent;
}while(root.parent.right == null);
} else {
//右节点的话需要继续回溯
AVLNode<K, V> tmp = null;
do {
//判断是不是左节点
tmp = root;
root = root.parent;
cHeight--; //当前层回溯
} while (root.isChildNode(tmp) == AVLNode.RIGHT_NODE_INDEX);
}
cNode = root; //位移到上一个还没有遍历右子树且有子树不为空的根节点
direction = 1;
}
} else {
//向下查找
//左节点优先
if (cNode.left != null && direction == 0) {
cNode = cNode.left;
} else {
if(cNode.right == null) {
cNode = cNode.parent;
continue; //如果右节点为空
}
cNode = cNode.right;
direction = 0;
}
cHeight++; //add height
}
} while (true);
return cNode;
}
//二叉树的平衡,检测和调整node位置来保证树的最大高度
private void balance(AVLNode<K, V> node) {
do {
int val = height(node.left) - height(node.right);
if (val > MAX_AVL_ALLOW_LIMIT) {
//左子树过长
//判断长度类型
val = height(node.left.left) - height(node.left.right);
if (val > 0) {
//LL型 右旋
rightRotation(node);
} else {
//LR型 左旋 -> 右旋
leftRotation(node.left);
rightRotation(node);
}
}
if (val < MIN_AVL_ALLOW_LIMIT) {
//右子树过长
//判断长度类型
val = height(node.right.left) - height(node.right.right);
if (val < 0) {
//RR型 左旋
leftRotation(node);
}
if (val > 0) {
//RL型 右旋 -> 左旋
rightRotation(node.right);
leftRotation(node);
}
}
node = node.parent;
} while (node != null);
}
//左旋转
private void leftRotation(AVLNode<K, V> node) {
replaceNodeParent(node.right, node);
AVLNode<K, V> tmp = node.right.left;
node.right.left = node;
node.right = tmp;
if(node.right != null) node.right.parent = node;
}
//右旋转
private void rightRotation(AVLNode<K, V> node) {
replaceNodeParent(node.left, node);
AVLNode<K, V> tmp = node.left.right;
node.left.right = node;
node.left = tmp;
if(node.left != null) node.left.parent = node;
}
//将父节点的parent置换成新节点, 新节点的parent设置成父节点的parent
private void replaceNodeParent(AVLNode<K, V> newNode, AVLNode<K, V> oldNode) {
if (oldNode == null) return;
//确定parent是否有父节点
if (oldNode.parent == null) {
newNode.parent = null;
root = newNode;
} else {
//确定parent是父节点的左节点或者右节点
if (oldNode.parent.isChildNode(oldNode) == AVLNode.LEFT_NODE_INDEX) {
oldNode.parent.left = newNode;
} else {
oldNode.parent.right = newNode;
}
}
//将old的父节点替换成newNode
newNode.setNode(oldNode.parent, AVLNode.PARENT_NODE_INDEX);
oldNode.setNode(newNode, AVLNode.PARENT_NODE_INDEX);
}
//node节点
class AVLNode<K, V> {
final K key;
V value;
AVLNode<K, V> left;
AVLNode<K, V> right;
AVLNode<K, V> parent;
static final int PARENT_NODE_INDEX = 0;
static final int LEFT_NODE_INDEX = 1;
static final int RIGHT_NODE_INDEX = 2;
AVLNode(K key, V value) {
this.key = key;
this.value = value;
}
//删除叶子结点
//1.left 2.right
void removeLeaf(int index) {
if(index == LEFT_NODE_INDEX) {
left.parent = null;
left = null;
}
if(index == RIGHT_NODE_INDEX) {
right.parent = null;
right = null;
}
}
//判断是否是parent的节点
//return 1.left 2.right
int isChildNode(AVLNode<K, V> node) {
if(node == left) return LEFT_NODE_INDEX;
if(node == right) return RIGHT_NODE_INDEX;
return -1;
}
void setNode(AVLNode<K, V> node, int index) {
switch (index) {
case PARENT_NODE_INDEX:
parent = node;
break;
case LEFT_NODE_INDEX:
if(left != null) left.parent = null;
left = node;
if(node != null) node.parent = this;
break;
case RIGHT_NODE_INDEX:
if(right != null) right.parent = null;
right = node;
if(node != null) node.parent = this;
break;
}
}
@Override
public String toString() {
return key.toString();
}
}
}
Demo
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class Demo {
public static void main(String[] args) {
AVLTree<Integer, String> tree = new AVLTree<>(Integer::compareTo);
Random random = new Random();
long timestamp = System.nanoTime();
for (int i = 0; i < 100000; i++) tree.add(i, String.valueOf(2 << i));
System.out.println(System.nanoTime() - timestamp);
timestamp = System.nanoTime();
for(int i = 0; i < 10000; i++) {
int key = random.nextInt(100000);
tree.get(key);
}
System.out.println(System.nanoTime() - timestamp);
Map<Integer, String> map = new HashMap<>();
timestamp = System.nanoTime();
for (int i = 0; i < 100000; i++) map.put(i, String.valueOf(2 << i));
System.out.println(System.nanoTime() - timestamp);
timestamp = System.nanoTime();
for(int i = 0; i < 10000; i++) {
int key = random.nextInt(100000);
map.get(key);
}
System.out.println(System.nanoTime() - timestamp);
}
}
后记
AVL树的不足
AVL虽然查找效率较为高效并且时间复杂度也相对稳定但是由于AVL树需要严格保持左右子树的高度差会造成插入和删除某个节点时耗费大量的时间,所以出于插入和查找的权衡考虑AVL树很少用在java的集合框架上,不过AVL树的兄弟红黑树(严格来讲红黑树也是一种平衡查找树)放弃了左右子树严格的高度差从而换来插入和删除节点的性能提升被较为广泛的应用到TreeMap,HashMap(jdk 1.8之后)中。
如上述有错误,希望指正,共同学习,谢谢~