C++——关联式容器(2):AVL树(平衡二叉树)

news2024/11/5 20:29:21

2.AVL树

2.1 AVL树的概念

        在学习了二叉搜索树后,我们发现了二叉搜索树可以根据大小比较来进行类似于折半查找的操作,使得搜索时间复杂度达到logn的水准。但是在面对极端情况下,如近似有序的序列,那么整棵树的时间复杂度就有可能退化成O(N)。

        为了避免这种极端的情况,同时为了保证搜索效率一定是二分查找法的logn, 我们希望对二叉树进行一些限制,使得其看起来更匀称一些。因此我们给出AVL树的特征:AVL树的任意结点的左右子树高度差不超过1。我们一般使用平衡因子的概念来标识高度差,即

结点的平衡因子=右树高度-左树高度

平衡因子的绝对值<=1

2.2 AVL树详解

2.2.1 pair介绍

        在搜索二叉树的学习中,我们接触到了两种形式——K模型和KV模型。其中KV模型实际上就是将两类值看作是一个整体,称之为一对键值对。当时我们是通过在搜索二叉树结点中定义出_key和_value两个成员变量来分别表示键值对中的key和value。

        在此处我们引入C++库中为我们提供的键值对类:pair。

 

        pair是一个模板类,两个参数分别表示键值对的两个值各自的类型,而其成员first和second则可以用于访问键值对的值。使用很简单,我们在AVL树的实现中将使用pair来作为结点的值。

        除了pair之外,再介绍一个和pair密切相关的函数——make_pair,它接受两个任意类型的参数,返回一个pair类型的对象,因此一般会用它来便捷创建pair对象。

2.2.2 AVL树的实现

2.2.2.1 AVL树的结点

        AVL树的结点应该包括:pair对象——用于存储数据;平衡因子——用于维护AVL树;左孩子、右孩子、父结点的指针——因为可能需要向上调整AVL树的结构,所以需要父结点指针。

	//AVL树的结点
	template<class K, class V>
	struct AVLTreeNode {
		//pair是一个结构体类型,它将一对值组合在一起,作为一个值
		//pair<T1,T2>作为结构体模板,T1和T2分别表示一对的两个类型
		//可以通过.first和.second来访问两个值
		pair<K, V> _pair;
		AVLTreeNode<K, V>* _left;
		AVLTreeNode<K, V>* _right;
		AVLTreeNode<K, V>* _parent;
		int _bf;//平衡因子=右子树高度-左子树高度

		AVLTreeNode(pair<K, V> kv)
			:_pair(kv)
			, _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _bf(0)
		{}
	};
2.2.2.2 默认成员函数

        在学会了二叉树的基本操作后,对于AVL树的简单操作并不难上手。拷贝构造可以通过前序遍历的方式创建结点并链接成为一棵树;赋值重载可以复用拷贝构造;析构函数则使用后序遍历的方法来析构结点。

	template<class K, class V>
	class AVLTree {
		typedef AVLTreeNode<K, V> AVLNode;
	public:
		//无参构造
		AVLTree()
			:_root(nullptr)
		{}

		//拷贝构造
		AVLTree(const AVLTree<K, V>& avl)
		{
			_root = copy(avl._root);
		}
	private:
		AVLNode* copy(AVLNode* root)
		{
			if (root == nullptr) return nullptr;
			AVLNode* newnode = new AVLNode(root->_pair);
			newnode->_left = copy(root->_left);
			newnode->_right = copy(root->_right);
			return newnode;
		}

		//赋值重载
	public:
		AVLTree<K, V>& operator=(const AVLTree<K, V> avl)
		{
			swap(this->_root, avl._root);
			return *this;
		}

		//析构
		~AVLTree()
		{
			Destroy(_root);
			_root = nullptr;
		}
	private:
		void Destroy(AVLNode* root)
		{
			if (root == nullptr) return;
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}
	private:
		AVLNode* _root;
    };
2.2.2.3 查找函数

        因为平衡二叉树也是一颗搜索二叉树,所以可以通过比较key的方法来完成查找。

	public:
		AVLNode* Find(const K& key)
		{
			AVLNode* cur = _root;
			while (cur)
			{
				if (cur->_pair.first < key)
				{
					cur = cur->_right;
				}
				else if (cur->_pair.first > key)
				{
					cur = cur->_left;
				}
				else
				{
					return cur;
				}
			}
			return nullptr;
		}

