【数据结构】万字长文图解+代码实现AVL树

目录

一、概念

二、图解

1.图解插入

2.图解右单旋

 3.图解左单旋

4.图解右左双旋

 5.图解左右双旋

6.验证是否是AVL树

 三、代码实现

一、概念

AVL树是一种高度平衡的二叉搜索树,得名于其发明者的名字(G. M. Adelson-Velskii和E. M. Landis)。

以下是关于AVL树的一些关键概念:

  1. 定义:AVL树是一种特殊的二叉搜索树,其中任何节点的左右子树的高度差绝对值不会超过1。如果任何时候节点的左右子树高度差超过1,就会通过一系列的旋转操作来恢复平衡。
  2. 特点
    • AVL树的左右子树的高度差的绝对值不超过1。
    • 每个节点的左子树和右子树也都是AVL树。
    • 每个节点都有一个平衡因子,它是左子树的高度减去右子树的高度。
  1. 平衡操作:为了保持树的平衡,AVL树在节点插入或删除后可能会变得不平衡。这时,它会通过旋转来重新平衡,旋转包括单旋转和双旋转两种基本类型。这些旋转确保了树在任何时候都满足平衡条件。
  2. 性能优势:由于AVL树始终保持平衡,因此它的搜索效率较高。在平均和最坏的情况下,搜索、插入和删除操作的时间复杂度都是O(log n),其中n是树中节点的数量。这使得AVL树非常适合用于需要频繁查找和更新操作的场景。

综上所述,AVL树以其严格的平衡条件和高效的旋转操作,保证了在各种操作下都能维持较高的性能,因此在需要快速查找和更新的数据结构应用中非常受欢迎。二叉搜索树在极端情况下数据有序查找速度会变成O(n),为了解决这一问题就有了AVL树,AVL树是一颗高度平衡的二叉搜索树,每棵树的左子树与右子树高度差不会超过1,这样降低了树的高度提升查找效率。适合静态数据的查找,不适合频繁的插入和删除

二、图解
1.图解插入

图解插入

首先我们有如下这样一颗AVL树,我们要在这颗树中插入1,80,90这三个元素,与二叉搜索树插入相同,我们首先需要将该节点进行插入。

在插入之前首先我们如何在代码中判断该节点是否平衡呢,我们需要在每个节点中维持一个平衡因子字段

平衡因子等于右子树子树高度减去左子树高度,由于AVL树左右子树的高度差不会超过2,所以当平衡因子等于2或者-2时这颗树就不平衡了。如上述图中各个节点的平衡因子如下

向上述AVL树中插入1时,首先我们需要按照二叉搜索树插入时一样进行插入

【数据结构】图解二叉搜索树的新增、搜索、删除-CSDN博客

在插入后这个时候我们通过观察发现此时这颗AVL树不平衡了,那么我们先要对这颗树上的节点进行平衡因子的更新,我们从插入的位置开始向上进行遍历,如果当前节点是父亲节点的左孩子就让父亲节点平衡因子--,因为是在左子树进行了插入而右子树不变 bf = 右子树高度- 左子树高度,左子树+1相当于 bf = 右子树高度- (左子树高度 + 1)= bf--,同理如果当前节点是父亲节点的右子树那么就让父亲节点的bf++

这个时候我们在更新平衡因子的过程中发现,有节点平衡因子为-2或者2不平衡了,这个时候我们就需要根据具体情况进行旋转来维持平衡。AVL树的四种旋转策略包括左单旋转、右单旋转、左双旋转和右双旋转

下面将通过图解来展示四种旋转方式

2.图解右单旋

上面在插入1,调整平衡因子的过程中我们发现此处平衡因子为-2,所以我们需要对该节点进行旋转调整,当前平衡因子为-2说明此时左树比较高,我们可以通过右旋让左树的高度降低

如上图所示,此时cur节点中也是左子树比右子树高所以我们只需进行右单旋

右单旋操作如下:我们要将父亲节点的左孩子指向左孩子的右孩子,然后将左孩子指向父亲。同时需要注意修改每个节点的父亲节点

旋转后如图所示

*******subLR此处指向的是44******

1.我们在这里旋转的父亲节点就是根节点,所以我们要更新根节点的指向,但是如果我们更新的是子树呢,我们需要记录这个旋转子树的父亲节点,再完成更新后需要将这颗子树新的根节点赋值给原来父亲节点的父亲节点的左或右

2.如果我们进行旋转的节点它是它父亲节点的右孩子,那么我们再旋转完成后需要将旋转节点的父亲节点的右孩子指向旋转后的树

