AVL树 ---(C++)

news2024/11/26 9:58:26

        本篇讲全面的讲解 AVL 树的插入,旋转以及验证 AVL 树的性能(本篇未实现删除代码)。至于为什么会有 AVL 树,这是因为简单的二叉搜索树并不能直接的保证搜索的效率,因为当我们在二叉搜索树中插入一段有序的序列的时候,二叉搜索树就会退化为单枝树,这个时候进行搜索的时候,时间复杂度就变为了 O(n^2),如下:

        但是通过 AVL 树的旋转就可以很好的解决这个问题,使树近似等于完全二叉树或者满二叉树。

AVL 树代码

        先给出代码,接着在下文中给出解释:

#pragma once
#include <iostream>
#include <assert.h>

using namespace std;

template <class K, class V>
struct AVLTreeNode {
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _balanceFactor;

	AVLTreeNode(const pair<K, V>& kv)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _balanceFactor(0)
	{}
};

template <class K, class V>
class AVLTree {
public:
	typedef AVLTreeNode<K, V> Node;
	Node* find(const K& key) {
		Node* cur = _root;
		while (cur) {
			if (cur->_kv.first < key)
				cur = cur->_right;
			else if (cur->_kv.first > key)
				cur = cur->_left;
			else
				return cur;
		}
		return nullptr;
	}

	// 插入删除查找遍历
	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.first > kv.first)
				parent = cur, cur = cur->_left;
			else
				return false;
		}
		// cur == nullptr
		cur = new Node(kv);
		//if (parent->_left == cur)
		//	parent->_left = cur;
		//else
		//	parent->_right = cur;
		if (parent->_kv.first > kv.first)
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;
		// 需要更新平衡因子
		// 如果是在父亲的左边,父亲的平衡因子减一、右边加一
		if (parent->_left == cur)
			parent->_balanceFactor--;
		else
			parent->_balanceFactor++;
		// 查看爷爷结点是否需要更新

		while (parent) {
			if (parent->_balanceFactor == 0) {
				break;
			}
			else if (parent->_balanceFactor == 1 || parent->_balanceFactor == -1) {
				if (parent == _root)
					break;
				// 现在的parent就不可能等于null
				parent = parent->_parent;
				cur = cur->_parent;
				if (parent->_left == cur)
					parent->_balanceFactor--;
				else
					parent->_balanceFactor++;
			}
			else if(parent->_balanceFactor == 2 || parent->_balanceFactor == -2) {
				if (parent->_balanceFactor == 2 && cur->_balanceFactor == 1)
					RotateLeft(parent);
				else if (parent->_balanceFactor == -2 && cur->_balanceFactor == -1)
					RotateRight(parent);
				else if (parent->_balanceFactor == -2 && cur->_balanceFactor == 1)
					RotateLeftRight(parent);
				else if (parent->_balanceFactor == 2 && cur->_balanceFactor == -1)
					RotateRightLeft(parent);
				else
					assert(false);
				break;
			}
			else {
				assert(false);
			}
		}

		return true;
	}

	void RotateRight(Node* parent) {
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		// 将左孩子的右节点链接到原父亲结点
		if (subLR) subLR->_parent = parent;
		parent->_left = subLR;
		
		Node* ppNode = parent->_parent;
		// 将左孩子变为原父亲结点的父亲
		subL->_right = parent;
		parent->_parent = subL;
		// 将爷爷结点重新链接
		if (ppNode == nullptr) {
			_root = subL;
			_root->_parent = nullptr;
		}
		else {
			if (ppNode->_left == parent)
				ppNode->_left = subL;
			else
				ppNode->_right = subL;
			subL->_parent = ppNode;
		}
		subL->_balanceFactor = parent->_balanceFactor = 0;
	}

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

		parent->_right = subRL;
		if (subRL) subRL->_parent = parent;
		
		subR->_left = parent;
		parent->_parent = subR;

		if (ppNode == nullptr) {
			_root = subR;
			_root->_parent = nullptr;
		}
		else {
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;
			subR->_parent = ppNode;
		}
		subR->_balanceFactor = parent->_balanceFactor = 0;
	}

	void RotateRightLeft(Node* parent) {
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int balanceFactor = subRL->_balanceFactor;
		RotateRight(subR);
		RotateLeft(parent);
		// 更新平衡因子
		subRL->_balanceFactor = 0;
		if (balanceFactor == -1) {
			parent->_balanceFactor = 0;
			subR->_balanceFactor = 1;
		}
		else if (balanceFactor == 1) {
			parent->_balanceFactor = -1;
			subR->_balanceFactor = 0;
		}
		else if (balanceFactor == 0) {
			parent->_balanceFactor = 0;
			subR->_balanceFactor = 0;
		}
		else {
			assert(false);
		}
	}

	void RotateLeftRight(Node* parent) {
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int balanceFactor = subLR->_balanceFactor;
		// 先左旋后右旋
		RotateLeft(subL);
		RotateRight(parent);

		subLR->_balanceFactor = 0;
		if (balanceFactor == -1) {
			subL->_balanceFactor = 0;
			parent->_balanceFactor = 1;
		}
		else if (balanceFactor == 1) {
			parent->_balanceFactor = 0;
			subL->_balanceFactor = -1;
		}
		else if (balanceFactor == 0) {
			parent->_balanceFactor = 0;
			subL->_balanceFactor = 0;
		}
		else {
			assert(false);
		}
	}

	void InOrder() {
		_InOrder(_root);
		cout << endl;
	}

	int height() {
		int h = _height(_root);
		return h;
	}

	int size() {
		int s = _size(_root);
		return s;
	}

	bool IsBalance() {
		return _IsBalance(_root);
	}

