AVL——平衡搜索树

✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:数据结构——AVL树
☂️<3>开发环境:Visual Studio 2022
💬<4>前言:AVL树是对二叉搜索树的严格高度控制,所以AVL树的搜索效率很高,但是这是需要付出很大的代价的,要维护父亲指针,和平衡因子。

目录

一.AVL的概念

二. AVL树节点及整体结构的定义

 三. AVL树的插入

1. 先按照二叉搜索树的规则将节点插入到AVL树中

2.根据插入的位置调整平衡因子

四.AVL树的旋转

1.左单旋

2.右单旋

3.左右双旋

4.右左双旋

5.总结:

五.AVL树的删除(了解)

六.AVL树的性能

七.完整代码及测试


一.AVL的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查
找元素相当于在顺序表中搜索元素,效率低下。
因此,两位俄罗斯的数学家G.M.Adelson-Velskii
E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

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

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在
O(log{_{2}}^{n}),搜索时间复杂度O(log{_{2}}^{n})

二. AVL树节点及整体结构的定义

//AVL树结点
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode(pair<K,V> kv):_kv(kv),_bf(0),_left(nullptr),_right(nullptr),_parent(nullptr){}pair<K, V> _kv;              //Key/Value数据int _bf;                     //平衡因子AVLTreeNode<K, V>* _left;    //结点的左子树AVLTreeNode<K, V>* _right;   //结点的右子树AVLTreeNode<K, V>* _parent;  //结点的双亲
};//AVL树定义
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool insert(pair<K,V> kv){}
private:Node* _root = nullptr;
};

 三. AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么
AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

插入过程:

1. 先按照二叉搜索树的规则将节点插入到AVL树中

    bool insert(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->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}//找到了合适的位置,创建新节点,出入位置cur = new Node(kv);//修改新节点的指向if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//未完待续....}

2.根据插入的位置调整平衡因子

平衡因子:右子树高度减去左子树高度。

cur 插入后,parent 的平衡因子一定需要调整,在插入之前,pParent 的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:

  1.  如果cur插入到parent的左侧,只需给parent的平衡因子-1即可
  2. 如果cur插入到parent的右侧,只需给parent的平衡因子+1即可

此时:parent的平衡因子可能有三种情况:0,正负1, 正负2

  1. 如果parent的平衡因子为0,说明插入之前parent的平衡因子为正负1,插入后被调整成0,此时满足  AVL树的性质,插入成功。
  2. 如果parent的平衡因子为正负1,说明插入前parent的平衡因子一定为0,插入后被更新成正负1,此 时以parent为根的树的高度增加,需要继续向上更新。
  3. 如果parent的平衡因子为正负2,则parent的平衡因子违反平衡树的性质,需要对其进行旋转处理。
