数据结构(六)——红黑树及模拟实现

目录

前言

红黑树的概念及性质

红黑树的效率

红黑树的结构

红黑树的插入

变色不旋转

单旋+变色

双旋+变色

插入代码如下所示:

红黑树的查找

红黑树的验证

红黑树代码如下所示:

小结


前言

在前面的文章我们介绍了AVL这一棵完全二叉搜索树,我们分析后发现AVL树查找的时间复杂度是O(logN),说明这是一个很优秀的数据结构,但是在底层实现我们发现,AVL树为了维持它的性质,是需要进行频繁的旋转的,这样会造成不必要的消耗,那么有没有一种数据结构既可保证它的时间复杂度为O(logN),又能避免频繁的旋转呢?这就是我们今天要介绍的红黑树,学习了红黑树之后我们将用红黑树作为底层,去封装实现一个模拟的map和set,下面让我们一起来学习吧。

参考文章:

据结构(五)——AVL树(平衡二叉搜索树)

红黑树的概念及性质

首先我们要知道什么是红黑树,与AVL树相比,红黑树的节点多了一个标志符号,这个标志符号表示节点的颜色,我们这里用枚举来表示,通过控制节点的颜色来判平衡以及判断是否需要旋转。由于红黑树的旋转不像AVL树那么频繁,所以它是一颗不完全平衡二叉搜索树。

下面我们引入路径的概念,我们把从根节点开始,一直到空节点的一条路线称为路径

它的规则如下所示:

规则一:根节点必须是黑色,其余节点的颜色不是黑色就是红色

规则二:每个红色节点的左右子树必须是红色的,即:不存在连续的红色节点

规则三:每条路径的黑色节点数量是相同的,其中我们把空节点视为黑色节点

规则四:最长路径的节点数量不超过最长路径节点数量的二倍

分析上面的规则我们发现,其中最关键也是最难维护的就是规则三,我们发现,在极端场景下,最短路径就是全为黑色节点的路径,最长路径就是红黑间隔的路径,所以维护好了规则三,规则四自然就实现了

红黑树的效率

由于有规则四的存在,我们设红黑树最短路径的节点数量是h,那么最长路径的节点数量就是2*h,我们设每条路径上黑色节点的数量是N,那么他们应该满足:2^{h}-1<=N<2^{2*h}-1,也就是说,如果我们要在红黑树当中进行增删查改,最少要走的长度是2^{h}-1,查找的次数就是log(N),最坏也就是查找2*log(N),分析下来,它的时间复杂度还是log(N)。

红黑树的结构

从上面的分析中我们知道,红黑树的大致框架与AVL树相似,只不过将平衡因子改成了用枚举表示的颜色,后续我们也是通过控制颜色来判平衡,因此红黑树的结构如下所示:

enum Colour
{BLACK,RED
};
template<class K, class V>
struct RBTreeNode
{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;
};

红黑树的插入

我们来分析红黑树的插入过程,首先由于它是一颗搜索树,所以按照搜索树的规则进行插入,更改插入结点的颜色,然后我们向上进行调整。

对于插入节点的颜色,我们发现,如果我们插入黑色,那么就在当前路径上增加了一个黑色节点,不符合每条路径上黑色节点数量相同这一规则,所以我们插入节点的颜色应该为红色。代码如下所示:

bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);_root->_col = 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{cout << "插入值已经存在,请重新插入" << endl;return false;}}cur = new Node(kv);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;
}

当我们插入红色节点后,会出下面几种情况:

情况一:父节点为黑色,不违反红黑树的规则,那么就停止向上调整

情况二:父节点为红色,违反规则,那么就需要向上调整