private:
	bool _IsBalance(Node* root) {
		if (root == nullptr)
			return true;
		int leftHeight = _height(root->_left);
		int rightHeight = _height(root->_right);
		if (abs(leftHeight - rightHeight) >= 2)
			return false;
		if (abs(root->_balanceFactor) >= 2)
			return false;
		return _IsBalance(root->_left) && _IsBalance(root->_right);
	}

	int _height(Node* root) {
		if (root == nullptr)
			return 0;
		int left = _height(root->_left);
		int right = _height(root->_right);
		int height = max(left, right);
		return height + 1;
	}

	int _size(Node* root) {
		if (root == nullptr)
			return 0;
		return _size(root->_left) + _size(root->_right) + 1;
	}

	void _InOrder(Node* root) {
		if (root == nullptr) return;
		_InOrder(root->_left);
		cout << root->_kv.first << " " << root->_kv.second << endl;
		_InOrder(root->_right);
	}
private:
	Node* _root = nullptr;
};

AVL 树的概念于抽象数据结构

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

        1. 它的左右子树都是 AVL 树

        2. 左右子树的高度之差(平衡因子)的绝对值不超过 1

        左右子树的高度差不超过 1,可以降低树的高度,减少平均搜索长度。如下:

        关于 AVL 树的抽象数据结构,我们首先需要抽象出 AVL 树节点的数据结构,在 AVL 树中,我们存储的关键数据为键值对 pair,AVL 树节点中的平衡因子。然后需要一个指向左子树的指针,指向右子树的指针同时还需要一个指向父节点的指针,可以让我们便于更新每个节点的平衡因子。如下:

template <class K, class V>
struct AVLTreeNode {
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _balanceFactor;

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _balanceFactor(0)
	{}
};

AVL 树的插入

        关于 AVL 树而言,只是在二叉搜索树的基础上引入了平衡因子,所以 AVL 树也可以看出二叉搜索树(左右高度差不大于1的二叉搜索树),所以对于 AVL 树的插入,可以分为以下两步:

        1. 按照二叉搜索树的方式插入新节点

        2. 调整节点的平衡因子。

        所以我们插入节点,只需要找到应该插入的位置,然后插入即可,寻找插入位置按照:键值小于当前节点,向左子树搜索,键值大于当前节点,向右子树搜索的原则,直到找到空节点为止,就是应该插入的位置。寻找的时候,还需要记录下每一次搜索的父节点,便于链接指针,如下:

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.first > kv.first)
			parent = cur, cur = cur->_left;
		else
			return false;
	}
	// cur == nullptr
	cur = new Node(kv);
    
    // 链接孩子节点和父节点
	if (parent->_kv.first > kv.first)
		parent->_left = cur;
	else
		parent->_right = cur;
	cur->_parent = parent;

	// 需要更新平衡因子...
	
	

	return true;
}

        插入成功,则返回 true,插入失败(树中已经存在键值)则返回 false。

        以上只是完成了插入,插入元素之后,我们还需要更新节点的平衡因子,更新平衡因子按照以下原则进行更新:

        1. 插入元素位置位于父节点的右边,父节点的平衡因子 +1;        

        2. 插入元素位置位于父节点的左边,父节点的平衡因子 -1

        3. 更新完父节点的平衡因子之后,父节点的平衡因子的取值可能为 0、正负1、正负2

        5. 父节点的平衡因子更新完之后为0,不会影响父节点的父节点的平衡,所以不用在往上更新。

        6. 父节点的平衡因子跟新完之后为正负1,说明原来父节点的平衡因子为0,这时还会影响父节点的父节点的平衡因子,所以需要继续向上更新。当某个节点的平衡原则为正负二的时候,我们就需要通过选择使树平衡

        如下:

// 需要更新平衡因子
// 如果是在父亲的左边,父亲的平衡因子减一、右边加一
if (parent->_left == cur)
	parent->_balanceFactor--;
else
	parent->_balanceFactor++;
// 查看爷爷结点是否需要更新

while (parent) {
	if (parent->_balanceFactor == 0) {
		break;
	}
	else if (parent->_balanceFactor == 1 || parent->_balanceFactor == -1) {
		if (parent == _root)
			break;
		// 现在的parent就不可能等于null
		parent = parent->_parent;
		cur = cur->_parent;
		if (parent->_left == cur)
			parent->_balanceFactor--;
		else
			parent->_balanceFactor++;
	}
	else if(parent->_balanceFactor == 2 || parent->_balanceFactor == -2) {
		if (parent->_balanceFactor == 2 && cur->_balanceFactor == 1)
			RotateLeft(parent);
		else if (parent->_balanceFactor == -2 && cur->_balanceFactor == -1)
			RotateRight(parent);
		else if (parent->_balanceFactor == -2 && cur->_balanceFactor == 1)
			RotateLeftRight(parent);
		else if (parent->_balanceFactor == 2 && cur->_balanceFactor == -1)
			RotateRightLeft(parent);
		else
			assert(false);
		break;
	}
	else {
		assert(false);
	}
}

        对于如上的代码中,其中最难的一步就是旋转,关于旋转一共会出现四种情况:左单旋、右单旋、左右双旋、右左双旋

AVL 树的旋转

        我们首先介绍右单旋,当新节点插入导较高左子树的左侧就会出现右单旋,关于右单旋出现的情况如下:

        当出现如上所示的情况时(父亲节点的平衡因子等于-2,左孩子节点的平衡因子为-1时),我们就需要进行右旋,也就是将左孩子作为父节点,父节点作为右孩子,在将左孩子的右节点链接到原父节点上。其中还有需要注意的点:右旋时的父节点不一定是根节点,所以我们在旋转的时候,还需要记录下父节点的父节点,最后将其链接到一起。

void RotateRight(Node* parent) {
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	// 将左孩子的右节点链接到原父亲结点
	if (subLR) subLR->_parent = parent;
	parent->_left = subLR;
	
	Node* ppNode = parent->_parent;
	// 将左孩子变为原父亲结点的父亲
	subL->_right = parent;
	parent->_parent = subL;
	// 将爷爷结点重新链接
	if (ppNode == nullptr) {
		_root = subL;
		_root->_parent = nullptr;
	}
	else {
		if (ppNode->_left == parent)
			ppNode->_left = subL;
		else
			ppNode->_right = subL;
		subL->_parent = ppNode;
	}
	subL->_balanceFactor = parent->_balanceFactor = 0;
}

        记得最后将节点的平衡因子设置为0。

        接着我们介绍左单旋:当新节点插入到较高右子树的右侧,关于这种情况如下:

        关于左单旋,其思想和右单旋基本一致,不过是将右单旋的给镜像了过来。所以当父节点的平衡因子为2,右节点的平衡因子为1的时候,我们就需要对树进行左单旋。也就是让右孩子的左节点作为父节点的右孩子,左节点作为父节点,原父节点作为左孩子的左节点。注意原父节点的父节点是否为 nullptr,最后需要更新节点的平衡因子。如下:

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

	parent->_right = subRL;
	if (subRL) subRL->_parent = parent;
	
	subR->_left = parent;
	parent->_parent = subR;

	if (ppNode == nullptr) {
		_root = subR;
		_root->_parent = nullptr;
	}
	else {
		if (ppNode->_left == parent)
			ppNode->_left = subR;
		else
			ppNode->_right = subR;
		subR->_parent = ppNode;
	}
	subR->_balanceFactor = parent->_balanceFactor = 0;
}

        第三种情况,左右双旋。左右双旋就是分别需要左旋一次,然后右旋一次,接着更新我们的平衡因子,如下:

        如上图所示,当左孩子节点的平衡因子为1,父节点的平衡因子为-2的时候,我们就需要进行左右双旋,当我们旋转之后,当前父节点的平衡因子一定为0,但原父节点和左孩子节点的平衡因子一共有三种情况,分别是0 0,1 0,0 -1。当 h = 0 的时候,插入的节点就是以上的60节点,旋转之后所有节点(一共就3个节点)都是为0,当节点插入到60的左边,那么30的平衡因子为0(如图),当节点插入到60的右边,90的平衡因子则为0。

        因为在单独调用左单选,右单旋之后,会将所有节点的平衡因子都置为0,所以我们需要进行特殊处理。如下:

void RotateLeftRight(Node* parent) {
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int balanceFactor = subLR->_balanceFactor;
	// 先左旋后右旋
	RotateLeft(subL);
	RotateRight(parent);

	subLR->_balanceFactor = 0;
	if (balanceFactor == -1) {
		subL->_balanceFactor = 0;
		parent->_balanceFactor = 1;
	}
	else if (balanceFactor == 1) {
		parent->_balanceFactor = 0;
		subL->_balanceFactor = -1;
	}
	else if (balanceFactor == 0) {
		parent->_balanceFactor = 0;
		subL->_balanceFactor = 0;
	}
	else {
		assert(false);
	}
}

        最后一种情况:右左双旋。也就是先右旋然后在左旋,也就是和以上的情况是堆成的情况,如下:

        对于需要右左旋转的情况为父节点为2,右孩子为1.关于转换的细节和以上的左右双旋的情况向对称,在这就不细讲了,代码如下:

void RotateRightLeft(Node* parent) {
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int balanceFactor = subRL->_balanceFactor;
	RotateRight(subR);
	RotateLeft(parent);
	// 更新平衡因子
	subRL->_balanceFactor = 0;
	if (balanceFactor == -1) {
		parent->_balanceFactor = 0;
		subR->_balanceFactor = 1;
	}
	else if (balanceFactor == 1) {
		parent->_balanceFactor = -1;
		subR->_balanceFactor = 0;
	}
	else if (balanceFactor == 0) {
		parent->_balanceFactor = 0;
		subR->_balanceFactor = 0;
	}
	else {
		assert(false);
	}
}

AVL 树的验证 + 测试

        接下来我们将对我们是新的 AVL 树进行验证,也就是看我们写出的代码是否符合 AVL 树的特性,其中主要包括特性测试和压力测试。在进行测试之前,我们需要先写出一些辅助函数,如下:

template <class K, class V>
class AVLTree {
public:
	typedef AVLTreeNode<K, V> Node;

	void InOrder() {
		_InOrder(_root);
		cout << endl;
	}

	int height() {
		int h = _height(_root);
		return h;
	}

	int size() {
		int s = _size(_root);
		return s;
	}

	bool IsBalance() {
		return _IsBalance(_root);
	}

private:
	bool _IsBalance(Node* root) {
		if (root == nullptr)
			return true;
		int leftHeight = _height(root->_left);
		int rightHeight = _height(root->_right);
		if (abs(leftHeight - rightHeight) >= 2)
			return false;
		if (abs(root->_balanceFactor) >= 2)
			return false;
		return _IsBalance(root->_left) && _IsBalance(root->_right);
	}

	int _height(Node* root) {
		if (root == nullptr)
			return 0;
		int left = _height(root->_left);
		int right = _height(root->_right);
		int height = max(left, right);
		return height + 1;
	}

	int _size(Node* root) {
		if (root == nullptr)
			return 0;
		return _size(root->_left) + _size(root->_right) + 1;
	}

	void _InOrder(Node* root) {
		if (root == nullptr) return;
		_InOrder(root->_left);
		cout << root->_kv.first << " " << root->_kv.second << endl;
		_InOrder(root->_right);
	}
private:
	Node* _root = nullptr;
};

        我们先进行特性测试,如下:

        如上所示,我们一共验证了两组数据,其中包含了左旋、右旋、左右双旋、右左双旋四种情况。

        接着进行暴力测试,生成一百万个数据,主要测试性能和插入是否成功:

        如上所示,插入一百万个数据也可以生成平衡树。

        测试源码如下:

void TestAVL01() {
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	// {16, 3, 7, 11, 9, 26, 18, 14, 15}
	AVLTree<int, int> avtree;
	
	for (auto e : a) {
		if (e == 4) {
			int i = 0;
		}
		avtree.insert(make_pair(e, e));
	}
	avtree.InOrder();
	cout << avtree.height() << endl;
	cout << avtree.size() << endl;
	cout << avtree.IsBalance() << endl;
}

void TestAVL02() {
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (int i = 0; i < N; i++) {
		v.push_back(rand() + 1);
	}
	size_t begin1 = clock();
	AVLTree<int,int> tree;
	for (auto e : v)
		tree.insert({e, e});
	size_t end1 = clock();
	cout << "insert" << end1 - begin1 << endl;

	cout << "Height:" << tree.height() << endl;
	cout << "Size:" << tree.size() << endl;

	size_t begin2 = clock();
	for (auto e : v)
		tree.find(e);
	size_t end2 = clock();
	cout << "find:" << end2 - begin2 << endl;

	cout << tree.IsBalance() << endl;
}

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

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

相关文章

SpringBoot 配置事务

SpringBoot 在启动时已经加载了事务管理器&#xff0c;所以只需要在需要添加事务的方法/类上添加Transactional即可生效&#xff0c;无需额外配置。 TransactionAutoConfiguration 事务的自动配置类解析&#xff1a; SpringBoot 启动时加载/META-INF/spring/org.springframewor…

kafka集成spark

1.新建Scala项目 具体教程可见在idea中创建Scala项目教程-CSDN博客 1.1右键项目名-添加框架支持-勾选scala 1.2main目录下新建scala目录-右键Scala目录-将目录标记为-勾选源代码根目录 1.3创建包com.ljr.spark 1.4引入依赖&#xff08;pox.xml) <dependencies><…

Java——二维数组

一、二维数组介绍 二维数组与一维数组很相似。可以说二维数组是元素为一维数组的数组&#xff0c;也就是一维数组的数组。每个元素可以通过行索引和列索引来访问。 1、二维数组的创建 我们知道&#xff0c;在 C 语言中&#xff0c;二维数组是一个连续的内存块&#xff0c;通…

【Python】使用pip安装seaborn sns及失败解决方法与sns.load_dataset(“tips“)

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。 &#x1f913; 同时欢迎大家关注其他专栏&#xff0c;我将分享Web前后端开发、人工智能、机器学习、深…

RISC-V MCU IDE MRS(MounRiver Studio)开发 编译后打印FLASH及RAM使用占比信息

以RISC-V MCU IDE MounRiver Studio(MRS)为例&#xff0c;首先我们选中目标工程&#xff0c;点击工具栏工程属性按钮&#xff0c;打开工程属性配置页&#xff1a; 在C/C Build->Settings->Tool Settings选项列表中单击GNU RISC-V Cross C Linker->Miscellaneous&#…

Anzo 跟单社区现已正式上线!即刻体验无与伦比的强大功能

Anzo 跟单社区现已正式上线! ANZO 跟单社区是一个颠覆性的创新跟单社区平台&#xff0c;作为新一代跟单社区&#xff0c;我们旨在让更多的用户享受跟单交易带来的便捷性和收益性。交易者可以通过跟单社区&#xff0c;学习和分享交易策略&#xff0c;轻松复制交易专家的交易策略…

人类记忆优化算法:针对全局优化问题的记忆启发优化器

Human memory optimization algorithm: A memory-inspired optimizer for global optimization problems 24年 Expert Systems With Applications sci一区 原文链接: https://doi.org/10.1016/j.eswa.2023.121597 Zhu D, Wang S, Zhou C, et al. Human memory optimization alg…

二进制文件的膨胀策略和使用 debloat 消除膨胀测试

在恶意软件的分析中有的 Windows 可执行文件&#xff08;PE 文件&#xff09;会通过膨胀策略来绕过防病毒一些防病毒的检查&#xff0c;比如上传云进行分析&#xff0c;因为文件太大了所以无法进行一些防病毒分析。一般的可执行文件有很多的膨胀策略&#xff0c;一般简单的膨胀…

Elasticsearch-经纬度查询(8.x)

