【C++】AVL(平衡二叉搜索树)树的原理及实现

文章目录

一、引言

二、AVL树的概念

三、AVL树的插入

3、1 AVL树的简单插入

3、2 旋转分类

 3、2、1 新节点插入较高右子树的右侧:左单旋

3、2、2 新节点插入较高左子树的左侧:右单旋

3、2、3 新节点插入较高左子树的右侧:左右双旋(先左后右)

3、2、4 新节点插入较高右子树的左侧:右左双旋(先右后左)

四、检查AVL树

4、1 高度差与平衡因子对比 

4、2 不同的测试用例进行测试

五、性能分析

4.1 查找操作的复杂度

4.2 插入操作的复杂度

六、总结


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:C++、数据结构 👀

💥 标题:AVL树💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️ 

一、引言

   二叉搜索树 虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下
  因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。因此就诞生出了AVL树。
AVL树是一种自平衡二叉搜索树,它在查找、插入和删除操作中都能保持较好的性能。它的命名来自于它的发明者,Adelson-Velsky和Landis。AVL树的特点是通过旋转操作来保持平衡,使得任何一个节点的左右子树高度差不超过1

  本文将介绍AVL树的概念、实现以及性能分析。首先,我们将解释AVL树的结构和基本概念。然后,我们将详细讨论如何实现AVL树,并提供C++语言的示例代码。最后,我们将对AVL树的性能进行分析。

二、AVL树的概念

  AVL树是一种二叉搜索树,它的每个节点包含一个关键字(键值对)、左右孩子指针和父结点指针。每个节点还包含一个平衡因子(balance factor),用于表示该节点的左右子树高度差(平衡因子=左子树高度−右子树高度。 

template<class K,class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv; //key-valueint _bf; //balance factorAVLTreeNode(const pair<K,V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0),_kv(kv){}
};

  一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树。
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)。
AVL树的平衡性规则是指任何一个节点的左右子树的高度差不超过1。即对于任意节点,其左子树高度和右子树高度的差绝对值不大于1。

三、AVL树的插入

  本篇文章重点介绍AVL树的插入操作。因为在插入操作中包含了所有重要操作。

3、1 AVL树的简单插入

  插入一个新节点到AVL树中,需要进行以下几个步骤:

  1. 执行二叉搜索树的插入操作,找到新节点应该插入的位置。
  2. 更新相关节点的平衡因子。
  3. 检查是否破坏了平衡性规则,如果破坏了,则进行相应的旋转操作来恢复平衡。

  接着,在新节点插入后,我们需要检查树的平衡性。

  1. 插入新节点后的平衡因子调整:由于插入可能导致从插入节点到根节点的路径上的某些节点的平衡被破坏,我们需要从新插入的节点开始向上遍历,更新所有受影响节点的平衡因子。
  2. 平衡调整:一旦检测到某个节点的平衡因子不在范围[-1, 1]之间,就需要对其进行平衡调整,以确保树的平衡性。根据插入节点所在的子树的情况,可以进行四种类型的旋转操作:左旋、右旋、左右旋和右左旋。
  3. 继续向上检查:在执行旋转操作后,可能会影响到更高层次的节点的平衡性。因此,我们需要继续向上检查,直到根节点为止,以确保整个树的平衡性。

  当我们找到合适位置进行插入后,将该节点进行连接,在更新其父节点及相关节点的平衡因子。如下图例子:

  假设我们在上图中的值为6的节点左孩子插入一个节点。新插入的节点的平衡因子为0。但是其父节点(值为6的节点和值为7的节点)的平衡因子都发生了变化:6的节点和值为7的节点的平衡因子分别变为-1和0。为什么呢?这就与更新平衡因子的规则有关了。

  更新平衡因子的规则:

  1. 新增在右,parent-> bf++;新增在左,parent-> bf--;
  2. 更新后,parent->bf ==1or-1,说明parent插入前的平衡因子是0,说明左右子树高度相等,插入后有一边高,parent高度变了,需要继续往上更新;
  3. 更新后,parent->bf == 0,说明parent插入前的平衡因子是1 or -1,说明左右子树一边高一边低,插入后两边一样高,插入填上了矮了那边,parent所在子树高度不变,不需要继续往上更新;
  4. 更新后,parent->bf == 2 or -2,说明parent插入前的平衡因子是1 or -1,已经平衡临界值,插入变成2 or -2,打破平衡,parent所在子树需要旋转处理。
  5. 更新后,parent->bf 大于 2 或者 小于 -2的值,不可能,如果存在,则说明插入前就不是AVL树,需要去检查之前操作的问题。

  通过对上述的理解,我们可写出如下代码:

