【C++ 第十四章】红黑树

news2024/9/22 19:24:11

前言:

学习本章,需要先学习 AVL树的 旋转,因为 红黑树也需要旋转调整来平衡,下面讲解将不赘述 旋转的原理和操作

红黑树的旋转 和 AVL树的旋转 唯一不同的是:旋转的判断使用逻辑

AVL树的旋转 可以通过 平衡因子 判断使用哪一种旋转

红黑树的旋转 则 直接通过 判断 爷爷 grandfather、父亲 parent、自己 cur 三种节点之间的位置关系 来 判断使用哪一种 旋转


其实原理都一样,只不过AVL树有了平衡因子,可以直接借助平衡因子判断,其核心还是爷父子三者位置关系


🦖1. 红黑树的概念

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

(即 最长路径 长度一定 小于等于 最短路径的*2 )



🦖2. 红黑树的 4 条性质(决定代码实现逻辑)

(1)二叉搜索树的结构

(2)根和叶子(NULL)都是黑色

(3)不存在连续的两个红色结点

(4)任一结点到叶所有路径黑结点数量相同


四条性质可以总结成 四条口诀

左根右、根叶黑、不红红、黑路同


🦖⭐(1)左根右

即 左根右 的 二叉搜索树结构

🦖⭐(2)根叶黑

       根和叶子(NULL)都是黑色

        划蓝色虚线的 节点 10 即为 根节点

        划蓝色虚线的 长方形节点,空节点 NULL

        这两种节点都必须是 黑色!



🦖⭐(3)不红红

不存在连续的两个红色结点

下图中 节点 7 和 节点 5 两个连续红节点的情况不合法,需要进一步调整(后序讲解)


🦖⭐(4)黑路同

任一结点到叶所有路径黑结点数量相同

下图中,每条路径的 黑色节点个数 都是 3 ,这就是合法

注意:一条路径的终点一定是 NULL 空节点!!!


🦖3. 为什么说 "红黑树的最长路径不会超过最短路径的两倍 "

因为所有路径黑色节点的数量必须相同(黑路同),同时 红色节点不能连续出现(不红红)

因此 最长路径一定是 一黑一红 的排列

则 最长的那条路径:即使下面再加一个 红色节点,也只是刚好 是最短路径的两倍,而绝对不会超过 最左边的最短路径


🦖4. 红黑树节点 定义

// 设置颜色枚举值
enum Colour {
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	typedef RBTreeNode<K, V> Node;

	pair<K, V> _data;  
	Node* _left;
	Node* _right;

	Node* _parent;
	Colour _col;

	RBTreeNode(const T& data)
		:_data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{}
};

🦖5. 红黑树的插入

🦖5.1 插入的节点 默认是 红色

为什么?看下面例子

🦖⭐当 插入的节点 7 为 黑色时

黑色节点 的 插入,必然会违反 ”黑路同“ 的性质,因此要对其他节点都进行调整(其他节点都要再加一个黑色节点)


当 插入的节点 7 为 红色 ,且 该节点插在 黑色节点 8 下面时

可以发现,插入一个 红色节点并没有影响 任何一个 红黑树的性质,即不用做出调整


当 插入的节点 7 为 红色 ,且 该节点插在 红色节点 8 下面时

若 在 红色节点后面插入一个 红色节点,会违反 ”不红红“ 的性质,则需要调整


综上所述,插入黑色节点就一定需要调整,插入红色节点却可能不需要调整

因此,插入红色节点的性价比最高

🦖5.2 插入节点后,若性质被破坏,分三种情况调整

(注意:是性质被破坏了,才需要调整,没被破坏不需要调整)

🦖(1)插入结点是根结点:

        直接将 节点变黑就行

如果非根节点:看叔叔颜色

🦖(2)插入结点的叔叔 uncle 是红色


该情况处理步骤:

1、将叔父爷变色(即除了自己 cur 以外的三个节点)


2、再将 cur 指向 爷爷

(然后继续对这个 cur 进行这 红黑树 4条性质的判定,看是否违反,即 从 cur 开始 继续向上调整)


🦖(3)插入结点的叔叔 uncle 是黑色:旋转 + 变色

注意:黑色节点也可以是 NULL 空节点


直接通过 判断 爷爷 grandfather、父亲 parent、自己 cur 三种节点之间的位置关系 来 判断使用哪一种 旋转

