Java实现数据结构---AVL树

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

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Starry-Leo

帮到了您,有闲钱,再打赏哦~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值