红黑树是如何实现的?

news2024/11/23 22:25:13

文章目录

  • 一、红黑树的概念
  • 二、红黑树的性质
  • 三、红黑树和AVL树对比
  • 四、红黑树的插入
    • 1. 红黑树的结点定义
    • 2. 父亲的颜色
    • 3. 叔叔的颜色为红色
    • 4. 叔叔不存在
    • 5. 叔叔存在且为黑
    • 6. 插入的抽象图
  • 五、红黑树的验证
    • 1. 检查平衡
    • 2. 计算高度与旋转次数
    • 3. 验证
  • 六、 红黑树与AVL树的比较

一、红黑树的概念

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

image-20230921131357000

二、红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点必须是黑色的(即任何路径没有连续的红色结点)
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(每条路径上的黑色结点个数相同,这里的路径包括空结点)
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点,已经不在是传统的叶子结点了,图中的NIL结点就是空结点)

这里对第四点做一补充说明

对于下面这棵树有11条路径,而不是5条路径,因为空结点也包括在内

image-20230921132948276

如果不清楚上面的这一点,如果遇到下面这棵树,可能会误以为他是红黑树,实际上它不是红黑树。

如果我们不看每个空结点的话,它看上去有四条路径,且每条路径都只有两个黑色结点,看上去好像是红黑树。但是实际上要包括空结点,且空结点是黑色的。所以一共有九条路径,最左边的一条路径只有两个黑色结点,其他路径都有三个黑色结点。

image-20230921133042188

第四点中说的虽然是每个结点的每条路径上的黑色结点个数相同,但是由于每个结点的祖先的路径是唯一确定的,所以我们只需要判断从根节点到每个空结点上的路径中黑色结点个数是否相同即可

那么为什么上面的性质可以保证最长路径不超过最长路径的两倍呢?

如下图所示, 最长路径就是黑红相间的情况,最短路径就是全黑的情况。其他路径都是在这两者之间的,此时我们也不难看出最长路径不超过最短路径的两倍。如果最短路径为N,那么最长路径就是2N-1,因为根节点一定是黑色的,最终的叶子结点也一定是黑色的。

image-20230921140144567

三、红黑树和AVL树对比

既然有了AVL树,那么为什么要有红黑树呢?其实是因为AVL树太严格了。它要控制平衡就需要付出代价。而红黑树要求并不严格,综合来看,红黑树的综合性能其实更优

AVL树红黑树
高度对比高度差不超过一最长路径不超过最短路径的两倍
插入1000个值logN---->102*logN---->20
插入10亿个值logN---->302*logN---->60

我们可以看到,虽然他们的高度有点差异,但是他们的效率都是同一个量级的,而且cpu的速度是非常快的,这点效率对于cpu几乎没有任何区别

性能是同一量级的,但是AVL树控制严格平衡是需要付出代价的,插入和删除需要大量的旋转。

四、红黑树的插入

1. 红黑树的结点定义

如下所示, 由于红黑树有红色结点和黑色结点两种颜色。所以不妨我们使用一个枚举类型来进行表示。红黑树里面我们需要有一个pair类型来进行存储key和value类型的数据。然后我们定义三个指针,分别指向父亲,左孩子,右孩子。最后定义其的颜色。在构造函数中,我们将其的颜色默认设置为红色。

设置为红色是比较有讲究的。那是因为我们大多数场景下都是去创建一个新节点去插入的。如果我们插入的这个新结点是黑色的话,那么造成的后果就是违反了规则4,即每条路径上的黑色结点个数相同,显然这种情况要进行补救的话是十分麻烦的。还不如去插入红色结点,如果插入的是红色结点的话,仅仅有可能会违反规则3,也就是红色结点的孩子必须是黑色结点。这一点的话,我们还有的补救,因为仅仅影响本条路径。

enum Color
{
	RED,
	BLACK
};