while (parent){//cur插入到parent的左侧if (parent->_left == cur){parent->_bf--;}else//cur插入到parent的右侧{parent->_bf++;}//需向上调整平衡因子if (parent->_bf == 1||parent->_bf==-1){cur = parent;parent = cur->_parent;}//无需向上调整平衡因子else if(parent->_bf==0){break;}//无需向上调整平衡因子,直接旋转处理else if (parent->_bf == 2||parent->_bf==-2){//旋转,旋转之后平衡因子已经平衡,可以直接推出break;}else//出现的其他的错误情况{assert(0);}}

四.AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,
使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

1.左单旋

新节点插入较高右子树的右侧

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

  • 60节点的左孩子可能存在,也可能不存在。
  • 30可能是整棵树根节点,也可能是子树根节点。

  如果是整棵树根节点,旋转完成后,要更整棵树新根节点;如果是子树根节点,可能是某个节点的左子树,也可能是右子树。

    void RotateL(Node* parent){//  a//     b//        c//找到需要旋转的结点Node* curR = parent->_right;Node* curRL = curR->_left;//调整结点,并且修改其父亲结点指针parent->_right = curRL;if (curRL)//可能为空{curRL->_parent = parent;}//在修改子树根节点之前,保存子树根节点的父亲Node* pparent = parent->_parent;//修改子树根节点curR->_left = parent;parent->_parent = curR;//子树根节点有可能是整棵树的根节点if (pparent == nullptr){_root = curR;_root->_parent = nullptr;}else//子树根节点不是整棵树的根节点{//还要看子树是它父亲的左孩子还是右孩子if (pparent->_left == parent){pparent->_left = curR;}else{pparent->_right = curR;}curR->_parent = pparent;}//修改平衡因子curR->_bf = parent->_bf = 0;}

2.右单旋

新节点插入较高左子树的左侧

 右单旋过程和左单旋转过程一模一样仅仅只是反过来。

    void RotateR(Node* parent){Node* curL = parent->_left;Node* curLR = curL->_right;parent->_left = curLR;if (curLR){curLR->_parent = parent;}Node* pparent = parent->_parent;curL->_right = parent;parent->_parent = curL;if (parent == _root){_root = curL;_root->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = curL;}else{pparent->_right = curL;}curL->_parent = pparent;}curL->_bf = parent->_bf = 0;}

3.左右双旋

新节点插入较高左子树的右侧---左右:先左单旋再右单旋

 将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再
考虑平衡因子的更新。

注意:旋转之前,60的平衡因子可能是 -1 / 0 / 1,旋转完成之后,根据情况对其他节点的平衡因子进行调整。

 当h=0,时60自己就是一个新插入的结点,此时他的平衡因子就是。

所以旋转之前,需要保存curLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节
点的平衡因子。

    void RotateLR(Node* parent){Node* curL = parent->_left;Node* curLR = curL->_right;//旋转之前,保存curLR的平衡因子,旋转完成之后,//需要根据该平衡因子来调整其他节点的平衡因子int curLR_bf = curLR->_bf;//先左单旋RotateL(curL);//再右单旋RotateR(parent);//保存curLR的平衡因子,判断插入结点的位置,根据插入结点的位置,//判断出其他结点的平衡因子if (curLR_bf == -1){parent->_bf = 1;curLR->_bf = 0;curL->_bf = 0;}else if (curLR_bf == 1){parent->_bf = 0;curL->_bf = -1;curLR->_bf = 0;}else if (curLR_bf == 0){parent->_bf = 0;curL->_bf = 0;curLR->_bf = 0;}else{assert(false);}}

4.右左双旋

右左双旋和左右双旋过程一模一样,仅仅只是反过来。

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

5.总结:

假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑
1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为curR

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

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

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

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

        //调整平衡因子while (parent){if (parent->_left == cur){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 1||parent->_bf==-1)//需向上调整平衡因子{cur = parent;parent = cur->_parent;}else if(parent->_bf==0)//无需向上调整平衡因子{break;}else if (parent->_bf == 2||parent->_bf==-2)//无需向上调整平衡因子,直接旋转{if (parent->_bf == 2 && parent->_right->_bf == 1){RotateL(parent);//左单旋}else if (parent->_bf == -2 && parent->_left->_bf == -1){RotateR(parent);//右单旋}else if (parent->_bf == 2 && parent->_left->_bf == -1){RotateRL(parent);//右左双旋}else if (parent->_bf == -2 && parent->_left ->_bf== 1){RotateLR(parent);//左右双旋}else{assert(false);//其他错误情况}break;}else{assert(0);}

五.AVL树的删除(了解)

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不
错与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。
具体实现学生们可参考《算法导论》《数据结构-用面向对象方法与C++描述》殷人昆版。

六.AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这
样可以保证查询时高效的时间复杂度,即log{_{2}}^{n}。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

七.完整代码及测试

AVL.hpp

#pragma once
#include<iostream>
#include<cassert>
using namespace std;template<class K, class V>
struct AVLTreeNode
{AVLTreeNode(pair<K,V> kv):_kv(kv),_bf(0),_left(nullptr),_right(nullptr),_parent(nullptr){}pair<K, V> _kv;              //Key/Value数据int _bf;                     //平衡因子AVLTreeNode<K, V>* _left;    //结点的左子树AVLTreeNode<K, V>* _right;   //结点的右子树AVLTreeNode<K, V>* _parent;  //结点的双亲
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool insert(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->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}//找到了合适的位置cur = new Node(kv);//if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//调整平衡因子while (parent){if (parent->_left == cur){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 1||parent->_bf==-1)//需向上调整平衡因子{cur = parent;parent = cur->_parent;}else if(parent->_bf==0)//无需向上调整平衡因子{break;}else if (parent->_bf == 2||parent->_bf==-2)//无需向上调整平衡因子,直接旋转{if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);//左单旋}else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);//右单旋}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);//右左双旋}else if (parent->_bf == -2 && cur ->_bf== 1){RotateLR(parent);//左右双旋}else{//cout << parent->_bf << ":" << /*parent->_left->_bf << ":" <<*/ parent->_right->_bf << endl;assert(false);//其他错误情况}break;}else{assert(false);}}return true;}void Inorder(){_inorder(_root);cout << endl;}private:void _inorder(Node* root){if (root == nullptr){return;}_inorder(root->_left);cout << root->_kv.first << " ";_inorder(root->_right);}void RotateL(Node* parent){//  a//     b//        c//找到需要旋转的结点Node* curR = parent->_right;Node* curRL = curR->_left;//调整结点,并且修改其父亲结点指针parent->_right = curRL;if (curRL)//可能为空{curRL->_parent = parent;}//在修改子树根节点之前,保存子树根节点的父亲Node* pparent = parent->_parent;//修改子树根节点curR->_left = parent;parent->_parent = curR;//子树根节点有可能是整棵树的根节点if (pparent == nullptr){_root = curR;_root->_parent = nullptr;}else//子树根节点不是整棵树的根节点{//还要看子树是它父亲的左孩子还是右孩子if (pparent->_left == parent){pparent->_left = curR;}else{pparent->_right = curR;}curR->_parent = pparent;}//修改平衡因子curR->_bf = parent->_bf = 0;}void RotateR(Node* parent){Node* curL = parent->_left;Node* curLR = curL->_right;parent->_left = curLR;if (curLR){curLR->_parent = parent;}Node* pparent = parent->_parent;curL->_right = parent;parent->_parent = curL;if (parent == _root){_root = curL;_root->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = curL;}else{pparent->_right = curL;}curL->_parent = pparent;}curL->_bf = parent->_bf = 0;}void RotateLR(Node* parent){Node* curL = parent->_left;Node* curLR = curL->_right;//旋转之前,保存pSubLR的平衡因子,旋转完成之后,//需要根据该平衡因子来调整其他节点的平衡因子int curLR_bf = curLR->_bf;//RotateL(curL);RotateR(parent);if (curLR_bf == -1){parent->_bf = 1;curLR->_bf = 0;curL->_bf = 0;}else if (curLR_bf == 1){parent->_bf = 0;curL->_bf = -1;curLR->_bf = 0;}else if (curLR_bf == 0){parent->_bf = 0;curL->_bf = 0;curLR->_bf = 0;}else{assert(false);}}void RotateRL(Node* parent){Node* curR = parent->_right;Node* curRL = curR->_left;int curRL_bf = curRL->_bf;RotateR(curR);RotateL(parent);if (curRL_bf == -1){parent->_bf = 0;curRL->_bf = 0;curR->_bf = 1;}else if (curRL_bf == 1){parent->_bf = -1;curRL->_bf = 0;curR->_bf = 0;}else if (curRL_bf == 0){parent->_bf = 0;curRL->_bf = 0;curR->_bf = 0;}else{assert(false);}}Node* _root = nullptr;
};

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"AVL.hpp"
int main()
{int arr1[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };int arr2[] = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};AVLTree<int, int> a1;AVLTree<int, int> a2;for (auto e : arr1){a1.insert(make_pair(e,e));}a1.Inorder();for (auto e : arr2){a2.insert(make_pair(e, e));}a2.Inorder();
}

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

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

相关文章

图床项目进度(一)——UI首页

1. 前言 前面我不是说了要做一个图床吗&#xff0c;现在在做ui。 我vue水平不够高&#xff0c;大部分参考b站项目照猫画虎。 vue实战后台 我使用ts&#xff0c;vite&#xff0c;vue3进行了重构。 当然&#xff0c;我对这些理解并不深刻&#xff0c;许多代码都是游离于表面&am…

小白到运维工程师自学之路 第七十九集 (基于Jenkins自动打包并部署Tomcat环境)2

紧接上文 4、新建Maven项目 clean package -Dmaven.test.skiptrue 用于构建项目并跳过执行测试 拉到最后选择构建后操作 SSH server webExec command scp 192.168.77.18:/root/.jenkins/workspace/probe/psi-probe-web/target/probe.war /usr/local/tomcat/webapps/ /usr/loca…

mysql基本操作

常用的数据类型 int 整型用于定义整数类型的数据float 单精度浮点4字节32位 准确表示到小数点后六位 double 双精度浮点8字节64位双精度浮点8字节64位char 固定长度的字符类型用于定义字符类型数据varchar可变长度的字符类型 text文本image图片de…

Java IO流(五)Netty实战[TCP|Http|心跳检测|Websocket]

Netty入门代码示例(基于TCP服务) Server端 package com.bierce.io.netty.simple; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGro…

使用动态IP是否会影响网络

今天我们要谈论的话题是关于动态IP和网络的关系。也许有些小伙伴对这个概念还比较陌生&#xff0c;但别担心&#xff0c;我会简单明了的给你理清楚。让我们一起看看动态IP到底能否影响到网络。 首先&#xff0c;我们先来搞明白什么是动态IP。在互联网世界中&#xff0c;每一个连…

跨越边界:从前端切图仔走进iOS开发(Swift版--上集)

本文简介 点赞 关注 收藏 学会了 本文将以前端开发者的视角&#xff0c;和各位工友进入iOS开发的世界。 本文以实战为导向&#xff0c;快速掌握iOS开发这个技能。 无论你是想要扩展技能领域&#xff0c;还是对iOS开发充满好奇&#xff0c;花一个下午学习本文都能打开iOS开…

【consul】

consul 一、什么是服务注册与发现1.11.2 二、 什么是consul2.1定义2.2特性2.2.1服务注册与发现&#xff1a;2.2.2健康检查&#xff1a;2.2.3Key/Value存储&#xff1a; 三、consul部署-datacenter &#xff1a;指定数据中心名称&#xff0c;默认是dc1。consul &#xff1a;指定…

flask获取请求对象的get和post参数

前言 get请求参数是在URL里面的&#xff0c;post请求参数是放在请求头里面的 get请求&#xff1a; index_page.route("/get") def get():var_a request.args.get("a", "jarvis")return "request:%s,params:%s,var_a:%s" %(request…

接口自动化如何封装mysql操作

数据查询类封装 1. 功能分析 可以连接不同sql数据库查一条数据&#xff0c;多条数据可以获取不同格式的数据 2. 封装成数据库查询类 封装思路: 数据库查询模块有多个功能&#xff0c;且需要复用&#xff0c;所以封装成类创建对象方法实现各种查询在构造方法中创建连…

Word中对象方法(Methods)的理解及示例(上)

【分享成果&#xff0c;随喜正能量】奋斗没有终点,任何时候都是一个起点&#xff0c;沉潜是为了蓄势待发&#xff0c;沉潜是为了等待因缘。鲸豚沉潜于大海&#xff0c;幽兰深藏于山谷&#xff0c;能够经得起沉潜的人&#xff0c;才会有更高的成就。正如一年的树木只能当柴烧&am…

基于Jenkins自动打包并部署docker环境

目录 1、安装docker-ce 2、阿里云镜像加速器 3、构建tomcat 基础镜像 4、构建一个Maven项目 实验环境 操作系统 IP地址 主机名 角色 CentOS7.5 192.168.200.111 git git服务器 CentOS7.5 192.168.200.112 Jenkins git客户端 jenkins服务器 CentOS7.5 192.168…

TCP最大连接数问题总结

最大TCP连接数量限制有&#xff1a;可用端口号数量、文件描述符数量、线程、内存、CPU等。每个TCP连接都需要以下资源&#xff0c;如图所示&#xff1a; 1、可用端口号限制 Q&#xff1a;一台主机可以有多少端口号&#xff1f;端口号与TCP连接&#xff1f;是否能修改&#x…

Wireshark数据抓包分析之互联网协议(IP协议)

一、实验目的&#xff1a; 通过wireshark抓包分析IP协议的具体内容 二、预备知识&#xff1a; 1.IP地址存在的意义&#xff0c;就是为了弥补mac地址的不足&#xff0c;用于区分不同的网络 2.还有一些ip协议、ip数据包这些东西 三、网络拓扑 四、实验过程&#xff1a; part1&…

Docker容器与虚拟化技术:Gitlab账户注册

目录 一、实验 1.gitlab 一、实验 1.gitlab (1) 概念 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。 &#xff08;2&#xff09;官网 The DevSecOps Platform | GitLab &#xff08;3&#…

OpenAI推出GPT-3.5Turbo微调功能并更新API;Midjourney更新局部绘制功能

&#x1f989; AI新闻 &#x1f680; OpenAI推出GPT-3.5Turbo微调功能并更新API&#xff0c;将提供GPT-4微调功能 摘要&#xff1a;OpenAI宣布推出GPT-3.5Turbo微调功能&#xff0c;并更新API&#xff0c;使企业和开发者能够定制ChatGPT&#xff0c;达到或超过GPT-4的能力。通…

uniapp - 实现卡片式胶囊单选后右上角出现 “√“ 对勾对号选中效果功能,适用于小程序h5网页app全平台通用(一键复制组件源码,开箱即用!)

效果图 uniapp全平台兼容(小程序/h5网页/app)实现点击选择后,右上角出现 √ 对号效果(角标形式展现),功能组件, 改个样式,直接复制使用该组件。 组件源码 在 components 组件文件夹下,随便建立一个 .vue 文件,一键复制下方源码。

Centos7查看磁盘和CUP统计信息iostat命令

Centos7查看磁盘和CUP统计信息iostat命令 Centos7内存高|查看占用内存命令 docker实战(一):centos7 yum安装docker docker实战(二):基础命令篇 docker实战(三):docker网络模式(超详细) docker实战(四):docker架构原理 docker实战(五):docker镜像及仓库配置 docker实战(六…

如何在App里拉起小程序?

什么是小程序运行时框架&#xff1f; FinClip 的小程序编程模型是分为多个页面&#xff0c;每个页面有自己的 template、CSS 和 JS&#xff0c;实际在运行的时候&#xff0c;业务逻辑的 JS 代码是运行在独立的 JavaScript 引擎中&#xff0c;每个页面的 template 和 CSS 是运行…

vue组件的scope 以及 如何样式穿透

个人复习&#xff01;&#xff01;&#xff01; 有什么用 让当前组件的样式不会修改到其它地方的样式&#xff0c;使用了data-v-hash的方式来使css有了它对应模块的标识 实现原理 1、给HTML的dom节点添加一个不重复的data属性(例如: data-v-5558831a)来唯一标识这个dom 元素…

Linux下彻底卸载jenkins

文章目录 1、停服务进程2、查找安装目录3、删掉相关目录4、确认已完全删除 1、停服务进程 查看jenkins服务是否在运行&#xff0c;如果在运行&#xff0c;停掉 ps -ef|grep jenkins kill -9 XXX2、查找安装目录 find / -name "jenkins*"3、删掉相关目录 # 删掉相…