AVL树性质和实现

AVL树

AVL是两名俄罗斯数学家的名字,以此纪念

与二叉搜索树的区别

AVL树在二叉搜索树的基础上增加了新的限制:需要时刻保证每个树中每个结点的左右子树高度之差的绝对值不超过1

因此,当向树中插入新结点后,即可降低树的高度,从而减少平均搜索长度

平衡因子

为了能方便处理平衡二叉搜索树的限制条件,通常会引入平衡因子的概念

某一节点的平衡因子=其右子树高度-其左子树高度

在AVL树中,并不是一定需要平衡因子的,有些代码的AVL树就没有平衡因子。

这里引入平衡因子只是更方便的去判断树是否平衡了

AVL树的效率推算

image-20231013200846180

我们知道树的增删查改的效率是与树的高度有关的

假如AVL树是满二叉树,此时:2h-1=N

假如AVL树不是满二叉树,设最底层的节点个数为X,此时:2h-X=N。X的范围为[1,最后一层结点树-1]

此时上述两种情况都可以得出树的高度的数量级为logN,因此增删查改的时间复杂度为O(logN)

AVL树的节点设计

首先,与二叉搜索树相比,每个节点多了一个平衡因子(balance factor)

其次,为了满足一定需求,还多了个_parent指针,指向节点的父亲

template<class K,class V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K,V>* _left;AVLTreeNode<K,V>* _right;AVLTreeNode<K,V>* _parent;int _bf;//平衡因子AVLTreeNode(const pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};

这是三叉链image-20231018232522035

AVL树的框架设计

template<class K,class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public://……
private:Node* _root=nullptr;
};

AVL树的插入

在面试时几乎不会让你手撕AVL树的插入。重要的是了解思想。

但让你手撕一个旋转是有可能的

插入的逻辑

