高阶数据结构——红黑树实现

news2025/5/29 9:00:49

目录

1.红黑树的概念

1.1 红黑树的规则:

1.2 红黑树的效率

2.红黑树的实现

2.1 红黑树的结构

2.2 红黑树的插入

2.2.1 不旋转只变色(无论c是p的左还是右,p是g的左还是右,都是一样的变色处理方式)

2.2.2 单旋+变色

2.2.3 双旋+变色

2.2.4 插入代码+总结:

2.3 红黑树的查找

2.4 红黑树的验证


1.红黑树的概念

咱们之前已经学过AVL树了,其实红黑树是一种能够控制平衡的二叉搜索树。咱们之前学习的AVL树,也是一种控制平衡的二叉搜索树,但是,AVL树,是通过平衡因子这个风向标来判断需不需要进行控制平衡,所以,AVL树的平衡很严格。但是红黑树就不一样了,红黑树有它的平衡判断原则,但是,这个的平衡的条件肯定比AVL树要松一些。可以想象为,AVL是一根绷紧的弦线,但是红黑树是一根比较松散的弦线。

红黑树是⼀棵二叉搜索树,他的每个结点增加⼀个存储位来表示结点的颜色,可以是红色或者黑色。 通过对任何⼀条从根到叶子的路径上各个结点的颜色进行约束,红黑树确保没有⼀条路径会比其他路 径长出2倍,因而是接近平衡的。

1.1 红黑树的规则:

那么咱们是如何判断一个树是红黑树的呢?

1. 每个结点不是红色就是黑色。

2. 根结点是黑色的。

3. 如果⼀个结点是红色的,则它的两个孩⼦结点必须是黑色的,也就是说任意⼀条路径不会有连续的 红色结点。(但是可以有连续的两个黑色节点)

4. 对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的黑色结点。

只要控制住了这四点规则,也就控制住了最长路径<=2*最短路径,也就控制住了红黑树的平衡

这里讲一下路径的问题:

请问这个红黑树有几条路径呢?

大家会不会说是2条?啊,恭喜大家,答错了。这里的路径是一定要把下面的空节点也给算上的!

而有些书上为了好让大家区分,就定义了一个NIL的黑色节点,这个其实就是代替了空节点,只不过更好数了而已。

并且,某一条路径上面,节点有几个,那么这条路径的长度就是几。

所以,这个红黑树是不是有6个路径啊!

那么,还是这个图,再来看一个问题:最短路径是多少?最长路径是多少?最短路径是2(18-10),最长路径是3 (18-30-40)。所以,咱们可以发现:

1.最短路径全是黑色节点。

2.最长路径一定小于等于最短路径的2倍。(当然在这幅图中,最长路径是小于最短路径的2倍的)。但是有一种极端的情况,他俩会相等。咱们待会说。

【1】关于上面的1.,如果一条路径全是黑色节点,那么这个路径一定是最短路径。但是,最短路径不一定是全黑节点。

所以,他俩的关系是这种。

【2】关于第二点:

 

• 由规则4可知,从根到NULL结点的每条路径都有相同数量的黑色结点,所以极端场景下,最短路径 就就是全是黑色结点的路径,假设最短路径长度为bh(blackheight)。

• 由规则2和规则3可知,任意⼀条路径不会有连续的红色结点,所以极端场景下,最长的路径就是⼀ 黑⼀红间隔组成,那么最长路径的长度为2*bh。

• 综合红黑树的4点规则而言,理论上的全黑最短路径和⼀黑⼀红的最长路径并不是在每棵红黑树都 存在的。假设任意⼀条从根到NULL结点路径的长度为h,那么bh<=h<=2*bh。

1.2 红黑树的效率

假设N是红黑树树中结点数量,h最短路径的长度,那么2h-1<=N<=2²*h-1,由此推出h ≈logN,也就是意味着红黑树增删查改最坏也就是走最长路径2 *logN,那么时间复杂度还是O(logN)。

红黑树的表达相对AVL树要抽象⼀些,AVL树通过高度差直观的控制了平衡。红黑树通过4条规则的颜 色约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对而言,插⼊相同数量的结点,红 黑树的旋转次数是更少的,因为他对平衡的控制没那么严格。