2.3 插入数据——旋转

        插入操作可以说是AVL树的重头戏了,因为AVL树维持其平衡的特征正是依靠每一次插入数据打破平衡后的调整逻辑,我们跟随着插入的逻辑一步步走。

2.3.1 第一步——插入结点

        这部分操作方式和二叉搜索树相同,即根据key的大小关系将待插入的结点插入到树的对应位置处。

2.3.2 第二步——调整平衡因子

        平衡因子=右树高度-左树高度,因为插入了新的结点会影响到树高度,所以接下来就是调整父结点的平衡因子。根据平衡因子的计算方法,当新结点位于左树则平衡因子减1,位于右树则平衡因子加1。

        以左右对称来看,在未插入结点之前的平衡因子是合法的,即可能是0或±1。在插入平衡因子后平衡因子发生改变的情况不同,所导致的结果和处理方式也不同。下面就讨论一下三种不同情况下的处理方式。

        a.平衡因子由0->±1:

        这种情况下,结点插入在了父结点左右任意一边,会导致层数增加一层,因此使得父结点平衡因子发生改变。是否向上产生影响取决于插入结点是否影响到了父结点所在子树的高度,因为这个高度是计算父结点上一层平衡因子的数据来源。因为0->±1的平衡因子变化一定是新增高度带来的结果,所以就必然会影响到上一层的平衡因子,所以需要向上更新。

        向上更新的思路就是将现在的父结点作为新插入的结点,因为新插入结点对于父结点来说就是这一侧子树高度+1,而已经知道父结点高度+1了,那么将其认为是新插入的结点就是合理的。对上层结点依旧采取同样的平衡因子修正判断方法即可。

        b.平衡因子由±1->0:

        在这种情况下,结点插入在了父结点子树原本空缺的一边,父结点子树高度没有发生变化所以就不需要向上更新祖先的平衡因子。

        c.平衡因子由±1->±2:

        这种情况表明新结点插入在了原本突出的一边,导致违反了平衡因子绝对值小于等于1的规则,因此需要进行一些操作来修正这种情况,使其重回AVL树。采取的方法即是旋转,而根据结点的不同布局形式,一共分为4种情况来分类讨论。我们以R代表右,L代表左。

        注:可以通过网上搜索动态图来更加直观理解旋转。

        ①RR失衡——左单旋

        RR即为根->right->right的形状。当出现RR情况的失衡时,就需要进行左单旋,在平衡因子角度就是2和1形式的失衡。

        为了方便描述左单旋的过程,我们为关键结点起个名字如下图。parent是父结点也是bf=2的结点,subR是parent的右孩子,subRL则是subR的左孩子(图中隐含在左树当中)。

        左单旋就是以subR为枢纽,以parent为悬臂向左(逆时针)旋转。旋转后结点之间的父子关系要发生变化,看似复杂实际上涉及到变化的结点有四个:parent,subR,subRL和parent的父结点(称为grandparent),而旋转实际上也就是处理这四个结点间的链接关系,笔者认为不必细说,具体可以参考图片和代码理解。

