C++【红黑树】

✨个人主页: 北 海
🎉所属专栏: C++修行之路
🎃操作环境: Visual Studio 2019 版本 16.11.17

成就一亿技术人


文章目录

  • 🌇前言
  • 🏙️正文
    • 1、认识红黑树
      • 1.1、红黑树的定义
      • 1.2、红黑树的性质
      • 1.3、红黑树的特点
    • 2、红黑树的插入操作
      • 2.1、抽象图
      • 2.2、插入流程
      • 2.3、单纯染色
      • 2.4、左单旋 + 染色
      • 2.5、右左双旋 + 染色
      • 2.6、具体实现代码
      • 2.6、注意事项及调试技巧
    • 3、AVL树 VS 红黑树
      • 3.1、红黑树的检验
      • 3.2、性能对比
  • 🌆总结


🌇前言

红黑树是平衡二叉搜索树中的一种,红黑树性能优异,广泛用于实践中,比如 Linux 内核中的 CFS 调度器就用到了红黑树,由此可见红黑树的重要性。红黑树在实现时仅仅依靠 红 与 黑 两种颜色控制高度,当触发特定条件时,才会采取 旋转 的方式降低树的高度,使其平衡

csf调度器


🏙️正文

1、认识红黑树

红黑树德国·慕尼黑大学Rudolf Bayer 教授于 1978 年发明,后来被 Leo J. GuibasRobert Sedgewick 修改为如今的 红黑树

大佬
红黑树 在原 二叉搜索树 节点的基础上,加上了 颜色 Color 这个新成员,并通过一些规则,降低二叉树的高度

如果说 AVL 树是天才设计,那么 红黑树 就是 天才中的天才设计,不同于 AVL 树的极度自律,红黑树 只在条件符合时,才会进行 旋转降高度,因为旋转也是需要耗费时间的

红黑树在减少旋转次数时,在整体性能上仍然没有落后 AVL 树太多

先来一睹 红黑树 的样貌

红黑树

注:红黑树在极限场景下,与 AVL 树的性能差不超过 2

1.1、红黑树的定义

红黑树 也是 三叉链 结构,不过它没有 平衡因子,取而代之的是 颜色

图示

红黑树 的节点定义如下:(这里是通过 枚举 定义的颜色)

//枚举出 红、黑 两种颜色
enum Color
{
	RED, BLACK
};

//红黑树的节点类
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(std::pair<K, V> kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)	//默认新节点为红色,有几率被调整
	{}

	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	std::pair<K, V> _kv;

	Color _col;
};

注意: 定义新节点时,颜色可以为 红 也可以为 黑,推荐为 红色,具体原因后面解释

1.2、红黑树的性质

结构上的绝妙注定了其离不开规则的限制红黑树 有以下几条性质:

  1. 每个节点不是 红色 就是 黑色
  2. 根节点是 黑色 的
  3. 如果一个节点是 红色 的,那么它的两个孩子节点都不能是 黑色 的(不能出现连续的红节点)
  4. 对于每个节点,从该节点到其所有后代的 NIF 节点的简单路径上,都包含相同数目的黑色节点(每条路径上都有相同数目的 黑色 节点)
  5. 每个叶子节点的 nullptr 称为 NIF 节点,并且默认为黑色,此处黑色仅用于路径判断,不具备其他含义

在这些规则的限制之下,红黑树 就诞生了

图示

红黑树 的性质还是比较重要的,可以花点时间结合图示深入理解

1.3、红黑树的特点

红黑树 在插入节点后,可以选择新节点为

  • 如果为 红 ,可能违反原则3,需要进行调整
  • 如果为 黑,必然违反原则4,并且因为这一条路,影响了其他所有路径,调整起来比较麻烦

因此 推荐在插入时,新节点默认为 红色,插入后,不一定调整,即使调整,也不至于 影响全局

图示

显然,红黑树 中每条路径都是 红黑相间 的,因为不能出现连续的 红节点,所以 黑节点的数量 >= 红节点

也就是说:红黑树中,最长路径至多为最短路径的两倍

  • 最长路径:红黑相间
  • 最短路径:全是黑节点

上图中的 最短路径3最长路径4,当然,最短路径 可以为 2