template<class K , class V>
struct RBTreeNode
{
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Color _color;
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_color(RED) // 插入红色结点,违反规则3,只影响一条路径,甚至可能不影响。如果插入黑色结点,违反规则4,影响所有路径
	{}
};

2. 父亲的颜色

因为我们要插入的结点是红色的,那么在我们插入的位置,它的父亲要么是红色的要么是黑色的。如果是黑色的,那么就是如下的情况

image-20230924165738466

我们可以看到,似乎并未影响整棵树的结构,不违反任何一条规则。最短路径仍然不超过最长路径的两倍。每条路径上的黑色结点个数仍然相等。所以如果插入一个新节点以后,如果此处它的父亲是黑色的,完美,不需要做出任何修改即可。如果父亲甚至都不存在,那么这个结点就是这颗树了。我们只需要将这个结点变为黑色即可。如果父亲是红色的话,那么就比较麻烦了。

如下图所示,就是插入的时候父亲为红色,显然已经违反了规则3了,此时我们需要对这颗树进行处理了。

image-20230924174004600

3. 叔叔的颜色为红色

如果我们插入了一个结点以后,此时,父亲结点为红色,且有父亲的兄弟,即叔叔,且叔叔的颜色是红色。

即如下的情况,uncle存在且为红

这样的话,我们可以将parent和uncle都变黑,然后让grandfather变红,即如下的情形

image-20230924181504937

这样的话,在黑色结点数量不变的条件下,使得连续红色结点的问题暂时解决了。现在原本cur为红色的问题转换为了grandfather为红色的问题。

此时的话,如果这个grandfather如果没有父亲了,那么根据规则1,我们只需要让他变为黑色即可。此时仅仅只是所有的路径多了一个黑色结点。如果grandfather有父亲,那么我们只需要让grandfather变为cur,继续向上处理即可

在这里继续向上处理的时候又分为以下几种情况

  1. grandfather没有父亲,那么直接让grandfather变黑即可
  2. grandfather有父亲,且父亲为黑色,那么由于前面的树本身就是满足红黑树规则,这里改变了之后仍然满足红黑树规则,那么不处理即可
  3. grandfather有父亲,且父亲为红色,这样的情况下,父亲为红色,就隐含了必然存在grandfather的grandfather,因为原来的树也要满足红黑树的规则,这样一来就是类似的问题继续往处理即可。

image-20230925163352597

然后由于此时恰好uncle存在且为红,那么我们只需要按照前面的逻辑进行处理即可,即uncle和parent均变黑,然后grandfather变为红

image-20230925163643420

然后又为了满足根节点为红,所以grandfather变为黑即可

image-20230925163816261

4. 叔叔不存在

如下图所示,当叔叔不存在的时候,我们还插入了一个新节点以后,我们发现最长路径已经超过了最短路径的两倍了。这时候单纯的进行变色,已经无法解决问题了。我们需要旋转了。

image-20230925164520373

所以我们就需要进行旋转+变色了。

  • 对于变色:与前面的情况类似,即本来parent和uncle都为红色的话,就把他们两个变为黑色,然后把grandfather变为红色就可以了。不过现在uncle不存在了。那我们就先不管它了,将parent变为黑色,grandfather变为红色就可以了。
  • 对于旋转:这个就需要我们进行具体的分析了,看看究竟是左旋还是右旋还是双旋。具体判断规则与AVL基本是一致的,如果是直线那么就是单旋,我们只需要分析谁高就可以了。如果是折线就是双旋,我们还是分析哪边高就可以了。

如上图所示的情形就是右单旋就可以了

image-20230925170559866

5. 叔叔存在且为黑

我们接着前面的图,我们继续插入一个新的结点

image-20230925171058231

那么此时的uncle存在且为红,我们进行相应的处理后,变为如下的情况

image-20230925171747589

这时候,我们遇到了新的情况,uncle存在且为黑

那么此时的处理情形就和前面的uncle不存在是一样的,直接旋转加变色。这里的如何旋转和变色统一结论后面详细讨论

