数据结构进阶 AVL树

news2025/4/4 17:06:44

作者:@小萌新
专栏:@数据结构进阶
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:介绍高阶数据结构:AVL树
在这里插入图片描述

AVL树

  • AVL树的概念
  • AVL树节点类的定义
  • AVL树的插入
  • AVL树的旋转
    • 左单旋
    • 右单旋
    • 左右双旋
    • 右左单旋
  • AVL树的验证
  • AVL树的查找
  • AVL树的修改
  • AVL树的删除
  • AVL树的性能

AVL树的概念

首先抛出一个问题 为什么AVL树会诞生呢?

还记不记得我们在学习二叉搜索树最后总结的时候说的一句话

在二叉搜索树是完全二叉树的情况下效率最高 此时的效率是 LogN

在二叉搜索时是单边树的情况下效率最差 此时效率退化至 N

为了解决这个退化的效率问题 AVL树诞生了

AVL树是由两位俄国科学家G.M. Adelson-Velsky和E.M.Landis在1962年发明的。他们在一篇论文中提出了AVL树这一概念,并将其命名为"AVL树",以纪念他们的姓氏的首字母。
AVL树是一种自平衡二叉搜索树,它能够保证插入、删除和查找操作的时间复杂度都是O(log n),因此在很多场合下都很有用。

AVL树可以是一颗空树 也可以是具有以下性质的一颗二叉搜索树

  1. 它的左右子树都是AVL树
  2. 树的左右子树高度差不超过一 (以下样例均为右子树减左子树)

在这里插入图片描述
上面就是一棵AVL树

蓝色数字表示的是左右子树的高度差值(以下简称平衡因子) 它们都是不超过1的

红色的数字表示储存数据的值 符合二叉搜索树的性质

我们可以发现 当一颗二叉搜索树是AVL树的时候 假设它一共有N个节点 那么它的高度非常接近LogN 同样的搜索效率也接近LogN

这也就解决了单边树的低效问题

AVL树节点类的定义

我们这里使用KV模型来实现AVL树

为了方便后续的操作 使用三叉链结构来完成它

在这里插入图片描述

一个节点除了指向它的左右两个子节点之外还指向它的父节点 一共三个指针 所以称为三叉链

此外 我们还增加一个int类型的数据 来记录平衡因子

代码表示如下

template<class K, class V>
struct AVLTreeNode
{
	// 三叉连
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	// 平衡因子
	int _bf;

	// 储存的键值对
	pair<K, V> _kv;


	// 构造函数
	AVLTreeNode(const pair<K , V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0) //因为构造出来之后没有左右子树 所以说平衡因子为0
	{}

};

AVL树的插入

AVL树的插入满足下面几个步骤

  1. 遵循二叉搜索树的插入原则 找到待插入位置
  2. 找到插入位置之后 插入节点
  3. 更新平衡因子 如果平衡因子不满足AVL树的条件 则使之满足

第一步第二步很简单 如果还有不明白的同学可以参考这篇博客

二叉搜索树的实现

接下来我们重点学习第三步

由于我们插入一个节点之后 只有可能是这个节点的父节点的高度受到影响

所以说我们只需要更新它的祖先节点 这也是我们为什么设计成三叉链结构的原因 - 方便找祖先节点

在这里插入图片描述
(这个图稍微有点漏洞 我们这里假设新增的节点是6.5 前面的节点都是浮点数)

新增节点之后我们开始更新平衡因子 遵循下面的规则

  1. 如果新增节点在父节点的右边 则平衡因子++
  2. 如果新增节点在父节点的左边 则平衡因子–

在这里插入图片描述
这里我们可以发现 如果有一个父节点的平衡因子更新成了0 则无需向上继续更新了

怎么解释这个现象呢?

我们都知道 平衡因子是根据父节点左右子树的高度来确定的

如果父节点的平衡因子被更新成了0 那么肯定就说明 原来有一边的高度是大于另一边的 而且是大于1

