【C++进阶】红黑树实现

news2024/9/29 10:56:36

文章目录

  • 红黑树的概念
  • 红黑树的性质
  • 红黑树节点的定义
  • 红黑树结构
  • 红黑树的插入
    • 1.按照二叉搜索的树规则插入新节点
    • 2.进行旋转和变色
    • 源码
  • 红黑树的验证
    • 中序遍历判断是否满足二叉搜索树
    • 判断是否满足红黑树
  • 完整源码

红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或
Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出俩倍,因而是接近平衡的。

那么有了AVL树,为什么还需要红黑树呢?

因为AVL树的平衡性更强,查找效率高,但是在插入时要进行更多的旋转,所以插入和删除时的效率更低,但是红黑树相比较AVL树的平衡性稍弱,只需要满足最长路径不超过最短路径的二倍,所以在插入删除时效率更高,只是在查找时稍慢一点,所以AVL树更适合用于对插入删除操作次数不多,需要经常进行查找的情况,而红黑树用去插入和删除次数较多的情况。

在这里插入图片描述

红黑树的性质

刚才前边提到了红黑树必须满足最长路径节点数不超过最短路径结点树的二倍,那么我们怎么样才能满足呢,其实只要满足以下红黑树的性质就可以了。

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

我们来分析一下这些性质,每个结点不是黑色就是红色,说明结点只有两种情况,而根结点为黑色也跟第五条性质对应,当为空树时,空节点也是黑色的,而一个节点为红色,他的子节点一定为黑色说明没有两个连续的红色结点,并且每条路径的黑色结点树相同,那么就说明一条路径上的红色节点数一定小于等于黑色结点数,则说明最长路径上的结点一定不超过最短路径的二倍。

在这里插入图片描述

红黑树节点的定义

由于节点的颜色不是红色就是黑色,所以采用枚举类型来定义结点的颜色。

枚举类型快速讲解:

在C++中,enum是一种枚举类型,用于定义一组命名的整型常量。枚举类型可以使代码更加具有可读性。可以使用enum关键字来定义枚举类型,如下所示:
enum Color {
RED,
GREEN,
BLUE
};
在上面的例子中,我们定义了一个名为Color的枚举类型,其中包含三种颜色:RED,GREEN和BLUE,它们的值分别为0,1和2。在枚举类型中,每个枚举值都可以用作整数,并且可以通过枚举名称来访问它们。
可以使用枚举类型来代替整数常量,这样可以使代码更加可读性高,而且更加易于维护。例如,下面的代码使用枚举类型来表示颜色:
Color color = RED;
if (color == GREEN) {
// do something
} else if (color == RED) {
// do something else
}
在上面的代码中,我们使用枚举类型来表示颜色,使代码更加易于理解。

并且为了方便找到父节点,所以使用三叉链的形式

下边是红黑树节点的实现

enum COLOUR
{
	RED,
	BLACK
};

template<class K,class V>
class RBtreeNode
{
public:
	RBtreeNode(const pair<K,V>& kv)
		:_kv(kv)
		,_col(RED)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
	COLOUR _col;
	RBtreeNode<K, V>* _left;
	RBtreeNode<K, V>* _right;
	RBtreeNode<K, V>* _parent;
	pair<K, V> _kv;
};

思考:那么为什么要把节点的颜色初始化为红色呢?

事实上,不论把结点的颜色设置为黑色还是红色,都可能会违反红黑树的性质,但是插入黑色节点就违背了每条路径上黑色结点树相同的性质,而插入红色节点就可能违背有两个相同红色节点的性质,但是我们比较代价,发现插入红色节点的代价更小。

红黑树结构

为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了
与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft
域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点。

template<class K,class V>
class RBtree
{
public:
	typedef RBtreeNode<K, V> Node;
private:
	RBtreeNode<K,V>* _root = nullptr;
};

红黑树的插入

由于红黑树本质上还是一个二叉搜索树,所以我们还是要进行第一步,就是先把节点通过二叉搜索树的规则插入,然后根据红黑树的性质进行旋转和变色。

1.按照二叉搜索的树规则插入新节点

二叉搜索树的插入在这里就不多赘述,可以参考前边的几篇文章,直接给出代码:

template<class K,class V>
class RBtreeNode
{
public:
	RBtreeNode(const pair<K,V>& kv)
		:_kv(kv)
		,_col(RED)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
	COLOUR _col;
	RBtreeNode<K, V>* _left;
	RBtreeNode<K, V>* _right;
	RBtreeNode<K, V>* _parent;
	pair<K, V> _kv;
};

template<class K,class V>
class RBtree
{
public:
	typedef RBtreeNode<K, V> Node;
	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		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 = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
private:
	RBtreeNode<K,V>* _root = nullptr;
};

2.进行旋转和变色

步骤一:如果结点插入之后,父结点为黑色,那么就不需要进行调整,如果插入后的父结点为红色,就必须进行调整。
在这里插入图片描述
当在出c,d,e处插入时,就不需要进行调整,如果在a,b处进行插入,就必须进行调整。

步骤二:判断父结点是祖父结点的左子树还是右子树

如果父结点在祖父结点的左边,那么说明叔叔节点就是祖父节点的右边,如果父结点在祖父结点的右边,那么说明叔叔节点就是组父结点的左边。

在弄清楚叔叔节点的情况之后,那么我们就可以把调整分为三种情况:

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

情况一:cur为红,p为红,g为黑,u存在且为红
在这里插入图片描述
如果是这种情况,那么不需要进行旋转,直接变色即可,将父节点变为黑色,祖父结点变为红色,如果祖父节点为根节点,那么就把祖父节点也变为黑色。
在这里插入图片描述
如果两个红色节点连在一起,就必须继续向上调整,所以情况一在变色完之后,要将p节点重新给cur,继续判断是否要进行调整。
在这里插入图片描述
情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑,且p,g,cur节点为一条直线

此时的cur结点,p结点,g节点是三个节点为一条线,只需要进行左右单旋后进行变色即可。
在这里插入图片描述

情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑,且p,g,cur节点为一条折线

当p节点,g节点,cur节点为一条折线时,先对p节点进行旋转,再对g结点旋转,旋转完之后,将g节点变为红色,cur节点变为黑色。
在这里插入图片描述


最后,当父结点为祖父的左节点或者为祖父的右节点时,旋转的方向不同,但是过程都是相同的。