AVL树是在二叉搜索树的基础上引入了平衡因子,因此AVL树的插入分为两个步骤:

  • 按照二叉搜索树的方式插入新节点(多了个链接_parent
  • 插入后,更新节点的平衡因子

首先注意:新插入的节点只会对其父亲和其祖先的平衡因子造成影响

插入节点的_parent的平衡因子是一定需要调整的;在插入之前其_parent的平衡因子有三个可能值1、-1、0,插入节点后:

  • 如果插入到_parent的左侧,则_parent的平衡因子–即可
  • 如果插入到_parent的右侧,则_parent的平衡因子++即可

此时:_parent的平衡因子可能有三大种情况:

  • 如果_parent的平衡因子为0,说明插入之前的平衡因子为正负1,插入后被调整成0,满足AVL树的性质,插入成功,插入结束
  • 如果_parent的平衡因子为±1,说明插入之前的平衡因子为0,此时树的高度增加,那么就需要继续向上更新祖先的平衡因子,直至某一祖先的平衡因子为0或者更新到根节点,才算插入成功,停止更新,插入结束。
  • 如果_parent的平衡因子为±2,此时违反了AVL树的性质,需要进行旋转处理。处理完成则算插入成功,插入结束
更新平衡因子

最坏的情况是一直更新到根,如下图:image-20231017154509542

因此在更新平衡因子时,我们的循环条件为:while(parent)

因为只有根节点的_parent为空。所以当更新完根节点的平衡因子后,循环结束

while (parent)
{if (cur == parent->_left)//节点插入在父亲左边{parent->_bf--;}else if (cur == parent->_right)//节点插入在父亲右边{parent->_bf++;}//进一步判断祖先节点的平衡因子if (parent->_bf == 0)//父亲的平衡因子为0,循环结束{break;}else if (parent->_bf == 1 || parent->_bf == -1)//父亲的平衡因子为1或-1,则需要继续向上调整{cur = parent;parent = cur->_parent;}else if (parent->_bf == 2 || parent->_bf == -2)//父亲的平衡因子为2或-2,则需要旋转,且选择完后树一定平衡,故结束循环{//左单旋if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}//右单旋if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}//右左双旋if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}//左右双旋if (parent->_bf == -2 &&	 cur->_bf == 1){RotateLR(parent);}break;}else//其他情况,此时说明在插入之前树就已经不是平衡树了{assert(false);}
}

最后一个else中的assert(false)看似是无用的,因为parent不可能是绝对值大于2的。但是代码都是人写的,不可排除一开始的树就是有问题的。因此这句代码很重要

AVL树的旋转

根据节点插入位置的不同,AVL树的旋转分为四种:

  • 新节点插入较高左子树的左侧–左左:右单旋
  • 新节点插入较高右子树的右侧–右右:左单旋
  • 新节点插入较高左子树的右侧—左右:先左单旋再右单旋(左右双旋)
  • 新节点插入较高右子树的左侧—右左:先右单旋再左单旋(右左双旋)

何时使用何种旋转:

假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑:

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR

    • 当pSubR的平衡因子为1时,执行左单旋

    • 当pSubR的平衡因子为-1时,执行右左双旋

  2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL

    • 当pSubL的平衡因子为-1是,执行右单旋

    • 当pSubL的平衡因子为1时,执行左右双旋

旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新

右单旋

image-20231019154016105

上图在插入前,AVL树是平衡的。新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可

在旋转过程中,有以下几种情况需要考虑:

  1. cur节点的右孩子可能存在,也可能不存在

  2. parent可能是根节点,也可能是子树

    如果是根节点,旋转完成后,要更新根节点

    如果是子树,可能是某个节点的左子树,也可能是右子树

这两点是所有旋转情况都需要考虑的

右单旋的核心操作:把cur的右孩子给到parent的左,再把parent给到cur的右

代码:

void RotateR(Node* parent)//parent的平衡因子绝对值为2
{Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;//把cur的右孩子给到parent的左if (curright)//如果cur的右孩子存在,则更新其父亲为parent{curright->_parent = parent;}cur->_right = parent;//再把parent给到cur的右Node* ppnode = parent->_parent;//记录parent的原父亲节点,用于对cur的父亲进行更新parent->_parent = cur;//更新parent的父亲//对cur的父亲进行更新if (parent == _root)//parent即为根节点{_root = cur;cur->_parent = nullptr;//那么cur作为新的根,其父亲为空}else//parent是子树{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}cur->_bf = parent->_bf = 0;//更新完后,平衡因子一定为0
}
左单旋

image-20231019153929844

左单旋的核心操作与右单旋的核心操作正好是镜像的

左单旋的核心操作:把cur的左给parent的右,再把parent给到cur的左

代码:

void RotateL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;//把cur的左给parent的右if (curleft)//如果cur的左存在,则更新其父亲{curleft->_parent = parent;} cur->_left = parent;//再把parent给到cur的左Node* ppnode = parent->_parent;//记录parent的原父亲节点,用于对cur的父亲进行更新parent->_parent = cur;//对cur的父亲进行更新if (parent == _root){_root = cur;cur->_parent = nullptr;}else//说明左旋的部分只是某棵树的局部{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}cur->_bf = parent->_bf = 0;
}
右左双旋

image-20231019155022899

先对90进行右单旋,再对30进行左单旋

两次旋转。第一次旋转使其变成单纯的右边高,第二次旋转对应的左单旋

双旋代码难的不是旋转,而是平衡因子的更新

从图中可以看到:最终60成了根,60的左孩子给了parent的右边,60的右孩子给了cur的左边

因此平衡因子的更新分为两种情况:

  • h == 0:那么60则作为新插入的节点,此时60的bf == 0,那么parent和cur的bf也一定为0
  • h>=0:
    • 假如新结点插入在60的左边,即60的bf == -1。那么最终parent的bf == 0,cur的bf == 1
    • 假如新结点插入在60的右边,即60的bf == 1。那么最终parent的bf == -1,cur的bf == 0
    • 而60作为根节点最终bf一定为0

代码:

void RotateRL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;//先右旋再左旋RotateR(cur);RotateL(parent);//更新平衡因子if (bf == 0){parent->_bf = 0;cur->_bf = 0;curleft->_bf = 0;}else if (bf == 1){parent->_bf = -1;cur->_bf = 0;curleft->_bf = 0;}else if (bf == -1){parent->_bf = 0;cur->_bf = 1;curleft->_bf = 0;}else{assert(false);}
}
左右双旋

image-20231019160543333

与右左双旋是镜像的,不再赘述

代码:

void RotateLR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;//先左旋再右旋RotateL(cur);RotateR(parent);//更新平衡因子if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else{assert(false);}
}
AVL树插入的完整代码
bool Insert(const pair<K, V>& kv)
{//先帮助插入节点找到正确位置if (_root == nullptr)//树为空{_root = new Node(kv);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){//插入节点大于根节点if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first)//插入节点小于根节点{parent = cur;cur = cur->_left;}else//所插入节点已经存在{return false;}}cur = new Node(kv);if (cur->_kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//控制平衡因子while (parent)//{if (cur == parent->_left)//节点插入在父亲左边{parent->_bf--;}else if (cur == parent->_right)//节点插入在父亲右边{parent->_bf++;}//进一步判断祖先节点的平衡因子if (parent->_bf == 0)//父亲的平衡因子为0,循环结束{break;}else if (parent->_bf == 1 || parent->_bf == -1)//父亲的平衡因子为1或-1,则需要继续向上调整{cur = parent;parent = cur->_parent;}else if (parent->_bf == 2 || parent->_bf == -2)//父亲的平衡因子为2或-2,则需要旋转,且选择完后树一定平衡,故结束循环{//左单旋if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}//右单旋if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}//右左双旋if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}//左右双旋if (parent->_bf == -2 &&	 cur->_bf == 1){RotateLR(parent);}break;}else//其他情况,此时说明在插入之前树就已经不是平衡树了{assert(false);}}}//左单旋
void RotateL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;} cur->_left = parent;Node* ppnode = parent->_parent;//记录parent的原父亲节点parent->_parent = cur;//对cur的父亲进行更新if (parent == _root){_root = cur;cur->_parent = nullptr;}else//说明左旋的部分只是某棵树的局部{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}cur->_bf = parent->_bf = 0;
}//右单旋
void RotateR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;//让右节点接到parent的左边,再将parent接到cur的右边parent->_left = curright;if (curright){curright->_parent = parent;}cur->_right = parent;Node* ppnode = parent->_parent;//记录parent的原父亲节点parent->_parent = cur;//对cur的父亲进行更新if (parent == _root){_root = cur;cur->_parent = nullptr;}else//说明右旋的部分只是某棵树的局部{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}cur->_bf = parent->_bf = 0;
}//右左双旋
void RotateRL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;//先右旋再左旋RotateR(cur);RotateL(parent);//更新平衡因子if (bf == 0){parent->_bf = 0;cur->_bf = 0;curleft->_bf = 0;}else if (bf == 1){parent->_bf = -1;cur->_bf = 0;curleft->_bf = 0;}else if (bf == -1){parent->_bf = 0;cur->_bf = 1;curleft->_bf = 0;}else{assert(false);}
}//左右双旋
void RotateLR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;//先左旋再右旋RotateL(cur);RotateR(parent);//更新平衡因子if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 1;cur->_bf = -1;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else{assert(false);}
}

AVL树的验证

验证AVL树分为两步:

  • 验证其为二叉搜索树:如果中序遍历历可得到一个有序的序列,就说明为二叉搜索树(这里就不详细介绍了,详情可以看二叉搜索树那里)

  • 验证其为平衡树:

    • 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
    • 节点的平衡因子是否计算正确
    int TreeHeight(Node* root)
    {if (root == nullptr)return 0;int leftHeight = TreeHeight(root->_left);int rightHeight = TreeHeight(root->_right);return rightHeight > leftHeight ? rightHeight + 1: leftHeight + 1;
    }bool IsBalance()//两个IsBalance构成重载
    {return IsBalance(_root);
    }bool IsBalance(Node* root)
    {if (root == nullptr)return true;int leftHeight = TreeHeight(root->_left);int rightHeight = TreeHeight(root->_right);if (rightHeight - leftHeight != root->_bf){cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;return false;}return abs(rightHeight - leftHeight) < 2&& IsBalance(root->_left)&& IsBalance(root->_right);
    }
    

    这里给出一个调试技巧:

    image-20231018182647226假如我运行出现了下面的情况

    image-20231018182657070

    我们现在知道在插入11的时候出了问题,那么就可以针对e=11时进行调试

    但如果现在有100个值,我在第99个值才出现问题,那是不是需要按F10按99次呢?

    两种方法,:

    • 一个是利用条件断点

      image-20231018183053645

    • 还一个是我们自己写代码让它停到想停的地方

      image-20231018183141199

      这里的int x=0;是随便写的,目的是能让断点在这里停下来。因为断点打在空行上是停不住的

      当然,我停在断点处不是为了调int x=0;,而是为了调下面