template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){if(_root==nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.second > kv.second){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent){if (parent->_right == cur){parent->_bf++;}else{parent->_bf--;}if (parent->_bf == 0){break;}else if (abs(parent->_bf) == 1){parent = parent->_parent;cur = cur->_parent;}else if (abs(parent->_bf) == 2){break;}else{assert(false);}}return true;}
private:Node* _root = nullptr;
};

3、2 旋转分类

  旋转也是有其自己的规则的:

  • 旋转后需要成为平衡树;
  • 旋转不能破环搜索树的规则。

  我们先看如下图的情况:

  我们在值为9的节点右边进行插入,我们发现其父节点和祖宗节点的平衡因子都发生了变化。此时我们是需要进行旋转调节的。但是我们也可以把该节点插入到其他节点下面,然后也可能会需要进行旋转。需要旋转的情况太多,所以我们需要进行分类。

  为了保持树的平衡,AVL树可能需要执行以下四种旋转操作之一:

  • 左旋(LL旋转)
  • 右旋(RR旋转)
  • 先左后右旋(LR旋转)
  • 先右后左旋(RL旋转)

  下面我们来一一解释各种旋转的原理。

 3、2、1 新节点插入较高右子树的右侧:左单旋

  左旋是在一个节点的右子树高度大于左子树高度时进行的旋转操作。通过左旋,可以将右子树的高度降低,使得整棵树重新恢复平衡。

  左旋的依据是,右子树的高度大于左子树的高度,因此需要将右子树中的某个节点旋转到根节点的位置,使得该节点成为新的根节点,并将原来的根节点作为新根节点的左子节点。

  具体需要左旋的情况如下:

  左旋是怎么实现的呢?如下图:

  上图就是把父节点中右树的左孩子给父节点父节点连接到右树的左边。通俗解释,把subRL赋给parent->_right(parent->_right=subRL),然后再将parent连接到subR的左(subR->_left=parent)。

  我们再分析是否符合旋转的规则。首先旋转后的高度是平衡的。其次符合搜索树的规则。但是我们不仅仅要更换节点,还要更新变换的父节点和平衡因子。我们改变了parent这棵树和子树subR。所以我们还需更新他们的父节点和平衡因子。我们看左旋的代码实现:

	void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;// 旋转Node* ppNode = parent->_parent;subR->_left = parent;parent->_right = subRL;// 更新父节点 if (subRL)  // h=0的情况下,subRL为nullptrsubRL->_parent = parent;parent->_parent = subR;if (_root == parent){subR->_parent = nullptr;_root = subR;}else{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}// 更新平衡因子subR->_bf = parent->_bf = 0;}

3、2、2 新节点插入较高左子树的左侧:右单旋

  右旋是在一个节点的左子树高度大于右子树高度时进行的旋转操作。通过右旋,可以将左子树的高度降低,使得整棵树重新恢复平衡。

  右旋的依据是,左子树的高度大于右子树的高度,因此需要将左子树中的某个节点旋转到根节点的位置,使得该节点成为新的根节点,并将原来的根节点作为新根节点的右子节点。

  右旋的情况如下如:

  我们再根据下图具体分析一下:

  右旋与左旋转很类似,我们都需要借助 parent 来维护该树的平衡。首先是要把subLR子树移动到parent的左树上。然后,再把parent移动到subL的右树上。操作完这两部后,树就平衡了。 我们可结合下图理解:

   不要忘记旋转后,要更新所变动节点的父节点和平衡因子,我们看代码:

	void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);subLR->_bf = 0;if (bf == 1){parent->_bf = 0;subL->_bf = -1;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;}else if (bf == 0){parent->_bf = 0;subL->_bf = 0;}else{assert(false);}}

3、2、3 新节点插入较高左子树的右侧:左右双旋(先左后右)

  左右旋是在一个节点的左子树的右子树高度大于左子树高度时进行的旋转操作。左右旋可以通过先进行一次左旋,再进行一次右旋来实现树的平衡。

  左右旋的依据是,左子树的右子树高度大于左子树的高度,导致左子树整体比右子树要高。为了保持树的平衡,需要对左子树进行左旋,然后再对原父节点进行右旋。 

  我们已经讲述了如下两种情况:

   那下种情况该怎么旋转呢?我们不妨先自己想一下看看怎么旋转。

  由于旋转的原因,我们还需再次展开子树b。上述情况又分为如下三种情况:

  上述三种情况只不过是更新平衡因子有所不同,旋转的方法还是相同的。都是先对值为30的子树进行左单旋,然后再对值为90的子树进行右单旋,旋转完成后再 考虑平衡因子的更新。整体的需安装过程如下:

  我们可以再结合着代码理解一下:

	void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);subLR->_bf = 0;if (bf == 1){parent->_bf = 0;subL->_bf = -1;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;}else if (bf == 0){parent->_bf = 0;subL->_bf = 0;}else{assert(false);}}