2.红黑树的实现

2.1 红黑树的结构

 // 枚举值表⽰颜⾊
 
enum Colour
 {
     RED,
     BLACK
 };
 // 这⾥我们默认按key/value结构实现
 
template<class K, class V>
 struct RBTreeNode
 {
 // 这⾥更新控制平衡也要加⼊parent指针
 
    pair<K, V> _kv;
    RBTreeNode<K, V>* _left;
    RBTreeNode<K, V>* _right;
    RBTreeNode<K, V>* _parent;
    Colour _col;
    RBTreeNode(const pair<K, V>& kv)
         :_kv(kv)
         , _left(nullptr)
         , _right(nullptr)
         , _parent(nullptr)
     {}
 };
 template<class K, class V>
 class RBTree
 {
     typedef RBTreeNode<K, V> Node;
 public:
 private:
     Node* _root = nullptr;
 };

来看一下红黑的大体的代码逻辑,这里与前面AVL树大体不同的是:这里没有了平衡因子bf了,取而代之的是颜色Colour,并且,枚举出两个颜色。

2.2 红黑树的插入

1. 插入⼀个值按二叉搜索树规则进行插入,插入后我们只需要观察是否符合红黑树的4条规则。

2. 如果是空树插入,新增结点是黑色结点。如果是非空树插入,新增结点必须红色结点,因为非空树 插入,新增黑色结点就破坏了规则4,规则4是很难维护的。

3. 非空树插入后,新增结点必须红色结点,如果父亲结点是黑色的,则没有违反任何规则,插入结束

4. 非空树插入后,新增结点必须红色结点,如果父亲结点是红色的,则违反规则3。进⼀步分析,c是 红色,p为红,g必为黑,这三个颜色都固定了(这句话很关键),关键的变化看u的情况,需要根据u分为以下几种 情况分别处理。

总的来说,插入黑色需要维护每条路径上的黑色节点数量,成本很大,所以插入红色节点。

说明:下图中假设我们把新增结点标识为c(cur),c的父亲标识为p(parent),p的父亲标识为 g(grandfather),p的兄弟标识为u(uncle)。

2.2.1 不旋转只变色(无论c是p的左还是右,p是g的左还是右,都是一样的变色处理方式)

c为红,p为红,g为黑,u存在且为红,则将p和u变黑,g变红。在把g当做新的c,继续往上更新。

分析:因为p和u都是红色,g是黑色,把p和u变黑,左边子树路径各增加⼀个黑色结点,g再变红,相 当于保持g所在子树的黑色结点的数量不变,同时解决了c和p连续红色结点的问题,需要继续往上更新 是因为,g是红色,如果g的父亲还是红色,那么就还需要继续处理(因为不可以有连续的两个红色节点)如果g的父亲是黑色,则处理结束 了;如果g就是整棵树的根,再把g变回黑色。

咱们先来看一种简单的情况:

上图就是简单的变色不需要旋转 。

来看这种情况的抽象图:

这是抽象图:这里c只能是原来就存在的节点,a,b,d,e,f均为抽象表示,说明其中还有节点是不固定的。所以这里的c不是新增,这样就可以与下面的节点圆一下,使得各个路径的黑色节点数量一致。但是若是c为新增,那么c下面的a,b可就啥都没了,那么这样的话,各个路径的黑色节点很难控制一致。

所以,这里的c为红色节点,是因为,你插入的时候,插入在了c的其中一个孩子节点的下面,再通过只变色不旋转的情况,使得c变成了红色节点。

【1】h==0

d/e/f代表每条路径拥有hb个黑色结点的子树,a/b代表每 条路径拥有hb-1个黑色结点的根为红的子树,hb>=0。

这里,c就一定是新增的节点了。如果这里c不是新增,那么要保持各个路径的黑色节点一致,很明显,如果,这儿为黑,不可以,因为右边路径的黑色节点只有一个。为红,也不可以,因为不能有连续的两个红色节点。

【2】h==1

【3】h==2

以上分别展示了hb==0/hb==1/hb==2的具体情况组合分析,当hb等于2时,这里组合 情况上百亿种,这些样例是帮助我们理解,不论情况多少种,多么复杂,处理方式⼀样的,变色再 继续往上处理即可,所以我们只需要看抽象图即可。