AVL树的删除

了解即可

image-20231019171629540

总结

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)

但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。

因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

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

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

相关文章

InSAR 滤波算法

目录 1.InSAR 滤波原理 2.InSAR 滤波算法 2.1 均值滤波 2.2 Goldstein 滤波 2.3 改进的Goldstein 滤波 2.4 精致 Lee 滤波 2.5 小波滤波2.6 NL-InSAR 滤波 2.7 InSAR-BM3D 滤波 3.参考文献 本文由CSDN点云侠原创&#xff0c;爬虫网站请自重。 InSAR 滤波是InSAR 技术处理中的一…

rviz添加qt插件

一、增加rviz plugin插件 资料&#xff1a;http://admin.guyuehome.com/42336 https://blog.51cto.com/u_13625033/6126970 这部分代码只是将上面两个链接中的代码整合在了一起&#xff0c;整合在一起后可以更好的理解其中的关系 1、创建软件包 catkin_create_pkg rviz_tel…

IntelliJ IDEA 如何修改默认Maven仓库地址

在使用idea过程中&#xff0c;每次新建项目或者打开项目时&#xff0c;maven仓库地址都会变为默认地址。如何修改默认地址&#xff0c;让其保持不变&#xff0c;如下这种方式可以简单快捷的设置。 1.打开idea&#xff0c;取消项目自动加载 2.点击 Customize,然后再点击 All se…

DeepLearning - 余弦退火热重启学习率 CosineAnnealingWarmRestartsLR

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/134249925 CosineAnnealingWarmRestartsLR&#xff0c;即 余弦退火热重启学习率&#xff0c;周期性修改学习率的下降和上升&#xff0c;间隔幅度逐…

K7系列FPGA进行FLASH读写1——CCLK控制(STARTUPE2原语)

最近的工作涉及对 FPGA 进行远程更新&#xff0c;也就是通过远程通信接口将 .bin 文件送到 FPGA&#xff0c;然后写入 FLASH&#xff0c;这样当 FPGA 重新上电后就可以执行更新后的程序了。因此第一步工作就是进行 FLASH 的读写控制。 然而如果尝试配置 FLASH 管脚时&#xff0…

Android Datastore 动态创建与源码解析

涉及到的知识点 1、协程原理---->很好的博客介绍&#xff0c;一个小故事讲明白进程、线程、Kotlin 协程到底啥关系&#xff1f; 2、Channel知识点---->Android—kotlin-Channel超详细讲解 3、Coroutines : CompletableDeferred and structured concurrency 封装的DataS…

数学建模比赛中常用的建模提示词(数模prompt)

以下为数学建模比赛中常用的建模提示词&#xff0c;希望对你有所帮助&#xff01; 帮我总结一下数学建模有哪些预测类算法&#xff1f; 灰色预测模型级比检验是什么意思? 描述一下BP神经网络算法的建模步骤 对于分类变量与分类变量相关性分析用什么算法 前10年的数据分别是1&a…

代码随想录 Day38 完全背包问题 LeetCode T70 爬楼梯 T322 零钱兑换 T279 完全平方数

前言 在今天的题目开始之前,让我们来回顾一下之前的知识,动规五部曲 1.确定dp数组含义 2.确定dp数组的递推公式 3.初始化dp数组 4.确定遍历顺序 5.打印dp数组来排错 tips: 1.当求取物品有限的时候用0-1背包,求取物品无限的时候用完全背包 结果是排列还是组合也有说法,当结果是组…

渗透实战靶机2wp

0x00 简介 1、测试环境 目标IP&#xff1a;10.xxxx 测试IP&#xff1a;192.168.139.128 测试环境&#xff1a;win10、kali等 测试时间&#xff1a;2021.7.22-2021.7.22 测试人员&#xff1a;ruanruan 2、测试过程 本次实战主要通过对收集到的端口、目录等信息进行持续整…

润和软件HopeStage与奇安信网神终端安全管理系统、可信浏览器完成产品兼容性互认证