image-20230925171823196

总结

  1. 红黑树的插入主要看uncle

  2. uncle存在且为红,变色+继续往上更新

  3. uncle不存在或uncle存在且为黑,旋转+变色

6. 插入的抽象图

  • 如下是第一种情况,当cur为红,p为红,g为黑,u存在且为红的条件下。

image-20230926163310656

在这种情况下,我们可以p和u改为黑色,g改为红,然后把g当成cur,继续向上调整。

image-20230926163631710

上面是我们的抽象图,我们如果要具体细化每一种情况的下是非常之麻烦的

比如说当a、b、c、d、e均为空,即cur是新增结点的时候,如下所示

image-20230926172512048

当a,b不为空,且cur是黑色结点。那么cde都是含有一个结点的子树,那么cde的每个样子都有四种情况,如下所示

image-20230926172700176

由于它可以在a,b这两个红色结点任意处进行插入,也就是说,可以在四个位置插入。

image-20230926172742003

那么这种变换的情况就复杂很多,有4*4*4*4共256种情况

如果cde有两个黑节点的话,那么情况将更加复杂,画图已经很难表示出来了

image-20230926174004784

显然我们如果用具象图的话,那么红黑树有无数种情况,所以我们只能使用抽象图来表示这种情况。

所以根据前面的分析,我们就可以写出如下的代码了。下面只是处理颜色的部分。

		//开始处理颜色
		while (parent && parent->_color == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandfather->_color = RED;

					cur = grandfather;
					parent = grandfather->_parent;
				}
				else if (uncle == nullptr || uncle->_color == BLACK)
				{

				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle&& uncle->_color = RED)
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandfather->_color = RED;

					cur = grandfather;
					parent = grandfather->_parent;
				}
				else if (uncle == nullptr || uncle->_color == BLACK)
				{

				}
			}

		}
  • 接下来,我们讨论第二种情况,即cur为红,p为红,g为黑,u不存在/u存在且为黑

image-20230929185023277

首先来分析uncle不存在的情况下,即如下的情况。此时我们可以注意到,由于uncle不存在,那么cur必然就是新插入的结点。此时我们就根据当前的cur与p的关系来确定是单旋还是双旋。旋转之后,进行变色。在这里的变色中,如果是单旋,就是g和p变色即可。如果是双旋,那么就是cur和g变色

image-20230929171305920

  1. p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反p为g的右孩子,cur为p的右孩子,则进行左单旋转

    p、g变色—p变黑,g变红

  2. p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,此时转化了为了第一步的情况

再来看一下uncle存在且为黑的情况下。由于uncle存在且为黑,所以cur之前必然为黑色的,因为为了满足每条路径上的黑色结点个数相同,就代表着,cur必须为先前向上处理后的,在向上处理过程中,cur变为了红色。

image-20230929185000121

当uncle存在且为黑的情况下,他的变色以及旋转规则都是与uncle不存在是一模一样的

  1. p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反p为g的右孩子,cur为p的右孩子,则进行左单旋转

    p、g变色—p变黑,g变红

  2. p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,此时转化了为了第一步的情况