所以,殊途同归,这种情况的处理方式都是一样的,所以,不需要过多的关注底层,多看看上面,即可判断出该用什么方法。 

这里还是要提一嘴,不管c位于p的左还是右,p位于g的左还是右,处理方式都是一样的。但是这个只限于uncle存在且为红的情况。因为后面,这些不同,他们的处理方式是不同的。

2.2.2 单旋+变色

c为红,p为红,g为黑,u不存在或者u存在且为黑,u不存在,则c⼀定是新增结点,u存在且为黑,则 c⼀定不是新增,c之前是黑色的,是在c的子树中插入,符合情况1,变色将c从黑色变成红色,更新上 来的。(原因同上)

之前,uncle存在且为红是一种情况,且只有一种处理方式,但是这种uncle不存在或者uncle存在且为黑,是一种情况,这种情况会由于c,p的位置的不同而产生不同的解决办法,在这里,咱们先介绍单旋+变色,最后总结的时候再来总结其他的方法。

分析:p必须变黑,才能解决,连续红色结点的问题,u不存在或者是黑色的,这里单纯的变色无法解 决问题,需要旋转+变色。 

看这个形态,像不像右单旋的形态呢?没错,很像,没错,这里的解决办法就是右单旋。

如果p是g的左,c是p的左,那么以g为旋转点进行右单旋,再把p变黑,g变红即可。p变成课这颗树新 的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为p的父亲是黑色还是红色或者空都不违反规则。(上面是黑节点,可以有两个连续的黑节点,上面是红节点,黑红相接,没有任何问题。)

这个形态像不像左单旋的形态模型,没错,很像,所以用左单旋来解决。 

如果p是g的右,c是p的右,那么以g为旋转点进行左单旋,再把p变黑,g变红即可。p变成课这颗树新 的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为p的父亲是黑色还是红色或者空都不违反规则。 

看这个,uncle不存在的情况,如果只用单个的变色,无法解决问题,因为变色变到最后,根节点成红色的了。

来看uncle存在且为黑的抽象图:

hb==1:
也是一样,不需要太去关注底层的节点有多复杂啥的,多看看上面的形态是啥样的。

2.2.3 双旋+变色

c为红,p为红,g为黑,u不存在或者u存在且为黑,u不存在,则c⼀定是新增结点,u存在且为黑,则 c⼀定不是新增,c之前是黑色的,是在c的子树中插入,符合情况1,变色将c从黑色变成红色,更新上 来的。

 

这个看着像不像咱们的那个先左后右双旋?没错,这个也是同样的解决办法:

如果p是g的左,c是p的右,那么先以p为旋转点进行左单旋,再以g为旋转点进行右单旋,再把c变 黑,g变红即可。c变成课这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且 不需要往上更新,因为c的色亲是黑色还是红色或者空都不违反规则。 

这个像咱们的先右后左双旋转模型,没错,这个的处理方法也是这种:

如果p是g的右,c是p的左,那么先以p为旋转点进行右单旋,再以g为旋转点进行左单旋,再把c变 黑,g变红即可。c变成课这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且 不需要往上更新,因为c的父亲是黑色还是红色或者空都不违反规则 。

2.2.4 插入代码+总结:

OK,那么接下来来看关键的代码:

//插入
bool Insert(const 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 (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kv.first < cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;//这个是return false,不是assert(false)
		}

	}
	//插入红节点
	cur = new Node(kv);
	cur->_col = RED;
	if (kv.first > parent->_kv.first)
	{
		parent->_right = cur;
	}
	else if (kv.first < parent->_kv.first)
	{
		parent->_left = cur;
	}
	cur->_parent = parent;
	//现在才是真正的插入后调节红黑树的平衡的时候
	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)//先分parent是grandfather的左还是右这一大类
			//这样才可以确定uncle是grandfather的左还是右
			//之后,由于c的位置在哪都可以,所以不用讨论c的情况
			//但是若是uncle为空或者为黑色,就需要讨论c的位置了
		{
			Node* uncle = grandfather->_right;
			if (uncle && uncle->_col == RED)
			{
				//  g
				//p   u
			//	c

				parent->_col = uncle->_col = RED;
				grandfather->_col = BLACK;

				cur = grandfather;
				parent = cur->_parent;//如果此时的cur就是根节点
				//那么这个程序这个继续往上判断就没有必要了,别忘了根节点是黑色
			}
			else if(uncle==nullptr||uncle->_col==BLACK)
			{
				//    g
				//p        u
			//   c      
				if (cur == parent->_left)
				{
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				//    g
				//p        u
			//         c
				else if (cur == parent->_right)
				{
					RotateL(parent);
					RotateR(grandfather);
					parent->_col = grandfather->_col = RED;
					cur->_col = BLACK;
				}
			}
			break;
		}
		else if (parent == grandfather->_right)
		{
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_col == RED)
			{
				//  g
				//u   p
			//			c
				parent->_col = uncle->_col = RED;
				grandfather->_col = BLACK;

				cur = grandfather;
				parent = cur->_parent;//如果此时的cur就是根节点
				//那么这个程序这个继续往上判断就没有必要了,所以才要判空
				// 但要是这样的话,根节点此时就是红色的呀,这好办,在最后加一个
				// 总的:将根节点的颜色置为黑色即可。(虽然很暴力,但是很有用)
				//别忘了根节点是黑色
			}
			else if (uncle == nullptr || uncle->_col == BLACK)
			{
				//    g
				//u       p
				//           c
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				//    g
				//u        p
			//         c
				else if (cur == parent->_right)
				{
					RotateR(parent);
					RotateL(grandfather);
					parent->_col = grandfather->_col = RED;
					cur->_col = BLACK;
				}
			}
		}
		break;
	}
	_root->_col = BLACK;
	return true;
}

大家看这代码,听我来给大家总结一下:

 

以上这两张图,是我认为 本章最核心的部分,就是理清楚各个情况即可。

2.3 红黑树的查找

按二叉搜索树逻辑实现即可,搜索效率为 O(logN)。

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

	return nullptr;
}

2.4 红黑树的验证

这⾥获取最长路径和最短路径,检查最长路径不超过最短路径的2倍是不可行的,因为就算满足这个条 件,红黑树也可能颜色不满足规则,当前暂时没出问题,后续继续插入还是会出问题的。所以我们还 是去检查4点规则,满足这4点规则,⼀定能保证最长路径不超过最短路径的2倍。 (因为咱们前面说过,只有保证4点规则,才可以保证接下来的一切)

1.规则1枚举颜色类型,天然实现保证了颜色不是黑色就是红色。

2.规则2直接检查根即可。(如果根是红色,就直接返回false即可)

3.规则3前序遍历检查,遇到红色结点查孩子不太方便,因为孩子有两个,且不⼀定存在,反过来检 查父亲的颜色就方便多了。

4.规则4前序遍历,遍历过程中用形参记录跟到当前结点的blackNum(黑色结点数量),前序遍历遇到 黑色结点就++blackNum,走到空就计算出了⼀条路径的黑色结点数量。再任意⼀条路径黑色结点 数量作为参考值,依次比较即可。

 接下来来看代码:
 

bool Check(Node* root, int blackNum, const int refNum)
{
	if (root == nullptr)
	{
		// 前序遍历走到空时,意味着一条路径走完了
		if (blackNum != refNum)
		{
			cout << "存在黑色结点的数量不相等的路径" << endl;
			return false;
		}

		return true;
	}

	// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了
	if (root->_col == RED && root->_parent->_col == RED)
	{
		cout << root->_kv.first << "存在连续的红色结点" << endl;
		return false;
	}

	if (root->_col == BLACK)
	{
		++blackNum;
	}

	return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum);
}
bool IsBalanceTree()
{
	if (_root == nullptr)
		return true;

	if (_root->_col == RED)
		return false;

	// 参考值
	int refNum = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
		{
			++refNum;
		}
		cur = cur->_left;
	}

	return Check(_root, 0, refNum);
}

OK,还有一个红黑树的删除,还是一样,在这就不做讲解了,大家有兴趣的,可以下去自行研究。

本篇完............... 

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

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

相关文章

