AVL树的插入和删除分析(图解和代码)

文章目录

  • 1. AVL树
    • 1.1 AVL树的概念
    • 1.2 AVL树节点的定义
    • 1.3AVL树的插入
    • 1.4 AVL树的删除
      • 查找要删除的节点
      • 判断要删除节点的类型
      • 从下往上调节平衡因子
      • 真正删除节点
      • 整体代码
    • 1.5 AVL树的性能分析

1. AVL树

1.1 AVL树的概念

二叉搜索树虽然能够缩短查找的效率,但是如果数据有序或者接近于有序的时候,二叉搜索树将会退化成单分支的树,查询元素无异于在顺序表中查找,效率低下

两位俄罗斯的数学家G.M.Adelson-Velski和和E.M.Landis发明了一种解决上述问题的方法:

当我们往二叉搜索树中插入元素的时候,如果能够保证每个节点的左右子树的高度的绝对值不超过1(这里的值可以根据实际调整),即可降低树的高度,从而减少平均搜索的长度,即AVL树

此时,如果一颗二叉搜索树是高度平衡的,那么它就是AVL树. 如果它有n个节点,其高度就可保持在 l o g 2 N log_2N log2N,

搜索的时间复杂度为 O ( l o g 2 N ) O(log_2N) O(log2N)

1.2 AVL树节点的定义

给每个节点添加一个平衡因子,表示当前节点的 右子树高度 - 左子树高度

class AVLTreeNode {private int val;private AVLTreeNode left;private AVLTreeNode right;private AVLTreeNode parrent;private int bf; //平衡因子public AVLTreeNode(int val) {this.val = val;}
}