3.如果我们进行旋转的节点它是它父亲节点的左孩子,那么我们再旋转完成后需要将旋转节点的父亲节点的左孩子指向旋转后的树

在完成上述操作后我们观察此图发现,我们还需要修改旋转后的平衡因子

这个时候我们就完成了右旋,但是需要注意的是,subLR有可能是为空的,在上述代码中subLR为空唯一影响的就是修改subRL的父亲节点,所以在此处我们需要加判断处理

 3.图解左单旋

我们完成了上述插入1的操作,并且通过右单旋维护了AVL树的平衡,此时我们再向上面树中插入80和90

首先我们按照二叉搜索树的插入方式进行插入

然后此时我们需要进行平衡因子的更新

更新过程中一直处于平衡状态,所以不需要进行旋转调整,接着我们插入90

开始调整平衡因子

父亲节点的右子树高,孩子节点中右子树也高所以我们只需进行左单旋转即可

左单旋操作如下:parent节点的left指向右孩子的左孩子也就是subRL,parent右孩子的左指针指向parent,同时parent的父亲指针指向subR,subRL的指针也指向parent,前提是subRL不为空

旋转后如图所示

此时在旋转完这个子树后我们需要将旋转节点的父亲节点指向旋转后新的根节点

与上面右旋转类似需要判断是父亲节点的左节点还是右节点或者是根节点

最后修改subR与parent的平衡因子即可

4.图解右左双旋

在下面这颗AVL树的基础上我们插入27

依旧按照二叉搜索树的规则插入

更新平衡因子

遇到2说明此时这颗parent指向的子树是不平衡的,那么我们观察发现parent的平衡因子为2说明右树高,但是cur指向的parent的右孩子平衡因子是-1说明左树高,这个时候我们需要先对cur进行一次右旋然后再对parent进行左选,也就是说我们需要进行右左双旋。先对cur进行右旋

然后对parent进行左旋转

接下来我们只需调整平衡因子即可,平衡因子的调节需要根据subRL的因子来进行判断所修改哪些节点的平衡因子

此时subRL的平衡因子是-1,我们要进行如下更新

下图中subRL平衡因子为1,进行如下更新

 5.图解左右双旋

在下面这颗AVL树中插入16

按照二叉搜索树的规则进行插入

开始调整平衡因子

先将cur节点进行左旋,然后再将parent进行右旋

在将parent进行右旋

最后我们需要根据subLR的平衡因子进行平衡因子调整,此时subLR是-1

下图中subLR的平衡因子为1

6.验证是否是AVL树

1.采用层序遍历

public void level(TreeNode root) {
        List<List<TreeNode>> ans = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<TreeNode> row = new ArrayList<>();
            while (size-- != 0) {
                TreeNode top = queue.poll();
                row.add(top);
                if (top != null) queue.offer(top.left);
                if (top != null) queue.offer(top.right);
            }
            ans.add(row);
        }


        for (List<TreeNode> x : ans) {
            for (TreeNode val : x) {
                if (val != null)
                    System.out.print(val.val + " ");
                else
                    System.out.print("null ");
            }
            System.out.println();
        }
    }

2.判断是否是平衡树

 private int maxDepth(TreeNode root) {
        // 1. 如果根节点为空则高度为0
        if (root == null) {
            return 0;
        }

        // 2. 计算左右子树高度
        int left = maxDepth(root.left);
        int right = maxDepth(root.right);
        // 3. 判断是否满足平衡二叉树
        if (left != -1 && right != -1 &&Math.abs(left - right) <= 1) {
            return Math.max(left,right) + 1;
        } else {
            // 3.1 不满足则返回-1
            return -1;
        }
    }

    public boolean isBalanced(TreeNode root) {
        // 1. 如果根节点为空则返回真
        if (root == null) {
            return true;
        }

        // 2. 根据求高度时返回值判断
        return maxDepth(root) >= 0;
    }

 三、代码实现