安卓学习笔记-声明式UI

声明式UI Jetpack Compose 是 Google 推出的用于构建 Android UI 的现代化工具包。它采用 声明式编程模型&#xff08;Declarative UI&#xff09;&#xff0c;用 Kotlin 编写&#xff0c;用于替代传统的 XML View 的方式。一句话概括&#xff1a;Jetpack Compose 用 Kotlin…

AI天气预报进入“大模型时代“:如何用Transformer重构地球大气模拟?

引言:从数值预报到AI大模型的范式变革 传统的天气预报依赖于数值天气预报(NWP, Numerical Weather Prediction),通过求解大气动力学方程(如Navier-Stokes方程)进行物理模拟。然而,NWP计算成本极高,依赖超级计算机,且难以处理小尺度天气现象(如短时强降水)。 近年来…

数据结构第3章 线性表 (竟成)

目录 第 3 章 线性表 3.1 线性表的基本概念 3.1.1 线性表的定义 3.1.2 线性表的基本操作 3.1.3 线性表的分类 3.1.4 习题精编 3.2 线性表的顺序存储 3.2.1 顺序表的定义 3.2.2 顺序表基本操作的实现 1.顺序表初始化 2.顺序表求表长 3.顺序表按位查找 4.顺序表按值查找 5.顺序表…

JAVA面试复习知识点

面试中遇到的题目&#xff0c;记录复习&#xff08;持续更新&#xff09; Java基础 1.String的最大长度 https://www.cnblogs.com/wupeixuan/p/12187756.html 2.集合 Collection接口的实现&#xff1a; List接口&#xff1a;ArraryList、LinkedList、Vector Set接口&#xff1a…

项目中的流程管理之Power相关流程管理

一、低功耗设计架构规划&#xff08;Power Plan&#xff09;   低功耗设计的起点是架构级的电源策略规划&#xff0c;主要包括&#xff1a;   电源域划分   基于功能模块的活跃度划分多电压域&#xff08;Multi-VDD&#xff09;&#xff0c;非关键模块采用低电压&#xf…

SLOT:测试时样本专属语言模型优化,让大模型推理更精准!

SLOT&#xff1a;测试时样本专属语言模型优化&#xff0c;让大模型推理更精准&#xff01; 大语言模型&#xff08;LLM&#xff09;在复杂指令处理上常显不足&#xff0c;本文提出SLOT方法&#xff0c;通过轻量级测试时优化&#xff0c;让模型更贴合单个提示。实验显示&#x…

《计算机组成原理》第 10 章 - 控制单元的设计

目录 10.1 组合逻辑设计 10.1.1 组合逻辑控制单元框图 10.1.2 微操作的节拍安排 10.1.3 组合逻辑设计步骤 10.2 微程序设计 10.2.1 微程序设计思想的产生 10.2.2 微程序控制单元框图及工作原理 10.2.3 微指令的编码方式 1. 直接编码&#xff08;水平型&#xff09; 2.…

【数据结构与算法】模拟

成熟不是为了走向复杂&#xff0c;而是为了抵达天真&#xff1b;不是为了变得深沉&#xff0c;而是为了保持清醒。 前言 这是我自己刷算法题的第五篇博客总结。 上一期笔记是关于前缀和算法&#xff1a; 【数据结构与算法】前缀和-CSDN博客https://blog.csdn.net/hsy1603914691…

PyTorch入门-torchvision

torchvision torchvision 是 PyTorch 的一个重要扩展库&#xff0c;专门针对计算机视觉任务设计。它提供了丰富的预训练模型、常用数据集、图像变换工具和计算机视觉组件&#xff0c;大大简化了视觉相关深度学习项目的开发流程。 我们可以在Pytorch的官网找到torchvision的文…

18、Python字符串全解析:Unicode支持、三种创建方式与长度计算实战

适合人群&#xff1a;零基础自学者 | 编程小白快速入门 阅读时长&#xff1a;约6分钟 文章目录 一、问题&#xff1a;Python的字符串是什么&#xff1f;1、例子1&#xff1a;多语言支持演示2、例子2&#xff1a;字符串不可变性验证3、答案&#xff1a;&#xff08;1&#xff09…