//左单旋
void RotateL(AVLNode* parent)
{
	AVLNode* subR = parent->_right;
	AVLNode* subRL = subR->_left;
	
	//结点链接三组:subR和parent、parent和sunRL、parent->_parent和subR
	subR->_left = parent;
	parent->_right = subRL;
	if (parent->_parent == nullptr)
	{
		_root = subR;
	}
	else if (parent->_parent->_left == parent)
	{
		parent->_parent->_left = subR;
	}
	else
	{
		parent->_parent->_right = subR;
	}

	subR->_parent = parent->_parent;
	parent->_parent = subR;
	if (subRL)	//右左子树为空树
		subRL->_parent = parent;

	//平衡因子修正
	//可以证明,在左旋处理后,左右subR和parent结点的平衡因子均为0,而且左旋后的子树与插入前相比高度不变,所以该子树的祖先结点平衡因子均不变
	subR->_bf = parent->_bf = 0;
}

        在旋转结束后要记得更新结点的平衡因子。上图中以一个参数h(h>=0)来表示各个子树的高度,因为合理的AVL子树在此处不影响旋转,所以抽象表示,而且为了满足2和1的平衡因子,所画三个子树一定是一样的高度。通过观察上图,可以看到在旋转之后的两个结论:①subR和parent的bf均变为了0;②整棵子树的高度前后都是h+2,这说明了没有影响祖先,不用向上更新平衡因子

         ②LL失衡——右单旋

 

        LL即为根->left->left的形状。当出现LL情况的失衡时,就需要进行右单旋,在平衡因子角度就是-2和-1形式的失衡。

        同样的,出于方便描述左单旋的过程,我们为关键结点起个名字如下图。parent是父结点也是bf=-2的结点,subL是parent的左孩子,subLR则是subL的右孩子(图中隐含在右树当中)。

        右单旋和左单旋就是镜像对称。右单旋以subL为枢纽,以parent为悬臂向右(顺时针)旋转。旋转后变化的结点有四个:parent,subL,subLR和parent的父结点(称为grandparent)。

		//右单旋
		void RotateR(AVLNode* parent)
		{
			AVLNode* subL = parent->_left;
			AVLNode* subLR = subL->_right;

			//结点链接三组:subL和parent、parent和sunLR、parent->_parent和subL
			subL->_right = parent;
			parent->_left = subLR;
			if (parent->_parent == nullptr)
			{
				_root = subL;
			}
			else if (parent->_parent->_left == parent)
			{
				parent->_parent->_left = subL;
			}
			else
			{
				parent->_parent->_right = subL;
			}

			subL->_parent = parent->_parent;
			parent->_parent = subL;
			if (subLR)	//左右子树为空树
				subLR->_parent = parent;

			//平衡因子修正
			//可以证明,在右旋处理后,左右subL和parent结点的平衡因子均为0,而且右旋后的子树与插入前相比高度不变,所以该子树的祖先结点平衡因子均不变
			subL->_bf = parent->_bf = 0;
		}

        最后更新结点的平衡因子。同样的道理,通过观察上图,可以看到:①subL和parent的bf均变为了0;②整棵子树的高度前后都是h+2,没有影响祖先,不用向上更新平衡因子

        ③LR失衡——左右双旋

        LR即为根->left->right的形状。当出现LR情况的失衡时,平衡因子是-2和1的形式,此时发现无论向左还是向右旋转都无法解决失衡的问题,于是引出了双旋的处理方法。双旋,顾名思义就是旋转两次,当发生了如上的LR失衡则需要左右双旋。

        关键结点依然是三个,parent是父结点也是bf=-2的结点,subL是parent的左孩子,subLR则是subL的右孩子(图中隐含在右树当中)。第一次的左旋以subL为轴;第二次的右旋则以parent为轴。因为双旋实际就是两次单旋,因此可以调用单旋的接口完成双旋,此处不再过多解释。

        值得注意的是双旋的平衡因子的更新。与单旋不同,双旋新结点位于左右树的位置不同使得subLR的平衡因子也不尽相同,导致会对最后结果中的平衡因子产生影响。通过讨论,可以得到三种情况下的分布,实际上就是subLR平衡因子为-1(插入在subLR的左子树)、1(插入在subLR的右子树)、0(subLR自身)。上图已经给出了-1情况的解析,下面给出另外两种情况。

		//左右双旋
		void RotateLR(AVLNode* parent)
		{
			AVLNode* subL = parent->_left;
			AVLNode* subLR = parent->_left->_right;

			//因为双旋调用单旋的函数,会改变平衡因子。单旋考虑的仅是需要单旋情况的平衡因子改变,我们此处只需要单旋的旋转操作而不接受其平衡因子的改变
			//所以在之后会重新进行双旋操作的平衡因子修正,但是为了确定修正方案,并且单旋会改变平衡因子,所以需要提前记录可以作为标识的平衡因子作为分支语句的条件
			int bf = subLR->_bf;

			RotateL(subL);
			RotateR(parent);

			//平衡因子修正
			//①subLR的左子树插入新结点——subLR的平衡因子为-1
			if (bf == -1)
			{
				parent->_bf = 1;
				subL->_bf = subLR->_bf = 0;
			}
			//②subLR的右子树插入新结点——subLR的平衡因子为1
			else if (bf == 1)
			{
				parent->_bf = subLR->_bf = 0;
				subL->_bf = -1;
			}
			//③subLR自身是被插入的新结点——subLR的平衡因子为0
			else
			{
				parent->_bf = subL->_bf = subLR->_bf = 0;
			}
		}

        最后更新结点的平衡因子,结果如代码和图片所示。可以看到整棵子树的高度前后都是h+2,没有影响祖先,不用向上更新平衡因子

        ④RL失衡——右左双旋 

 

        RL即为根>right->left-的形状。当出现RL情况的失衡时,平衡因子是2和-1的形式,需要右左双旋。

        关键结点依然是三个,parent是父结点也是bf=2的结点,subR是parent的左孩子,subRL则是subR的左孩子(图中隐含在左树当中)。第一次的右旋以subR为轴;第二次的左旋则以parent为轴。

        同样的,根据新结点位于左右树的位置不同,subRL的平衡因子也不尽相同,最后结果中的平衡因子也有三种情况:subRL平衡因子为-1(插入在subRL的左子树)、1(插入在subRL的右子树)、0(subRL自身)。上图已经给出了-1情况的解析,下面给出另外两种情况。

		//右左双旋
		void RotateRL(AVLNode* parent)
		{
			AVLNode* subR = parent->_right;
			AVLNode* subRL = parent->_right->_left;

			//因为双旋调用单旋的函数,会改变平衡因子。单旋考虑的仅是需要单旋情况的平衡因子改变,我们此处只需要单旋的旋转操作而不接受其平衡因子的改变
			//所以在之后会重新进行双旋操作的平衡因子修正,但是为了确定修正方案,并且单旋会改变平衡因子,所以需要提前记录可以作为标识的平衡因子作为分支语句的条件
			int bf = subRL->_bf;

			RotateR(subR);
			RotateL(parent);

			//平衡因子修正
			//①subRL的左子树插入新结点——subRL的平衡因子为-1
			if (bf == -1)
			{
				subR->_bf = 1;
				parent->_bf = subRL->_bf = 0;
			}
			//②subRL的右子树插入新结点——subRL的平衡因子为1
			else if (bf == 1)
			{
				subR->_bf = subRL->_bf = 0;
				parent->_bf = -1;
			}
			//③subRL自身是被插入的新结点——subRL的平衡因子为0
			else
			{
				parent->_bf = subR->_bf = subRL->_bf = 0;
			}
		}

        最后更新结点的平衡因子,结果如代码和图片所示。可以看到整棵子树的高度前后都是h+2,没有影响祖先,不用向上更新平衡因子

