AVL树 ---(C++)

        本篇讲全面的讲解 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.mfbz.cn/a/695815.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STC90C51驱动LCD1602、LCD12864、OLED

主控芯片&#xff08;STC90C516RDPG5151028&#xff09;介绍 ROM64K,RAM1280字节&#xff0c;40Pin&#xff0c;3个定时器&#xff0c;1个串口&#xff0c;8个中断源&#xff08;分别是&#xff1a;外部中断0(INTO)、外部中断 1(INT1)、外部中断 2(INT2)、外部中断 3(INT3)、定…

【微信小程序开发(从零到一)】——个人中心页面的实战项目(二)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

「动态规划」如何计算能获得多少点数?

740. 删除并获得点数https://leetcode.cn/problems/delete-and-earn/description/ 给你一个整数数组nums&#xff0c;你可以对它进行一些操作。每次操作中&#xff0c;选择任意一个nums[i]&#xff0c;删除它并获得nums[i]的点数。之后&#xff0c;你必须删除所有等于nums[i] …

【网络安全的神秘世界】web应用程序安全与风险

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 第一章&#xff1a;web应用程序安全与风险 web攻击基础知识 1、什么是web应用攻击 web攻击的本质&#xff0c;就是通过http协议篡改应用程序&#xff0…

虚拟机ping不通主机,但是主机可以ping通虚拟机

我在Windows10系统安装了虚拟机&#xff0c;设置的主机与虚拟机的连接方式是桥接&#xff0c;安装好后&#xff0c;发现虚拟机ping不通主机&#xff0c;但是主机可以ping通虚拟机。 我的操作是&#xff1a;关闭防火墙&#xff0c;发现虚拟机可以ping通主机了。说明是Windows10…

python后端结合uniapp与uview组件tabs,实现自定义导航按钮与小标签颜色控制

实现效果&#xff08;红框内&#xff09;&#xff1a; 后端api如下&#xff1a; task_api.route(/user/task/states_list, methods[POST, GET]) visitor_token_required def task_states(user):name_list [待接单, 设计中, 交付中, 已完成, 全部]data []color [#F04864, …

CPP初级:模板的运用!

目录 一.泛型编程 二.函数模板 1.函数模板概念 2.函数模板格式 3.函数模板的原理 三.函数模板的实例化 1.隐式实例化 2.显式实例化 3.模板参数的匹配原则 四.类模板 1.类模板的定义格式 2.类模板的实例化 一.泛型编程 泛型编程&#xff1a;编写与类型无关的通用代码…

express入门01服务器搭建以及get和post请求的监听

微搭提供了后端API的能力&#xff0c;但是不同的版本收费差别巨大&#xff0c;因为使用的门槛限制了中小企业使用低代码平台。那可不可以既要又要呢&#xff1f;答案是肯定的&#xff0c;那其实掌握一定的后端框架&#xff0c;借助我们在低代码中已经熟练掌握的技能其实是比较容…

2024.6.9 七

Python的time库 先导入库 import time相关函数 time.time() 返回当前时间的时间戳(一个记录时间的浮点数),从1970年开始算的 time.localtime(sec) 返回一个指定时间戳(sec)的struct_time对象,是一个元组封装起来的,默认是当地时间 struct_time对象 tm_year 年 tm_mon 月 tm_…

CDR2024软件破解Keygen激活工具2024最新版

CorelDRAW Graphics Suite2024最新版&#xff0c;这是一款让我爱不释手的图形设计神器&#xff01;作为一个软件评测专家&#xff0c;我一直在寻找一款能够提升我的设计效率和创造力的工具。而这款软件&#xff0c;简直就是为我量身定制的&#xff01;&#x1f389; 「CorelDR…

算法金 | AI 基石,无处不在的朴素贝叶斯算法

大侠幸会&#xff0c;在下全网同名「算法金」 0 基础转 AI 上岸&#xff0c;多个算法赛 Top 「日更万日&#xff0c;让更多人享受智能乐趣」 历史上&#xff0c;许多杰出人才在他们有生之年默默无闻&#xff0c; 却在逝世后被人们广泛追忆和崇拜。 18世纪的数学家托马斯贝叶斯…

温度传感器十大品牌

温度传感器品牌排行榜-十大热电偶品牌-热敏电阻品牌排行-Maigoo品牌榜

TikTok Shop账号需要防关联吗?

在TikTokShop作为新兴的电商销售渠道中&#xff0c;保护账号的安全和隐私&#xff0c;防止账号关联成为了重要的任务。为了更好地理解为何需要防关联以及如何进行防范&#xff0c;让我们深入探讨一下这个问题。 为什么要防关联&#xff1f; 1. 账号异常风险&#xff1a;防关联…

电容十大品牌供应商

十大电容器品牌&#xff0c;电解电容-陶瓷电容-超级电容器品牌排行榜-Maigoo品牌榜

Android gradle kts 8.0以上版本配置签名和修改APK输出名字

目录 概述修改签名配置新建签名文件目录配置签名信息使用签名信息打包 修改APK名称 概述 之前写过一篇文章是通过Kotlin的Dsl结合gradle编写的插件来管理项目依赖&#xff0c;我是从一个开源项目叫DanDanPlayAndroid项目上学到的&#xff0c;那时还没有使用toml文件来管理项目…

Linux入门学习(2)

1.相关复习新的指令学习 &#xff08;1&#xff09;我们需要自己创建一个用户&#xff0c;这个用户前期可以是一个root用户&#xff0c;后期使用创建的普通用户 &#xff08;2&#xff09;文件等于文件内容加上文件属性,对于文件的操作就包括对于文件内容的操作和文件属性&…

Apache SeaTunnel社区5月月报更新!

各位热爱 SeaTunnel 的小伙伴们&#xff0c;社区 5 月份月报来啦&#xff01; SeaTunnel 正在迅猛发展&#xff0c;积极投入社区项目建设的小伙伴将促进SeaTunnel不断提升数据同步的高可扩展性、高性能及高可靠性。欢迎关注每月月报更新&#xff0c;期待在下个月的Merge Star月…

Redis持久化说明

Redis的持久化是指将内存中的数据持久化到磁盘中&#xff0c;以保证数据在重启或宕机后不会丢失。 Redis提供了两种主要的持久化方式&#xff1a;RDB(Redis DataBase)和AOF(Append Only File)。 RDB&#xff08;Redis DataBase&#xff09; 1、RDB快照原理 RDB持久化方式会定…

STM32 | 独立看门狗 | RTC(实时时钟)

01、独立看门狗概述 在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入停滞状态,发生不可预料的后果,所以出于对单片机运行状…

ffmpeg视频解码原理和实战-(5)硬件加速解码后进行渲染并输出帧率

头文件&#xff1a; xvideoview.h #ifndef XVIDEO_VIEW_H #define XVIDEO_VIEW_H #include <mutex> #include <fstream> struct AVFrame;void MSleep(unsigned int ms);//获取当前时间戳 毫秒 long long NowMs();/// 视频渲染接口类 /// 隐藏SDL实现 /// 渲染方案…