因为 单旋 LL型 和 RR 型的原理一致,双旋 LR 型 和 RL 型的原理一致

下面我就以 LL 型 和 LR 型举例讲解旋转的步骤

🦖LL 型:


1、以爷爷为旋转点,向右旋转

2、变色:爷变红,父变黑

🦖LR 型


1、先 以 father 为旋转点 旋转,再以 爷爷 为旋转点 旋转

2、爷变红,cur 变黑


🦖小结:都是固定步骤

单旋 LL型 和 RR 型

        旋转:以爷爷为旋转点 左旋 或 右旋(父亲为 旋转中心轴)

        变色:爷变红,父变黑

双旋 LR 型 和 RL 型

        旋转:先 旋转 父亲 father,再 旋转 爷爷

        变色:爷变红,cur 变黑


🦖5.3 插入节点中 旋转变色逻辑 代码讲解

根据上面的讲解,可以发现,决定红黑树 旋转 or 变色 的是  爷父子的位置关系 和 叔叔的颜色

因此代码逻辑 也要 以这两点为中心 设计

🦖伪代码:

因为插入节点是 红色,父亲为空时,违反 "不红红" 的性质,则进入循环执行调整

while ( 父亲不为空  同时  父亲的颜色为红色 )

{

        if ( 父亲是  爷爷 的 孩子 ) 

                if ( 叔叔是 色 ) 

                else if ( 叔叔是 色 ) 

                         if ( cur 是 父亲的 )

                         else if ( cur 是 父亲的 )

        else if ( 父亲是  爷爷 的 孩子 ) 

                if ( 叔叔是色 ) 

                else if ( 叔叔是 色 ) 

                         if ( cur 是 父亲的 左 )

                         else if ( cur 是 父亲的 )

}

🦖实际代码:

// 变色调整:
while (parent && parent->_col == RED) {

	Node* Grandfather = parent->_parent;
	/*
			   g
			p     u
	*/
	// 父亲是  爷爷 的左孩子
	if (parent == Grandfather->_left) {
		Node* Uncle = Grandfather->_right;

		// 叔叔是 红色:三人变色,cur指爷
		if (Uncle && Uncle->_col == RED) {
			parent->_col = BLACK;
			Uncle->_col = BLACK;
			Grandfather->_col = RED;

			cur = Grandfather;
			parent = cur->_parent;
		}
		// 叔叔是 黑色:旋转后变色
		else if (Uncle == nullptr || Uncle->_col == BLACK) {
			// 看 cur 的位置:决定单旋 or 双旋

			if (cur == parent->_left) {
				/*  右单旋 + 变色
							 g
						 p       u
					 c
				*/
				rotateLL(Grandfather);
				// 爷变红,父变黑
				Grandfather->_col = RED;
				parent->_col = BLACK;
			}
			else if (cur == parent->_right) {
				/*  双旋(先左旋后右旋) + 变色
							  g
						 p        u
							c
				*/
				rotateRR(parent);  // p 先 左旋
				rotateLL(Grandfather);  //  g 再右旋
				// 爷变红,cur 变黑
				Grandfather->_col = RED;
				cur->_col = BLACK;
			}
			break;  // 旋转后,退出循环
		}
	}

	// 父亲是  爷爷 的右孩子
	else if (parent == Grandfather->_right) {
		Node* Uncle = Grandfather->_left;

		// 叔叔是 红色:三人变色,cur指爷
		if (Uncle && Uncle->_col == RED) {
			parent->_col = BLACK;
			Uncle->_col = BLACK;
			Grandfather->_col = RED;

			cur = Grandfather;
			parent = cur->_parent;
		}
		// 叔叔是 黑色:旋转后变色
		else if (Uncle == nullptr || Uncle->_col == BLACK) {
			// 看 cur 的位置:决定单旋 or 双旋

			if (cur == parent->_right) {
				/*  左单旋 + 变色
							 g
						 u       p
									  c
				*/
				rotateRR(Grandfather);
				// 爷变红,父变黑
				Grandfather->_col = RED;
				parent->_col = BLACK;
			}
			else if (cur == parent->_left) {
				/*  双旋(先右旋后左旋) + 变色
							   g
						  u         p
								  c
				*/
				rotateLL(parent);  // p 先 右旋
				rotateRR(Grandfather);  //  g 再左旋
				// 爷变红,cur 变黑
				Grandfather->_col = RED;
				cur->_col = BLACK;
			}
			break;  // 旋转后,退出循环
		}
	}
}