插入一个新的节点之后两边的高度差就被填平了

那么对于父节点的父节点来说 这边子树的高度并没有变化 所以说我们不需要向上更新了

在这里插入图片描述
所以说我们有具体的规则如下

  • 如果父节点的平衡因子更新之后结果为1或者-1 则需要继续向上更新祖先节点的平衡因子
  • 如果父节点的平衡因子更新之后结果为0 则不需要继续向上更新祖先节点的平衡因子
  • 如果父节点的平衡因子更新之后结果为2或者-2 则需要进行旋转操作来处理异常

我们假设目前的节点是cur 它的父节点是parent

因为我们执行的是插入操作 所以说我们的节点肯定是增加的 就看增加的是父节点的左子树还是右子树

因为我们不能确定是左子树还是右子树 所以要执行判断

  • 如果cur是parent的左节点 则平衡因子–
  • 如果cur是parent的右节点 则平衡因子++

但是到这里还没完 我们要需要继续向上更新 执行下面的代码

cur = parent;
parent = parent->_parent;

最差的情况下 我们一直要更新到根节点

按照上面的规则 我们写出代码

	bool insert(const pair <K,V>& kv)
	{
		if (_root == nullptr) // 如果AVL树为空 则直接为其根节点
		{
			_root = new Node(kv);
			return true;
		}

		// 如果不为空 则按照二叉树的插入方式 通过key值 即first来比较
		Node* cur = _root; // cur确定当前位置
		Node* parent = nullptr; // parent最后连接用 
		while (cur) // 当cur不为空的时候一直走
		{
			if (kv.first > cur->_kv.first) // 如果插入值大于当前节点 向右移
			{
				cur = cur->_right;
				parent = cur;
			}
			else if (kv.first < cur->_kv.first) // 如果插入值小于当前节点 向右移
			{
				cur = cur->_left;
				parent = cur;
			}
			else // 到这里这种情况说明等于 则插入失败 返回false
			{
				return false;
			}
		}

		// 将待插入节点插入到树中
		cur = new Node(kv);
		// 根据kv的key值来分辨插入的是左子树还是右子树
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent; // 因为是三叉链 所以要更新父节点
		}
		else // 因为走到这里了kv的key值不可能相等 所以说else就表示小于
		{
			parent->_left = cur;
			cur->_parent = parent; // 因为是三叉链 所以要更新父节点
		}


		// 更新平衡因子
		while (cur != _root) // 最坏的情况 一直更新到根节点 但是cur不可能到根节点
		{
			// 如果是右则平衡因子++ 反之则--
			if (cur = parent->_left)
			{
				parent->_bf--;
			}
			else if (cur = parent->_right)
			{
				parent->_bf++;
			}

			// 平衡因子更新完毕 此时开始查看父亲节点的平衡因子是多少 是否结束 是否需要旋转
			if (parent->_bf == 0) // 如果父亲的平衡因子是0 则停止更新 直接返回
			{
				return true;
			}
			else if (parent->_bf == 1 || parent->_bf == -1) // 如果父亲的平衡因子是1或-1 则继续往上更新
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2) // 如果父亲的平衡因子是2或-2 则进行旋转操作
			{
				// 旋转
			}


		}
	}

省略了旋转操作的插入代码就在上面 接下来我们重点研究下AVL的旋转操作

AVL树的旋转

首先我们要明确一点 当parent节点的平衡因子是2或者-2时候 cur的平衡因子必然是1或者-1

因为如果cur的平衡因子是0 有两种情况

  1. cur为新增节点 这个时候由于我们插入的是AVL树 所以其父节点只有可能是1 -1 或者0
  2. cur为更新后的节点 此时cur为其子节点的父节点 其平衡因子更新到了0 不会继续向上更新 所以cur的父节点一定是有正常的平衡因子 1 -1 或者0

