C++【红黑树】

news2024/9/24 5:30:49

✨个人主页: 北 海
🎉所属专栏: 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++【模板初阶】

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

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

相关文章

chatgpt赋能python:Python中是否有局部变量?-完全解析

Python 中是否有局部变量&#xff1f;- 完全解析 Python 是一种高级编程语言&#xff0c;它因其易学、可读性强、开发速度快、功能丰富、能够快速交互、具有跨平台特性等方面而备受欢迎。其中一块关键功能是变量&#xff0c;变量可以存储值&#xff0c;以供稍后使用&#xff0…

[保姆级啰嗦教程] Tesseract OCR 5在Windows 10下编译安装及测试 (亲测成功)

作为一个优秀的文字识别&#xff08;OCR&#xff09;库&#xff0c;Tesseract最早并非开源软件&#xff0c;它是HP实验室在1985-1994年开发的专属软件&#xff0c;直到2005年&#xff0c;HP及内华达大学拉斯维加斯分校以开源的形式发布&#xff0c;然后由Google从2006年开始赞助…

[SpringBoot 分布式调度elasticjob 整合 ]

目录 &#x1f96b;前言: &#x1f96b;配置作业 &#x1f96b;实现任务处理类 &#x1f96b;启动SpringBoot应用程序 &#x1f96c;下面是代码是我另一个文章看见 记录的笔记, 我前面也使用了elastic-job做重试机制,有兴趣可以看一下 &#x1f96c;依赖: &#x1f96c;…

基于MATLAB的CFAR检测仿真程序分享

基于MATLAB的CFAR检测仿真&#xff0c;得到平均CFAR检测。 完整程序&#xff1a; clc; clear; close all; warning off; addpath(genpath(pwd)); cfar phased.CFARDetector(NumTrainingCells,200,NumGuardCells,50,Method,CA); % Expected probability of False Alarm (no u…

【瑞萨RA_FSP】CTSU——电容按键检测

文章目录 一、1. 电容按键介绍二、电容按键原理三、瑞萨QE在电容按键上面的运用四、电容按键实验1. 硬件设计2. FSP配置3.复制文件4.主函数 一、1. 电容按键介绍 电容式感应触摸按键可以穿透绝缘材料外壳 8mm &#xff08;玻璃、塑料等等&#xff09;以上&#xff0c;准确无误…

OpenStack(2)--项目(租户)、用户、角色

一、项目&#xff08;租户&#xff09;、用户、角色的关系 重点理解项目&#xff08;project/租户&#xff09;、用户&#xff08;user&#xff09;、角色&#xff08;role&#xff09;三者之间的关系&#xff0c;首先这三者都可以单独新建&#xff0c;但是绑定关系是通过open…

10 分钟玩转Elastcisearch——数据可视化分析

在当今这个快速发展的科技时代&#xff0c;Elasticsearch 已经成为企业和开发者的重要技术工具。随着数据的爆发式增长&#xff0c;Elasticsearch 可以帮助个人和企业更好的理解数据、发现数据中的规律趋势和模式、并从海量数据中洞察业务价值。 为了帮助开发者能够快速上手&am…

基于SpringBoot的电子文档管理系统(源码、文档、数据库)

网上文件管理系统所用的资料库是由 SpringBoot架构所建立的 Mysql资料库。在进行设计的时候&#xff0c;要充分地保证了系统代码拥有良好的可读性、实用性、易扩展性、通用性、便于后期维护、操作容易、页面简洁等优势。 一、开发工具及技术介绍 &#xff08;1&#xff09;.J…

JavaScript Day03 对象详解

文章目录 1. 什么是对象?2.对象的创建2.1 字面量模式2.2 构造函数模式 3 对象的访问4.新增删除对象中的属性5.Object显示类型转换(强制类型转换)5.1-ECMAScript中可用的3种强制类型转换如下&#xff1a;-Boolean(value)-String(value)-Number(value) 5.2-Object类型到Boolean类…

chatgpt赋能python:Python中的模块查找位置详解