// 根节点强制变色
_root->_col = BLACK;

🦖5.4 insert 函数 总代码

// 插入
bool insert(const pair<K, V>& kv) {
	if (_root == nullptr) {
		_root = new Node(kv);
		_root->_col = BLACK; // 根节点一定是黑的
		return true;
	}


	Node* cur = _root;
	Node* parent = cur;
	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 的位置插入该节点
	cur = new Node(kv);
	cur->_col = RED;  // 新增节点给 红的


	// 父连子,子连父

	if (parent->_kv.first > kv.first) parent->_left = cur;
	else  parent->_right = cur;
	cur->_parent = parent;


	// 变色调整:
	while (parent && parent->_col == RED) {

		Node* Grandfather = parent->_parent;
		/*
					g
				p     u
		*/
		// 父亲是  爷爷 的左孩子
		if (parent == Grandfather->_left) {
			Node* Uncle = Grandfather->_right;

			// 叔叔是 红色:三人变色,cur指爷
			if (Uncle && Uncle->_col == RED) {
				parent->_col = BLACK;
				Uncle->_col = BLACK;
				Grandfather->_col = RED;

				cur = Grandfather;
				parent = cur->_parent;
			}
			// 叔叔是 黑色:旋转后变色
			else if (Uncle == nullptr || Uncle->_col == BLACK) {
				// 看 cur 的位置:决定单旋 or 双旋

				if (cur == parent->_left) {
					/*  右单旋 + 变色
								 g
							 p       u
						 c
					*/
					rotateLL(Grandfather);
					// 爷变红,父变黑
					Grandfather->_col = RED;
					parent->_col = BLACK;
				}
				else if (cur == parent->_right) {
					/*  双旋(先左旋后右旋) + 变色
								  g
							 p        u
								c
					*/
					rotateRR(parent);  // p 先 左旋
					rotateLL(Grandfather);  //  g 再右旋
					// 爷变红,cur 变黑
					Grandfather->_col = RED;
					cur->_col = BLACK;
				}
				break;
			}
		}

		// 父亲是  爷爷 的右孩子
		else if (parent == Grandfather->_right) {
			Node* Uncle = Grandfather->_left;

			// 叔叔是 红色:三人变色,cur指爷
			if (Uncle && Uncle->_col == RED) {
				parent->_col = BLACK;
				Uncle->_col = BLACK;
				Grandfather->_col = RED;

				cur = Grandfather;
				parent = cur->_parent;
			}
			// 叔叔是 黑色:旋转后变色
			else if (Uncle == nullptr || Uncle->_col == BLACK) {
				// 看 cur 的位置:决定单旋 or 双旋

				if (cur == parent->_right) {
					/*  左单旋 + 变色
								 g
							 u       p
										  c
					*/
					rotateRR(Grandfather);
					// 爷变红,父变黑
					Grandfather->_col = RED;
					parent->_col = BLACK;
				}
				else if (cur == parent->_left) {
					/*  双旋(先右旋后左旋) + 变色
								   g
							  u         p
									  c
					*/
					rotateLL(parent);  // p 先 右旋
					rotateRR(Grandfather);  //  g 再左旋
					// 爷变红,cur 变黑
					Grandfather->_col = RED;
					cur->_col = BLACK;
				}
				break;
			}
		}
	}

	// 修改一:根节点强制变色
	_root->_col = BLACK;

	return false;
}

🦖6. 红黑树的删除

红黑树的删除本节不做讲解,有兴趣的同学可参考:《算法导论》或者《STL源码剖析》 http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html

🦖7. 红黑树与AVL树的比较

        之前讲到的AVL树,它是左右子树的高度相差不会超过一,可以发现 AVL树 对于平衡的要求会更加严格,因此 AVL树在树高上面要比红黑树控制的更加平衡,查询节点的时间复杂度为 logN