所以说我们这里就有四种情况

  • parent的平衡因子是2 cur的平衡因子是1
  • parent的平衡因子是2 cur的平衡因子是-1
  • parent的平衡因子是-2 cur的平衡因子是1
  • parent的平衡因子是-2 cur的平衡因子是-1

然后我们根据这四种情况来讨论如何旋转

左单旋

在这里插入图片描述
从上图我们可以观察发现 parent的平衡因子被更新为了2 此时肯定要触发旋转

旋转后的图片大概是这样子

在这里插入图片描述

经过旋转后 平衡因子全部恢复正常 并且此时将这颗二叉树作为一个整体 它的高度还是h+2

所以不用继续往上更新平衡因子了

那么我们具体来看看实现左单旋要分几步

  1. 让 subr 左子树 subrl 成为 parent 的右子树
  2. 让 parent 成为 subr 的左子树
  3. 让 subr 作为整个子树的根 (如果parent不是根节点的话)
  4. 更新平衡因子

此时 subr 和 parent 的平衡因子都更新为0

而由于二叉搜索树的性质 所有的节点是从小到大排列的 我们这个旋转并没有改变这个性质 所以说这个旋转是完全合法的

此外 观察第一张图我们可以知道

左单旋对应着 parent的平衡因子是2 cur的平衡因子是1 这种情况

我们左单旋的代码表示如下

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* grandparent = parent->_parent;


		// 建立subR和parent的联系
		subR->_left = parent;
		parent->_parent = subR;

		// 建立parent和subRL的联系
		parent->_right = subRL;
		if (subRL) // 有可能subRL为空
		{
			subRL->_parent = parent;
		}

		// 建立grandparent和sunR的联系
		// 这里要分grandparent是否存在以及parent是grandparent的左还是右
		if (grandparent) // 如果进去了 则说明grandparent存在 
		{
			// 如果存在 要分清楚parent在左边还是右边
			if (grandparent->_left == parent)
			{
				grandparent->_left = subR;
			}
			else
			{
				grandparent->_right = subR;
			}
			subR->_parent = grandparent;
		}
		// 否则就是不存在 我们更新_root 并且将subR的父节点置空
		else
		{
			_root = subR;
			subR->_parent = nullptr;
		}


		// 更新平衡因子
		subR->_bf = 0;
		parent->_bf = 0;
	}

只要按照上面的步骤一行行写 应该是不难理解的

这里尤其要注意的是 我们使用的是不熟悉的三叉链结构

右单旋

在这里插入图片描述
从上图我们可以观察发现 parent的平衡因子被更新为了-2 此时肯定要触发旋转

旋转后的图片大概是这样子

在这里插入图片描述
经过旋转后 平衡因子全部恢复正常 并且此时将这颗二叉树作为一个整体 它的高度还是h+2

所以不用继续往上更新平衡因子了

那么我们具体来看看实现右单旋要分几步

  1. 让 subl 的右子树 sublr 成为 parent 的左子树
  2. 让 parent 成为 subl 的左子树
  3. 让 subr 作为整个子树的根 (如果parent不是根节点的话)
  4. 更新平衡因子

此时 subl 和 parent 的平衡因子都更新为0

而由于二叉搜索树的性质 所有的节点是从小到大排列的 我们这个旋转并没有改变这个性质 所以说这个旋转是完全合法的

此外 观察第一张图我们可以知道

右单旋对应着 parent 的平衡因子是-2 cur 的平衡因子是-1 这种情况

我们右单旋的代码表示如下

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* grandparent = parent->_parent;


		// 建立subL和parent之间的联系
		subL->_right = parent;
		parent->_parent = subL;

		// 建立parent和subLR之间的联系 
		parent->_left = subLR;
		if (subLR) // subLR可能不存在 
		{
			subLR->_parent = parent;
		}

		// 建立grandparent和subl之间的联系
		// 这里要分grandparent是否存在以及parent是grandparent的左还是右
		if (grandparent)
		{
			// 如果存在 要分清楚parent在左边还是右边
			if (grandparent->_left == parent)
			{
				grandparent->_left = subL;
			}
			else
			{
				grandparent->_right = subL;
			}

			subL->_parent = grandparent;
		}
		else // 这里就是表示parent就是根节点
		{
			_root = subL;
			subL->_parent = nullptr;
		}


		// 更新平衡因子
		subL->_bf = 0;
		parent->_bf = 0;
	}