接下来我们重点分析情况二。由于不能有两个连续的红色节点,所以我们应当把父节点变成黑色,但是将父节点变成黑色后我们就在当前路径上凭空增加了一个黑色节点,这样破坏了黑色节点数量相同这一规则,那么我们就需要调整其他路径上黑色节点的数量,如果是这样的话,是不符合我们的预期的,我们再仔细观察红黑树的结构,我们把父节点的父节点定义为grandfather(祖父节点),那么祖父节点的另一个子节点称为uncle(叔节点),我们发现,无论什么情况,只要涉及到需要向上调整,祖父节点总是黑色的,此时,如果叔节点存在且为红色的话,我们就只需要将父节点和叔节点颜色变为黑色,再将祖父节点变为红色,这样就解决了这个问题;如果叔节点不存在或者存在且为空节点,那么此时我们就需要做特殊处理。

通过上述分析,我们总结一下红黑树的插入过程:

第一步:插入节点。按照二叉搜索树的规则插入节点,链接父节点,将插入节点的颜色改成红色,如果是根节点,就变为黑色。

第二步:向上调整黑色节点的数量,让其满足红黑树的规则。

接下来我们逐步来分析向上调整。

根据上面的分析我们知道,我们可以根据uncle节点的有无以及颜色,可以分为下面两种情况:

情况一:uncle节点存在且颜色为红色

情况二:uncle节点存在且为黑色或者uncle不存在

如下图所示:

对于情况一,我们只需要将父节点和uncle节点颜色变为黑色,然后再将祖父节点颜色变为红色,然后向上更新parent节点和grandfather节点,因此我们需要一个循环,循环结束的条件是:parent为空或者parent的颜色为黑色。

对于情况二,我们发现这种情况又需要分为两种场景,一种是插入节点是parent的左节点,另一种是插入节点是parent的右节点,这两种情景无论我们怎么变色都没有办法满足红黑树的规则,为此我们需要寻找一种新的解决办法。

回到红黑树的定义,我们知道,红黑树是一颗不完全平衡二叉搜索树,从前面的AVL树我们知道,要成为一颗平衡树,我们要做的就是旋转,而旋转分为左旋和右旋,那么我们尝试将AVL树的旋转带入红黑树当中,那么对于parent与cur都位于同一侧节点的这种情况,我们使用单旋,对于parent与cur位于不同一侧节点的这种情况,我们使用双旋。

综上所述,红黑树的插入可以分为以下三种不同的情况:

变色不旋转

条件:grandfather为黑,parent与cur为红色,uncle存在且为红。

解决办法:parent与cur变为黑色,grandfather变为红色,然后再将grandfather当成新的cur,继续向上更新。

分析:由于cur与parent都是红色,所以我们应该把parent变为黑色,这样就在当前节点凭空增加了一个黑色节点,从而导致黑色节点失衡;由于uncle是红色,为了让黑色节点数量保持平衡,所以我们需要把uncle也变成黑色,然后让grandfather变为红色。

单旋+变色

条件:grandfather为黑色,parent与cur为红色,uncle不存在或者uncle存在且为黑,并且parent与cur位于同一侧

解决办法:以grandfather节点为旋转点,如果位于左侧则右旋;如果位于右侧则左旋,旋转结束后让parent变为黑色,grandfather变成红色

分析:由于uncle是黑色节点,如果我们直接按照第一种情况进行变色,那么就会导致uncle这条路径上始终少一个节点,这样导致节点失衡,所以我们要先进行旋转,旋转结束后parent成为了cur与grandfather的父节点,为了让黑色节点数量保持平衡,我们需要让parent变成黑色,让grandfather变成红色

双旋+变色

条件:grandfather为黑色,parent与cur为红色,uncle不存在或者uncle存在且为黑,并且parent与cur位于不同侧

解决办法:首先以parent为旋转点进行一次旋转,然后再以grandfather为旋转点进行一次旋转,旋转结束后让cur变成黑色,让grandfather变成红色。

分析:由于uncle是黑节点,cur与parent位于相反的两侧,如果我们旋转一次,结果还是这种情况,所以我们需要进行两次旋转,旋转结束后,为了保持黑色节点数量的平衡,我们需要将cur变成黑色,让grandfather变成红色