源码

	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		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 = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//此时父结点为存在且为红,说明祖父结点一定存在且为黑
		while (parent && parent->_col == RED)
		{
			Node* grandfater = parent->_parent;
			assert(grandfater);
			assert(grandfater->_col == BLACK);
			//父节点为祖父结点的左节点
			if (parent == grandfater->_left)
			{
				Node* uncle = grandfater->_right;

				//叔叔存在且叔叔为红色,第一种情况直接变色即可
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					//继续向上判断
					cur = grandfater;
					parent = cur->_parent;
				}

				//叔叔不存在或者叔叔为黑色,二三种情况
				else
				{
					//判断子节点为父结点的左还是右
					//此处的两个关系为一条直线,所以属于第二种情况,进行单旋后变色
					if (cur == parent->_left)
					{
						RotateR(grandfater);
						grandfater->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfater);

						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
			//父节点为祖父节点的右节点
			else
			{
				Node* uncle = grandfater->_left;
				//叔叔存在且叔叔为红色,第一种情况直接变色即可
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					//继续向上判断
					cur = grandfater;
					parent = cur->_parent;
				}

				//叔叔不存在或者叔叔为黑色,二三种情况
				else
				{
					//判断子节点为父结点的左还是右
					//此处的两个关系为一条直线,所以属于第二种情况,进行单旋后变色
					if (cur == parent->_right)
					{
						RotateL(grandfater);
						grandfater->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfater);

						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;
		return true;
	}
	
void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		Node* pparent = parent->_parent;

		parent->_parent = subL;
		subL->_right = parent;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
			subL->_parent = pparent;
		}
	}
	void RotateL(Node* parent)
	{
		//左旋parent,先保存parent的右节点
		Node* subR = parent->_right;
		//保存右节点的左节点
		Node* subRL = subR->_left;

		Node* pparent = parent->_parent;

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

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

红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质

中序遍历判断是否满足二叉搜索树

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

在这里插入图片描述

判断是否满足红黑树

	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}
		if (_root->_col == RED)
		{
			cout << "根节点不是黑色的" << endl;
			return false;
		}
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if(cur->_col==BLACK)
				benchmark++;
			cur = cur->_left;
		}
		return PrevCheck(_root,benchmark,0);
	}
	
	bool PrevCheck(Node* cur,int& benchmark,int blackNum)
	{
		if (cur == nullptr)
		{
			if (blackNum != benchmark)
			{
				cout << "黑色结点的数量不相等" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}
		if (cur->_col == BLACK)
		{
			blackNum++;
		}
		if (cur->_col == RED && cur->_parent->_col==RED)
		{
			cout << "存在两个连续的红节点" << endl;
			return false;
		}
		return PrevCheck(cur->_left, benchmark, blackNum)
			&& PrevCheck(cur->_right, benchmark, blackNum);
	}

在这里插入图片描述

完整源码

RBTree.h

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

enum COLOUR
{
	RED,
	BLACK
};

template<class K,class V>
class RBtreeNode
{
public:
	RBtreeNode(const pair<K,V>& kv)
		:_kv(kv)
		,_col(RED)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
	COLOUR _col;
	RBtreeNode<K, V>* _left;
	RBtreeNode<K, V>* _right;
	RBtreeNode<K, V>* _parent;
	pair<K, V> _kv;
};

template<class K,class V>
class RBtree
{
public:
	typedef RBtreeNode<K, V> Node;
	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		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 = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//此时父结点为存在且为红,说明祖父结点一定存在且为黑
		while (parent && parent->_col == RED)
		{
			Node* grandfater = parent->_parent;
			assert(grandfater);
			assert(grandfater->_col == BLACK);
			//父节点为祖父结点的左节点
			if (parent == grandfater->_left)
			{
				Node* uncle = grandfater->_right;

				//叔叔存在且叔叔为红色,第一种情况直接变色即可
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					//继续向上判断
					cur = grandfater;
					parent = cur->_parent;
				}

				//叔叔不存在或者叔叔为黑色,二三种情况
				else
				{
					//判断子节点为父结点的左还是右
					//此处的两个关系为一条直线,所以属于第二种情况,进行单旋后变色
					if (cur == parent->_left)
					{
						RotateR(grandfater);
						grandfater->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfater);

						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
			//父节点为祖父节点的右节点
			else
			{
				Node* uncle = grandfater->_left;
				//叔叔存在且叔叔为红色,第一种情况直接变色即可
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					//继续向上判断
					cur = grandfater;
					parent = cur->_parent;
				}

				//叔叔不存在或者叔叔为黑色,二三种情况
				else
				{
					//判断子节点为父结点的左还是右
					//此处的两个关系为一条直线,所以属于第二种情况,进行单旋后变色
					if (cur == parent->_right)
					{
						RotateL(grandfater);
						grandfater->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfater);

						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;
		return true;
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}
		if (_root->_col == RED)
		{
			cout << "根节点不是黑色的" << endl;
			return false;
		}
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if(cur->_col==BLACK)
				benchmark++;
			cur = cur->_left;
		}
		return PrevCheck(_root,benchmark,0);
	}
private:
	bool PrevCheck(Node* cur,int& benchmark,int blackNum)
	{
		if (cur == nullptr)
		{
			if (blackNum != benchmark)
			{
				cout << "黑色结点的数量不相等" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}
		if (cur->_col == BLACK)
		{
			blackNum++;
		}
		if (cur->_col == RED && cur->_parent->_col==RED)
		{
			cout << "存在两个连续的红节点" << endl;
			return false;
		}
		return PrevCheck(cur->_left, benchmark, blackNum)
			&& PrevCheck(cur->_right, benchmark, blackNum);
	}
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		Node* pparent = parent->_parent;

		parent->_parent = subL;
		subL->_right = parent;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
			subL->_parent = pparent;
		}
	}
	void RotateL(Node* parent)
	{
		//左旋parent,先保存parent的右节点
		Node* subR = parent->_right;
		//保存右节点的左节点
		Node* subRL = subR->_left;

		Node* pparent = parent->_parent;

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

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;
			}
			subR->_parent = pparent;
		}
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}
private:
	RBtreeNode<K,V>* _root = nullptr;
};