3、2、4 新节点插入较高右子树的左侧:右左双旋(先右后左)

  右左双旋与左右双旋大同小异,只不过是在不同的情况下进行了不同顺序的旋转。旋转的思路和情况分类是一模一样的。我们先看一下需要右左双旋的情况:

  同样可按照旋转的需求,划分三种情况(与左右双旋类似)。这里就不在具体画出。具体的旋转过程如下:

  再对不同情况下的平衡因子进行更新,代码如下:

	void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);subRL->_bf = 0;if (bf == 1){subR->_bf = 0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;parent->_bf = 0;}else if (bf == 0){parent->_bf = 0;subR->_bf = 0;}else{assert(false);}}

四、检查AVL树

  当我们自己完成AVL树代码后,我们还需要检查一下是否正确。通过什么方式检查呢? 

4、1 高度差与平衡因子对比 

  我们首先想到的是通过中序遍历打印出来,看其是否是有序的。这样就算验证完成了吗?并不是!!!

  我们不仅仅要验证是否为搜索树,话要看其是否为平衡树。但是验证是否是平衡树时,又该怎么检查是否平衡呢?可不可以通过遍历整棵树,看其平衡因子绝对值是否小于等于1呢?怎么就感觉好像又可以,好像又不太行呢?平衡因子是我们自己更新的,还需要我们自己去检查吗?当然不用!!!我们需要求出该子树的高度差与平衡因子比较看是否相同,这样检验才是比较合理的。

  有了上述的思路,我们在看代码是怎么实现的,代码如下:

public:void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){return _IsBalance(_root);}
private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " " << root->_kv.second << endl;_InOrder(root->_right);}bool _IsBalance(Node* root){if (root == nullptr){return true;}int leftHigh = TreeHeight(root->_left);int rightHigh = TreeHeight(root->_right);int subHigh = rightHigh - leftHigh;if (root->_bf != subHigh){cout << "平衡因子有误:" << root->_kv.first << endl;return false;}return abs(subHigh) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}int TreeHeight(Node* root){if (root == nullptr){return 0;}return max(TreeHeight(root->_left), TreeHeight(root->_right)) + 1;}

4、2 不同的测试用例进行测试

  我们先来看一段特殊的测试代码:

void TestAVLTree1()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };  // 测试双旋平衡因子调节AVLTree<int, int> t1;for (auto e : a){t1.Insert(make_pair(e, e));}t1.InOrder();cout << "IsBalance:" << t1.IsBalance() << endl;
}

   先看一下屏蔽掉右左双旋更新平衡因子代码的结果,输出结果如下:

  结果是有序的,但是平衡因子是有问题的!我们再把这段代码放开再看其输出结果:

  只有一组数据并不能很好的说明问题,我们再随机生成数据进行多次测试,代码如下:

void TestAVLTree2()
{size_t N = 10000;srand(time(0));AVLTree<int, int> t1;for (size_t i = 0; i < N; ++i){int x = rand();t1.Insert(make_pair(x, i));} cout << "IsBalance:" << t1.IsBalance() << endl;
}

五、性能分析

4.1 查找操作的复杂度

  AVL树的查找操作与普通的二叉搜索树相同,都是按照比较大小的规则在树中查找特定的元素。由于AVL树是平衡二叉搜索树,它保持了左右子树高度的平衡,因此查找操作的平均时间复杂度为O(log n),其中n是树中节点的数量

  在平衡状态下,每一层的节点数大约是前一层的两倍,这保证了树的高度始终保持在logarithmic级别。这种高度平衡的特性使得查找操作的时间复杂度始终保持在O(log n)的范围内,使得AVL树在大量数据的情况下依然能够快速有效地进行查找。

  总的来说,AVL树的查找操作性能非常出色,是其设计的主要优势之一。