1.3AVL树的插入

  1. 按照二叉搜索树的插入方法,将节点插入到AVL树中
  2. 此时由于插入了新的节点,AVL树的平衡可能会被破坏,就需要调节平衡因子,保证树的高度平衡
 public boolean insert (int val) {AVLTreeNode newNode = new AVLTreeNode(val);if(root == null) {root = newNode;return true;}AVLTreeNode cur = root;AVLTreeNode parent = null;while (cur != null) {parent = cur;if(cur.val == val) {// 不能插入return false;}else if(cur.val > val) {cur = cur.left;}else {cur = cur.right;}}// 循环出来之后,parent就是要插入的新节点的双亲节点,需要判定新节点是在parent的左边还是右边if(parent.val > val) {parent.left = newNode;}else {parent.right = newNode;}newNode.parent = parent;//从下往上依次调节平衡因子cur = newNode;while (parent != null) {if(parent.left == cur) {parent.bf--;}else {parent.bf++;}if(parent.bf == 0) {break;}else if(parent.bf == 1 || parent.bf == -1) {// 继续往上调整cur = parent;parent = cur.parent;}else {// 高度不平衡,需要进行旋转,下面进行讨论}}return true;}

对于需要旋转的情况,分为以下几种情况:

  1. 某个节点bf == -1,该节点的双亲节点bf == -2,即左树偏高

如图所示,调整到24节点的时候就要进行旋转,像这种直接单对224节点进行右单旋即可

具体方法就是,将需要右旋的节点(parent)的 左孩子节点(subL) 向上提, 将parent节点作为subL的右孩子,如果subL原本存在右孩子(subLR),那么将subLR作为旋转后parent的左孩子

具体代码:

 private void rotateRight(AVLTreeNode parent) {AVLTreeNode subL = parent.left;AVLTreeNode subLR = subL.right; // 可能为空AVLTreeNode pParent = parent.parent; // 需要记录parent的双亲节点parent.parent = subL;subL.right = parent;parent.left = subLR;if(subLR != null) {subLR.parent = parent;}if(root == parent) {root = subL;root.parent = null;}else {if(pParent.left == parent) {pParent.left = subL;}else {pParent.right = subL;}subL.parent = pParent;}parent.bf = 0;subL.bf = 0;}
  1. 某个节点bf == 1,该节点的双亲节点bf == 2,即左树偏高,即右树偏高

此时的方法与右单旋类似,需要进行左单旋

private void rotateLeft(AVLTreeNode parent) {AVLTreeNode subR = parent.right;AVLTreeNode subRL = subR.left;AVLTreeNode pParent = parent.parent;parent.parent = subR;subR.left = parent;parent.right = subRL;if(subRL != null) {subRL.parent = parent;}if(root == parent) {root = subR;subR.parent = null;}else {if(pParent.left == parent) {pParent.left = subR;}else {pParent.right = subR;}subR.parent = pParent;}parent.bf = 0;subR.bf = 0;}
  1. 上述两种情况是**需要旋转的节点与偏高子树的孩子节点在同一条直线上,也就是平衡因子要么是2 1搭配2要么是-2 -1搭配,都是同号的,**但是存在异号的情况

对于这种情况,简单的单旋已经没有作用了,此时需要先对subL进行右单旋,目的就是将"曲线" 变成我们上面讨论的"直线",再对parent进行左单旋即可

值得注意的是,旋转完修改平衡因子的时候,需要修改parent,subL,subLR三处的平衡因子

但是原本subLR的平衡因子可能是1,即我们插入的是右节点,就会有一点区别

此时我们在代码中进行特判即可

 private void rotateLR(AVLTreeNode parent) {AVLTreeNode subL = parent.left;AVLTreeNode subLR = subL.right; // 此时由于subL.br == 1,因此subLR不为空int bf = subLR.bf;rotateLeft(subL);rotateRight(parent);if(bf == -1) {subLR.bf = 0;parent.bf = 1;subL.bf = 0;}else if(bf == 1) { // 这里不能直接写else,下面进行解释subLR.bf = 0;parent.bf = 0;subL.bf = -1;}}

代码中这里不能直接写else,因为bf可能为0

而这种情况的平衡因子我们已经在单旋中调整过了,就不必再进行调整了

  1. 第4种实际上就是第三种反过来,原理是一样的

代码实现为:

private void rotateRL(AVLTreeNode parent) {AVLTreeNode subR = parent.right;AVLTreeNode subRL = subR.left;int bf = subRL.bf;rotateRight(subR);rotateLeft(parent);if(bf == 1) {parent.bf = -1;subR.bf = 0;subRL.bf = 0;}else if(bf == -1){parent.bf = 0;subR.bf = 1;subRL.bf = 0;}}

因此插入的全部代码为:

 public boolean insert (int val) {AVLTreeNode newNode = new AVLTreeNode(val);if(root == null) {root = newNode;return true;}AVLTreeNode cur = root;AVLTreeNode parent = null;while (cur != null) {parent = cur;if(cur.val == val) {// 不能插入return false;}else if(cur.val > val) {cur = cur.left;}else {cur = cur.right;}}// 循环出来之后,parent就是要插入的新节点的双亲节点,需要判定新节点是在parent的左边还是右边if(parent.val > val) {parent.left = newNode;}else {parent.right = newNode;}newNode.parent = parent;//从下往上依次调节平衡因子cur = newNode;while (parent != null) {if(parent.left == cur) {parent.bf--;}else {parent.bf++;}if(parent.bf == 0) {break;}else if(parent.bf == 1 || parent.bf == -1) {// 继续往上调整cur = parent;parent = cur.parent;}else {if(parent.bf == 2) {if(cur.bf == 1) {rotateLeft(parent);}else {rotateRL(parent);}}else {if(cur.bf == -1) {rotateRight(parent);}else {rotateLR(parent);}}break; //此时新的parent.bf会变成0,不必再进行调整了}}return true;}

1.4 AVL树的删除

AVL树的删除步骤为:

  1. 找到要删除的节点
  2. 判断删除节点的类型.有不同的处理方式(下面进行讨论)
    1. 待删除节点的左孩子为空
    2. 待删除节点的右孩子为空
    3. 左右节点都不为空
  3. 从下往上调节平衡因子

查找要删除的节点

这一步比较简单,我们直接代码演示:

 public AVLTreeNode remove(int key) {if(root == null) {return null;}AVLTreeNode cur = root;AVLTreeNode parent = cur;while(cur != null) {if(cur.val > key) {parent = cur;cur = cur.left;}else if(cur.val < key) {parent = cur;cur = cur.right;}else {return removeNode(parent,cur);}}return null;}

判断要删除节点的类型

  1. 待删除节点的左孩子为空

此时直接让改变指向即可,但是需要考虑平衡因子的问题(下面再进行讨论)

但是需要注意,如果删除的节点刚好是根节点,那就直接让根节点指向原本根节点的右边即可,并且此时不必关心平衡因子的问题,因为根节点的右孩子所在的树,本身就是平衡的,那就直接让根节点指向原本根节点的右边即可,并且此时不必关心平衡因子的问题,因为根节点的右孩子所在的树,本身就是平衡的

  1. 待删除节点的右孩子为空

同样直接改变引用即可,也需要注意平衡因子的变化

如果删除的节点刚好是根节点,那就直接让根节点指向原本根节点的左边即可,并且此时不必关心平衡因子的问题,因为根节点的左孩子孩子所在的树,本身就是平衡的

  1. 待删除节点的右孩子均不为空

此时关于节点的删除类似于二叉搜索树节点的删除,需要寻找替换节点,

即可以找到待删除节点的左树的最大值(或者右树的最小值)进行值的替换,接着将替罪羊删除即可,本文采取方式二

同样需要考虑平衡因子的替换

因为需要调节平衡因子,因此我们可以先找到最终要删除的节点以及要删除节点的父亲节点的引用,进行平衡因子的调节之后,再进行真正的删除操作,以下是这一部分的代码:

 private AVLTreeNode removeNode(AVLTreeNode parent, AVLTreeNode cur) {AVLTreeNode delNode = null; // 最终要删除的节点AVLTreeNode pDelNode = null;// 最终要删除的节点的父亲节点if(cur.left == null) {if(cur == root) {root = cur.right;if(root != null) {root.parent = null;}return cur; // 不需要考虑平衡因子的影响,因为右树本身就平衡}else {pDelNode = parent;delNode = cur;}}else if(cur.right == null) {if(cur == root) {root = cur.left;if(root != null) {root.parent = null;}return cur;// 不需要考虑平衡因子的影响,因为左树本身就平衡}else {pDelNode = parent;delNode = cur;}}else {AVLTreeNode minRightNode = cur.right;AVLTreeNode pMinRightNode = cur;// 找到右树的最小值while (minRightNode.left != null) {pMinRightNode = minRightNode;minRightNode = minRightNode.left;}cur.val = minRightNode.val;delNode = minRightNode;pDelNode = pMinRightNode;}}

从下往上调节平衡因子

注意,如果调节后的bf是-1 或者 1,说明原本的高度是没有整体的高度是没有变化的,不会影响上层,因此不必再进行调整(这点与插入节点有区别)

代码为:

        AVLTreeNode curDelNode = delNode;AVLTreeNode pCurDelNode = pDelNode;// 记录下两个节点,以防旋转的过程丢失原本的引用// 更新平衡因子while (delNode != root) {if(delNode == pDelNode.left) {pDelNode.bf++;}else {pDelNode.bf--;}if(pDelNode.bf == 0) {// 继续向上调整delNode = pDelNode;pDelNode = pDelNode.parent;}else if(pDelNode.bf == -1 || pDelNode.bf == 1) {break;}else {// 进行旋转.下面进行讨论}delNode = pDelNode;pDelNode = pDelNode.parent;}}

接下来我们进行旋转的讨论:

前两种情况与插入操作一致,但是需要继续向上调整平衡因子

第三种是删除操作出现的特殊情况,旋转完后,整棵树一定是平衡的,因此需要手动需将平衡因子更改,不必再向上调整了

上面三种情况存在反方向的另外三种情况,原理一致

代码为:

 AVLTreeNode curDelNode = delNode;AVLTreeNode pCurDelNode = pDelNode;// 记录// 更新平衡因子while (delNode != root) {if(delNode == pDelNode.left) {pDelNode.bf++;}else {pDelNode.bf--;}if(pDelNode.bf == 0) {// 继续向上调整delNode = pDelNode;pDelNode = pDelNode.parent;}else if(pDelNode.bf == -1 || pDelNode.bf == 1) {break;}else {// 进行旋转if(pDelNode.bf == 2) {if(pDelNode.right.bf == 1) {AVLTreeNode tmp = pDelNode.right;//先记录,以供继续向上调整rotateLeft(pDelNode);pDelNode = tmp;}else if(pDelNode.right.bf == -1){AVLTreeNode tmp = pDelNode.right.left;rotateRL((pDelNode));pDelNode = tmp;}else {// 特殊讨论AVLTreeNode tmp = pDelNode.right;rotateLeft(pDelNode);pDelNode = tmp;pDelNode.bf = -1;pDelNode.left.bf = 1;break;}}else {if(pDelNode.left.bf == -1) {AVLTreeNode tmp = pDelNode.left;rotateRight(pDelNode);pDelNode = tmp;}else if(pDelNode.left.bf == 1){AVLTreeNode tmp = pDelNode.left.right;rotateLR(pDelNode);pDelNode = tmp;}else {// 特殊讨论AVLTreeNode tmp = pDelNode.left;rotateRight(pDelNode);pDelNode = tmp;pDelNode.bf = 1;pDelNode.right.bf = -1;break;}}delNode = pDelNode;pDelNode = pDelNode.parent;}}

真正删除节点

最后利用我们先前记录下的引用,进行删除节点即可

        //删除if(curDelNode.left == null) {if(curDelNode == pCurDelNode.left) {pCurDelNode.left = curDelNode.right;if(curDelNode.right != null) {curDelNode.right.parent = pCurDelNode;}}else {pCurDelNode.right = curDelNode.right;if(curDelNode.right != null) {curDelNode.right.parent = pCurDelNode;}}}else {if(curDelNode == pCurDelNode.left) {pCurDelNode.left = curDelNode.left;if(curDelNode.left != null) {curDelNode.left.parent = pCurDelNode;}}else {pCurDelNode.right = curDelNode.left;if(curDelNode.left != null) {curDelNode.left.parent = pCurDelNode;}}}

整体代码

    public AVLTreeNode remove(int key) {if(root == null) {return null;}AVLTreeNode cur = root;AVLTreeNode parent = cur;while(cur != null) {if(cur.val > key) {parent = cur;cur = cur.left;}else if(cur.val < key) {parent = cur;cur = cur.right;}else {return removeNode(parent,cur);}}return null;}private AVLTreeNode removeNode(AVLTreeNode parent, AVLTreeNode cur) {AVLTreeNode delNode = null;AVLTreeNode pDelNode = null;if(cur.left == null) {if(cur == root) {root = cur.right;if(root != null) {root.parent = null;}return cur;}else {pDelNode = parent;delNode = cur;}}else if(cur.right == null) {if(cur == root) {root = cur.left;if(root != null) {root.parent = null;}return cur;}else {pDelNode = parent;delNode = cur;}}else {AVLTreeNode minRightNode = cur.right;AVLTreeNode pMinRightNode = cur;while (minRightNode.left != null) {pMinRightNode = minRightNode;minRightNode = minRightNode.left;}cur.val = minRightNode.val;delNode = minRightNode;pDelNode = pMinRightNode;}AVLTreeNode curDelNode = delNode;AVLTreeNode pCurDelNode = pDelNode;// 记录// 更新平衡因子while (delNode != root) {if(delNode == pDelNode.left) {pDelNode.bf++;}else {pDelNode.bf--;}if(pDelNode.bf == 0) {// 继续向上调整delNode = pDelNode;pDelNode = pDelNode.parent;}else if(pDelNode.bf == -1 || pDelNode.bf == 1) {break;}else {// 进行旋转if(pDelNode.bf == 2) {if(pDelNode.right.bf == 1) {AVLTreeNode tmp = pDelNode.right;rotateLeft(pDelNode);pDelNode = tmp;}else if(pDelNode.right.bf == -1){AVLTreeNode tmp = pDelNode.right.left;rotateRL((pDelNode));pDelNode = tmp;}else {// 特殊讨论AVLTreeNode tmp = pDelNode.right;rotateLeft(pDelNode);pDelNode = tmp;pDelNode.bf = -1;pDelNode.left.bf = 1;break;}}else {if(pDelNode.left.bf == -1) {AVLTreeNode tmp = pDelNode.left;rotateRight(pDelNode);pDelNode = tmp;}else if(pDelNode.left.bf == 1){AVLTreeNode tmp = pDelNode.left.right;rotateLR(pDelNode);pDelNode = tmp;}else {// 特殊讨论AVLTreeNode tmp = pDelNode.left;rotateRight(pDelNode);pDelNode = tmp;pDelNode.bf = 1;pDelNode.right.bf = -1;break;}}delNode = pDelNode;pDelNode = pDelNode.parent;}}//删除if(curDelNode.left == null) {if(curDelNode == pCurDelNode.left) {pCurDelNode.left = curDelNode.right;if(curDelNode.right != null) {curDelNode.right.parent = pCurDelNode;}}else {pCurDelNode.right = curDelNode.right;if(curDelNode.right != null) {curDelNode.right.parent = pCurDelNode;}}}else {if(curDelNode == pCurDelNode.left) {pCurDelNode.left = curDelNode.left;if(curDelNode.left != null) {curDelNode.left.parent = pCurDelNode;}}else {pCurDelNode.right = curDelNode.left;if(curDelNode.left != null) {curDelNode.left.parent = pCurDelNode;}}}return curDelNode;}

1.5 AVL树的性能分析

AVL树是一颗绝对平衡的二叉搜索树,要求每个节点的左右孩子的高度差绝对值不超过1,可以保证查询高效的时间复杂度,避免出现单分支的情况,即 l o g 2 N log_2N log2N

弊端在于,如果要对AVL树做一些结构的修改,必须插入节点、删除节点,就很可能会涉及到旋转操作,性能低下,最差情况下的删除甚至要一直延续到根节点

因此:AVL树适合数据的个数是静态的(不会改变)的情况

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/463425.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL-基础汇总

MySQL-基础汇总 数据库对于任何一个从事后台开发的人说都是永远躲不掉的&#xff0c;任何系统或程序离开了数据的支持都变的毫无意义。而管理数据的工具——数据库就显得尤为重要。本章节我们的核心就是 MySQL&#xff0c;相信很多小伙伴跟我一样&#xff0c;也沉浸在增、删、…

一条sql语句是怎么执行的?

一、问题 InnoDB存储引擎&#xff0c;执行了下列语句&#xff1a; UPDATE user SET name "小明" WHERE id1002; 其中id是主键&#xff0c;这条SQL语句的执行过程是怎样的&#xff1f; 二、答案 首先客户端与MySQL连接器进行连接&#xff0c;然后分析器经过词法…

MySQL数据库迁移到DM8数据库

1. 达梦新建zsaqks库 2. 打开DM数据迁移工具 3. 新建工程 4. 迁移 - 右击 - 新建迁移 下一步 5. 选择迁移方式 6. MySQL数据源 请输入MySQL数据库信息 7. DM数据库目的 请输入达梦数据库信息 8. 迁移选项 保持对象名大小写(勾选) 9. 指定模式 指定是从数据源复制对象。 10.…

Qt 练习做一个登录界面

练习做一个登录界面 效果 UI图 UI代码 <?xml version"1.0" encoding"UTF-8"?> <ui version"4.0"><class>Dialog</class><widget class"QDialog" name"Dialog"><property name"ge…

minikube 的 Kubernetes 入门教程--(五)

本文记录 Minikube 在 Kubernetes 上安装 WordPress 和 MySQL。 这两个应用都使用 PersistentVolumes 和 PersistentVolumeClaims 保存数据。 在深入这些步骤之前&#xff0c;先分享来自kubernetes.io教程。 链接>>使用持久卷部署 WordPress 和 MySQL | Kubernetes 获…

HarmonyOS 私仓搭建

1. HarmonyOS 私仓搭建 私仓搭建文档&#xff1a;https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-ohpm-repo-quickstart-V5   发布共享包[https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-har-publish-0000001597973129-V5]…

根据问题现象、用户操作场景及日志打印去排查C++软件问题,必要时尝试去复现问题

目录 1、概述 2、通过现有信息无法定位问题时&#xff0c;则需要尝试去复现问题 3、非崩溃问题与崩溃问题的一般排查思路 3.1、非崩溃问题的排查思路 3.2、崩溃问题的排查思路 4、难以复现问题的可能原因总结 4.1、问题难以复现&#xff0c;可能和某种特殊的业务场景或操…

《JVM第3课》运行时数据区

无痛快速学习入门JVM&#xff0c;欢迎订阅本免费专栏 运行时数据区结构图如下&#xff1a; 可分为 5 个区域&#xff0c;分别是方法区、堆区、虚拟机栈、本地方法栈、程序计数器。这里大概介绍一下各个模块的作用&#xff0c;会在后面的文章展开讲。 类加载子系统会把类信息…

class 100 KMP算法原理和代码详解

1. KMP 算法介绍 1.1 暴力方法 暴力方法就是将两个字符串进行一个一个比较 这个知道就行了, 我们的重点是 KMP 算法 1.2 KMP 算法介绍 暴力方法的时间复杂度是&#xff1a;O(n * m), 使用 KMP 算法可以将时间复杂度优化到&#xff1a;O(n m). 暴力方法时间慢的原因是&…

不基于Gin手撸一个RPC服务

目标 实现一个GRPC框架&#xff0c;可以通过grpc-ui来对接口进行访问。也可以使用client来直接调用服务端服务 准备&#xff08;这边以Mac系统举例&#xff09; 安装homebrew&#xff08;如果没有安装的话&#xff09; /bin/bash -c "$(curl -fsSL https://raw.github…

大数据治理:策略、技术与挑战

随着信息技术的飞速发展&#xff0c;大数据已经成为现代企业运营和决策的重要基础。然而&#xff0c;大数据的复杂性、多样性和规模性给数据管理带来了前所未有的挑战。因此&#xff0c;大数据治理应运而生&#xff0c;成为确保数据质量、合规性、安全性和可用性的关键手段。本…

Web应用性能测试工具 - httpstat

在数字化时代&#xff0c;网站的性能直接影响用户体验和业务成功。你是否曾经在浏览网页时&#xff0c;遇到加载缓慢的困扰&#xff1f;在这个快速变化的互联网环境中&#xff0c;如何快速诊断和优化Web应用的性能呢&#xff1f;今天&#xff0c;我们将探讨一个强大的工具——h…

宝藏虚拟化学习资料大全

最近发现了关于虚拟化的宝藏资料&#xff0c;瑞斯拜&#xff01;原文链接如下&#xff1a; 500篇关于虚拟化的经典资料&#xff0c;含CPU虚拟化&#xff0c;磁盘虚拟化&#xff0c;内存虚拟化&#xff0c;IO虚拟化。 目录 &#x1fa90; 虚拟化基础 &#x1f343; 虚拟化分类&…

【源码+文档】基于SpringBoot+Vue旅游网站系统【提供源码+答辩PPT+参考文档+项目部署】

作者简介&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容&#xff1a;&#x1f31f;Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…

微服务核心——网关路由

目录 前言 一、登录存在的问题归纳 二、*微服务网关整体方案 三、认识微服务网关 四、网关鉴权实现 五、OpenFeign微服务间用户标识信息传递实现 六、微服务网关知识追问巩固 前言 本篇文章具体讲解微服务中网关的实现逻辑、用于解决什么样的问题。其中标题中标注* 涉…

移植 AWTK 到 纯血鸿蒙(HarmonyOS NEXT)系统 (0) - 序

移植 AWTK 到 纯血鸿蒙 (HarmonyOS NEXT) 系统 (0) - 序 前段时间纯血鸿蒙系统 HarmonyOS 5.0&#xff08;又称 HarmonyOS NEXT&#xff09;正式推出&#xff0c;这是继苹果 iOS 和安卓系统后&#xff0c;全球第三大移动操作系统。纯正国产操作系统登场&#xff0c;国人无不欢…

docker-compose安装rabbitmq 并开启延迟队列和管理面板插件(rabbitmq_delayed_message_exchange)

问题&#xff1a; 解决rabbitmq-plugins enable rabbitmq_delayed_message_exchange &#xff1a;plugins_not_found 我是在docker-compose环境部署的 services:rabbitmq:image: rabbitmq:4.0-managementrestart: alwayscontainer_name: rabbitmqports:- 5672:5672- 15672:156…

SpringBoot AOP介绍、核心概念、相应实现

文章目录 AOP介绍AOP的核心概念切面(Aspect)切点(Join Point)语法具体解释 增强(Advice)织入(weaving) 相应实现权限校验日志输出 AOP介绍 AOP全称Aspect Oriented Programming意为面向切面编程&#xff0c;通过预编译和运行期间通过动态代理来实现程序功能统一维护的技术。AO…

Python 数据结构对比:列表与数组的选择指南

文章目录 &#x1f4af;前言&#x1f4af;Python中的列表&#xff08;list&#xff09;和数组&#xff08;array&#xff09;的详细对比1. 数据类型的灵活性2. 性能与效率3. 功能与操作4. 使用场景5. 数据结构选择的考量6. 实际应用案例7. 结论 &#x1f4af;小结 &#x1f4af…

CSS 超出一行省略号...,适用于纯数字、中英文

文本超出显示省略号... 代码&#xff1a; .ellipsis{ overflow: hidden; -webkit-line-clamp:1; text-overflow: ellipsis; display: -webkit-box; -webkit-box-orient: vertical; word-break: break-all; /** 纯数字、中英文都适用 */ }