对于 AVL 树来说,下面这个场景必然 旋转降高度,但 红黑树 就不必,因为 没有违背性质

图示

综上, 红黑树 是一种折中且优雅的解决方案,不像 AVL 那样极端(动不动就要旋转),而是只有触发特定条件时,才会发生旋转,并且在极端场景下, 两者查询速度差异不过 2 倍,但在插入、删除、修改等可能涉及旋转的操作中,红黑树就领先太多了

假设在约 10 亿 大小的数据中进行查找

  • AVL 树至多需要 30 次出结果
  • 红黑树至多需要 60 次出结果

但是,区区几十次的差异,对于 CPU 来说几乎无感,反而是频繁的旋转操作令更费时间

记住:红黑树在实际中性能更好,适用性更强;AVL 树适用存储静态、不轻易修改的数据


2、红黑树的插入操作

2.1、抽象图

在演示 红黑树 的插入操作时,也需要借助 抽象图,此时的 抽象图 不再代表高度,而是代表 黑色节点 的数量

图示

抽象图中关注的是 黑色节点 的数量

2.2、插入流程

红黑树 的插入流程也和 二叉搜索树 基本一致,先找到合适的位置,然后插入新节点,当节点插入后,需要对颜色进行判断,看看是否需要进行调整

插入流程:

  • 判断根是否为空,如果为空,则进行第一次插入,成功后返回 true
  • 找到合适的位置进行插入,如果待插入的值比当前节点值大,则往 右 路走,如果比当前节点值小,则往 左 路走
  • 判断父节点与新节点的大小关系,根据情况判断链接至 左边 还是 右边
  • 根据颜色,判断是否需要进行 染色、旋转 调整高度

整体流程如下(不包括染色调整的具体实现)

bool Insert(const std::pair<K, V> kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = BLACK;	//根节点一定是黑色
		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 = new Node(kv);
	if (parent->_kv.first < kv.first)
		parent->_right = cur;
	else
		parent->_left = cur;
	cur->_parent = parent;

	//判断是否需要 染色、旋转
	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;	//祖父节点
		//……
	}
	
	return true;
}

红黑树 如何调整取决于 叔叔,即 父亲兄弟节点

并且如果 父亲,直接插入就行了,不必调整

如果 父亲为红 并且 叔叔也为红,可以只通过 染色 解决当前问题,然后向上走,继续判断是否需要调整

如果 父亲为红 并且 叔叔为黑 或者 叔叔不存在,此时需要 旋转 + 染色根据当前节点与父亲的位置关系,选择 单旋 或 双旋,值得一提的是 旋转 + 染色 后,不必再向上判断,可以直接结束调整

关于旋转的具体实现,这里不再展开叙述,可以复用 AVL 中的旋转代码,并且最后不需要调整平衡因子

《C++【AVL树】》

注意: 红黑树的调整可以分为 右半区 和 左半区 两个方向(根据 grandfatherparent 的位置关系而定),每个方向中都包含三种情况:单纯染色、单旋+染色、双旋+染色,逐一讲解费时费力,并且两个大方向的代码重复度极高,因此 下面的旋转操作基于 右半区

左半区 的操作和 右半区 基本没啥区别,可以去完整代码中求证

2.3、单纯染色

如果 父亲 为黑色,则不需要调整,不讨论这种情况,下面三种情况基本要求都是:父亲为红

当新节点插入后,如果 叔叔 节点也为 红色,那么可以通过将 祖父 节点的黑色素下放给 父亲和叔叔祖父节点 变为 红色,这样调整仍可确保 每条路径中的黑色节点数目相同

单次染色还不够,需要从 grandfather 处继续向上判断是否需要 调整单纯染色后,向上判断可能会变成其他情况,这是不确定的,具体情况具体分析

单纯染色 的操作如下:

注意:c 表示当前节点,p 表示父亲节点,u 表示叔叔节点,g 表示祖父节点

单纯染色
修正: 动图中语句修正为 “父亲为红,叔叔也为红,直接染色即可”

单次染色 结束后,更新 curgrandfather 的位置,并同步更新 parent,继续判断是需要进行 单纯染色单旋 + 染色 还是 双旋 + 染色

本质:将公共的黑色下放给两个孩子

代码片段如下(右半分区)

//在右半区操作
Node* uncle = grandfather->_left;	//叔叔节点