左右双旋

在这里插入图片描述

左右双旋一般分三步走 图片表示如下

第一步 插入新增节点 触发旋转机制

在这里插入图片描述

第二步 以30为旋转点进行左单旋

在这里插入图片描述
第三步 以90为旋转点进行右单旋

在这里插入图片描述
左右双旋的步骤如下

  1. 以subL为旋转点进行左单旋
  2. 以parent为旋转点进行右单旋
  3. 更新平衡因子

而由于二叉搜索树的性质 所有的节点是从小到大排列的 我们这个旋转并没有改变这个性质 所以说这个旋转是完全合法的

此外 观察第二张图我们可以知道

左右双旋对应着 parent 的平衡因子是-2 cur 的平衡因子是1 这种情况

左右双旋后 平衡因子的更新随着subLR原始平衡因子的不同分为以下三种情况

  1. 当sublr的平衡因子是-1时 左右双旋后parent subl sublr的平衡因子分别更新为 1 0 0
  2. 当sublr的平衡因子是 1时 左右双旋后parent subl sublr的平衡因子分别更新为 0 -1 0
  3. 当sublr的平衡因子是 0时 左右双旋后parent subl sublr的平衡因子分别更新为 0 0 0

产生这三种情况的原因分别是

插入的节点可能是sublr 的左子树 右子树 或者是sublr本身

代码表示如下

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = parent->_left->_right;
		int bf = subLR->_bf;

		// 以subL作为旋转点左单旋
		RotateL(subL);


		// 以parent作为旋转点右单旋
		RotateR(parent);

		// 平衡因子
		if (bf == 1)
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 0)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false); 
		}
	}

右左单旋

在这里插入图片描述

右左双旋一般分三步走 图片表示如下

第一步 插入新增节点 触发旋转机制

在这里插入图片描述

第二步 以90作为节点进行右单旋

在这里插入图片描述

第三步 以30作为节点进行左单旋

在这里插入图片描述

右左双旋的步骤如下

  1. 以subR为旋转点进行右单旋
  2. 以parent为旋转点进行左单旋
  3. 更新平衡因子

而由于二叉搜索树的性质 所有的节点是从小到大排列的 我们这个旋转并没有改变这个性质 所以说这个旋转是完全合法的

此外 观察第二张图我们可以知道

右左双旋对应着 parent 的平衡因子是2 cur 的平衡因子是-1 这种情况

右左双旋后 平衡因子的更新随着subLR原始平衡因子的不同分为以下三种情况

  1. 当subrl的平衡因子是-1时 左右双旋后parent subr subrl的平衡因子分别更新为 1 0 0
  2. 当subrl的平衡因子是-1时 左右双旋后parent subr subrl的平衡因子分别更新为 0 -1 0
  3. 当subrl的平衡因子是-1时 左右双旋后parent subr subrl的平衡因子分别更新为 0 0 0

产生这三种情况的原因分别是

插入的节点可能是subrl 的左子树 右子树 或者是subrl本身

代码表示如下

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		//  以subR为轴进行右单旋
		RotateR(subR);

		//  以parent为轴进行左单旋
		RotateL(parent);

		//  更新平衡因子
		if (bf == 1)
		{
			subRL->_bf = 0;
			parent->_bf = -1;
			subR->_bf = 0;
		}
		else if (bf == -1)
		{
			subRL->_bf = 0;
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 0)
		{
			subRL->_bf = 0;
			parent->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false); 
		}
	}

AVL树的验证

首先AVL树是一颗二叉搜索树 所以说它满足中序遍历后是有序的这个原则 我们可以使用中序遍历来验证