所以最终插入的完整代码为

	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_color = BLACK; //根节点必须是黑色
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			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->_color == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandfather->_color = RED;

					cur = grandfather;
					parent = grandfather->_parent;
				}
				else if (uncle == nullptr || uncle->_color == BLACK)
				{
					if (parent->_left == cur)
					{
						RotateR(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
						break;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
						break;
					}
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandfather->_color = RED;

					cur = grandfather;
					parent = grandfather->_parent;
				}
				else if (uncle == nullptr || uncle->_color == BLACK)
				{
					if (parent->_right == cur)
					{
						RotateL(grandfather);
						grandfather->_color = RED;
						parent->_color = BLACK;

						break;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;

						break;
					}
				}
			}

		}

		_root->_color = BLACK;
		return true;
	}

	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		Node* ppnode = parent->_parent;
		//改变parent
		parent->_right = curleft;
		parent->_parent = cur;
		//改变curleft
		if (curleft != nullptr)
		{
			curleft->_parent = parent;
		}
		//改变cur
		cur->_left = parent;
		cur->_parent = ppnode;
		//改变ppnode
		if (ppnode == nullptr)
		{
			_root = cur;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}
		}
	}

	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		Node* ppnode = parent->_parent;

		//改变parent
		parent->_left = curright;
		parent->_parent = cur;
		//改变curright
		if (curright != nullptr)
		{
			curright->_parent = parent;
		}
		//改变cur
		cur->_right = parent;
		cur->_parent = ppnode;
		//改变ppnode
		if (ppnode == nullptr)
		{
			_root = cur;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}
		}
	}

五、红黑树的验证

当我们写完一个比较复杂的程序的时候,我们最好去写一个辅助代码去验证它

1. 检查平衡

如下代码所示,可以去检测我们实现的红黑树当插入数据以后是否会出现不平衡的现象。检查利用的就是每条路径上黑色结点个数相同与不能出现连续的两个红色结点这两条规则。

	bool IsBalance()
	{
		return IsBalance(_root);
	}
	bool IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		if (root->_color == RED)
		{
			return false;
		}
		int benchmark = 0;
		Node* cur = root;
		while (cur)
		{
			if (cur->_color == BLACK)
			{
				benchmark++;
			}
			cur = cur->_left;
		}
		return CheckColor(root, 0, benchmark);
	}
	bool CheckColor(Node* root, int blacknum, int benchmark)
	{

		//检查每条路径的黑色结点数量是否相等
		if (root == nullptr)
		{
			if (blacknum != benchmark)
			{
				return false;
			}
			return true;
		}
		if (root->_color == BLACK)
		{
			blacknum++;
		}
		//检查颜色
		if (root->_color == RED && root->_parent && root->_parent->_color == RED)
		{
			cout << root->_kv.first << ":" << "出现两个连续的红色结点" << endl;
			return false;
		}
		return CheckColor(root->_left, blacknum, benchmark) && CheckColor(root->_right, blacknum, benchmark);
	}

2. 计算高度与旋转次数

高度的代码很简单,如下所示

	int Height()
	{
		return Height(_root);
	}
	int Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftheight = Height(root->_left);
		int rightheight = Height(root->_right);

		return leftheight > rightheight ? 1 + leftheight : 1 + rightheight;
	}

对于旋转次数,我们可以直接设置一个变量专门计数。每旋转一次这个值加一次

	int Getrotatecount()
	{
		return _rotatecount;
	}

3. 验证

int main()
{
	RBTree<int, int> rb;
	srand(time(0));
	for (int i = 0; i < 10000; i++)
	{
		int tmp = rand();
		rb.Insert(make_pair(tmp, tmp));
		//rb.Insert(make_pair(i, i));

		//cout << rb.IsBalance() << endl;
		//cout << i << ":" << tmp << endl;
	}
	cout << "height:" << rb.Height() << endl;
	cout << "rotatecount:" << rb.Getrotatecount() << endl;
	cout << rb.IsBalance() << endl;
	return 0;
}

我们使用上面的随机数来进行测试

运行结果如下所示

image-20230929223920622

六、 红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是logN,红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

从源代码中我们也可以看出来,其实map与set的底层都是红黑树,而且是kv结构的红黑树

image-20230929224532662

image-20230929224609714

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

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

相关文章

QT使用前的知识

QT使用前的知识 常用的快捷键 源文件的内容解释 .pro文件的解释 头文件的解释 构建新的对象—组成对象树 槽函数 自定的信号和槽 槽函数的信号是一个重载函数时 电机按钮触发信号 调用无参数的信号 断开信号