void TestRBtree()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };  // 测试双旋平衡因子调节
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBtree<int, int> t1;
	for (auto e : a)
	{
		t1.insert(make_pair(e, e));
	}
	t1.InOrder();
}
void TestRBtree2()
{
	size_t N = 1000;
	srand(time(0));
	RBtree<int, int> t1;
	for (size_t i = 0; i < N; ++i)
	{
		int x = rand();
		cout << "Insert:" << x << ":" << i << endl;
		t1.insert(make_pair(x, i));
	}
	cout << "IsBalance:" << t1.IsBalance() << endl;
}

test.cpp

#include"RBtree.h"
using namespace std;
int main()
{
	TestRBtree2();
	return 0;
}

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

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

相关文章

基于spss的多元统计分析 之 单/双因素方差分析 多元回归分析(1/8)

实验目的&#xff1a; 1&#xff0e;掌握单样本t检验、两样本t检验、配对样本t检验、单因素方差分析、多元回归分析的基本原理&#xff1b; 2&#xff0e;熟悉掌握SPSS软件或者R软件关于单因素、多因素方差分析、多元回归分析的基本操作&#xff1b; 3&#xff0e;利用实验指导…

2.3C++保护成员

C 保护成员 在C中&#xff0c;可以使用保护成员 protected&#xff0c;来提高代码的安全性。 我用大白话解释一下什么是保护成员&#xff1a;说白了就是为了防止其他类直接访问或修改其成员加的一个措施。 目的是保护&#xff0c;成员的私有性和可见性。 C 类的保护 可以为…

web 语音通话 jssip

先把封装好的地址安上&#xff08;非本人封装&#xff09;&#xff1a;webrtc-webphone: 基于JsSIP开发的webrtc软电话 jssip中文文档&#xff1a;jssip中文开发文档&#xff08;完整版&#xff09; - 简书 jssip使用文档&#xff1a;&#xff08;我没有运行过&#xff0c;但…

Nginx服务器,在window系统中的使用(前端,nginx的应用)

简介&#xff1a;Nginx是一个轻量级、高性能的HTTP和反向代理web服务器&#xff0c;且支持电子邮件&#xff08;IMAP/POP3&#xff09;代理服务&#xff0c;特点是占用内存少&#xff0c;并发能力强&#xff0c;给我们来了很多的便利&#xff0c;国内大部分网站都有使用nginx&a…

18款奔驰S350升级后排座椅记忆功能,提升您乘坐舒适性

带有记忆功能的座椅可以存储三个的座椅设置以及行车电脑中的舒适性设置。只要按一下按钮就可以跳到记忆模式&#xff0c;让座椅回到上一次设置。

使用 BigQuery Omni,发现跨云地理空间分析的优势

【本文由 Cloud Ace 整理发布。Cloud Ace 是谷歌云全球战略合作伙伴&#xff0c;拥有 300 多名工程师&#xff0c;也是谷歌最高级别合作伙伴&#xff0c;多次获得 Google Cloud 合作伙伴奖。作为谷歌托管服务商&#xff0c;我们提供谷歌云、谷歌地图、谷歌办公套件、谷歌云认证…

第十章详解synchronized锁升级

文章目录 升级的流程为什么要引入锁升级这套流程多线程访问情况具体流程 轻量级锁如何使用CAS实现轻量级锁CAS加锁成功CAS加锁失败CAS进行解锁 总结何时变为重量级锁 锁膨胀自旋优化 偏向锁主要作用偏向状态测试撤销偏向锁 撤销 - 调用对象 hashCode撤销 - 其它线程使用对象撤销…

js:codemirror实现在线代码编辑器代码高亮显示

CodeMirror is a versatile text editor implemented in JavaScript for the browser. It is specialized for editing code, and comes with a number of language modes and addons that implement more advanced editing functionality. 译文&#xff1a;CodeMirror是一个多…

第二章:软件工程师必备的网络基础

目录 一、网线的制作 二、集线器、交换机介绍 三、路由器的配置 一、网线的制作 1.1、水晶头 ​​​ 1.2、网线钳 1.3、网线的标准 T568A标准&#xff08;交叉线&#xff09;&#xff1a; 适用链接场合&#xff1a;电脑-电脑、交换机-交换机、集线器-集线器 接线顺序&…

【正点原子STM32连载】第三十九章 触摸屏实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html# 第三…