因为红黑树的最长路径 可以为最短路径的2倍,因此 查询节点的时间复杂度为 log 2*N

        所以在查询上面红黑树要略逊于  AVL树,当然在时间复杂度上都是同一个数量级,都是O(logN),差距不会太大

        恰恰因为 AVL树 要严格的控制树的平衡,因此 插入删除 操作后,旋转的次数较多

        而 红黑树 插入删除 操作中,旋转的次数较少

        所以相比之下, AVL树 在查询上边呢更高效;红黑树 在插入删除上边更高效

        在实际应用当中呢,红黑树 用的更广泛一些,比如说 C++的STL 当中的 map 和 set 都是基于红黑树实现的(下一个章节会讲解 【map 和 set 对红黑叔的封装】)

Java 库 、 linux内核 、其他一些库 都有使用 红黑树

🦖8. ⭐红黑树的完整代码

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

///

// 设置颜色枚举值
enum Colour {
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	typedef RBTreeNode<K, V> Node;

	pair<K, V> _kv;
	Node* _left;
	Node* _right;

	Node* _parent;
	Colour _col;

	RBTreeNode(const pair<K, V>& data)
		:_kv(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{}
};



template<class K, class V>
class RBTree
{
public:
	typedef RBTreeNode<K, V> Node;


	RBTree() = default;

	~RBTree() {
		destory(_root);
		_root = nullptr;
	}


	// 查找
	Node* find(const K& key) {
		Node* cur = _root;
		while (cur) {
			if (key > cur->_kv.first) {
				cur = cur->_right;
			}
			else if (key < cur->_kv.first) {
				cur = cur->_left;
			}
			else {
				return cur;
			}
		}
		return nullptr;
	}