插入代码如下所示:

bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);_root->_col = 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{cout << "插入值已经存在,请重新插入" << endl;return false;}}cur = new Node(kv);cur->_col = RED;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->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;
}
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;if (subRL)subRL->_parent = parent;Node* grandfather = parent->_parent;parent->_parent = subR;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (grandfather->_left == parent){grandfather->_left = subR;}else{grandfather->_right = subR;}subR->_parent = grandfather;}}
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;if (subLR)subLR->_parent = parent;Node* grandfather = parent->_parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (grandfather->_left == parent){grandfather->_left = subL;}else{grandfather->_right = subL;}subL->_parent = grandfather;}
}

旋转逻辑在这篇文章介绍:

数据结构(五)——AVL树(平衡二叉搜索树)

红黑树的查找

红黑树的查找与二叉搜索树相同,都是通过遍历比较节点值与目标值的大小知道找到目标值所在的节点然后返回或者直到遍历完这颗树为止,其代码如下所示:

Node* Find(const pair<K, V>& kv)
{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 cur;}}cout << "目标值不存在" << endl;return nullptr;
}

红黑树的验证

前面我们验证红黑树时使用的方法是验证平衡因子以及左右子树高度差,实际上我们是按照AVL树的规则进行验证的,那么对于红黑树而言我们要通过它的规则进行验证,检查最长路径不超过最短路径的二倍吗?这样显然是不现实的,那么我们是通过什么方法去验证呢?我们还是通过递归去验证,不同的是我们引入了一个新的参数——参考值,这个值是用来记录从根节点到空节点这一整条路径中黑色节点的数量的,我们用这个值作为参考去比较其他路径上黑色节点的数量是否与这个值相同。其代码如下所示:

bool Check(Node* root, int blackNum, const int refNum)
{if (root == nullptr){// 前序遍历⾛到空时,意味着⼀条路径⾛完了//cout << blackNum << endl;if (refNum != blackNum){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 IsBalance()
{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);
}

红黑树代码如下所示:

enum Colour
{BLACK,RED
};
template<class K, class V>
struct RBTreeNode
{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:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = 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{cout << "插入值已经存在,请重新插入" << endl;return false;}}cur = new Node(kv);cur->_col = RED;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->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;if (subRL)subRL->_parent = parent;Node* grandfather = parent->_parent;parent->_parent = subR;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (grandfather->_left == parent){grandfather->_left = subR;}else{grandfather->_right = subR;}subR->_parent = grandfather;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;if (subLR)subLR->_parent = parent;Node* grandfather = parent->_parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (grandfather->_left == parent){grandfather->_left = subL;}else{grandfather->_right = subL;}subL->_parent = grandfather;}}Node* Find(const pair<K, V>& kv){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 cur;}}cout << "目标值不存在" << endl;return nullptr;}bool Check(Node* root, int blackNum, const int refNum){if (root == nullptr){// 前序遍历⾛到空时,意味着⼀条路径⾛完了//cout << blackNum << endl;if (refNum != blackNum){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 IsBalance(){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);}
private:Node* _root = nullptr;
};

小结

本篇文章我们着重介绍了红黑树的概念以及插入逻辑的实现,红黑树作为一颗不完全平衡二叉搜索树有着独特的地方,它是通过节点的颜色来判断是否平衡以及是否需要旋转操作,它省去了频繁旋转所带来的消耗,从而更加追求效率,总的来说,红黑树是一颗非常优秀的数据结构

以上就是本篇博客的主要内容,如果对您有所帮助的话,记得点赞、评论、关注加转发,您的支持就是我创作的最大动力

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

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

相关文章

c# 数据结构 链表篇 有关双向链表的一切

本人能力有限,如有不足还请斧正 目录 0.双向链表的好处 1.双向链表的分类 2.不带头节点的标准双向链表 节点类:有头有尾 链表类:也可以有头有尾 也可以只有头 增 头插 尾插 删 查 改 遍历 全部代码 3.循环双向链表 节点类 链表类 增 头插 尾插 删 查 遍历…

Numba 从零基础到实战:解锁 Python 性能新境界

Numba 从零基础到实战&#xff1a;解锁 Python 性能新境界 一、引言 在 Python 的世界里&#xff0c;性能一直是一个备受关注的话题。Python 以其简洁易读的语法和丰富的库生态&#xff0c;深受开发者喜爱&#xff0c;但在处理一些计算密集型任务时&#xff0c;其执行速度往往…

单位门户网站被攻击后的安全防护策略

政府网站安全现状与挑战 近年来&#xff0c;随着数字化进程的加速&#xff0c;政府门户网站已成为政务公开和服务公众的重要窗口。然而&#xff0c;网络安全形势却日益严峻。国家互联网应急中心的数据显示&#xff0c;政府网站已成为黑客攻击的重点目标&#xff0c;被篡改和被…

Spring Boot 项目三种打印日志的方法详解。Logger,log,logger 解读。

目录 一. 打印日志的常见三种方法&#xff1f; 1.1 手动创建 Logger 对象&#xff08;基于SLF4J API&#xff09; 1.2 使用 Lombok 插件的 Slf4j 注解 1.3 使用 Spring 的 Log 接口&#xff08;使用频率较低&#xff09; 二. 常见的 Logger&#xff0c;logger&#xff0c;…

NI的LABVIEW工具安装及卸载步骤说明

一.介绍 最近接到个转交的项目&#xff0c;项目主要作为上位机工具开发&#xff0c;在对接下位机时&#xff0c;有用到NI的labview工具。labview软件是由美国国家仪器&#xff08;NI&#xff09;公司研制开发的一种程序开发环境&#xff0c;主要用于汽车测试、数据采集、芯片测…

cmd 终端输出乱码问题 |Visual Studio 控制台输出中文乱码解决

在网上下载&#xff0c;或者移植别人的代码到自己的电脑&#xff0c;使用VS运行后&#xff0c;控制台输出中文可能出现乱码。这是因为源代码的编码格式和控制台的编码格式不一致。 文章目录 查看源代码文件编码格式查看输出控制台编码格式修改编码格式修改终端代码页 补充总结 …

A009-基于pytest的网易云自动化测试

题 目 :基于pytest的网易云自动化测试 主要内容 综合应用所学的软件测试理论和方法,实现网易云的功能自动化测试。 (1)自动化测试介绍; (2)自动化功能测试框架介绍; (3)设计功能测试用例 (4)书写自动化测试脚本; (5)测试评价与结论。 任务要求 (1)能…

LVGL Video控件和Radiobtn控件详解

LVGL Video控件和Radiobtn控件详解 一、 Video控件详解1. 概述2. 创建和初始化3. 基本属性设置4. 视频控制5. 回调函数6. 高级功能7. 注意事项 二、Radiobtn控件详解1. 概述2. 创建和初始化3. 属性设置4. 状态控制5. 组管理6. 事件处理7. 样式设置8. 注意事项 三、效果展示四、…

AbortController:让异步操作随时说停就停

AbortController&#xff1a;让异步操作随时说停就停 一、什么是 AbortController&#xff1f; AbortController 是 JavaScript 在浏览器和部分 Node.js 环境中提供的全局类&#xff0c;用来中止正在进行或待完成的异步操作&#xff08;如 fetch() 请求、事件监听、可写流、数…

机器学习 从入门到精通 day_04

1. 决策树-分类 1.1 概念 1. 决策节点 通过条件判断而进行分支选择的节点。如&#xff1a;将某个样本中的属性值(特征值)与决策节点上的值进行比较&#xff0c;从而判断它的流向。 2. 叶子节点 没有子节点的节点&#xff0c;表示最终的决策结果。 3. 决策树的…

C++ Primer (第五版)-第十三章 拷贝控制

文章目录 概述13.1拷贝、赋值与销毁合成拷贝构造函数拷贝初始化参数和返回值拷贝初始化的限制编译器可以绕过拷贝构造函数拷贝运算符析构函数三/五原则使用default阻止拷贝合成的拷贝控制成员可能是删除的 private拷贝控制拷贝控制和资源管理行为像值的类类值拷贝赋值运算符定义…

Vue el-from的el-form-item v-for循环表单如何校验rules(一)

实际业务需求场景&#xff1a; 新增或编辑页面&#xff08;基础信息表单&#xff0c;一个数据列表的表单&#xff09;&#xff0c;数据列表里面的表单数是动态添加的。数据可新增、可删除&#xff0c;在表单保存前&#xff0c;常常需要做表单必填项的校验&#xff0c;校验通过以…

测试100问:http和https的区别是什么?

哈喽&#xff0c;大家好&#xff0c;我是十二&#xff0c;今天给大家分享的问题是&#xff1a;http和https的区别是什么&#xff1f; 首先我们要知道 HTTP 协议传播的数据都是未加密的&#xff0c;也就是明文的&#xff0c;因此呢使用 http协议传输一些隐私信息也就非常不安全&…

YOLOv3超详细解读(三):源码解析:数据处理模块

一、概述 YOLOv3&#xff08;You Only Look Once v3&#xff09;是一种高效的目标检测算法&#xff0c;其数据处理模块是训练和推理流程的核心部分。本文将深入分析Ultralytics团队基于PyTorch实现的YOLOv3源码中的数据处理模块&#xff0c;重点探讨数据加载、预处理和数据增强…

每日算法(双指针算法)(Day 1)

双指针算法 1.算法题目&#xff08;移动零&#xff09;2.讲解算法原理3.编写代码 1.算法题目&#xff08;移动零&#xff09; 2.讲解算法原理 数组划分&#xff0c;数组分块&#xff08;快排里面最核心的一步&#xff09;只需把0改为tmp 双指针算法&#xff1a;利用数组下标来…

2025蓝桥杯python A组省赛 题解

真捐款去了&#xff0c;好长时间没练了&#xff0c;感觉脑子和手都不转悠了。 B F BF BF 赛时都写假了&#xff0c; G G G 也只写了爆搜。 题解其实队友都写好了&#xff0c;我就粘一下自己的代码&#xff0c;稍微提点个人的理解水一篇题解 队友题解 2025蓝桥杯C A组省赛 题…

测试基础笔记第四天(html)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 html介绍1. 介绍2.骨架标签3.常用标签标题标签段落标签超链接标签图片标签换行和空格标签布局标签input标签&#xff08;变形金刚&#xff09;form标签列表标签 htm…

10 穴 汽车连接器的15个设计特点

汽车行业严重依赖卓越的电气系统来确保功能和可靠性。这些系统的关键组件是 10 腔连接器&#xff0c;它为布线和信号传输提供解决方案。制造商和工程师必须仔细评估这些连接器的设计特性&#xff0c;以优化性能和安全性。 本博客研究了汽车 10 腔连接器的 15 个设计特征&#…

Summary

一、数据结构 1.1 哈希 主要是HashMap和HashSet&#xff1b;其中HashSet底层是一个HashMap属性。 // 获取HashMap元素,HashSet均不支持 map.keySet (); // Set<k> map.values (; // Collection<V> map.entrySet();//Set<Map.Entry<K,V>> for (Map.E…

【Leetcode-Hot100】最小覆盖子串

题目 解答 想到使用双指针哈希表来实现&#xff0c;双指针的left和right控制实现可满足字符串。 class Solution(object):def minWindow(self, s, t):""":type s: str:type t: str:rtype: str"""len_s, len_t len(s), len(t)hash_map {}for…