代码表示如下

	void _Inorder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}


		_Inorder(root->_left);
		cout << root->_kv.first << " ";
		_Inorder(root->_right);
	}

	void Inorder()
	{
		_Inorder(_root);
	}

但是中序遍历只能证明这棵树是二叉搜索树 要证明它是AVL树我们必须还要证明它的平衡因子全部无异常

我们可以从根节点开始 往上遍历每个子树来判断它是否是AVL树 如果全部是AVL树 则可以验证

如果有一颗不是 则证明不是AVL树

//判断是否为AVL树
bool IsAVLTree()
{
	int hight = 0; //输出型参数
	return _IsBalanced(_root, hight);
}
//检测二叉树是否平衡
bool _IsBalanced(Node* root, int& hight)
{
	if (root == nullptr) //空树是平衡二叉树
	{
		hight = 0; //空树的高度为0
		return true;
	}
	//先判断左子树
	int leftHight = 0;
	if (_IsBalanced(root->_left, leftHight) == false)
		return false;
	//再判断右子树
	int rightHight = 0;
	if (_IsBalanced(root->_right, rightHight) == false)
		return false;
	//检查该结点的平衡因子
	if (rightHight - leftHight != root->_bf)
	{
		cout << "平衡因子设置异常:" << root->_kv.first << endl;
	}
	//把左右子树的高度中的较大值+1作为当前树的高度返回给上一层
	hight = max(leftHight, rightHight) + 1;
	return abs(rightHight - leftHight) < 2; //平衡二叉树的条件
}

AVL树的查找

AVL树的查找方式与二叉搜索树相同

  1. 如果树为空树 则查找失败 返回nullptr
  2. 如果key值小于当前节点 则进入当前节点的子树查找
  3. 如果key值大于当前节点 则进入当前节点的子树查找
  4. 如果key值等于当前节点 则返回当前节点

代码表示如下

	Node* find(const K& key)
	{
		Node* cur = _root;

		while (cur) // 一直查找到空为止
		{
			if (key < cur->_kv.first) // 小于
			{
				cur = cur->_left;
			}
			else if (key < cur->_kv.first) // 大于
			{
				cur = cur->_right;
			}
			else // 等于
			{
				return cur
			}
		}
		// 走到这里就是没找到
		return nullptr;
	}

AVL树的修改

我们所说的修改一般是值对于value值的修改

一般步骤如下

  1. 找到对应的key值的结点
  2. 对其value值进行修改

代码表示如下

	bool Modify(const K& key, const V& value)
	{
		Node* ret = find(key);
		// 有可能不存在
		if (ret == nullptr)
		{
			return false;
		}
		ret->_kv.second = value;
		return true;
	}

AVL树的删除

AVL树的删除满足下面几个步骤

  1. 遵循二叉搜索树的删除原则 找到待删除位置
  2. 找到删除位置之后 删除节点
  3. 更新平衡因子 如果平衡因子不满足AVL树的条件 则使之满足

由于我们学习AVL树主要是为了后面的红黑树做铺垫 需要重点掌握的是AVL树的插入

并且AVL树的删除在平时的笔试面试中考察的很少 或者说几乎没有考察 所以同学们可以跳过这一部分的学习

AVL树的性能

AVL树是一颗近乎完全二叉树的二叉搜索树 所以说它查询的时间复杂度非常低 为Log N

但是如果要对于AVL树做一些结构修改的操作 它的性能就会十分低下 比如在插入和删除时 可能要经过大量的旋转操作

因此 如果是需要一种查询高效的数据结构 AVL树是十分合适的
但是 如果它经常要被修改 AVL树的性能就可能不太匹配了

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

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

相关文章

多线程适用接口及常见类

日升时奋斗&#xff0c;日落时自省 目录 1、Callable接口 1.1、Callable方式 1.2、非Callable方式 2、JUC&#xff08;java.util.concurrent&#xff09;的常见类 2.1、ReentrantLock 2.2、信号量Semaphore 2.3、CountDownLatch 3、线程安全的集合类 3.1、多线程使用A…

