文章目录
AVL树简介
二叉搜索树在树较为平衡状态下,可以有效的提高数据的插入、删除、查询效率,但是不可避免二叉搜索树随着数据的插入、删除有可能会变为链表结构(利用有序的数据源依次添加),极大的降低效率,所以二叉搜索树最好时时刻刻都在相对平衡状态,也就是AVL的由来。
AVL树也称为平衡二叉树,AVL树是一种特殊的二叉搜索树,特点是:树中任意节点的左右子树的最大高度差为1。
如果对树与二叉搜索树不熟悉的话,可以查看我的另一篇博客:数据结构(六)二叉树
上面的两棵树,左边的是AVL树,它的任何节点的两个子树的高度差别都<=1;而右边的不是AVL树,因为5的两颗子树的高度相差为2(以4为根节点的树的高度是3,而以7为根节点的树的高度是1)。
旋转的定义
旋转是以某节点为进行旋转,然后相关节点进行重新关联的操作,旋转分为左旋和右旋:
在AVL树的旋转大致分为4情况:
1. 左左旋转(LL)—以左子节点为轴,进行右旋
左左旋转也称之为LL旋转,其含义是:节点X左子树比右子树的高度大2,且插入的节点位于节点(X)的左子节点(XL)的左子树(T1)上。
2. 右右旋转(RR)—以右子节点为轴,进行左旋
右右旋转也称之为RR旋转,其含义是:节点X右子树比左子树的高度大2,且插入的节点位于节点(X)的右子节点(XR)的右子树(T3)上。
3. 左右旋转(LR)—先左子树左旋,然后以当前左子节点为轴,进行右旋
左右旋转也称之为LR旋转,其含义是:节点X左子树比右子树的高度大2,且插入的节点位于节点(X)的左子节点(XL)的右子树上。
4. 右左旋转(RL)—先右子树右旋,然后以当前右子节点为轴,进行左旋
右左旋转也称之为RL旋转,其含义是:节点X右子树比左子树的高度大2,且插入的节点位于节点(X)的右子节点(XR)的左子树上。
AVL树实现
1. 节点类定义
package com.xxliao.datastructure.tree.avl_tree;
/**
* @author xxliao
* @description: AVL树的节点类
* @date 2024/5/28 23:46
*/
public class Node {
//数据
public int data;
//高度,单个node的高度为1,空树高度为-1
public int height;
//左子节点
public Node left;
//右子节点
public Node right;
//构造方法
public Node(int data) {
this.data = data;
this.height= 0;//添加后会自增高度,初始化为0
}
}
2. AVL类定义
package com.xxliao.datastructure.tree.avl_tree;
/**
* @author xxliao
* @description: AVL树实现类
* @date 2024/5/28 23:47
*/
public class AVLTree {
// 记录根节点
public Node root;
// 结点个数
public int size;
// 构造方法
public AVLTree() {
root = null;
size = 0;
}
// 返回树中的节点数量
public int size() {
return size;
}
// 判断是否为空
public boolean isEmpty() {
return root == null;
}
// 返回节点的高度
public int height(Node node) {
return node == null ? -1 : node.height;
}
// 返回根节点
public Node getRoot() {
return root;
}
}
3. 4种旋转情况(依照上面旋转图进行梳理)
/**
* 四种旋转方式中的LL旋转, 当是节点左子树的左子树节添加节点造成的不平衡,采用LL情况,节点整体右旋
*/
public Node llRotate(Node node) {
Node nodeLeft = node.left; // 记录左子节点
node.left = nodeLeft.right;// 将坐子节点的右节点 给当前节点的左子节点
nodeLeft.right = node; //
node.height = Math.max(height(node.right), height(node.left)) + 1;
nodeLeft.height = Math.max(height(nodeLeft.left), node.height) + 1;
return nodeLeft;
}
/**
* 四种旋转方式中的RR旋转, 当是节点右子树的右子树节添加节点造成的不平衡,采用RR情况,节点整体左旋
*/
public Node rrRotate(Node node) {
Node nodeRight = node.right;
node.right = nodeRight.left;
nodeRight.left = node;
node.height = Math.max(height(node.right), height(node.left)) + 1;
nodeRight.height = Math.max(height(nodeRight.right), node.height) + 1;
return nodeRight;
}
/**
* 四种旋转方式中的LR旋转, 当是节点左子树的右子树节添加节点造成的不平衡,采用LR情况,先左子树左旋、然后节点整体右旋
*/
public Node lrRotate(Node node) {
node.left = rrRotate(node.left);// 左子树先左旋
return llRotate(node);// 整体右旋
}
/**
* 四种旋转方式中的RL旋转, 当是节点右子树的左子树节添加节点造成的不平衡,采用RL情况,先左子树右旋、然后节点整体左旋
*/
public Node rlRotate(Node node) {
node.right = llRotate(node.right);// 右子树先右旋
return rrRotate(node);// 整体左旋
}
4. 新增节点
/**
* 插入数据
*/
public void insert(int data) {
root = insert(root, data);
size++;
}
/**
* 插入数据,私有递归
*/
private Node insert(Node node, int data) {
if (node == null) {
return new Node(data);
}
if (data > node.data) {
// 插入数据位置在当前节点右子树
node.right = insert(node.right, data);
if (height(node.right) - height(node.left) == 2) {
// node右子树高度比node左子树高度大2,发生不平衡
if (node.right.data > data) {
// 在右子树的左子树上,rl旋转
node = rlRotate(node);
} else {
// 在右子树的左子树上,rr旋转
node = rrRotate(node);
}
}
} else if (data < node.data) {
// 插入数据位置在当前节点左子树,继续向当前节点的左子节点添加
node.left = insert(node.left, data);
if (height(node.left) - height(node.right) == 2) {
// node左子树高度比node右子树高度大2,发生不平衡
if (node.left.data > data) {
// 还是在左子树的左子树上,ll旋转
node = llRotate(node);
} else {
// 在左子树的右子树上,lr旋转
node = lrRotate(node);
}
}
} else {
// 相等,无需再次添加
return null;
}
// 树高度加1
node.height = Math.max(height(node.right), height(node.left)) + 1;
return node;
}
5. 删除节点
/**
* 删除元素
*/
public void remove(int data) {
root = remove(root, data);
size--;
}
/**
* 删除节点,私有递归
*/
private Node remove(Node node, int data) {
if (node == null) {
return null;
}
if (data > node.data) {
// 删除节点在当前节点的右子树上
node.right = remove(node.right, data);
if (height(node.left) - height(node.right) == 2) {
// 从右侧删除,那么左子数高度比右子树高度大2,不平衡,调整(旋转)节点左子树
Node tempNode = node.left;
if (height(tempNode.left) > height(tempNode.right)) {
// 删除后node的左子树不平衡,并且node左子树的左子树高度比右子树大,LL旋转
node = llRotate(node);
} else {
// 删除后node的左子树不平衡,并且node左子树的右子树高度比左子树大,LR旋转
node = lrRotate(node);
}
}
} else if (data < node.data) {
// 删除节点在当前节点的左子树上
node.left = remove(node.left, data);
if (height(node.right) - height(node.left) == 2) {
// 从左侧删除,那么右子树高度比左子树高度大2,不平衡,调整(旋转)节点右子树
Node tempNode = node.right;
if (height(tempNode.left) > height(tempNode.right)) {
// 删除后node的右子树不平衡,并且node右子树的左子树高度比右子树大,RL旋转
node = rlRotate(node);
} else {
// 删除后node的右子树不平衡,并且node右子树的右子树高度比左子树大,RR旋转
node = rrRotate(node);
}
}
} else {
// 相等,找到要删除的元素
if (node.left == null && node.right == null) {
// 删除的节点为叶子节点
node = null;
} else if (node.left != null && node.right == null) {
// 删除节点有且只有左子节点,左子节点直接上移成为新的node节点
node = node.left;
node.height = Math.max(height(node.left), height(node.left)) + 1;
} else if (node.left == null && node.right != null) {
// 删除节点有且只有右子节点,右子节点直接上移成为新的node节点
node = node.right;
node.height = Math.max(height(node.left), height(node.left)) + 1;
} else {
// 删除节点有左子节点,还有右子节点,这里找出删除左子树最大节点替换,或者右子树最小节点替换,这里选用左子树最大节点
Node temp = node.left;
while (temp.right != null) {
temp = temp.right;
}
// 将最大值赋给node节点
node.data = temp.data;
// 删除最大值节点
node.left = remove(node.left, temp.data);
// 调整删除后的高度
node.height = Math.max(height(node.left), height(node.left)) + 1;
}
}
size--;
return node;
}
6. AVL树的遍历
// 前序遍历
public void preOrder(Node node) {
if (node != null) {
// 根节点
System.out.print(node.data + " ");
// 左子树遍历
preOrder(node.left);
// 右子树遍历
preOrder(node.right);
}
}
// 中序遍历
public void infixOrder(Node node) {
if (node != null) {
// 左子树遍历
infixOrder(node.left);
// 根节点
System.out.print(node.data + " ");
// 右子树遍历
infixOrder(node.right);
}
}
// 后序遍历
public void postOrder(Node node) {
if (node != null) {
// 左子树遍历
postOrder(node.left);
// 右子树遍历
postOrder(node.right);
// 根节点
System.out.print(node.data + " ");
}
}
7. 测试
package com.xxliao.datastructure.tree.avl_tree;
/**
* @author xxliao
* @description: AVL树 测试客户端
* @date 2024/5/28 23:49
*/
public class AVLTreeTestClient {
public static void main(String[] args) {
//创建树
AVLTree avlTree = new AVLTree();
//数据源,无序的1~10
int[] array = {4,6,1,3,2,7,8,9,5,10};
//添加数据
for (int i = 0; i < array.length; i++) {
avlTree.insert(array[i]);
}
//前序遍历
System.out.print("前序遍历");
avlTree.preOrder(avlTree.getRoot());
System.out.println();
//中序遍历
System.out.print("中序遍历");
avlTree.infixOrder(avlTree.getRoot());
System.out.println();
//后序遍历
System.out.print("后序遍历");
avlTree.postOrder(avlTree.getRoot());
System.out.println();
//删除5
System.out.println("删除5");
avlTree.remove(5);
//前序遍历
System.out.print("前序遍历");
avlTree.preOrder(avlTree.getRoot());
System.out.println();
//中序遍历
System.out.print("中序遍历");
avlTree.infixOrder(avlTree.getRoot());
System.out.println();
//后序遍历
System.out.print("后序遍历");
avlTree.postOrder(avlTree.getRoot());
System.out.println();
}
}
测试结果:
完整代码
https://github.com/xxliao100/datastructure_algorithms.git