GPT-4科研实践:数据可视化、统计分析、编程、机器学习数据挖掘、数据预处理、代码优化、科研方法论

查看原文>>>GPT4科研实践技术与AI绘图 GPT对于每个科研人员已经成为不可或缺的辅助工具&#xff0c;不同的研究领域和项目具有不同的需求。例如在科研编程、绘图领域&#xff1a;1、编程建议和示例代码: 无论你使用的编程语言是Python、R、MATLAB还是其他语言&#x…

详解C语言—文件操作

目录 1. 为什么使用文件 2. 什么是文件 3. 文件的使用 文件指针 文件的打开和关闭 三个标准的输入/输出流&#xff1a; 4. 文件的顺序读写 对字符操作&#xff1a; fputc&#xff1a; fgetc&#xff1a; 练习复制整个文件&#xff1a; 对字符串操作&#xff1a;…

C++ 并发编程实战 第七章 设计无锁数据结构

目录 7.1 定义和推论 7.1.1 非阻塞型数据结构 7.1.2 无锁数据结构 7.1.3 无需等待的数据结构 7.1.4 无锁数据结构的优点和缺点 7.2 无锁数据结构范例 7.2.1 实现线程安全的无锁栈 7.2.2 制止麻烦的内存泄漏&#xff1a;在无锁数据结构中管理内存 7.2.3 运用风险指针检…

排序:败者树和置换选择排序(解决外部排序中的优化问题)

1.算法目的&#xff08;败者树&#xff09; 解决多路平衡归并带来的问题。 在外部排序中&#xff0c;使用k路平衡归并策略, 选出一个最小元素需要对比关键字(k-1)次&#xff0c; 导致内部归并所需时间增加。&#xff08;可用“败者树”进行优化&#xff09; 2.败者树的定义 …

Spring源码分析(四) Aop全流程

一、Spring AOP基础概念 1、基础概念 连接点(Join point)&#xff1a;能够被拦截的地方&#xff0c;Spring AOP 是基于动态代理的&#xff0c;所以是方法拦截的&#xff0c;每个成员方法都可以称之为连接点&#xff1b;切点(Poincut)&#xff1a;每个方法都可以称之为连接点&…

从1开始的Matlab(快速入门)

MATLAB软件版本&#xff1a;MATLAB R2016b 本文是博主从零开始学Matlab的记录&#xff0c;适合第一次接触Matlab的同学阅读。 一、基础介绍 1.1界面认识 1.2变量命名 注&#xff1a;Matlab中的注释 %% 独占一行的注释&#xff08;有上下横线分割&#xff09; % 普通注释 …

Node.js 是如何处理请求的

前言&#xff1a;在服务器软件中&#xff0c;如何处理请求是非常核心的问题。不管是底层架构的设计、IO 模型的选择&#xff0c;还是上层的处理都会影响一个服务器的性能&#xff0c;本文介绍 Node.js 在这方面的内容。 TCP 协议的核心概念 要了解服务器的工作原理首先需要了…

【小白专属01】SpringBoot框架搭建

目录 前言 一、搭建环境 二、开始SpringBoot框架搭建 1. 打开IDEA 2. 新建项目 3. 选择版本和依赖 4. 目录结构 5. 启动项目 前言 上节回顾 上一节我们对智慧仓库管理系统的项目背景和项目效果进行的梳理&#xff0c;主要就是功能模块和实现步骤进行展示。想要从零开…

关于Adobe Acrobat Reader升级后界面布局变化-新旧布局的选择切换

Adobe Acrobat Reader 不知何时自动升级了&#xff0c;升级后界面布局发生了较大的变化&#xff1a;工具栏放到了左侧、书签栏放到了右侧。 如果对新界面布局不习惯&#xff0c;可在新界面下通过路径**【菜单】->【禁用新的 Acrobat Reader】、并重启 Acrobat Reader 后切…

【微信小程序】WXML模板语法