有源电力滤波器及配电能效平台在污水处理厂中的应用

【摘要】为减少污水处理设备产生的各次谐波&#xff0c;通过确定主要谐波源&#xff0c;检测和计算谐波分量&#xff0c;采用有源电力滤波器进行谐波治理&#xff0c;大幅降低了电力系统中的三相电流畸变率&#xff0c;提高了电能质量&#xff1b;抑制了谐波分量&#xff0c;减…

doris docker部署和本地化部署 1.2.4.1版本

写在前面 以下操作语句按顺序执行即可&#xff0c;注意切换目录的命令一定记得执行&#xff0c;如果需要改动的地方会有${}注释&#xff0c;其余不需要任何改动&#xff0c;默认安装版本为1.12.4&#xff08;稳定版&#xff09; 本地化部署 下载 # 创建目录 mkdir /data/sof…

软件测试日常工作和前景是怎么样的?

笔者从测试的工作情况&#xff0c;职业发展&#xff0c;还有测试的工作日常等等来给大家讲解一下软件测试到底是什么样的工作&#xff1f; 通俗来说软件测试工程师就相当于一个质检员&#xff0c;专门处理软件测试质量的工作&#xff0c;不管是功能测试也好&#xff0c;性能测…

BK7231N开发平台原厂烧录工具使用说明

BK7231N开发平台原厂烧录工具使用说明 烧录流程介绍 1.打开原厂烧录工具 以管理员身份打开名为 bk_writer_gui_V1.6.3.exe 的可执行文件。 2. 烧录对象 烧录对象选择 BK7231n 3.烧录地址 当我们烧录UA文件的时候&#xff0c;需要把起始地址设置为&#xff1a; 0X00011000。…

Windows提示“找不到rgss202j.dll”怎么办?

Rgss202j.dll文件是Windows操作系统最重要的系统文件之一&#xff0c;它包含了一组程序和驱动函数。如果此文件丢失或损坏&#xff0c;驱动程序将无法正常工作&#xff0c;并且相应的应用程序也将无法正常启动且运行。通常情况下&#xff0c;造成Rgss202j.dll文件无法找到的原因…

爬虫 - ProtoBuf 协议

一、抓取请求 以下是请求的大致内容&#xff1a; 是乱码&#xff0c;需要解析。 二、解析 通过分析 request 和 response 的 Content-Type: application/x-protobuf 得知&#xff1a;使用了谷歌的 protobuf 协议来传输数据&#xff0c;需要破解。 大致破解过程&#xff…

随时随地保持连接:数字游民适用的远程桌面

随着世界迅速适应数字革命&#xff0c;一种全新的职业——数字游民应运而生。数字游民指利用技术远程办公的专业人群&#xff0c;这是一种允许人们在旅行中办公、不受地点限制的工作生活方式。游牧式工作生活趋势并非一时的风尚&#xff0c;而是我们工作观念的彻底转变&#xf…

MUR8060PT-ASEMI快恢复二极管MUR8060PT

编辑-Z MUR8060PT在TO-247封装里采用的2个芯片&#xff0c;其尺寸都是140MIL&#xff0c;是一款高耐压大电流快恢复二极管。MUR8060PT的浪涌电流Ifsm为600A&#xff0c;漏电流(Ir)为10uA&#xff0c;其工作时耐温度范围为-55~150摄氏度。MUR8060PT采用抗冲击硅芯片材质&#x…

实在智能率先拥抱大模型技术,旗下“AI+RPA”系列产品迎来全面智能升级

实在智能RPA ​ AI时代&#xff0c;所有产品都将迎来用大模型进行全面智能升级。 随着以ChatGPT等为代表的生成式AI持续火热&#xff0c;大型语言模型&#xff08;Large Language Model, LLM&#xff09;领域的研发和布局在国内外有目共睹&#xff0c;微软、谷歌、百度系等生…

回收小程序是什么?有什么特点?

回收小程序旨在为用户提供便捷、环保的废品回收服务。以下是关于上门回收小程序的介绍&#xff1a;回收小程序旨在解决废品回收的难题&#xff0c;为用户提供一种方便、可持续的回收方式。通过小程序&#xff0c;可以轻松预约回收服务&#xff0c;将废品交由专业回收人员处理&a…