目录 一、开发环境 二、pom文件 三、ES配置文件 四、ES相关字段 五、ES半径查询 ES的字段类型:geo_point&#xff0c;可以实现以一个点为中心的半径查询(geo_distance query) ES 地里位置查询: 半径查询(geo_distance query)查询指定矩形内的数据(geo_bounding_box quer…

[AI Google] 使用 Gemini 取得更多成就:试用 1.5 Pro 和更多智能功能

总结 Google 正在为超过 35 种语言的 Gemini Advanced 订阅者推出 Gemini 1.5 Pro。此次更新包括 100 万个 token 的上下文窗口、改进的数据分析功能和增强的多模态图像理解。新功能包括用于自然对话的 Gemini Live、先进的规划工具和可定制的 Gems。更新还集成了更多 Google …

基于STM32开发的智能农业监控系统

目录 引言环境准备智能农业监控系统基础代码实现&#xff1a;实现智能农业监控系统 4.1 土壤湿度传感器数据读取4.2 温湿度传感器数据读取4.3 水泵与风扇控制4.4 用户界面与数据可视化应用场景&#xff1a;农业环境监测与管理问题解决方案与优化收尾与总结 1. 引言 随着智能…

SkyWalking之P0核心业务场景输出调用链路应用

延伸扩展&#xff1a;XX核心业务场景 路由标签打标、传播、检索 链路标签染色与传播 SW: SkyWalking的简写 用户请求携带HTTP头信息X-sw8-correlation “X-sw8-correlation: key1value1,key2value2,key3value3” 网关侧读取解析HTTP头信息X-sw8-correlation&#xff0c;然后通过…

Navicat导入json文件(json文件数据导入到MySQL表中)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

在Modelarts上微调量化Llama3,并用docker部署

本文概述 本文先使用llama-factory去微调llama3大模型&#xff0c;然后使用llama.cpp去量化模型并打包为docker部署到服务器上让qq机器人能够调用服务&#xff0c;实现qq群内问答。 效果展示 环境准备 本文使用华为云的Modelarts的notebook去进行的模型微调 ubuntu20.04&#x…

揭开FFT时域加窗的奥秘

FFT – Spectral Leakage 假设用于ADC输出数据分析的采样点数为N&#xff0c;而采样率为Fs&#xff0c;那我们就知道&#xff0c;这种情况下的FFT频谱分辨率为δf&#xff0c;那么δfFs/N。如果此时我们给ADC输入一个待测量的单频Fin&#xff0c;如果此时Fin除以δf不是整数&a…

IP地址冲突检测(Address Conflict Detect)记录

学习目标&#xff1a; 提示&#xff1a;ACD(IP地址冲突检测)原理学习与抓包分析 学习记录&#xff1a; 1、Address Conflict Detection地址冲突检测&#xff0c;简称ACD。RFC 5227提出ACD机制。其中ACD将arp request分为ARP probe和ARP announcement两种&#xff1b; ACD定义…

数据中心网络运维探讨

数据中心网络运维探讨 数据中心网络运维通过科学的网络架构设计、实时监控管理、智能化运维工具和全面的安全防护&#xff0c;确保网络的高效、安全运行。它不仅提升了运维效率和网络可靠性&#xff0c;还保障了业务的连续性和数据安全。随着技术的不断进步&#xff0c;智能化…

常见机器学习的原理及优略势

有监督 一、线性回归&#xff08;Linear Regression) 1. 算法原理 线性回归&#xff08;Linear Regression&#xff09;是一种基本的回归算法&#xff0c;它通过拟合一个线性模型来预测连续型目标变量。线性回归模型的基本形式是&#xff1a;y w1 * x1 w2 * x2 … wn * …

在python中关于元组的操作

创建元组 如上图所示&#xff0c;a&#xff08;&#xff09;和b tuple(),,这两种方式都可以创建出元组。 在创建元组的时候&#xff0c;指定初始值 如上图所示&#xff0c;也可以在创建元组的时候&#xff0c;指定初始值。 同列表一样元组中的元素也可以是任意类型的。 同列…

Map深度学习

Map Map是一个键值对的集合&#xff0c;和object类似&#xff0c;Map作为构造函数&#xff0c;可以通过全局对象获取到。需要通过new操作创建实例对象&#xff0c;直接调用会报错。Map构造函数接受一个iterable类型的函数&#xff0c;用来初始化Map。 var m new Map([[1, &qu…