4.2 插入操作的复杂度

  在AVL树中进行插入操作时,需要首先执行标准的二叉搜索树的插入操作,将新节点插入到合适的位置。然后,需要进行平衡调整,以确保插入操作不会破坏树的平衡性。

  插入操作的时间复杂度包括两个部分:插入节点的时间复杂度和平衡调整的时间复杂度。插入节点的时间复杂度是O(log n),其中n是树中节点的数量,这是因为AVL树保持了树的高度平衡,使得每一层的节点数大约是前一层的两倍。

  平衡调整的时间复杂度取决于进行的旋转操作次数。最坏情况下,插入一个节点可能需要执行O(log n)次旋转操作,但平均情况下,插入节点的时间复杂度仍然是O(log n)。这是因为在绝大多数情况下,只需要进行少数次旋转操作即可将树恢复平衡。

  总体来说,AVL树的插入操作的平均时间复杂度是O(log n),使得在插入新元素时仍然能够保持高效的性能。

六、总结

  AVL树是一种自平衡的二叉搜索树,其平衡性质使得在查找、插入和删除操作上都能保持较高的性能。通过限制树的平衡因子在一定范围内,AVL树能够在任何时刻保持树的高度平衡,从而避免了退化成链表的情况。

  在AVL树的操作中,插入和删除操作可能会导致树的平衡性被破坏,因此需要进行相应的平衡调整操作,包括左旋、右旋、左右旋和右左旋等。这些操作能够使树保持平衡,继续提供高效的性能。

  AVL树的性能分析表明,查找、插入和删除操作的平均时间复杂度均为O(log n),其中n为树中节点的数量。虽然平衡调整可能会增加一些开销,但总体上AVL树仍然能够保持高效的操作性能。

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

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

相关文章

数据库执行新增时,字段写值错乱/字段值写反了 的问题

今天给表加了个字段&#xff0c;执行新增后查看表&#xff0c;发现数据库执行新增完成后&#xff0c;字段写值错乱了&#xff0c;表现为这两个字段的值写反了↓↓↓↓↓↓ 排查了xml中所有赋值的地方&#xff0c;全都没有问题 字段与属性的通用映射&#xff1a; <resultMap…

【已解决】Java 中使用 ES 高级客户端库 RestHighLevelClient 清理百万级规模历史数据

&#x1f389;工作中遇到这样一个需求场景&#xff1a;由于ES数据库中历史数据过多&#xff0c;占用太多的磁盘空间&#xff0c;需要定期地进行清理&#xff0c;在一定程度上可以释放磁盘空间&#xff0c;减轻磁盘空间压力。 &#x1f388;在经过调研之后发现&#xff0c;某服务…

编织人工智能:机器学习发展历史与关键技术全解析

文章目录 1. 引言1.1 机器学习的定义1.2 重要性和应用场景重要性应用场景 2. 机器学习的早期历史2.1 初期理论与算法感知机决策树 2.2 早期突破支持向量机神经网络初探 3. 21世纪初期的发展3.1 集成学习方法随机森林XGBoost 3.2 深度学习的崛起卷积神经网络&#xff08;CNN&…

css-4:元素水平垂直居中的方法有哪些?如果元素不定宽高呢?

1、背景 在开发中&#xff0c;经常遇到这个问题&#xff0c;即让某个元素的内容在水平和垂直方向上都居中&#xff0c;内容不仅限于文字&#xff0c;可能是图片或其他元素。 居中是一个非常基础但又是非常重要的应用场景&#xff0c;实现居中的方法存在很多&#xff0c;可以将这…

Spring IOC

◆ 传统Javaweb开发的困惑 ◆ IoC、DI和AOP思想提出 ◆ Spring框架的诞生 Spring | Home IOC控制反转&#xff1a;BeanFactory 快速入门 package com.xiaolin.service.Impl;import com.xiaolin.dao.UserDao; import com.xiaolin.service.UserService;public class UserServic…

Intel 4工艺太难了!酷睿Ultra终于突破5GHz

无论是14nm还是10nm&#xff0c;Intel这些年的新工艺都有一个通性&#xff1a;刚诞生的时候性能平平&#xff0c;高频率都上不去&#xff0c;只能用于笔记本移动端(分别对应5代酷睿、10代酷睿)&#xff0c;后期才不断成熟&#xff0c;比如到了13代酷睿就达到史无前例的6GHz。 接…

【Linux】守护进程

1 相关概念 1.1 守护进程的概念 守护进程也叫做精灵进&#xff0c;是运行在后台的一种特殊进程。它独立于控制终端并且可以周期性的执行某种任务或者处理某些发生的事件。 守护进程是非常有用的进程&#xff0c;在Linux当中大多数服务器用的就是守护进程。比如&#xff0c;web…

前端 select 标签如何创建下拉菜单?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 代码示例⭐ 代码讲解⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏…

【网络基础知识铺垫】