近日&#xff0c;江苏润和软件股份有限公司&#xff08;以下简称“润和软件”&#xff09;HopeStage 操作系统与奇安信网神信息技术&#xff08;北京&#xff09;股份有限公司&#xff08;以下简称“奇安信”&#xff09;终端安全管理系统、可信浏览器完成产品兼容性测试。 测试…

阿里云二级域名绑定与宝塔Nginx反向代理配置

在阿里或者腾讯...各大域名商买好域名&#xff0c;备案解析好&#xff0c;目标URL&#xff0c;是真正的地址&#xff0c;比如一些端口&#xff0c;后者会自动填写。 注意ssl配置好&#xff0c;这里不要带反代端口

vue中异步更新$nextTick

1.需求 编辑标题, 编辑框自动聚焦 点击编辑&#xff0c;显示编辑框让编辑框&#xff0c;立刻获取焦点 2.代码实现 <template><div class"app"><div v-if"isShowEdit"><input type"text" v-model"editValue"…

王道p18 第12题假设 A中的 n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出A的主元素。若存在主元素,则输出该元素:否则输出-1

视频讲解在&#xff1a;&#x1f447; p18 第12题 c语言实现王道数据结构课后习题_哔哩哔哩_bilibili 从前向后扫描数组元素&#xff0c;标记出一个可能成为主元素的元素 Num。然后重新计数&#xff0c;确认 Num 是否是主元素。 我们可分为以下两步: 1.选取候选的主元素。依…

YOLO目标检测——汽车头部尾部检测数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;用于训练自动驾驶系统中的车辆感知模块&#xff0c;以实现对周围车辆头部和尾部的准确检测和识别数据集说明&#xff1a;汽车头部尾部检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富标签说明&#xff1a;使用lableimg标注软…

【JMeter】后置处理器的分类以及场景介绍

1.常用后置处理器的分类 Json提取器 针对响应体的返回结果是json格式的会自动生成新的变量名为【提取器中变量名_MatchNr】,取到的个数由jsonpath expression取到的个数决定 可以当作普通变量调用,调用语法:${提取器中变量名_MatchNr}正则表达式提取器 返回结果是任何数据格…

一款好用的PDF转翻页电子书网站

​你是否曾经遇到过PDF文件无法翻页或者阅读不便的问题&#xff1f;今天给大家推荐一款好用的PDF转翻页电子书网站&#xff0c;让你轻松阅读PDF文件&#xff0c;不再烦恼翻页问题&#xff01; 一、网站介绍 这款FLBOOK在线制作电子杂志网站支持多种电子文件格式转换&#xff0…

JWT简介 JWT结构 JWT示例 前端添加JWT令牌功能 后端程序

目录 1. JWT简述 1.1 什么是JWT 1.2 为什么使用JWT 1.3 JWT结构 1.4 验证过程 2. JWT示例 2.1 后台程序 2.2 前台加入jwt令牌功能 1. JWT简述 1.1 什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准&#xff08;(RFC 7…

〔001〕虚幻 UE5 安装教程

✨ 目录 🎈 下载启动程序🎈 注册个人账户🎈 选择引擎版本🎈 选择安装选项🎈 虚幻商城的使用🎈 每月免费插件🎈 安装插件🎈 下载启动程序 下载地址:https://www.unrealengine.com/zh-CN/download点击上面地址,下载 UE5 启动程序并安装🎈 注册个人账户 打开商…

Linux多虚拟主机和配置限制访问与日志

目录 一、多虚拟主机 1.配置单网卡多个ip 2.给每个主机站点设置主页 3.测试访问 二、限制访问 1.限制所有 2.放行192.168.0.0/24网段访问 三、日志与状态页 1.定义访客日志 2.状态页配置 一、多虚拟主机 1.配置单网卡多个ip ip address add 192.168.0.231/24 dev e…

案例研究|腾讯音乐娱乐集团与JumpServer共探安全运维审计解决方案

近年来&#xff0c;得益于人民消费水平的提升以及版权意识的加强&#xff0c;用户付费意愿和在线用户数量持续增长&#xff0c;中国在线音乐市场呈现出稳定增长的发展态势。随着腾讯音乐于2018年12月上市&#xff0c;进一步推动了中国在线音乐市场的发展。 腾讯音乐娱乐集团&a…