C++手撕红黑树:从0到200行,拿下STL map底层核心

C++手撕红黑树:从0到200行,拿下STL map底层核心

文章目录

C++手撕红黑树:从0到200行,拿下STL map底层核心

1. 红黑树的概念

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

1.1 红黑树的规则

  1. 每个结点不是红色就是黑色。
  2. 根结点是黑色的。
  3. 如果一个结点是红色的,则它的两个孩子结点必须是黑色的(即任意一条路径不会有连续的红色结点)。
  4. 对于任意一个结点,从该结点到其所有 NULL 结点的简单路径上,均包含相同数量的黑色结点。
说明:《算法导论》等书籍中补充了“每个叶子结点(NIL)都是黑色的”规则。这里所指的叶子结点不是传统意义上的叶子结点,而是我们说的空结点(NIL),也叫外部结点。引入 NIL 是为了准确标识所有路径,但在实现细节中通常忽略 NIL 结点,了解概念即可。

在这里插入图片描述


在这里插入图片描述

1.2 红黑树如何确保最长路径不超过最短路径的2倍?

  • 由规则4可知,从根到 NULL 结点的每条路径都有相同数量的黑色结点。极端场景下,最短路径一定是全为黑色结点的路径,假设最短路径长度为 bh(black height)。
  • 由规则2和规则3可知,任意一条路径不会有连续的红色结点。极端场景下,最长路径就是一黑一红间隔组成,那么最长路径的长度为 2 * bh
  • 综合红黑树的4点规则,理论上的全黑最短路径和一黑一红的最长路径并不一定在每棵红黑树中都存在。假设任意一条从根到 NULL 结点的路径长度为 h,那么 bh <= h <= 2 * bh

1.3 红黑树的效率

假设 N 是红黑树中结点数量,h 是最短路径的长度,那么:

2^h - 1 <= N <= 2^(2*h) - 1

由此推出 h ≈ logN,即红黑树增删查改的最坏情况是走最长路径 2*logN,时间复杂度仍为 O(logN)

红黑树的表达相对 AVL 树要抽象一些。AVL 树通过高度差直观地控制平衡,而红黑树通过4条规则的颜色约束间接实现了近似平衡。两者效率属于同一档次,但红黑树在插入相同数量的结点时旋转次数更少,因为它对平衡的控制没那么严格。


在这里插入图片描述

2. 红黑树的实现

2.1 红黑树的结构

// 枚举值表示颜色enumColour{ RED, BLACK };// 这里默认按 key/value 结构实现template<classK,classV>structRBTreeNode{// 更新控制平衡需要加入 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<classK,classV>classRBTree{typedef RBTreeNode<K, V> Node;public:// ...private: Node* _root =nullptr;};

2.2 红黑树的插入

2.2.1 插入的大概过程

  1. 按二叉搜索树规则插入新结点。
  2. 如果是空树插入,新增结点为黑色;如果是非空树插入,新增结点必须为红色(否则会破坏规则4)。
  3. 非空树插入后,如果父亲结点是黑色,则插入结束;如果父亲结点是红色,则违反规则3,需要进一步处理。
约定c 为当前结点(cur),p 为父亲(parent),g 为祖父(grandfather),u 为叔叔(uncle,即 p 的兄弟)。

2.2.2 情况1:变色

条件c 为红,p 为红,g 为黑,u 存在且为红。
处理:将 pu 变黑,g 变红,然后把 g 当作新的 c,继续往上更新。

  • 无论 cp 的左还是右,pg 的左还是右,处理方法相同。

如果 g 是根,最后再将 g 变回黑色。

在这里插入图片描述

2.2.3 情况2:单旋 + 变色

条件c 为红,p 为红,g 为黑,u 不存在或为黑。

  • 如果 u 不存在,c 一定是新增结点。
  • 如果 u 存在且为黑,c 一定不是新增,而是由情况1变色更新上来的。

处理

  • pg 的左,cp 的左:以 g 为旋转点进行右单旋,再将 p 变黑,g 变红。

pg 的右,cp 的右:以 g 为旋转点进行左单旋,再将 p 变黑,g 变红。