【SpringMVC】使用SpringMVC处理JSON格式的数据

目录 一、前言 二、ResponseBody 三、RequestBody 四、HttpMessageConverter 相关文章&#xff08;可以关注我的SpringMVC专栏&#xff09; SpingMVC专栏SpingMVC专栏一、前言我们在使用Servlet处理前端请求&#xff0c;使用Json格式的数据&#xff0c;通常引入外部提供的一些…

拉伯证券|利好来了,145万手封涨停!低位+低市值“无主”股揭秘

二三四五或将完毕多年“无主”局势。 周末大消息不断。蚂蚁集团1月7日在官网发布关于持续完善公司管理的公告。公告显现&#xff0c;蚂蚁集团调整首要股东投票权&#xff0c;强化与阿里巴巴集团的隔离&#xff0c;阿里巴巴开创人马云抛弃了对蚂蚁集团的实践操控权&#xff0c;蚂…

JavaScript 原型链

文章目录原型链本质 - 对象间的关联关系instanceof 和 isPrototypeOf__proto__的大致实现委托原型链 原型链就是一系列对象的链接。通常来说&#xff0c;这个链接的作用是&#xff1a;如果在对象上没有找到需要的属性或者方法引用&#xff0c;引擎就会继续在[[Prototype]]关联的…

餐饮业数字化提速,OceanBase助海底捞变身“云上捞”

在海底捞火锅智慧餐厅&#xff0c;你会看到忙得团团转的机械臂和传菜机器人&#xff0c;顾客在智能点餐系统上下单&#xff0c;机械臂和传菜机器人就会着手备菜、传菜、上菜&#xff0c;服务人员则有更多时间专心应答顾客的各种询问。这种新模式&#xff0c;也为海底捞家喻户晓…

Springboot新手开发 基本总结

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final&#xff0c;一名热爱技术的在校学生。 &#x1f4dd;个人主页&#xff1a;个人主页1 || 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;后端专栏 &#x1f4e7;如果文章知识点有错误的地方&#xff0c;…

网络实验之HSRP协议

一、HSRP协议简介 HSRP&#xff1a;热备份路由器协议&#xff08;HSRP&#xff1a;Hot Standby Router Protocol&#xff09;&#xff0c;是cisco平台一种特有的技术&#xff0c;是cisco的私有协议。该协议中含有多台路由器&#xff0c;对应一个HSRP组。该组中只有一个路由器承…

mac自定义环境变量,mac自定义python变量,自定义通用变量(任意名字)

文章目录几个常用的命令工作原理知道原理后已经有了环境变量没有该环境变量几个常用的命令 功能命令查看当前环境变量echo $PATH编辑文件&#xff08;注&#xff1a;这里是直接编辑bash_profile&#xff09;vi ~/.bash_profile编辑i退出编辑esc回到命令&#xff08;用命令来保…

Apollo实现cron语句的热配置

GitHub项目地址 Gitee项目地址 Apollo&#xff08;阿波罗&#xff09;是携程框架部门研发的分布式配置中心&#xff0c;能够集中化管理应用不同环境、不同集群的配置&#xff0c;配置修改后能够实时推送到应用端&#xff0c;并且具备规范的权限、流程治理等特性&#xff0c;适…

stl algorithms 算法

所有泛型算法&#xff0c;除了少数例外&#xff0c;前两个参数均为一组iterator&#xff0c;用来标识欲遍历容器元素的范围&#xff0c;范围从第一个iterator所指位置开始&#xff0c;至第二个iterator所指位置&#xff08;并不包括&#xff09;结束 如 int arr[3]{1,2,3} ve…

管理客户信息并非易事