5月27日复盘-Transformer介绍

5月27日复盘 二、层归一化 层归一化&#xff0c;Layer Normalization。 Layer Normalizatioh和Batch Normalization都是用来规范化中间特征分布&#xff0c;稳定和加速神经网络训练的&#xff0c;但它们在处理方式、应用场景和结构上有本质区别。 1. 核心区别 特征BatchNo…

MyBatis-Plus一站式增强组件MyBatis-Plus-kit(更新2.0版本):零Controller也能生成API?

MyBatis-Plus-Kit &#x1f680; MyBatis-Plus-Kit 是基于MyBatis-Plus的增强组件&#xff0c;专注于提升开发效率&#xff0c;支持零侵入、即插即用的能力扩展。它聚焦于 免写 Controller、代码一键生成、通用响应封装 等核心场景&#xff0c;让您只需专注业务建模&#xff0…

实时数仓flick+clickhouse启动命令

1、启动zookeeper zk.sh start 2、启动DFS&#xff0c;Hadoop集群 start-dfs.sh 3、启动yarn start-yarn.sh 4、启动kafka 启动Kafka集群 bin/kafka-server-start.sh -daemon config/server.properties 查看Kafka topic 列表 bin/kafka-topics.sh --bootstrap-server local…

【Git】Commit Hash vs Change-Id

文章目录 1、Commit 号2、Change-Id 号3、区别与联系4、实际场景示例5、为什么需要两者&#xff1f;6、总结附录——Gerrit 在 Git 和代码审查工具&#xff08;如 Gerrit&#xff09;中&#xff0c;Commit 号&#xff08;Commit Hash&#xff09; 和 Change-Id 号 是两个不同的…

【KWDB创作者计划】_KWDB分布式多模数据库智能交通应用——高并发时序处理与多模数据融合实践

导读&#xff1a;本文主要探讨了基于KWDB的分布式多模数据库智能交通应用场景&#xff0c;进行了高并发时序处理与多模数据融合实践方向的思考。探索智慧交通领域的数据实时处理与存储资源利用方面的建设思路。 本文目录 一、智能交通数据架构革命   1.1 传统架构瓶颈  …

Java集合框架与三层架构实战指南:从基础到企业级应用

一、集合框架深度解析 1. List集合的武林争霸 ArrayList&#xff1a; 数组结构&#xff1a;内存连续&#xff0c;查询效率O(1) 扩容机制&#xff1a;默认扩容1.5倍&#xff08;源码示例&#xff09; private void grow(int minCapacity) {int oldCapacity elementData.len…

6个月Python学习计划 Day 2 - 条件判断、用户输入、格式化输出

6个月Python学习计划&#xff1a;从入门到AI实战&#xff08;前端开发者进阶指南&#xff09; Python 基础入门 & 开发环境搭建 &#x1f3af; 今日目标 学会使用 input() 获取用户输入掌握 if/else/elif 条件判断语法熟悉格式化输出方式&#xff1a;f-string、format() …

目标检测 TaskAlignedAssigner 原理

文章目录 TaskAlignedAssigner 原理和代码使用示例 TaskAlignedAssigner 原理和代码 原理主要是结合预测的分类分数和边界框与真实标注的信息&#xff0c;找出与真实目标最匹配的锚点&#xff0c;为这些锚点分配对应的目标标签、边界框和分数。 TaskAlignedAssigner 是目标检…

游戏:元梦之星游戏开发代码(谢苏)

《元梦之星》是一款轻松社交派对游戏,玩家们可以化身星宝,体验纯粹的游玩乐趣,收获简单的快乐。无论i人e人,都能轻松找到属于自己的社交方式。 《元梦之星》的快乐,可以是闯关夺冠时的激动&#xff0c;谁是狼人推理的巧妙&#xff0c;峡谷3V3打赢团战的爽感。也可以是星梦广场开…

TCP协议原理与Java编程实战:从连接建立到断开的完整解析

1.TCP协议核心&#xff1a;面向连接的可靠通信基石 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是互联网的“可靠信使”&#xff0c;属于传输层协议&#xff0c;其核心在于面向连接和可靠传输。它通过严谨的握手机制与数据控制逻辑&am…