文章目录 1 :peach:计算机网络背景:peach:1.1 :apple:网络发展:apple: 2 :peach:协议:peach:2.1 :apple:协议分层:apple:2.2 :apple:OSI七层模型:apple:2.3 :apple:TCP/IP模型:apple:2.4 :apple:TCP/IP模型与操作系统的关系:apple: 3 :peach:网络传输基本流程:peach:4 :peach:网…

MybatisPlus存在 sql 注入漏洞(CVE-2023-25330)解决办法

首先我们了解下这个漏洞是什么&#xff1f; MyBatis-Plus TenantPlugin 是 MyBatis-Plus 的一个为多租户场景而设计的插件&#xff0c;可以在 SQL 中自动添加租户 ID 来实现数据隔离功能。 MyBatis-Plus TenantPlugin 3.5.3.1及之前版本由于 TenantHandler#getTenantId 方法在…

DeviceNet主站网关转ETHERCAT连接ethercat总线伺服如何控制

大家好&#xff0c;今天要和大家分享一款自主研发的通讯网关——捷米JM-ECTM-DNT。这款产品可是解决了不同协议设备数据交换的麻烦问题&#xff0c;让我们一起来看看它的神奇之处吧&#xff01; 这款通讯网关有什么特别的呢&#xff1f;首先&#xff0c;它可以连接DEVICENET总…

火车头标题伪原创【php源码】

大家好&#xff0c;给大家分享一下python怎么读取文件中的数据&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 火车头采集ai伪原创插件截图&#xff1a; python是一门非常火爆且相对易学的编程语言&#xff0c;应用在各种场景。许多人想学…

electron+vue3全家桶+vite项目搭建【13.1】ipc通信的使用,主进程与渲染进程之间的交互

文章目录 引入IPC通信[主/渲染]进程对应渲染进程>主进程代码测试测试效果 主进程>渲染进程代码测试测试效果 双向通信代码测试测试效果 引入 electron项目常常由一个主进程和多个渲染进程构成&#xff0c;渲染进程之间是隔离的&#xff0c;而所有渲染进程都和主进程共享…

vscode 格式问题

1、EditorConfig for VS Code 插件 shift alt f 格式化文件&#xff08;VS Code格式化按键&#xff09;&#xff0c;如下图&#xff0c;每个缩进4空格 代码如下 创建文件名 .editorconfig root true [*] charset utf-8 indent_style space indent_size 2 end_of_…

Docker 启动 Nacos 报错:No DataSource set

​ &#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 …

Microsoft Message Queuing Denial-of-Service Vulnerability

近期官方公布了一个MSMQ的拒绝服务漏洞&#xff0c;可能因为网络安全设备的更新&#xff0c;影响业务&#xff0c;值得大家关注。 漏洞具体描述参见如下&#xff1a; Name: Microsoft Message Queuing Denial-of-Service Vulnerability Description: Microsoft Message Queuing…

java 版本企业招标投标管理系统源码+多个行业+tbms+及时准确+全程电子化tbms

​ 功能描述 1、门户管理&#xff1a;所有用户可在门户页面查看所有的公告信息及相关的通知信息。主要板块包含&#xff1a;招标公告、非招标公告、系统通知、政策法规。 2、立项管理&#xff1a;企业用户可对需要采购的项目进行立项申请&#xff0c;并提交审批&#xff0c;查…

静态页面与动态页面的区别及部署jpress应用

简述静态网页和动态网页的区别 静态网页&#xff1a; 1、首先是静态网页&#xff0c;静态网页每个网页中都有一个固定的URL&#xff0c;网页URL以htm、HTML、jpg、.gif、.mp4等常见形式为后缀&#xff0c;而且不含有问号&#xff1b; 2、静态网页内容一经发布到网页服务器上…

Java 8:让你的代码更简洁、高效和灵活的新特性

Java 8 ——企业中使用最普遍的版本&#xff0c;那么了解它的新特性是非常有必要的 目录 一、函数式接口 二、Lamdba表达式 三、方法引用 四、Stream API 3.1 创建 方法一&#xff1a;通过集合 方法二&#xff1a;通过数组 方法三&#xff1a;通过Stream的of() 方法四…

《JeecgBoot系列》JeecgBoot(ant-design-vue) 识别字段中指定内容并修改该行文字颜色

JeecgBoot(ant-design-vue) 识别字段中指定内容并修改该行文字颜色 需求&#xff1a;将生产工厂是配件工厂的行改变颜色标注 一、修改table组件内容 在<a-table></a-table>内添加:rowClassName"tableRowClass" <a-table>...:rowClassName"t…