客户信息是企业的重要资产&#xff0c;是企业日积累月的价值沉淀&#xff0c;管理客户信息对于企业来说并不是一件容易的事&#xff0c;只有妥善管理客户信息&#xff0c;才能为企业创造更多价值。前言众所周知&#xff0c;客户信息是企业的重要资产&#xff0c;是企业日积累月…

rtu遥测终端机应用及安装介绍

1、设备介绍 设备集遥测终端机功能和视频录像机功能为一体&#xff0c;融合先进的3G/4G/WIFI通信技术、实现水文/水资源/环保212/TCP Modbus/MQTT等数据的采集、视频、图像存储、显示、控制、报警及传输等智能值守功能。 2、设备应用方向 本设备可广泛适用于带视频监测的水…

Jenkins 构建过程中提示 GPG 错误

错误信息如下&#xff1a;[INFO] --- maven-gpg-plugin:3.0.1:sign (sign-artifacts) rets-io --- gpg: no default secret key: No secret keygpg: signing failed: No secret key这个问题的原因应该是我们最近把我们的项目发布到中央 Maven 仓库中&#xff0c;但是发布项目到…

社区发现系列05:图的构建

想要挖掘作弊团伙首先先要构建社交网络图&#xff0c;然后用算法或者策略挖掘作弊团伙&#xff0c;那么如何构建社交网络图呢&#xff1f;下面给大家介绍一些实战经验&#xff0c;主要从电商和互金小额贷款两个业务场景来说&#xff1a; 1、电商业务 由于电商业务涉及的业务场…

《Spring揭秘》读书笔记1:IoC和AOP

1 Spring框架的由来 Spring框架的本质&#xff1a;提供各种服务&#xff0c;以帮助我们简化基于POJO的Java应用程序开发。 各种服务实现被划分到了多个相互独立却又相互依赖的模块当中&#xff1a; Core核心模块&#xff1a;IoC容器、Framework工具类。 AOP模块&#xff1a;S…

万物皆可灵活用工?灵活用工模式最契合的行业是哪些?

灵活用工VS传统用工 1&#xff09;传统用工 企业与员工签订劳动合同&#xff0c;双方出现问题纠纷适用于劳动法&#xff0c;关系固定&#xff0c;企业责任大。养着你&#xff0c;难&#xff0c;辞了你&#xff0c;更难。企业成本相对较高、负担较重&#xff08;薪资、福利&am…

Hudi的核心概念 —— 表类型(Table Types)

文章目录Copy On WriteMerge On ReadCOW 与 MOR 的对比查询类型&#xff08;Query Types&#xff09;Snapshot QueriesIncremental QueriesRead Optimized Queries不同表支持的查询类型Copy On Write 在 COW 表中&#xff0c;只有数据文件/基本文件&#xff08;.parquet&#…

【Java寒假打卡】Java基础-集合HashSet

【Java寒假打卡】Java基础-集合Set概述哈希值hashSet原理HashSet存储学生对象并遍历小结概述 底层数据结构是哈希表不能保证存储和去除的顺序完全一致没有带索引的方法&#xff0c;所以不能使用普通的for循环进行遍历 使用增强for循环或者迭代器进行遍历由于是Set集合&#xf…

广告业务系统 之 承前启后 —— “消息中心”

文章目录广告业务系统 之 承前启后 —— “消息中心”消息中心物料同步链路图模块设计之 “一分为二”模块交互图之 “强一致性设计”奇怪交互图的数据链路数据一致性问题日志中心广告业务系统 之 承前启后 —— “消息中心” 消息中心 消息中心&#xff0c;是为 投放引擎 做…

韩顺平老师的Linux基础学习笔记 (上)

Linux学习笔记 前言&#xff1a;本系列笔记的参考由 2021韩顺平 一周学会Linux 总结而成&#xff0c;希望能给学习Linux的同学一些帮助。也感谢韩老师录制的视频给我带来了非常巨大的收获&#xff01; 目录&#xff1a; 韩顺平老师的Linux基础学习笔记 (上)韩顺平老师的Linu…