	// 插入
	bool insert(const pair<K, V>& kv) {
		if (_root == nullptr) {
			_root = new Node(kv);
			_root->_col = BLACK; // 根节点一定是黑的
			return true;
		}


		Node* cur = _root;
		Node* parent = cur;
		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 的位置插入该节点
		cur = new Node(kv);
		cur->_col = RED;  // 新增节点给 红的


		// 父连子,子连父

		if (parent->_kv.first > kv.first) parent->_left = cur;
		else  parent->_right = cur;
		cur->_parent = parent;


		// 变色调整:
		while (parent && parent->_col == RED) {

			Node* Grandfather = parent->_parent;
			/*
						g
					p     u
			*/
			// 父亲是  爷爷 的左孩子
			if (parent == Grandfather->_left) {
				Node* Uncle = Grandfather->_right;

				// 叔叔是 红色:三人变色,cur指爷
				if (Uncle && Uncle->_col == RED) {
					parent->_col = BLACK;
					Uncle->_col = BLACK;
					Grandfather->_col = RED;

					cur = Grandfather;
					parent = cur->_parent;
				}
				// 叔叔是 黑色:旋转后变色
				else if (Uncle == nullptr || Uncle->_col == BLACK) {
					// 看 cur 的位置:决定单旋 or 双旋

					if (cur == parent->_left) {
						/*  右单旋 + 变色
									 g
								 p       u
							 c
						*/
						rotateLL(Grandfather);
						// 爷变红,父变黑
						Grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else if (cur == parent->_right) {
						/*  双旋(先左旋后右旋) + 变色
									  g
								 p        u
									c
						*/
						rotateRR(parent);  // p 先 左旋
						rotateLL(Grandfather);  //  g 再右旋
						// 爷变红,cur 变黑
						Grandfather->_col = RED;
						cur->_col = BLACK;
					}
					break;
				}
			}

			// 父亲是  爷爷 的右孩子
			else if (parent == Grandfather->_right) {
				Node* Uncle = Grandfather->_left;

				// 叔叔是 红色:三人变色,cur指爷
				if (Uncle && Uncle->_col == RED) {
					parent->_col = BLACK;
					Uncle->_col = BLACK;
					Grandfather->_col = RED;

					cur = Grandfather;
					parent = cur->_parent;
				}
				// 叔叔是 黑色:旋转后变色
				else if (Uncle == nullptr || Uncle->_col == BLACK) {
					// 看 cur 的位置:决定单旋 or 双旋

					if (cur == parent->_right) {
						/*  左单旋 + 变色
									 g
								 u       p
											  c
						*/
						rotateRR(Grandfather);
						// 爷变红,父变黑
						Grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else if (cur == parent->_left) {
						/*  双旋(先右旋后左旋) + 变色
									   g
								  u         p
										  c
						*/
						rotateLL(parent);  // p 先 右旋
						rotateRR(Grandfather);  //  g 再左旋
						// 爷变红,cur 变黑
						Grandfather->_col = RED;
						cur->_col = BLACK;
					}
					break;
				}
			}
		}

		// 修改一:根节点强制变色
		_root->_col = BLACK;

		return false;
	}

	// RR型:左单旋
	void rotateRR(Node* parent) {
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* parentParent = parent->_parent;

		// 1、subRL变成parent的右孩子
		parent->_right = subRL;
		// subRL 是有可能为 空的
		if (subRL) {
			subRL->_parent = parent;
		}

		// 2、parent变成subR的左孩子
		subR->_left = parent;
		parent->_parent = subR;


		// 3、subR变成当前子树的根
		// parentParent 是指 刚开始的 parent 的父亲:若 parent 是 _root 则 parentParent 为空,否则不为空,则该树就是子树
		if (parentParent) {
			if (parent == parentParent->_right)
				parentParent->_right = subR;
			else parentParent->_left = subR;

			subR->_parent = parentParent;
		}
		// 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空
		else {
			_root = subR;
			subR->_parent = nullptr;
		}
	}

	// LL型:右单旋
	void rotateLL(Node* parent) {
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* parentParent = parent->_parent;

		// 1、subLR变成parent的左孩子
		parent->_left = subLR;
		// subRL 是有可能为 空的
		if (subLR) {
			subLR->_parent = parent;
		}



		// 2、parent变成subL的右孩子
		subL->_right = parent;
		parent->_parent = subL;


		// 3、subL 变成当前子树的根
		// parentParent 是指 刚开始的 parent 的父亲:若 parent 是 _root 则 parentParent 为空,否则不为空,则该树就是子树
		if (parentParent) {
			if (parent == parentParent->_right)
				parentParent->_right = subL;
			else parentParent->_left = subL;

			subL->_parent = parentParent;
		}
		// 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空
		else {
			_root = subL;
			subL->_parent = nullptr;
		}
	}

	// LR 型:subL 先 左旋, parent 右旋
	void rotateLR(Node* parent) {
		rotateRR(parent->_left);
		rotateLL(parent);
	}

	// RL 型:subR 先 右旋, parent 左旋
	void rotateRL(Node* parent) {
		rotateLL(parent->_right);
		rotateRR(parent);
	}

	// 中序遍历
	void InOrder() {
		_InOrder(_root);
		cout << '\n';
	}


	// 获取该树的高度
	int Height() {
		return _Height(_root);
	}

	// 获取节点个数
	int Size() {
		return _Size(_root);
	}
	 
	// 判断是否是 红黑树
	bool IsValidRBTree() {
		if (_root == nullptr) return false;
		else if (_root && _root->_col == RED) return false;

		// 遍历一条路,记录一条路上一共固定有多少个黑色节点
		int cnt = 0;
		Node* cur = _root;
		while (cur) {
			if (cur->_col == BLACK) cnt++;
			cur = cur->_left;
		}
		return _IsValidRBTree(_root, 0, cnt);
	}

private:

	//  判断是否是 红黑树
	bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
	{
		// 1、看根节点是否是 黑的
		// 2、看每条路径的 黑色节点数量是否相同
		// 3、检查是否有连续的红节点:遇到一个红节点就判断其父亲是否是 红的


		//走到null之后,判断 k 和 blackCount 是否相等:即一条路径上的 黑色节点数量是否为固定值
		if (pRoot == nullptr)
		{
			if (k != blackCount)
			{
				cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}

		// 统计黑色节点的个数
		if (pRoot->_col == BLACK)
			k++;

		// 检测当前节点与其双亲是否都为红色
		Node* pParent = pRoot->_parent;
		if (pParent && pParent->_col == RED && pRoot->_col == RED)
		{
			cout << "违反性质三:没有连在一起的红色节点" << endl;
			return false;
		}
		return _IsValidRBTree(pRoot->_left, k, blackCount) && _IsValidRBTree(pRoot->_right, k, blackCount);
	}


	int _Size(Node* pRoot) {
		if (pRoot == nullptr) return 0;
		//if (pRoot->_left == nullptr && pRoot->_right == nullptr) return 1;

		return 1 + _Size(pRoot->_left) + _Size(pRoot->_right);
	}
	int _Height(Node* pRoot) {
		if (pRoot == nullptr)
			return 0;

		return 1 + max(_Height(pRoot->_left), _Height(pRoot->_right));
	}


	// 销毁一棵树:后序遍历
	void destory(Node* root) {
		if (root == nullptr) {
			return;
		}

		destory(root->_left);
		destory(root->_right);
		delete root;
	}


	void _InOrder(const Node* root) {
		if (root == nullptr) {
			return;
		}
		_InOrder(root->_left);
		cout << (root->_kv).first << " : " << (root->_kv).second << '\n';
		_InOrder(root->_right);
	}

	Node* _root = nullptr;
};



参考文献和资料

B站 up :蓝不过海呀

【红黑树 - 定义, 插入, 构建】https://www.bilibili.com/video/BV1Xm421x7Lg?vd_source=bea8fdb0eb9c0c7d500ffd191a292977

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

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

相关文章

关于c++ grpc 和 c# grpc 通信的问题 以及 grpc 认证问题

一、c 和 c# 通信 c# 端服务器 如果域名 输入的是 https &#xff0c;则 c 端需要匹配使用&#xff0c;也就是c 端需要进行安全认证。如果是http 则c 端不需要认证&#xff08;基于c#的grpc 未 通信成功&#xff09; 参考如下网址可以写一个简单的 .net grpc服务器 &#xff08…

基于java的综合小区管理系统论文.doc

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统综合小区管理系统信息管理难度大&#xff0c;容错率低&am…

diamond安装与使用

1.前言 diamond是一款用于蛋白质和翻译后DNA搜索的序列比对工具&#xff0c;专为大规模序列数据的高性能分析设计。其主要特点包括&#xff1a; - 与BLAST相比&#xff0c;蛋白质和翻译后DNA的成对比对速度快100倍至10000倍。 2. 参考 https://github.com/bbuchfink/diamond …

微知-lspci如何查看pcie设备树状结构(-t)

对于查看pcie设备列表除了看是否存在 还需要看拓扑结构。如何看&#xff1f; lspci -t以减号为分割说明 第一列数字是域段 和 bus id。比如0000:00中0000是域 00是busid 第二列 01.2中01是device id。2是functionid 如果还有下游设备device还有一个指定busid的序号

[数据集][目标检测]夜间老鼠检测数据集VOC+YOLO格式316张1类别+视频文件1个

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;316 标注数量(xml文件个数)&#xff1a;316 标注数量(txt文件个数)&#xff1a;316 标注类别…

I.MX6U交叉编译Qt项目-思维导图-学习笔记-基于正点原子阿尔法开发板

I.MX6U交叉编译Qt项目 安装交叉编译器 交叉编译器介绍 拷贝fsl-imx-x11-glibc-x86_64-meta-toolchain-qt5-cortexa7hf-neon-toolchain-4.1.15-2.1.0.sh至ubuntu 执行下面的指令修改脚本的权限&#xff0c;修改权限后可以看到此脚本颜色显示改变&#xff0c;说明修改成功 chmod…

理解Flink算子链

前言 对于flink初学者,如果我们观察WebUi中任务执行情况,可能会有一个疑惑,为什么节点和代码中的算子对不上? 从WebUi上看起来像是一个节点,会把转换处理的很多个任务都连接在一起,合并成了一个“大任务”。这又是怎么回事呢? 算子间的数据传输 我们先来考察一下算子…

Vue 旋转动画效果

目录 前言效果演示具体代码实现 前言 这里记录一个旋转动画&#xff0c;在鼠标经过的时候停止&#xff0c;鼠标离开继续旋转。 实现思路&#xff1a; 利用keyframes关键字定义一个旋转动画 效果演示 具体代码实现 <template><div class"container"><…

Cuda 基于Cmake创建Cuda项目

文章目录 一、简介1.1GPU的优势1.2 CUDA:通用并行计算平台和编程模型 二、Cuda项目三、实现效果参考资料 一、简介 1.1GPU的优势 图形处理单元(GPU)1在相同的价格和功率范围内提供比CPU高得多的指令吞吐量和内存带宽。许多应用程序利用这些更高的功能在GPU上比在CPU上运行得更…

ESP32-C3在MQTT访问时出现“transport_base: Poll timeout or error”问题的分析(7)

接前一篇文章:ESP32-C3在MQTT访问时出现“transport_base: Poll timeout or error”问题的分析(6) 上一回说到笔者分析base_poll_write函数中的select()返回的结果可能并不是超时而是负值,并且在base_poll_write函数中添加了打印,如下所示: 当时想着是更新固件并烧录之后…

学习笔记 韩顺平 零基础30天学会Java(2024.8.20)

P522 HashSet源码解读1 P523 HashSet源码解读2 开发技巧&#xff1a;在需要辅助变量或局部变量的时候再创建 P524 HashSet源码解读3 当单链表超过8个&#xff0c;但是还可以扩容的时候&#xff0c;将会把整条链表放到扩容后的最后应该位置上&#xff08;由老师讲解的16到32引起…

Windows SDK(八)模态对话框与非模态对话框

对话框的创建 对话框分为模态对话框和非模态对话框两种形式 模态对话框&#xff1a;自建消息循环&#xff0c;并且通过EnableWindow禁用了父窗口的键盘鼠标输入达到阻塞父窗口的功能。当有对话框&#xff0c;父窗口阻塞时&#xff0c;透过对话框是无法对父窗口进行操作的&…

【一起学Rust | 框架篇 | Tauri2.0框架】tauri中rust和前端的相互调用(rust调用前端)

文章目录 前言1. rust中调用前端2. 如何向前端发送事件3. 前端监听事件4. 执行js代码 前言 近期Tauri 2.0 rc版本发布&#xff0c;2.0版本迎来第一个稳定版本&#xff0c;同时官方文档也进行了更新。Tauri是一个使用Rust构建的框架&#xff0c;可以让你使用前端技术来构建桌面…

Redis7基础篇(六)

redis复制 目录 redis复制 前引 概念 能干嘛 怎么玩 基本操作 案例演示 前期配置 实操 常用三招 一主二仆 薪火相传 反客为主 复制原理和工作流程 复制的缺点 前引 redis复制这一篇有承上启下的作用 前面的 redis几乎是单机版 一台redis 概念 能干嘛 水平扩容…

收银系统源码-购物卡与会员卡的区别

很多门店都会推出一些只有指定商品才可以使用的购物卡或不同额度、不同折扣的消费卡&#xff0c;用来进行一些商品的促销或会员储值。如今时代实体卡容易丢失&#xff0c;很多人出门不愿意带钱包卡包。如果您有这样的功能需求&#xff0c;快来看看千呼新零售2.0购物卡功能吧&am…

[FSCTF 2023]寻找蛛丝马迹

点一下&#xff0c;看到只有页面背景颜色变化 查看一下页面源码吧&#xff0c;发现第一段flag 访问一下styles.css,看到顶部有一串乱码&#xff08;应该是flag一部分&#xff09;我们用火狐的修复文字编码工具 进行修复后显示 然后再访问script.js,依旧是乱码&#xff0c;同样…

数据湖之Delta Lake

Delta Lake&#xff1a;数据湖存储层概述 Delta Lake 是一种开源的存储层技术&#xff0c;构建在 Apache Spark 的基础之上&#xff0c;旨在解决传统数据湖的可靠性、性能和数据一致性问题。它通过引入 ACID 事务、数据版本控制、时间旅行和统一的批处理与流处理等特性&#x…

【SCI/EI/SCOPUS/CNKI】第三届先进材料与装备制造国际会议(AMEM2024)

会议日期&#xff1a;2024年12月28-30日 会议地点&#xff1a;中国-云南省-昆明市 会议官网&#xff1a;https://www.iaast.cn/meet/home/Bx93wRT 出版检索&#xff1a;EI、Scopus等数据库收录 【主办单位】 国际应用科学与技术协会(IAAST) 【主讲嘉宾】 【论文出版与检…

20 数据可视化

20 数据可视化 本章概述一. `elasticsearch`实现数据统计1.1 创建用户信息索引1.1.1 控制台创建`aggs_user`索引1.1.2 `aggs_user`索引结构初始化1.1.3 创建`aggs_user`索引的`EO`对象1.1.4 用户类型枚举1.1.5 数据初始化****************************************************…

引入本地iconfont图标

iconfont-阿里巴巴图标库官网&#xff0c;搜索想要的图标 1、加入购物车 2、添加至项目 头部的资源管理&#xff0c;点我的项目 找到对应的项目&#xff0c;点击 下载至本地&#xff0c;是个zip压缩包 解压缩一下&#xff0c;copy一下文件&#xff0c;放到项目里&#xff0c;…