if (uncle && uncle->_col == RED)
{
	//染色、向上更新即可
	grandfather->_col = RED;
	parent->_col = uncle->_col = BLACK;
	cur = grandfather;
	parent = cur->_parent;
}
else
{
	//此时需要 旋转 + 染色
	//……
}

叔叔 存在且为 很好处理,难搞的是 叔叔 不存在或 叔叔,需要借助 旋转 降低高度

注意: 此时的五个抽象图,都代表同一个具象图;如果 parent 为空,证明 cur 为根节点,此时需要把根节点置为 黑色,在返回 true 前统一设置即可

2.4、左单旋 + 染色

单旋:右右、左左,此时在 右半区,所以当 叔叔 不存在或者为 黑色 且节点位于 父亲右边 时,可以通过 左单旋 降低高度

如果在左半区,节点位于父亲的左边时,则使用 右单旋 降低高度

在高度降低后,需要使用 染色 确保符合 红黑树 的性质

旋转 思想很巧妙,在 旋转 + 染色 后,可以跳出循环,结束调整

左旋转 + 染色 的操作如下:

注意:c 表示当前节点,p 表示父亲节点,u 表示叔叔节点,g 表示祖父节点

图示

显然,旋转 + 染色 后,parent 是一定会被修改为 黑色 的,所以不必再往上判断调整,因为现在已经很符合性质了(即使 parent 的父亲是 红色,也不会出现连续的 红色节点

本质:parent 的左孩子托付给 grandfather 后,parent 往上提,并保证不违背性质

代码片段如下(右半分区)

//在右半区操作
Node* uncle = grandfather->_left;	//叔叔节点

if (uncle && uncle->_col == RED)
{
	//染色、向上更新即可
	//……
}
else
{
	//此时需要 旋转 + 染色
	if (parent->_right == cur)
	{
		//右右,左单旋 ---> parent 被提上去了
		RotateL(grandfather);
		grandfather->_col = RED;
		parent->_col = BLACK;
		cur->_col = RED;
	}
	else
	{
		//右左,右左双旋 ---> cur 被提上去了
		//……
	}

	//旋转后,保持平衡,可以结束调整
	break;
}

注意: 这种情况多半是由 单纯染色 转变而来的,所以不同区域的抽象图有不同的情况,必须确保能符合红黑树的性质

2.5、右左双旋 + 染色

双旋:右左、左右,此时在 右半区,所以当 叔叔 不存在或者为 黑色 且节点位于 父亲左边 时,可以通过 右左双旋 降低高度

如果在左半区,节点位于父亲的右边时,则使用 左右双旋 降低高度

在高度降低后,需要使用 染色 确保符合 红黑树 的性质

旋转 思想很巧妙,在 旋转 + 染色 后,可以跳出循环,结束调整

右左双旋 + 染色 的操作如下:

注意:c 表示当前节点,p 表示父亲节点,u 表示叔叔节点,g 表示祖父节点

图示

双旋 其实就是两个不同的 单旋,不过对象不同而已,先 右旋转 parent,再 左旋转 grandfather 就是 右左双旋

本质:cur 的右孩子托付给 parent,左孩子托付给 grandfather 后,把 cur 往上提即可,并保证不违背 红黑树 的性质

代码片段如下(右半分区)

Node* grandfather = parent->_parent;	//祖父节点
if (grandfather->_right == parent)
{
	//在右半区操作
	Node* uncle = grandfather->_left;	//叔叔节点

	if (uncle && uncle->_col == RED)
	{
		//染色、向上更新即可
		//……
	}
	else
	{
		//此时需要 旋转 + 染色
		if (parent->_right == cur)
		{
			//右右,左单旋 ---> parent 被提上去了
			//……
		}
		else
		{
			//右左,右左双旋 ---> cur 被提上去了
			RotateR(parent);
			RotateL(grandfather);
			grandfather->_col = RED;
			parent->_col = RED;
			cur->_col = BLACK;
		}

		//旋转后,保持平衡,可以结束调整
		break;
	}

注意: 双旋的情况也可以由 单纯变色 转变而来,同样的,不同区域的抽象图代表不同的含义;对 parent 进行右单旋,对 grandfather 进行左单旋

2.6、具体实现代码

总的来说,红黑树 的插入操作其实比 AVL 还要略显简单,画图分析后,确认如何 染色 就行了,下面是插入操作的完整源码(包括左、右单旋)

插入

bool Insert(const std::pair<K, V> kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = BLACK;	//根节点一定是黑色
		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 = new Node(kv);
	if (parent->_kv.first < kv.first)
		parent->_right = cur;
	else
		parent->_left = cur;
	cur->_parent = parent;

	//判断是否需要 染色、旋转
	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;	//祖父节点
		if (grandfather->_right == parent)
		{
			//在右半区操作
			Node* uncle = grandfather->_left;	//叔叔节点

			if (uncle && uncle->_col == RED)
			{
				//染色、向上更新即可
				grandfather->_col = RED;
				parent->_col = uncle->_col = BLACK;
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//此时需要 旋转 + 染色
				if (parent->_right == cur)
				{
					//右右,左单旋 ---> parent 被提上去了
					RotateL(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
					cur->_col = RED;
				}
				else
				{
					//右左,右左双旋 ---> cur 被提上去了
					RotateR(parent);
					RotateL(grandfather);
					grandfather->_col = RED;
					parent->_col = RED;
					cur->_col = BLACK;
				}

				//旋转后,保持平衡,可以结束调整
				break;
			}
		}
		else
		{
			//在左半区操作
			Node* uncle = grandfather->_right;	//叔叔节点

			//同理,进行判断操作
			if (uncle && uncle->_col == RED)
			{
				//直接染色
				grandfather->_col = RED;
				parent->_col = uncle->_col = BLACK;
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//需要 旋转 + 染色
				if (parent->_left == cur)
				{
					//左左,右单旋 ---> parent 被提上去
					RotateR(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
					cur->_col = RED;
				}
				else
				{
					//左右,左右双旋 ---> cur 被提上去
					RotateL(parent);
					RotateR(grandfather);
					grandfather->_col = RED;
					parent->_col = RED;
					cur->_col = BLACK;
				}

				break;
			}
		}
	}

	//再次更新根节点的颜色,避免出问题
	_root->_col = BLACK;
	return true;
}

左单旋

//左单旋
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	//将 subR 的孩子交给 parent
	parent->_right = subRL;
	if (subRL != nullptr)
		subRL->_parent = parent;

	//提前保存 parent 的父节点信息
	Node* pparent = parent->_parent;

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

	//更新 subR 的父亲
	if (pparent == nullptr)
	{
		//此时 parent 为根,需要改变根
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
		//根据不同情况进行链接
		if (pparent->_right == parent)
			pparent->_right = subR;
		else
			pparent->_left = subR;
		subR->_parent = pparent;
	}
}

右单旋

//右单旋
void RotateR(Node* parent)
{
	//基本原理和左单旋一致

	Node* subL = parent->_left;
	Node* subLR = subL->_right;

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

	Node* pparent = parent->_parent;

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

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

关于左右单旋的详细细节,可以去隔壁 AVL 树的文章查看,红黑树 这里不必阐述

2.6、注意事项及调试技巧

红黑树 这里也涉及很多等式 == 判断,一定要多多注意,不要漏写 =

三叉链 结构,要注意 父指针 的调整

红黑树 的调整情况如下:

  • 右半区
    • 右右 左单旋
    • 右左,右左双旋
  • 左半区
    • 左左,右单旋
    • 左右,左右双旋

得益于前面 AVL 树旋转操作的学习,红黑树 这里在编写 旋转 相关代码时,没什么大问题

红黑树DeBug 逻辑与 AVL 树一致,这里额外分享一个 DeBug 技巧:

  • 当 随机插入 数据出错时,可以借助文件读写操作,将出错的数据保存下来,然后再次输入,反复进行调试,即可找出 Bug
  • 因为是 随机插入 时出现的问题,所以需要保存一下数据样本

关于 红黑树 详细操作可以参考这篇 Blog:《红黑树(C++实现)》


3、AVL树 VS 红黑树

AVL 树 和 红黑树平衡二叉搜索树 的两种优秀解决方案,既然两者功能一致,那么它们的实际表现如何呢?可以通过大量随机数插入,得出结果

当然,在切磋之前,需要先验证一下之前写的 红黑树 的正确性

3.1、红黑树的检验

可以借助红黑树 的性质,从下面这三个方面进行检验:

  • 验证根节点是否为 黑色节点
  • 验证是否出现连续的 红色节点
  • 验证每条路径中的 黑色节点 数量是否一致

判断黑色节点数量,需要先获取 基准值

  • 简单,先单独遍历一遍,其中的路径,这里选择了最左路径,将这条路径中获取的黑色节点数作为基准值,传给函数判断使用

孩子不一定存在,但父亲一定存在(当前节点为 红色 的情况下)

  • 所以当节点为 红色 时,判断父亲是否为黑色,如果不是,则非法!
		//合法性检验
		bool IsRBTree() const
		{
			if (_root->_col != BLACK)
			{
				std::cerr << "根节点不是黑色,违反性质二" << std::endl;
				return false;
			}

			//先统计最左路径中的黑色节点数量
			int benchMark = 0;	//基准值
			Node* cur = _root;
			while (cur)
			{
				if (cur->_col == BLACK)
					benchMark++;
				cur = cur->_left;
			}

			//统计每条路径的黑色节点数量,是否与基准值相同
			int blackNodeSum = 0;
			return _IsRBTree(_root, blackNodeSum, benchMark);
		}

protected:
		bool _IsRBTree(Node* root, int blackNodeSum, const int benchMark) const
		{
			if (root == nullptr)
			{
				if (blackNodeSum != benchMark)
				{
					std::cerr << "某条路径中的黑色节点数出现异常,违反性质四" << std::endl;
					return false;
				}
				return true;
			}

			if (root->_col == BLACK)
			{
				blackNodeSum++;
			}
			else
			{
				//检查当前孩子的父节点是否为 黑节点
				if (root->_parent->_col != BLACK)
				{
					std::cerr << "某条路径中出现连续的红节点,违反性质三" << std::endl;
					return true;
				}
			}

			return _IsRBTree(root->_left, blackNodeSum, benchMark) && _IsRBTree(root->_right, blackNodeSum, benchMark);
		}

通过代码插入约 10000 个随机数,验证是否是红黑树

结果

鉴定为 合法,并且高度有 16,比 AVL 树略高一层(情理之中)

3.2、性能对比

红黑树 不像 AVL 树那样过度自律,其主要优势体现在 插入数据 时的效率之上,可以通过程序对比一下

void RBTreeTest2()
{
	srand((size_t)time(NULL));

	AVLTree<int, int> av;
	RBTree<int, int> rb;

	int begin1, begin2, end1, end2, time1 = 0, time2 = 0;

	int n = 100000;
	int sum = 0;
	for (int i = 0; i < n; i++)
	{
		int val = (rand() + i) * sum;
		sum ++;

		begin1 = clock();
		av.Insert(make_pair(val, val));
		end1 = clock();
		
		begin2 = clock();
		rb.Insert(make_pair(val, val));
		end2 = clock();

		time1 += (end1 - begin1);
		time2 += (end2 - begin2);
	}

	cout << "插入 " << sum << " 个数据后" << endl;
	cout << "AVLTree 耗时: " << time1 << "ms" << endl;
	cout << "RBTree 耗时: " << time2 << "ms" << endl;
	cout << "=================================" << endl;
	cout << "AVLree: " << av.IsAVLTree() << " | " << "高度:" << av.getHeight() << endl;
	cout << "RBTree: " << rb.IsRBTree() << " | " << "高度:" << rb.getHeight() << endl;
}

图示

此时数据量太小了,还不能体现 红黑树 的价值,还好这次测试,红黑 比 AVL

红黑树还是有实力的

红黑树setmap 的底层数据结构,在下篇文章中,将会进一步完善 红黑树,并用我们自己写的 红黑树 封装 set / map,最后可以和库中的切磋一下~

本文中涉及的源码:《RBTree 博客》


🌆总结

以上就是本次关于 C++【红黑树】的全部内容了,在本文中,我们首先了解了什么是 红黑树,然后对其进行了实现,作为数据结构中的大哥,红黑树 还是有一定难度的,作为被广泛使用的优秀数据结构,简单掌握还是很有必要的


星辰大海

相关文章推荐

C++ 进阶知识

C++【AVL树】

C++【set 和 map 学习及使用】

C++【二叉搜索树】

C++【多态】

C++【继承】

STL 之 泛型思想

C++【模板进阶】

C++【模板初阶】

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

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

相关文章

三分钟学习一个python小知识1-----------我的对python的基本语法的理解

文章目录 一、变量定义二、数据类型三、条件语句四、循环语句五、函数定义总结 一、变量定义 在Python中&#xff0c;使用等号&#xff08;&#xff09;进行变量的定义&#xff0c;并不需要声明变量的类型&#xff0c;Python会自动根据赋值的数据类型来判断变量的类型&#xf…

chatgpt赋能python:Python构造和析构:介绍和实例

Python 构造和析构&#xff1a;介绍和实例 当你编写 Python 程序时&#xff0c;你可能会注意到一个名为构造函数和析构函数的概念。这些函数可以在创建和删除一个对象时自动执行一些操作。本文将深入介绍 Python 中的构造和析构概念。 构造函数 Python 使用一种名为 __init_…

戴尔U盘重装系统Win10步骤和详细教程

戴尔电脑深受用户们的喜欢&#xff0c;那么如何使用U盘给戴尔电脑重装Win10系统呢&#xff0c;这让很多用户都犯难了&#xff0c;以下就是小编给大家分享的戴尔U盘重装系统Win10步骤和详细教程&#xff0c;按照这个教程操作&#xff0c;就能顺利完成戴尔U盘重装Win10系统的操作…

3、互联网行业及产品经理分类

上一篇文章&#xff1a;2、产品经理的工作内容_阿杰学编程的博客-CSDN博客 1、产品经理分类 我们把产品经理划分成这样两个大的类型&#xff0c;一个是传统行业的&#xff0c;一个是互联网行业的。这个简单了解一下就行。 这个里面会发现绝大多数也是体育劳动&#xff0c;你比…

Nautilus Chain:模块化Layer3的先行者

“模块化特性的 Nautilus Chain 正在成为 Layer3 的早期定义者之一&#xff0c;并有望进一步推动区块链更广泛的应用与实践 ” 自以太坊创始人 Vitalik Buterin 在去年提出 Layer3 的概念后&#xff0c;行业始终对“Layer3”进行讨论&#xff0c;并期望推动该概念&#xff0c;从…

微服务框架

流量入口Nginx 在上图中可以看到&#xff0c;Nginx作为整个架构的流量入口&#xff0c;可以理解为一个外部的网关&#xff0c;它承担着请求的路由转发、负载均衡、动静分离等功能。作为一个核心入口点&#xff0c;Nginx肯定要采用多节点部署&#xff0c;同时通过keepalived来实…

【云原生 · Docker】轻松学会dockerfile构建镜像

目录 &#x1f349;dockerfile是什么 &#x1f349;镜像的缓存特性 &#x1f349;dockerfile命令 &#x1f352;FROM &#x1f352;RUN &#x1f352;CMD &#x1f352;LABEL &#x1f352;EXPOSE &#x1f352;ENV &#x1f352;ADD &#x1f352;COPY &#x1f352;ENTRYPOIN…

Background-1 基础知识 sqli-Labs Less1-Less-4

文章目录 一、Less-1二、Less-2三、Less-3四、Less-4总结 一、Less-1 http://sqli:8080/Less-1/?id1在第一关我们可以尝试增加一个单引号进行尝试 http://sqli:8080/Less-1/?id1错误显示如下&#xff1a; near 1 LIMIT 0,1 at line 1推测语法的结构 select *from where **…

【从零开始学习JAVA | 第六篇】面向对象综合训练

目录 前言&#xff1a; 1.文字版格斗游戏&#xff1a; 2.对象数组1 前言&#xff1a; 前面我们已经讲解了JAVA面向程序对象思想的关键要素&#xff1a;封装。我们将利用本篇进行几个小型的练习&#xff0c;帮助我们更好的理解面向对象编程这种思想。 1.文字版格斗游戏&#x…

SpringSecurity过滤指定url【.antMatchers(***).permitAll()】失效问题

SpringSecurity过滤指定url【.antMatchers(***).permitAll()】失效问题 问题描述 在使用SpringSecurity作为后端验证框架时&#xff0c;遇到配置一些接口不需要token验证&#xff0c;直接放行&#xff0c;但是配置之后没有生效&#xff0c;一直究其原因。 项目配置 因为要进…

ES6相关概念

什么是ES6&#xff1f; ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。 为什么使用 ES6 ? 每一次标准的诞生都意味着语言的完善&#xff0c;功能的加强。JavaScript语言本身也有一些令人不满意的地方。 变量提升特性增加了程序运行…

SpringBoot整合jwt+redis+随机验证码+Vue的登录功能

一、运行效果展示 &#xff01;注意&#xff1a;前端的Vue项目中要引入element-ui和axios # npm安装element-ui、axios npm insatll element-ui -S npm install axios -S # 在main中引入 // 引入ElementUI import ElementUI from element-ui import element-ui/lib/theme-chalk…

大数据Doris(四十七):开启Steam Load记录

文章目录 开启Steam Load记录 一、停止 Doris 集群 二、在 node3-node5 BE 节点上配置 be.conf 三、重新启动 Doris 集群 开启Steam Load记录 后续执行Stream Load 导入任务后&#xff0c;我们会在Doris集群中会查询对应Stream Load任务的情况&#xff0c;默认BE是不记录S…

【UE】滑动UI

效果 步骤 1. 新建一个控件蓝图&#xff0c;这里命名为“WBP_Slide” 2. 在关卡蓝图添加如下节点来显示控件蓝图 3. 打开“WBP_Slide”&#xff0c;添加一个滚动框控件 设置滚动框的锚点 设置滚动朝向为水平 在滚动框中添加一个画布面板 在画布面板中添加一个图像控件 由于我有…

STM32ARM体系结构(嵌入式学习)

STM32&ARM体系结构 1. STM321.1 简介1.2 STM32的优势1.3 命名规范 2. ARM体系结构2.1 ARM体系结构面试题&#xff1a;谈谈你对ARM的认识&#xff1f;1.ARM公司2.ARM处理器3.ARM技术 目前主流处理器架构&#xff1f;精简指令集RISC和复杂指令集CISC的区别&#xff1f;精简指…

电商数据分析方案:丰富经验护航,分析一步到位

如果做电商数据分析的每一步都从零开始&#xff0c;摸着石头过河&#xff0c;反复测试修改。一通忙活下来&#xff0c;成果没见多少&#xff0c;人力物力成本倒是节节攀升&#xff0c;试问又有多少企业承受得住&#xff1f;如果有一套一步到位的数据分析方案&#xff0c;是不是…

Linux学习[15]bash学习深入1---bash的功能---变量详解

文章目录 前言&#xff1a;1. bash功能2. 变量2.1 变量赋值2.2 unset取消变量2.3 环境变量 总结 前言&#xff1a; 之前在学树莓派相关内容的时候&#xff0c;对bash脚本的简单上手做了一个总结&#xff0c;并且归纳到下面三个博客。 当时参考的书为《从树莓派开始玩转linux》…

LwIP RAW API 实现UDP多播收发

LwIP RAW API 实现UDP多播收发实现 1、初始化 static struct udp_pcb *multicast_pcb NULL; static ip_addr_t mutlcast_send_ip; static ip_addr_t mutlcast_recv_ip;static void udp_recv_multicast(void *arg, struct udp_pcb *pcb, struct pbuf *p,const ip_addr_t *add…

结构化GPT用例,在CSDN私密社区中死磕@ada 探索SpringBoot

在CSDN私密社区中死磕ada 探索SpringBoot Q: Spring的核心概念是哪些&#xff1f;Q: Spring MVC的核心概念是哪些&#xff1f;Q: SpringBoot的核心概念有哪些&#xff1f;Q: 介绍下SpringBoot AutoConfiguration的机制。Q: SpringBootConfiguration 和 Configuration 的区别是&…

C# 学习(一)概述

今天开始学习 C#&#xff0c;所有学习资料来源于&#xff1a; 菜鸟教程 一、C# 简介 C# 是 .NET 框架的一部分&#xff0c;随之创造出来的语言&#xff0c;所以了解 C# 前&#xff0c;需要知道 .NET 是个什么东西。 1.1 .NET 框架介绍 .NET 是微软提出的 Web 的一种软件开发…