模板与绑定 1.数据绑定 1&#xff09;在data中定义数据 在页面对应的.js文件中把数据定义到data对象中即可 2&#xff09;在WXML中使用数据 把data中的数据绑定到页面中渲染&#xff0c;使用Mustache&#xff08;双大括号{{}}&#xff09;将变量名包起来即可&#xff0c;格式…

Codeforces Round 892 (Div. 2) - E. Maximum Monogonosity 思维dp 详细解析

题目链接 好久没有写题了复健一下qwq 题目大意 解题思路 这题目还挺妙的 首先考虑比较正常的dp&#xff0c; d p [ i ] [ j ] dp[i][j] dp[i][j] 为前 i i i的长度选 j j j个长度的最大价值&#xff0c;那么转移方程是&#xff1a; 图片来自&#xff1a;图片来源 但是这个是 …

(自学)黑客————网络安全技术

如果你想自学网络安全&#xff0c;首先你必须了解什么是网络安全&#xff01;&#xff0c;什么是黑客&#xff01;&#xff01; 1.无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面性&#xff0c;例如 Web 安全技术&#xff0c;既有 Web 渗透2.也有 Web 防…

2023 年 Bitget Wallet 测评

对Bitget Wallet钱包的看法 Bitget Wallet在安全性、产品实力和使用体验方面可与Metamask媲美&#xff0c;甚至有所超越&#xff0c;唯一稍显不足的是知名度稍逊一筹。在众多钱包中&#xff0c;Bitget Wallet是拥有最全面的钱包之一&#xff0c;尤其适合那些希望一步到位&…

Golang 协程池 Ants 实现原理,附详细的图文说明和代码

Golang 协程池 Ants 实现原理&#xff0c;附详细的图文说明和代码。 1 前置知识点 1.1 sync.Locker sync.Locker 是 go 标准库 sync 下定义的锁接口&#xff1a; // A Locker represents an object that can be locked and unlocked. type Locker interface {Lock()Unlock() …

【C++11】多线程

多线程创建线程thread提供的成员函数获取线程id的方式线程函数参数的问题线程join场景和detach 互斥量库&#xff08;mutex&#xff09;mutexrecursive_mutexlock_guard 和 unique_lock 原子性操作库&#xff08;atomic&#xff09;条件变量库&#xff08;condition_varuable&a…

Linux 回收内存到底怎么计算anon/file回收比例,只是swappiness这么简单?

概述 Linux内核为了区分冷热内存,将page以链表的形式保存,主要分为5个链表,除去evictable,我们主要关注另外四个链表:active file/inactive file,active anon和inactive anon链表,可以看到这主要分为两类,file和anon page,内存紧张的时候,内核开始从inactive tail定…

上网Tips: Linux截取动态效果图工具_byzanz

链接1 链接2 安装&#xff1a; sudo apt-get install byzanz 查看指令 说明 byzanz-record --help日常操作 xwininfo点击 待录制窗口 左上角 byzanz-record -x 72 -y 64 -w 1848 -h 893 -d 10 --delay5 -c /home/xixi/myGIF/test.gif小工具 获取鼠标坐标 xdotool getm…

windows下python开发环境的搭建 python入门系列 【环境搭建篇】

在正式学习Python之前要先搭建Python开发环境。由于Python是跨平台的&#xff0c;所以可以在多个操作系统上进行编程 一、python的下载安装与配置 1、Python解释器 1. 要进行Python开发&#xff0c;首先需要Python解释器&#xff0c;这里说的安装Python就是安装Python解释器…

测试必备 | 测试工程师必知的Linux命令有哪些?

在日常的测试工作中&#xff0c;涉及到测试环境搭建及通过查看日志来定位相关问题时经常会用到Linux&#xff0c;在测试工程师的面试中也经常会有笔试或面试的题目来考查测试人员对Linux的熟悉程度&#xff0c;这里分享下测试工程师需知的 Linux 命令有哪些。 Linux 作为一种常…