Python中的模块查找位置详解 作为一门广受欢迎的高级编程语言&#xff0c;Python 拥有丰富的库和模块&#xff0c;这些工具让开发者能够更加高效地编写代码。但是&#xff0c;有时候当你在使用 Python 模块时&#xff0c;你可能会遇到找不到模块或者无法导入模块的错误。这时候…

【从零开始学习JAVA | 第四篇】标准的JavaBean类的构建

目录 前言&#xff1a; 构造方法&#xff1a; 构造标准JavaBean类&#xff1a; 对象内存图&#xff1a; 前言&#xff1a; 本片会详细的介绍JAVA中JavaBean类的手动构建&#xff0c;IDEA为我们提供的快捷键生成JAVA bean的方式&#xff0c;以及介绍一个可以快速生成…

OTT 的快速频道切换(FCC)

FCC&#xff08;Fast Channel Change&#xff09;快速频道切换是一种由FCC服务器下发特制的以I帧为起始的单播节目流来提升频道切换时间的方法, 通过在网络中部署FCC服务&#xff0c;可以回避等待I帧时间与IGMP交互时间&#xff0c;提升I帧传输时间&#xff0c;从而提高频道切换…

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

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

pkg打包nestjs项目问题点整理

打包运行过程中的警告 Warning Cannot include directory %1 into executable. The directory must be distributed with executable as %2. %1: node_modules\puppeteer.local-chromium %2: path-to-executable/puppeteer 解决方法&#xff1a; 警告大义为在路径如%1的某个目…

chatgpt赋能python:用Python进行nan值的查询

用 Python 进行 nan 值的查询 在数据分析和机器学习的过程中&#xff0c;我们经常会遇到 NaN 值&#xff0c;NaN 是代表不是数字的特殊值&#xff0c;通常意味着在数据中有缺失或者不可识别的数据。由于机器学习等技术需要处理的数据来源繁杂&#xff0c;有时候我们在数据处理…

第八章 总结【编译原理】

第八章 总结【编译原理】 前言推荐第八章 总结8.1 符号表的组织与作用8.1.1符号表的作用8.1.2符号表的组织方式 8.2 整理与查找8.2.1线性表8.2.2 对折查找与二叉树8.2.3杂凑技术 8.3 名字的作用范围8.3.1FORIRAN的符号表组织8.3.2Pascal的符号表组织 8.4 符号表的内容 最后 前言…

python基础学习4【Matplotlib、散点图、折线图绘制、读取存储不同数据源的数据(csv、txt、excel)、编码】

Matplotlib数据可视化基础&#xff08;绘图基础语法和常用参数&#xff09; 创建画布与子图 plt.figure()、plt.title()、plt.savefig()保存绘制的图、plt.show() 展示&#xff1a; plt.legend():创建图例 figure.add_subplot()&#xff1a;向figure添加一个Axes作为一subp…

极致呈现系列之:Echarts仪表盘的光影奇迹

目录 仪表盘介绍仪表盘的基本结构Echarts仪表盘的常用数据配置项Echarts仪表盘的常用样式配置项创建基本的仪表盘自定义仪表盘样式应用场景 仪表盘介绍 仪表盘通常模拟了传统汽车仪表盘的样式&#xff0c;包括刻度、指针、表盘等元素&#xff0c;用于显示单一指标或数据。它能…

【JavaWeb】前端之HTML基础认知

目录 1、第一个HTML程序 1.1、创建第一个HTML文件 1.2、HTML文件的基本结构 2、HTML常见标签 2.1、注释标签 2.2、标题标签&#xff1a;h1-h6 2.3、段落标签&#xff1a;p 2.4、换行标签&#xff1a;br 2.5、格式化标签 2.6、图片标签&#xff1a;img 2.7、超链接标签&…

vue源码理解之模板编译和组件化

一&#xff1a;模板编译 1、模板编译的主要目标是将模板(template)转换为渲染函数(render) template > render() 2、模板编译必要性 Vue 2.0需要用到VNode描述视图以及各种交互&#xff0c;手写显然不切实际&#xff0c;因此用户只需编写类似HTML代码的Vue模板&#xff0c;…