在这里插入图片描述

2.2.4 情况3:双旋 + 变色

条件c 为红,p 为红,g 为黑,u 不存在或为黑,且 cp 的方向不一致。

处理

  • pg 的左,cp 的右:先以 p 为旋转点进行左单旋,再以 g 为旋转点进行右单旋,最后将 c 变黑,g 变红。

pg 的右,cp 的左:先以 p 为旋转点进行右单旋,再以 g 为旋转点进行左单旋,最后将 c 变黑,g 变红。

在这里插入图片描述

2.3 红黑树的插入代码实现

boolInsert(const pair<K, V>& kv){if(_root ==nullptr){ _root =newNode(kv); _root->_col = BLACK;returntrue;} Node* parent =nullptr; Node* cur = _root;while(cur){if(cur->_kv.first < kv.first){ parent = cur; cur = cur->_right;}elseif(cur->_kv.first > kv.first){ parent = cur; cur = cur->_left;}else{returnfalse;}} cur =newNode(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(parent == grandfather->_left){ Node* uncle = grandfather->_right;if(uncle && uncle->_col == RED){// 情况1:叔叔存在且为红 parent->_col = uncle->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = cur->_parent;}else{// 情况2/3:叔叔不存在或为黑if(cur == parent->_left){// 单旋RotateR(grandfather); parent->_col = BLACK; grandfather->_col = RED;}else{// 双旋RotateL(parent);RotateR(grandfather); cur->_col = BLACK; grandfather->_col = RED;}break;}}else{ Node* uncle = grandfather->_left;if(uncle && uncle->_col == RED){// 情况1:叔叔存在且为红 parent->_col = uncle->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = cur->_parent;}else{// 情况2/3:叔叔不存在或为黑if(cur == parent->_right){RotateL(grandfather); parent->_col = BLACK; grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather); cur->_col = BLACK; grandfather->_col = RED;}break;}}} _root->_col = BLACK;returntrue;}
旋转代码与 AVL 树相同,只需调整指针,无需更新平衡因子。

2.4 红黑树的查找

按二叉搜索树逻辑实现,时间复杂度 O(logN)

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

2.5 红黑树的验证

不能简单地通过检查最长路径不超过最短路径2倍来验证,因为即使满足该条件,颜色规则也可能被破坏。必须检查4点规则:

  1. 根结点为黑色。
  2. 无连续红色结点。
  3. 每条路径黑色结点数量相同。
boolCheck(Node* root,int blackNum,constint refNum){if(root ==nullptr){if(refNum != blackNum){ cout <<"存在黑色结点数量不相等的路径"<< endl;returnfalse;}returntrue;}if(root->_col == RED && root->_parent->_col == RED){ cout << root->_kv.first <<"存在连续的红色结点"<< endl;returnfalse;}if(root->_col == BLACK){ blackNum++;}returnCheck(root->_left, blackNum, refNum)&&Check(root->_right, blackNum, refNum);}boolIsBalance(){if(_root ==nullptr)returntrue;if(_root->_col == RED)returnfalse;// 参考值:最左路径的黑色结点数int refNum =0; Node* cur = _root;while(cur){if(cur->_col == BLACK)++refNum; cur = cur->_left;}returnCheck(_root,0, refNum);}

Read more

1200PLC与爱普生机器人modbus_TCP通讯

1200PLC与爱普生机器人modbus_TCP通讯

1.前言 首先申明一下我的硬件信息 机器人:C4-A601S 控制器:RC700 PLC:西门子S7-1200(CPU:1217C/DC/DC/DC) 2.控制器IP地址查看及修改 在配置控制器相关信息时需要先用网线连接PC与机器人控制器连接,爱普生机器人出厂设定网址为192.168.0.1(我这里是之前修改过了) 若默认没有显示以太网连接,点击右侧的增加,选择“通过以太网连接到控制器”后点击确定 如果控制器网址被修改过了,不知道是多少,可以用一根PC线,一头接在控制器的“开发用PC连接专用USB端口”另一头接在电脑USB口 这时候再在通讯处选择USB连接就可以通上了 现在就可以在“系统配置”处看到控制器的IP地址以及相关信息了,如果有需要也可以直接在这修改IP地址。 3.机器人控制器配置 网线连接好后开始配置通讯相关信息 1.控制设备 控制设备修改为远程I/O 2.现场总线 现场总线类型修改为“Modbus TCP”

By Ne0inhk

养龙虾-------【多openclaw 对接飞书多应用】---多个大龙虾机器人群聊

🚀 MiniMax Token Plan 惊喜上线!新增语音、音乐、视频和图片生成权益。邀请好友享双重好礼,助力开发体验! 好友立享 9折 专属优惠 + Builder 权益,你赢返利 + 社区特权! 👉 立即参与:https://platform.minimaxi.com/subscribe/token-plan?code=2NMAwoNLlZ&source=link 最近玩了下大龙虾,对接飞书后玩的不亦乐乎,妥妥滴私人助理。但是也萌发一个想法,多个机器人可以自己聊天吗?那会不会把世界给聊翻了。于是我马上搜寻各个配置方式,却是找到了可以配置多个机器人得群聊方式。 1.首先创建多个应用添加机器人,分别和部署得多个openclaw系统对接具体对接参考我写的【 养龙虾-------【openclaw 对接飞书、钉钉、微信 】—移动AI助理】 2.手工拉群并添加机器人: 3.把群id配置进各个龙虾配置文件里面 接下来就可以群聊了

By Ne0inhk
WorkBuddy 使用指南:从零开始配置 QQ 机器人,解锁桌面智能体新玩法

WorkBuddy 使用指南:从零开始配置 QQ 机器人,解锁桌面智能体新玩法

文章目录 * 前言 * 下载 WorkBuddy * 认识 WorkBuddy * 插件类型 * 配置 QQ 机器人 * 登录 QQ 开放平台并注册激活账号 * 配置超级管理员、主体及认证信息 * 创建 QQ 机器人 * 获取 AppID 和 AppSecret * 从 Claw 中获取 Webhook * 在 QQ 开发平台配置回调地址 * 开始使用 WorkBuddy Claw * 总结 前言 在大家还在沉迷于如何搭建 OpenClaw 的时候,腾讯竟然悄悄公测了 WorkBuddy。这是一款面向全角色的桌面智能体,下达指令即可自动生成文档、表格、图表及 PPT 等可视化成果,能够自主规划并交付多模态复杂任务结果,支持多 Agents 并行工作,极致提效,

By Ne0inhk
从模仿学习到知识传递:示教学习、行为克隆与逆强化学习在机器人中的应用边界

从模仿学习到知识传递:示教学习、行为克隆与逆强化学习在机器人中的应用边界

点击 “AladdinEdu,你的AI学习实践工作坊”,注册即送-H卡级别算力,沉浸式云原生集成开发环境,80G大显存多卡并行,按量弹性计费,教育用户更享超低价。 从模仿学习到知识传递:示教学习、行为克隆与逆强化学习在机器人中的应用边界 引言:从“看一遍就会”到“举一反三”——机器人技能习得的进阶之路 人类具有非凡的模仿能力:观看一次如何系鞋带,我们便能尝试复现;观察他人使用新工具,我们能很快领悟其要领。这种通过观察与模仿来快速获取新技能的能力,正是我们期望赋予机器人的核心智能之一。模仿学习,作为让机器人从演示中学习的核心技术,被视为解决复杂机器人编程难题、实现技能快速部署的关键路径。然而,从“形似”的模仿到“神似”的理解,再到能够灵活“迁移”应用,其间横亘着巨大的技术鸿沟。 传统的编程方式在面对非结构化环境中的灵巧操作、动态交互等任务时显得力不从心。强化学习虽能通过试错自主探索最优策略,但其极低的样本效率和高昂的探索成本,使其在物理机器人上的直接应用步履维艰。模仿学习,特别是从人类演示中学习,提供了一条潜在的捷径:利用人类专家“压缩”在演示中的先验知识和精妙技巧,引导机器

By Ne0inhk