package avl;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Test {
    static class TreeNode {
        public int val;
        public int bf;
        public TreeNode left;
        public TreeNode right;
        public TreeNode parent;

        public TreeNode(int val) {
            this.val = val;
        }
    }

    public TreeNode root;

    public void insert(int val) {
        // 1. 先进行插入
        // 1.1 如果根节点为空直接创建一个节点让root指向它
        if (root == null) {
            root = new TreeNode(val);
            return;
        }
        // 1.2 此时根节点不为空,遍历AVL树找到合适的插入位置
        TreeNode cur = root, parent = null;
        while (cur != null) {
            // 1.2.1 如果当前节点值比待插入的值小那么就去右子树中寻找合适的插入位置
            if (cur.val < val) {
                // 1.2.2 记录父亲节点
                parent = cur;
                cur = cur.right;
            } else if (cur.val > val) {
                // 1.2.3 如果当前节点值比待插入的值大那么就去左子树中寻找合适的插入位置
                parent = cur;
                cur = cur.left;
            } else {
                // 1.2.4 如果有相同的元素则直接返回
                return;
            }
        }
        // 1.3 找到合适的位置
        TreeNode node = new TreeNode(val);
        if (parent.val > val) {
            // 1.3.1 如果待插入值比父亲节点小就插入到左边
            parent.left = node;
        } else {
            // 1.3.2 如果待插入值比父亲节点大就插入到右边
            parent.right = node;
        }
        // 1.4 将插入节点的父亲节点进行赋值
        node.parent = parent;

        // 2. 进行平衡因子的修改
        cur = node;
        while (parent != null) {
            // 2.1 判断父亲节点的平衡因子变化情况
            if (cur == parent.left) {
                parent.bf--;
            } else {
                parent.bf++;
            }

            // 2.2 对当前父亲节点的平衡因子进行判断
            if (parent.bf == 0) {
                // 2.2.1 此时说明已经平衡不再需要调整
                break;
            } else if (parent.bf == -1 || parent.bf == 1) {
                // 2.2.2 此时说明当前父亲节点所在的子树平衡但是父亲节点的父亲平衡是未知的,继续遍历
                cur = parent;
                parent = cur.parent;
            } else {
                // 2.2.3 此时说明当前树已经不平衡,我们需要通过旋转来调整

                // 3. 进行旋转
                if (parent.bf == -2) {
                    // 3.1 左树高
                    if (cur.bf == -1) {
                        // 3.1.1 进行右旋:    parent.bf = -2   cur.bf = -1
                        rotateRight(parent);
                    } else {
                        // 3.1.2 进行左右双旋: parent.bf = -2   cur.bf = 1
                        rotateLeftRight(parent);
                    }
                } else {
                    // 3.2 右树高
                    if (cur.bf == 1) {
                        // 3.2.1 进行左旋:    parent.bf = 2   cur.bf = 1
                        rotateLeft(parent);
                    } else {
                        // 3.2.2 进行右左双旋: parent.bf = 2   cur.bf = -1
                        rotateRightLeft(parent);
                    }
                }
                break;
            }
        }
    }

    private void rotateLeftRight(TreeNode parent) {
        // 1. 记录待右旋后左旋的节点
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        int bf = subLR.bf;

        // 2. 双旋
        rotateLeft(parent.left);
        rotateRight(parent);

        // 3. 更新平衡因子
        if (bf == -1) {
            parent.bf = 1;
            subL.bf = subLR.bf = 0;
        } else if (bf == 1) {
            subLR.bf = parent.bf = 0;
            subL.bf = -1;
        }
    }

    private void rotateRightLeft(TreeNode parent) {
        // 1. 记录待右旋后左旋的节点
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;
            
        // 2. 双旋
        rotateRight(parent.right);
        rotateLeft(parent);

        // 3. 更新平衡因子
        if (bf == -1) {
            subRL.bf = parent.bf = 0;
            subR.bf = 1;
        } else if (bf == 1) {
            parent.bf = -1;
            subR.bf = subRL.bf = 0;
        }
    }

    private void rotateLeft(TreeNode parent) {
        // 1. 记录需要节点的位置
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;

        // 2. 开始旋转
        parent.right = subRL;
        subR.left = parent;
        if (subRL != null) subRL.parent = parent;
        TreeNode pParent = parent.parent;
        parent.parent = subR;

        // 3. 将原来节点旋转后的子树与其父亲建立联系
        if (parent == root) {
            root = subR;
            subR.parent = null;
        } else if (parent == pParent.right) {
            pParent.right = subR;
            subR.parent = pParent;
        } else {
            pParent.left = subR;
            subR.parent = pParent;
        }

        // 4. 调整平衡因子
        subR.bf = 0;
        parent.bf = 0;
    }

    private void rotateRight(TreeNode parent) {
        // 1. 记录需要节点的位置
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;

        // 2. 开始旋转
        parent.left = subLR;
        subL.right = parent;
        if (subLR != null)  subLR.parent = parent;
        TreeNode pParent = parent.parent;  // 在修改parent的parent之前先记录下来后面会使用
        parent.parent = subL;

        // 3. 将原来节点旋转后的子树与其父亲建立联系
        if (parent == root) {
            root = subL;
            subL.parent = null;
        } else if (parent == pParent.left) {
            pParent.left = subL;
            subL.parent = pParent;
        } else {
            pParent.right = subL;
            subL.parent = pParent;
        }

        // 4. 调整平衡因子
        subL.bf = 0;
        parent.bf = 0;
    }

    public static void main(String[] args) {
        Test tree = new Test();
        tree.insert(1);
        tree.insert(11);
        tree.insert(33);
        tree.insert(80);
        tree.insert(55);

        tree.level(tree.root);
        System.out.println(tree.isBalanced(tree.root));
    }

    /**
     * 下面代码为验证是否是AVL树
     * @param root
     */
    public void level(TreeNode root) {
        List<List<TreeNode>> ans = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<TreeNode> row = new ArrayList<>();
            while (size-- != 0) {
                TreeNode top = queue.poll();
                row.add(top);
                if (top != null) queue.offer(top.left);
                if (top != null) queue.offer(top.right);
            }
            ans.add(row);
        }


        for (List<TreeNode> x : ans) {
            for (TreeNode val : x) {
                if (val != null)
                    System.out.print(val.val + " ");
                else
                    System.out.print("null ");
            }
            System.out.println();
        }
    }

    private int maxDepth(TreeNode root) {
        // 1. 如果根节点为空则高度为0
        if (root == null) {
            return 0;
        }

        // 2. 计算左右子树高度
        int left = maxDepth(root.left);
        int right = maxDepth(root.right);
        // 3. 判断是否满足平衡二叉树
        if (left != -1 && right != -1 &&Math.abs(left - right) <= 1) {
            return Math.max(left,right) + 1;
        } else {
            // 3.1 不满足则返回-1
            return -1;
        }
    }

    public boolean isBalanced(TreeNode root) {
        // 1. 如果根节点为空则返回真
        if (root == null) {
            return true;
        }

        // 2. 根据求高度时返回值判断
        return maxDepth(root) >= 0;
    }
}
  • 50
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MySQL多数据源是指在一个应用程序中同时使用多个不同的MySQL数据库来存储和管理数据的技术。它可以帮助开发人员更灵活地处理各种数据库操作,提高程序的性能和可扩展性。下面是一个完整的MySQL多数据源教程。 一、设置数据库连接信息 1. 在应用程序的配置文件中,创建多个数据库连接的配置项。例如,可以为每个数据源创建一个配置项,分别命名为db1、db2等。 2. 在配置项中,设置每个数据源的连接信息,包括数据库地址、用户名、密码等。 二、创建数据源管理器 1. 创建一个数据源管理器类,用于管理多个数据源。该类需要实现数据源的动态切换和获取。 2. 使用Java的线程安全的数据结构,如ConcurrentHashMap来存储数据源信息。将配置文件中的数据库连接信息加载到数据结构中。 3. 实现方法来切换不同的数据源,通过传入数据源的名称来切换到对应的数据库。 三、实现数据源切换 1. 在应用程序中,根据业务需求选择需要使用的数据源。可以通过调用数据源管理器的方法来切换数据源。 2. 在DAO层的代码中,根据当前使用的数据源名称,选择对应的数据源进行数据库操作。 四、使用多数据源进行数据库操作 1. 在DAO层的代码中,区分不同的数据源,并将数据库操作的代码包装在对应的数据源中。 2. 在业务层的代码中,调用DAO层的方法来进行数据库操作。不同的数据源会自动切换。 五、处理事务 1. 如果需要在一个事务中操作多个数据源,可以使用分布式事务的方式来处理。 2. 可以使用开源的分布式事务框架,如Atomikos、Bitronix等来实现多数据源的事务管理。 六、监控和维护 1. 使用监控工具来监控多个数据源的使用情况,包括连接数、查询次数等。 2. 定期对数据库进行维护,包括索引优化、数据清理等工作,以保证数据库的性能和稳定性。 通过以上步骤,我们可以实现MySQL多数据源的配置和使用。使用多数据源可以更好地管理和处理不同的数据库操作,在提高程序性能和可扩展性的同时,也提供了更灵活的数据操作方式。同时,需要注意合理选择和配置数据源,以及监控和维护数据库,以保证系统的运行效率和数据的安全性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

1886i

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值