2.3.3 插入函数代码

        最后给出上述插入结点和调整平衡因子主函数的代码。其思路我们已经分析过,调用的旋转接口也一一解释,读者可根据详细的注释理解。

		//插入
	public:
		bool Insert(const pair<K, V>& kv)
		{
			//第一个结点特殊处理
			if (_root == nullptr)
			{
				_root = new AVLNode(kv);
				return true;
			}

			AVLNode* cur = _root;
			AVLNode* parent = nullptr;
			while (cur)
			{
				if (cur->_pair.first > kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_pair.first < kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
					return false;
			}
			cur = new AVLNode(kv);
			if (parent->_pair.first > kv.first)
				parent->_left = cur;
			else
				parent->_right = cur;
			cur->_parent = parent;
			
			//更新平衡因子
			while (parent)
			{
				//parent的平衡因子:在左插入时-1,右插入时+1
				if (cur == parent->_left)
					parent->_bf--;
				else
					parent->_bf++;

				//①parent的平衡因子0->±1,表示结点插入在左右任意一边,该子树实际上新增了一层,需要向上更新
				if (parent->_bf == 1 || parent->_bf == -1)
				{
					//因为插入结点使得子树变高了一层,所以祖先也可以根据左插入-1,右插入+1的规则进行平衡因子的修改,所以变量向上传递一层即可
					cur = parent;
					parent = parent->_parent;
				}
				//②parent的平衡因子±1->0,表示结点插入在空缺的一边,没有新增层数,不需要向上更新
				else if (parent->_bf == 0)
				{
					break;
				}
				//③parent的平衡因子±1->±2,表示结点插入在已经盈余的一边,不再满足AVL树的要求,需要旋转处理
				else
				{
					//旋转处理
					//左单旋:本就突出的右子树的右子树插入新结点,形成RR的形状破坏平衡
					//以subR为中心,subR的父结点parent为悬臂左旋,那么parent与其左子树将成为subR的左子树,而subR原先的左子树subRL将成为旋转下来的父结点的右子树
					//这种RR的情况下,parent的平衡因子为2,subR的平衡因子为1
					if (parent->_bf == 2 && cur->_bf == 1)
					{
						RotateL(parent);
					}
					//右单旋:本就突出的左子树的左子树插入新结点,形成LL的形状破坏平衡
					//以subL为中心,subL的父结点parent为悬臂右旋,那么parent与其右子树将成为subL的右子树,而subL原先的右子树subLR将成为旋转下来的父结点的左子树
					else if (parent->_bf == -2 && cur->_bf == -1)
					{
						RotateR(parent);
					}
					//左右双旋:本来突出的左子树的右子树插入新结点,形成LR的形状破坏平衡
					//对于这种情况,需要先左旋再右旋,左旋针对subL,完成后再对parent右旋
					else if (parent->_bf == -2 && cur->_bf == 1)
					{
						RotateLR(parent);
					}
					//右左双旋:本来突出的右子树的左子树插入新结点,形成RL的形状破坏平衡
					//对于这种情况,需要先右旋再左旋,右旋针对subR,完成后再对parent左旋
					else if(parent->_bf == 2 && cur->_bf == -1)
					{
						RotateRL(parent);
					}
					else
					{
						assert(0);
					}

					//因为旋转情况的判断与执行发生在插入新结点后的子树,在旋转后,该子树的高度与未插入结点时的高度一致,因此无需对子树的祖先进行平衡因子修正,可以直接结束循环
					break;
				}
			}
			return true;
		}

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

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

相关文章

【Godot4.3】多边形的斜线填充效果基础实现

概述 图案&#xff08;Pattern&#xff09;填充是一个非常常见的效果。其中又以斜线填充最为简单。本篇就探讨在Godot4.3中如何使用Geometry2D和CanvasItem的绘图函数实现斜线填充效果。 基础思路 Geometry2D类提供了多边形和多边形以及多边形与折线的布尔运算。按照自然的思…

Spring-@Bean的处理流程

Bean前置知识 1 需要再Configuration Class中才能被解析 2 静态Bean也就是标注在static方法上的 实例Bean标注在普通方法上的 所有的Bean在创建之前都会变成BeanDefinition,其中有这样两个属性&#xff1a; setFactoryMethodName&#xff1a;静态方法 setFactoryBeanName&…

【详解 Java 注解】

前言&#xff1a; 注解&#xff08;Annotation&#xff09;是Java中的一种特殊符号&#xff0c;用来为代码提供额外的信息。它不会改变程序的逻辑&#xff0c;只是用来给编译器或工具提供指示。例如&#xff0c;Override 表示一个方法是重写了父类的方法&#xff0c;Deprecated…

[论文笔记]LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale

引言 今天带来第一篇量化论文LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale笔记。 为了简单&#xff0c;下文中以翻译的口吻记录&#xff0c;比如替换"作者"为"我们"。 大语言模型已被广泛采用&#xff0c;但推理时需要大量的GPU内…

GPU 带宽功耗优化

移动端GPU 的内存结构&#xff1a; 先简述移动端内存cache结构&#xff1b;上图的UMA结构 on-Chip memory 包括了 L1、L2 cache&#xff0c;非常关键的移动端的 Tiles 也是保存在 on-chip上还包括寄存器文件&#xff1a;提供给每个核心使用的极高速存储。 共享内存&#xff08…

【C/C++】web服务器项目开发总结【请求 | 响应 | CGI】

博客主页&#xff1a;花果山~程序猿-CSDN博客 文章分栏&#xff1a;Linux_花果山~程序猿的博客-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能吧&#xff01;让我们一起努力&#xff0c;一起成长&#xff01; 目录 一&#xff0c;背景 二&…

机器学习(西瓜书)第 4 章 决策树

4.1 决策树基本流程 决策树模型 基本流程 在第⑵种情形下&#xff0c;我们把当前结点标记为叶结点&#xff0c;并将其类别设定为该结点所含样本最多的类别&#xff1b;在第⑶种情形下&#xff0c;同样把当前结点标记为叶结点&#xff0c;但将其类别设定为其父结点所含样本最多…

VMware时提示系统尚未修改安装失败

安装VMware安装失败&#xff0c;提示系统尚未修改 有以下解决方案&#xff1a; 1.操作系统不兼容 2.安装文件损坏 3.安装程序错误 4.硬件问题 解决&#xff1a;由于重装系统前&#xff0c;安装过VAware&#xff0c;所以应该操作系统&#xff0c;硬件没有问题。下载一个软件v…

多线程篇(阻塞队列- ArrayBlockingQueue)(持续更新迭代)

目录 一、源码分析 1. 先看个关系图 2. 构造方法 3. 核心属性 4. 核心功能 入队&#xff08;放入数据&#xff09; 出队&#xff08;取出数据&#xff09; 5. 总结 一、源码分析 1. 先看个关系图 PS&#xff1a;先看个关系图 ArrayBlockingQueue是最典型的有界阻塞队…

CSDN文章无水印转成PDF

文章目录 一、打开检查二、点击进入控制台三、在控制台中输入代码 一、打开检查 f11或者右键打开检查 二、点击进入控制台 三、在控制台中输入代码 (function(){ use strict;var articleBox $("div.article_content");articleBox.removeAttr("style&quo…

sping boot 基于 RESTful 风格,模拟增删改查操作

RESTful -> 增&#xff1a;post 删&#xff1a;delete 改: put 查: get RESTful 资源路径&#xff0c;一般以 s 复数结尾 以下是代码示例&#xff1a; package com.example.springboot.controller;import org.springframework.web.bind.annotation.*;RestControll…

EmguCV学习笔记 C# 9.3 移动检测类

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

汽车网络安全的未来:将车辆视为端点

汽车行业面临着许多与其他行业的成功企业相同的网络安全风险和威胁&#xff0c;但它也在应对一些独特的风险和威胁。 Nuspire 的首席威胁分析师 Josh Smith&#xff08;一家在汽车领域有着深厚根基并保护通用汽车和斯巴鲁等客户的托管安全服务提供商&#xff09;谈到了当前的风…

【AcWing】852. spfa判断负环

#include<iostream> #include<algorithm> #include<cstring> #include<queue> using namespace std;const int N 1e510;int n,m; int h[N],w[N],e[N],ne[N],idx; int dist[N],cnt[N];//cnt存最短路径的边数 bool st[N];void add(int a,int b,int c){e[…

一文讲懂扩散模型

一文讲懂扩散模型 扩散模型&#xff08;Diffusion Models, DM&#xff09;是近年来在计算机视觉、自然语言处理等领域取得显著进展的一种生成模型。其思想根源可以追溯到非平衡热力学&#xff0c;通过模拟数据的扩散和去噪过程来生成新的样本。以下将详细阐述扩散模型的基本原理…

发动机制造5G智能工厂工业物联数字孪生平台,推进制造业数字化转型

发动机制造作为高端制造业的核心领域之一&#xff0c;正积极探索并引领这一变革。其中&#xff0c;发动机制造5G智能工厂物联数字孪生平台的兴起&#xff0c;不仅为发动机制造业注入了新的活力&#xff0c;也为整个制造业的数字化转型树立了新的标杆。发动机制造5G智能工厂物联…

Linux Centos 7网络配置

本步骤基于Centos 7&#xff0c;使用的虚拟机是VMware Workstation Pro&#xff0c;最终可实现虚拟机与外网互通。如为其他发行版本的linux&#xff0c;可能会有差异。 1、检查外网访问状态 ping www.baidu.com 2、查看网卡配置信息 ip addr 3、配置网卡 cd /etc/sysconfig…

致远个性化之--发起流程页面,去掉【查看流程】按钮

需求 近期在做的项目中&#xff0c;遇到一个需求&#xff0c;想把发起流程页面中的【查看流程】按钮去掉&#xff0c;只让员工预测流程&#xff0c;知道自己的事项流程走向&#xff0c;不让看全局流程图。包含PC端和移动端&#xff0c;以及微协同端。 如下图效果示例&#xff1…

SVN下载安装使用方法

目录 &#x1f315;SVN是什么&#xff1f;&#x1f319;SVN跟Git比的优势&#x1f319;SVN的用处 &#x1f315;下载安装使用方法 &#x1f315;&#x1f319;⭐ &#x1f315;SVN是什么&#xff1f; 代码版本管理工具 它能记住你每次的修改 查看所有的修改记录 恢复到任何历…

如何读.Net Framework 的源码?

.Net Framework的源码可以从这里下载 Download 也可以在线直接浏览 https://referencesource.microsoft.com 这里我们以System.IO.Directory.CreateDirectory函数为例&#xff0c;来说明如何去读.Net Framework的源码。 在ReferenceSource在线界面的搜索框里输入Directory.Cr…