C++ - 红黑树 介绍 和 实现

前言

 前面 学习了 AVL树,AVL树虽然在 查找方面始终拥有 O(log N )的极高效率,但是,AVL 树在插入 ,删除等等 修改的操作当中非常的麻烦,尤其是 删除操作,在实现当中细节非常多,在实现上非常难掌控。具体可以看以下两篇文章:
C++ - AVL 树 介绍 和 实现 (上篇)_chihiro1122的博客-CSDN博客

C++ - AVL树实现(下篇)- 调试小技巧_chihiro1122的博客-CSDN博客

 而 红黑树 在构建就下个对容易一些了。

红黑树 介绍 

红黑树和 AVL树一样,都是 二叉平衡搜索树,红黑树的构建是在 每个结点除了 孩子的链接关系,和值以外,加一个位置存储该结点的颜色,一个结点的颜色只有   red 和 black 两种。红黑树通过对 任意一条路径上的各个结点的颜色限制确保没有一条路径会比其他路径长出两倍

 相比于 AVL树,红黑树对于平衡的要求更加宽松,他只是要求最长路径最多是 最短路径的两倍;

而 AVL树 是严格限死了 左右子树高度不能超过 2 。

也就是说,红黑树在高度上 要比 AVL树要高一些。

虽然 红黑树 在平衡上更加的宽松,但是实际的效率却依旧非常的高,并不比 AVL树差,至于为什么我们在下述来验证。

而且 ,红黑树不会进行 旋转,尽管在 AVL树当中旋转是常量级的,但是,数量多了之后,还是有很多的消耗。

在AVL树当中,插入和删除都要经过很多次的旋转。也就造成不小的消耗。

而 AVL树 相比于 红黑树 多出来的消耗在 红黑树看来是没有必要的。

 如下图当中的分析:

红黑树树的性质(规则)

  • 每个结点不是红色就是黑色
  •  根节点是黑色的
  • 如果一个节点是红色的,则它的两个孩子结点必须是黑色的 。(每一条从根结点开始的路径,没有重复的红色结点)
  • 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点。(红黑树当中每一条路径上,黑色结点数是相同的)(在一条路径当中,黑色结点的占比一定 大于 或 等于 1 /2 )
  • 每一个 “叶子结点” 都是黑色的,注意:此处的叶子结点不是我们以前理解的 最后一个结点,在红黑树当中的 这里的 “叶子结点” 实际上是最后一个 NULL 结点(所有的 NIL 结点都是 黑色的)。如下图所示:
     

 在红黑树当中,对 这个 NULL 结点进行了重新定义,取名为 NIL 结点而且我们上述所说的路径,是指:从根结点到最后的 NIL 结点,为一条路径。

 就比如上述,如果不加上 NULL ,那么计算出来的路径数目就是 5 条;但是如果加上 NULL ,来计算的话就是 11 条,而11 跳才是正确。

 也就是说,如果你不加上最后一个 NULL 结点来用 红黑树的规则来判断 一颗树是不是红黑树的话,机会出错,比如下述例子:
 

 如果,你按照我们以前认为,最后一个结点就是叶子结点,而且在算上路径时候没有加上最后的NULL。那么你就会认为,这是一颗红黑树;

但,实际上这不是一颗红黑树。 

 其实,按照上述 的 第五条性质,其实就是在每一条路径的后面都加上了 一个 黑色结点。

insert()插入函数 

 首先我们来想一下,当一颗红黑树当中已经有结点了,如下所示,那么当我们插入一个结点是插入红色的结点还是 插入一个黑色的结点好呢?

 如果插入 黑色结点就会违反 条件4,;如果插入 红色结点就会违反 条件3;

如果要违反条件的话,可能是违反 条件3更好。

因为 ,如果违反条件4,就会影响全部路径;而如果 违反条件3 就只会 影响一个路径,不会是全部路径。

如下所示,我们插入一个黑色结点:
 

 在插入A结点之后,到A结点的这条路径的 黑色结点的个数就 +1了,但是其他路径上都没有变化,此时就影响到其他 路径了。

但是如果插入的是一个红色的结点,只影响一个路径,所以,当发生错误的时候我们可以修正:

 上述插入的B结点,在 22 的下面,22 是红色结点,这个位置就不行但是如果是插入在 15 的下面,15 是一个黑色的结点,是可以的。

 如果 红色结点 B 插入到 22 的下面,此时 B 的父亲结点是 红色的,说明B 一定会有 父亲的父亲,也就是说 grandfather。因为 整棵树只有根结点才没有父亲,而根结点必须是 黑色的:

 如上所示,红色结点一定有 父亲。也就是说,出现上述情况的话,grandfather 结点是肯定存在的。

 红黑树的插入,修改过程

 我们上述讨论了,插入的结点必须是红色的结点,插入红色的结点的话,就会影响到父亲(新插入结点的路径)。

 具体例子理解

所以,此时我们要从该路径上进行修改。

 此时违法的规则就是 B 和 22 两个红色结点连在一起了,所以我们就想着把 22 边黑,但是变黑的话,就会影响这棵树当中的黑节点个数。

所以,在更新完 25 这颗子树之后,可能会像 AVL 树当中的平衡因子一样往上更新的。

如上述图,单次对于子树的修改:
需要先找到 新插入结点 父亲的 亲兄弟(uncle)。

如果 uncle 存在且为 红,那么就把 parent 和 uncle 都变 黑:

 但是此时我们发现,变黑之后,对于 22 和 27 这两条路径来说,黑色结点个数相比于其他路径,在增多了一个,所以,此时我们在让 grandfather 结点变红,就可以解决黑色结点个数问题:

 但是,对于 grandfather 结点不一定都是这种的处理方法:

  • 如果 grandfather 结点没有父亲,说明此时 grandfather 结点是 整棵树的根结点,那么把 grandfather 结点变红就行。
  • 如果 grandfather 有父亲,grandfather 是 黑色,就结束了。
  • 如果 grandfather 有父亲,grandfather 是 红色,有和上述问题一样了,需要找 uncle。

 我们继续来看上述例子:
 

 在上述修改之后, 发现 17 和 25 又是两个红色结点链接在一起了,此时就要对这个链接关系进行修改,此时就好比是 新插入了 25 结点。

找 uncle(8),uncle 为红,就把 parent 和 uncle 一起改为黑,此时的 grandfather 没有父亲结点了,就不用再变黑了:

 如果按照上述的过程来修改的话,我们发现,相当于是在每一条路径当中都增加了一个 黑色结点。

黑色结点其实是有好处的,比如上述的例子的那种,我们插入的新结点一定是 红色的,那么插入在黑色结点后面就不用像上述一样进行修改,如上图所示,我们只有在 6 后面和在 B 后面插入结点才会像上述一样进行修改。

 上述是 有uncle的情况,如果 parent 没有亲兄弟,也就是 cur 没有 uncle的话,应该怎么办呢?

 如上述所示,cur 没有 uncle,我们不能直接把 parent 变黑,因为会多出一个 黑色结点;有人又说,多出来一个黑色结点的话,把 grandfather 在变 红不就行了,但是 如果把 grandfather 变红的话,指向 uncle 的路径上就少一个 黑色结点了

而且我们发现,如果在 cur 位置插入的话,13 的右子树的上的路径长度已经比 左子树 上的路径长度多出 2 倍了。

所以,不管是否 多出 2 倍,在没有 uncle的情况下,我们应该进行旋转来调整位置

判断旋转方式还是和在 AVL树当中判断方式一样,旋转方式请看一下博客:

C++ - AVL 树 介绍 和 实现 (上篇)_chihiro1122的博客-CSDN博客

 像上述,就是发生了的右单旋,但是旋转之后发现,颜色上还是 违反了规则。

所以此时要变色。

所以,当uncle 不存在的时候,处理规则就是 旋转 + 变色。 

 uncle存在且为红的情况,在修改之后也有可能会出现 最长路径和最短路径 长度超过两倍的情况(uncle 为黑的情况):
 

 修改:

 此时我们发现,如果我们单纯的把 17 变黑,已经不能解决问题了,此时也是需要旋转的。

13 的右子树已经明显的高了,要对右子树进行旋转,此时发生左单旋。如果是下述情况就是 双旋:
 

 cur , parent , grandfather 构成折线,就要发生双旋。

但是,在 AVL 树当中我们判断旋转的方式是利用 平衡因子的方式来判断的,但是在红黑树当中没有平衡因子,我们需要判断  cur , parent , grandfather 三者的链接关系来 确认要哪一种旋转。

 我没发现,红黑树优势不仅仅在于旋转变少了,他是在修改过程当中就会把很多结点变黑,那么在以后插入当中,插入到黑节点后面就不必再进行修改了。

 小总结

红黑树的插入,关键看 uncle。

  • 如果 uncle 存在且为红,变色+ 往上进行处理
  • 如果 uncle存在且为黑,旋转+变色+往上进行处理
  • 如果 uncle不存在,旋转 + 变色 + 往上进行处理

 抽象图理解 红黑树插入过程

 按照上述说明,出现需要修改的情况,都是 红红黑的结构,也就是 cur 为红, parent 为红,grandfather 为黑的情况,而修改过程当中,判别不用的修改方式,是看 uncle 的。

我们先来总结一下 解决方案

  • 情况一: cur为红,p为红,g为黑,u存在且为红。                                                                                                                                                                                                                                       解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。                                  
  • 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑。                                                                                                                                                                                                                         解决方法:p为g的左孩子,cur为p的左孩子,则进行右单旋转;                                                               p为g的右孩子,cur为p的右孩子,则进行左单旋转。                                                               p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;                                                           p为g的右孩子,cur为p的左孩子,则针对p做右单旋转

具体情况分析:

情况一:

 比如上述是 的抽象图,他可能是 一颗子树,也可能是 整棵树。

那么,当我们在 a ,b 后面插入的话,可能就会应发 情况一

当 c/d/e 子树每条路径当中只有 一个 黑色结点

 如上述情况我们就要使用 情况一的处理方式了。

 对于情况一,因为不旋转,对于新结点的插入位置,是不管的,主要是 在 a 和 b 的四个位置插入都是引发情况一,或者不引发。

至于 cur 可能本来就是 grandfather 的黑色结点,只是在 a,b当中没有个进行修改,把 grandfather 修改为了 红色。

当然,还有 cur 本来就是 新增结点的情况,也就是 a  和 b 两个子树都是 空:

 但是处理方式还是情况一的方式。


 当 c/d/e 子树每条路径当中有两个黑色结点:

 

 c/d/e 子树每条路径当中有两个黑色结点的情况太多了,上图当中只是简单列出一些。

在 上述 只有一个黑结点的路径当中,只用一层就修改,就修改到了 cur 结点;而在当前 路径当中有两个黑结点,就是修改两层,才修改到 cur 结点的颜色的。(这里的层,一层就是 一个 cur parent grandfather 组合子树)

 反正就是 路径当中有两个黑色结点的情况,要比 只有一个黑结点的情况复杂得多,但是最终还是属于情况一,都是使用 情况一的方式来进行解决。

 情况二:

 情况二当中的uncle有两种情况:
一种是 uncle不存在:

 如果 uncle 不存在,那么 cur 一定是新插入的结点,因为如果 cur 不是新插入结点,那么 cur 和 p 当中一定至少有一个是 黑色的。

 另一种是 uncle 的颜色是 黑色:

 如果 uncle 是黑色的话,cur 这个结点一定不是 新插入的结点,因为,假设在插入之前,也就相当于是没有 cur 这个结点,g - > p -> null 路径 和  g -> u -> ········ 路径 两个路径上 黑色结点个数不一样。所以,插入之前的树不可能是红黑树。

所以,uncle 为黑的这种情况,一定是  cur 当中的子树发生了修改,往上修改的时候影响到了 cur :

 修改过程如下:都是旋转 + 变色的方式。只不过旋转当中有四种方式,具体要看 cur parent grandfather 三者之间的链接关系

  像上述这种情况, c 子树的路径当中每条之后一个黑结点,d 和 e 可能为空,也可能有一个 红结点。

 同样的,c  d  e 三个子树当中的黑色结点可以按照上述的规律增加,那么就是不同的具体例子。

比如,c是 两个黑色结点的红黑树,那么 d 和 e 就是一个结点的红黑树。

 红黑树的结果是无穷无尽的,每一种结构也相对复杂,但是上述都是属于 情况二,解决方式都是一样的。

  • 情况二当中,旋转+变色之后,就不需要网上进行处理了,为在旋转+变色之后,这棵子树的根结点一定是 黑色的,不会是 红色的,那么黑色的结点不管和 红色的结点还是和 黑色的结点链接都是不会出现问题的。
  • 在情况一当中之所以要继续往上更新,是因为,情况一修改出来的根结点是红色的。

 红黑树的验证

 红黑树的检测分为两步:

  • 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  •  检测其是否满足红黑树的性质

 检测是否满足红黑树性质:
 

bool CheckColor(Node* root, int blacknum, int benchamark){// 当走到叶子结点的 null 指针处,也就是 NIL结点处if (root == nullptr){// 如果计算出的路径黑色结点长度 和 外部计算的不一样// 说明不是红黑树if (blacknum != benchamark){cout << "路径黑色结点个数不一样" << endl;return false;}return true;}// 用于递归计算 路径的黑色结点个数if (root->_color == BLACK)blacknum++;// 如果当前结点为 红色,且当前结点的父亲也是红色,就不是红黑树if (root->_parent && root->_parent->_color == RED && root->_color == RED){cout << "有连续红色" << endl;return false;}// 左右子树递归return CheckColor(root->_left, blacknum, benchamark)&& CheckColor(root->_right , blacknum, benchamark);}// 外部调用接口bool isBalance(){return isBalance(_root);}// 内部封装函数bool isBalance(Node* root){if (root == nullptr)return true;// 如果整棵树的 根结点不是 黑色的就不是红黑树if (root->_color != BLACK){cout << "根结点不是黑色" << endl;return false;}// 基准值// 在递归外部计算出左路第一条路径的 黑色结点值int benchmark = 0;Node* cur = root;while(cur){if (cur->_color == BLACK)benchmark++;cur = cur->_left;}return CheckColor(root, 0, benchmark);}

我们使用两个函数相互调用来实现,外层函数判断整棵树的根结点是不是 黑色的;

而且计算左边第一条路径的黑色结点个数。其实可以随便找一条路径,因为这个只是一个基准值,不需要是正确的,因为红黑树当中的每一条路径都需要黑色结点数相同。

然后用内层函数递归遍历红黑树。

内层函数当中,计算出每一条黑色结点的个数;判断是否有连续的红色结点;

检测其是否满足二叉搜索树,就直接中序遍历打印就行了,这里就不实现了。

 红黑树的删除

红黑树的删除和 AVL树一样,都比插入要复杂,可以看下面这个博客来了解:
红黑树 - _Never_ - 博客园 (cnblogs.com)

完整代码实现

 

#pragma once// 节点的颜色
enum Color { RED, BLACK };// 红黑树节点的定义
template<class K, class V>
struct RBTreeNode
{RBTreeNode(pair<K ,V> kv  , Color color = RED): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _color(color){}RBTreeNode<K, V>* _left; // 节点的左孩子RBTreeNode<K, V>* _right; // 节点的右孩子RBTreeNode<K, V>* _parent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)pair<K, V> _kv; // 节点的值域Color _color; // 节点的颜色
};template<class K ,class V>
class RBTree
{typedef RBTreeNode<K,V> Node;
public:bool insert(pair<K , V> kv){// 搜索二叉树的插入逻辑// // 如果当前树为空,直接用头指针只想新结点if (_root == nullptr){_root = new Node(kv);_root->_color = BLACK;return true;}// 不为空接着走Node* cur = _root;    // 用于首次插入时候指针的迭代Node* parent = nullptr;while (cur){// 如果当前新插入的 key 值比 当前遍历的结点 key 值大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);cur->_color = RED;// 再次判断大小,判断 cur应该插入到 parent 的那一边if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}// 链接 新插入结点 cur 的_parent 指针cur->_parent = parent;// 红黑树调整高度(平衡高度)的逻辑while (parent && parent->_color == RED){// parent 为 红,parent->_parent 一定不为空Node* grandfather = parent->_parent;// 如果父亲是在 祖父的左if (parent == grandfather->_left){Node* uncle = grandfather->_right;// 叔叔存在且为红if (uncle && uncle->_color == RED){// 变色parent->_color = uncle->_color = BLACK;grandfather->_color = RED;// 向上迭代cur = grandfather;parent = cur->_parent;}// uncle 不存在 或者 存在且为黑else{if (cur == parent->_left){//       g//     p//  cRotateR(grandfather);parent->_color = BLACK;grandfather->_color = RED;}else  // cur == parent->_right{//       g//     p//         cRotateL(parent);RotateR(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break; // 不需要再往上更新}}else  // parent = grandfather->_right{Node* uncle = grandfather->_left;// uncle 存在 且 uncle 的颜色是红色if (uncle && uncle->_color == RED){parent->_color = uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;parent = cur->_parent;}else // 不存在 或者 存在且为黑色{if (cur == parent->_right){//  g//     p//        cRotateL(grandfather);parent->_color = BLACK;grandfather->_color = RED;}else // cur = parent->_left{//   g//     p//  cRotateR(parent);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break; // 不用再往上更新}}}// 不管上述如何修改,红黑树的根结点永远是黑的// 所以我们这里既直接硬性处理_root->_color = BLACK;return true;}void RotateL(Node* parent){Node* cur = parent->_right; // 存储 parent 的右孩子Node* curleft = cur->_left; // 存储 cur 的左孩子parent->_right = curleft;if (curleft)                // 判断 cur 的左孩子是否为空{curleft->_parent = parent;  // 不为空就 修改 cur 的左孩子的_parent 指针}cur->_left = parent;// 留存一份 根结点指针Node* ppnode = parent->_parent;parent->_parent = cur;// 如果parent 是根结点if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}void RotateR(Node* parent){Node* cur = parent->_left;Node* curRight = cur->_right;parent->_left = curRight;if (curRight){curRight->_parent = parent;}cur->_right = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}bool CheckColor(Node* root, int blacknum, int benchamark){// 当走到叶子结点的 null 指针处,也就是 NIL结点处if (root == nullptr){// 如果计算出的路径黑色结点长度 和 外部计算的不一样// 说明不是红黑树if (blacknum != benchamark){cout << "路径黑色结点个数不一样" << endl;return false;}return true;}// 用于递归计算 路径的黑色结点个数if (root->_color == BLACK)blacknum++;// 如果当前结点为 红色,且当前结点的父亲也是红色,就不是红黑树if (root->_parent && root->_parent->_color == RED && root->_color == RED){cout << "有连续红色" << endl;return false;}// 左右子树递归return CheckColor(root->_left, blacknum, benchamark)&& CheckColor(root->_right , blacknum, benchamark);}// 外部调用接口bool isBalance(){return isBalance(_root);}// 内部封装函数bool isBalance(Node* root){if (root == nullptr)return true;// 如果整棵树的 根结点不是 黑色的就不是红黑树if (root->_color != BLACK){cout << "根结点不是黑色" << endl;return false;}// 基准值// 在递归外部计算出左路第一条路径的 黑色结点值int benchmark = 0;Node* cur = root;while(cur){if (cur->_color == BLACK)benchmark++;cur = cur->_left;}return CheckColor(root, 0, benchmark);}
private:Node* _root = nullptr;
};

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

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

相关文章

第52节:cesium 3DTiles模型特效+选中高亮(含源码+视频)

结果示例: 完整源码: <template><div class="viewer"><vc-viewer @ready="ready" :logo="false"><vc-navigation

云上亚运:所使用的高新技术,你知道吗?

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号&#xff1a;网络豆云计算学堂 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a; 网络豆的主页​​​​​ 目录 前言 一.什么是云上亚运会 二.为什么要使用云…

【Newman+Jenkins】实施接口自动化测试

一、是什么Newman Newman就是纽曼手机这个经典牌子&#xff0c;哈哈&#xff0c;开玩笑啦。。。别当真&#xff0c;简单地说Newman就是命令行版的Postman&#xff0c;查看官网地址。 Newman可以使用Postman导出的collection文件直接在命令行运行&#xff0c;把Postman界面化运…

负载均衡原理及应用

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

AI AIgents时代 - (三.) AutoGPT和AgentGPT

前两篇讲解了Agent的原理和组件&#xff0c;这节我将给大家介绍两个agent项目&#xff0c;给出它们的工作原理和区别&#xff0c;并教大家亲手尝试使用 Agents&#x1f389; &#x1f7e2; AutoGPT&#x1f916;️ 我们的老朋友&#xff0c;之前文章也专门写过。AutoGPT 是一…

【C++杂货铺】一颗具有搜索功能的二叉树

文章目录 一、二叉搜索树概念二、二叉搜索树的操作2.1 二叉搜索树的查找2.2 二叉搜索树的插入2.3 二叉搜索树的删除 三、二叉搜索树的实现3.1 BinarySearchTreeNode&#xff08;结点类&#xff09;3.2 BinarySearchTree&#xff08;二叉搜索树类&#xff09;3.2.1 框架3.2.2 in…

108. 将有序数组转换为二叉搜索树

给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 高度平衡 二叉搜索树。 高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。 示例 1&#xff1a; 输入&#xff1a;nums [-10,-3,0,5,9] 输…

【Spark】win10配置IDEA、saprk、hadoop和scala

终于&#xff0c;要对并行计算下手了哈哈哈。 一直讲大数据大数据&#xff0c;我单次数据处理量大概在1t上下&#xff0c;是过亿级的轨迹数据。 用python调用multiprogress编写的代码&#xff0c;用多线程也要一个多月跑完。 我对这个效率不太满意&#xff0c;希望能快一点再快…

Python_ithheima_第二阶段

第一章 01-初识对像 02 类的成员方法 03 类和对象 04 构造方法 05 魔术方法 06 封装 07 封装的课后练习题讲解 08 继承的基础语法 pass关键字的功能是“语法补全” 同名成员或方法&#xff0c;谁先来谁优先级高 09 复写父类成员和调用父类成员 10 变量的类型注解 11 函数和方法…

Qt---day4---9.20

qt完成时钟&#xff1a; 头文件&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPaintEvent> #include <QtDebug> #include <QPainter> #include <QTimerEvent> #include <QTime>QT_BEGIN_NAMESPACE names…

ROS2 的行为树 — 第 1 部分:解锁高级机器人决策和控制

一、说明 在复杂而迷人的机器人世界中&#xff0c;行为树&#xff08;BT&#xff09;已成为决策过程中不可或缺的一部分。它们提供了一种结构化、模块化和高效的方法来对机器人的行为进行编程。BT起源于视频游戏行业&#xff0c;用于控制非玩家角色&#xff0c;他们在机器人领域…

Kafka 常见问题

文章目录 kafka 如何确保消息的可靠性传输Kafka 高性能的体现利用Partition实现并行处理利用PageCache 如何提高 Kafka 性能调整内核参数来优化IO性能减少网络开销批处理数据压缩降低网络负载高效的序列化方式 kafka 如何确保消息的可靠性传输 消费端弄丢了数据 唯一可能导致…

MinGW相关错误

1、go编译c报错 cc1.exe: sorry, unimplemented: 64-bit mode not compiled in 参考&#xff1a;BeifangCc go编译c报错 cc1.exe: sorry, unimplemented: 64-bit mode not compiled in 说明当前gcc是32位&#xff0c;无法在当前64位机器上正常工作&#xff0c;需要更新gcc 下载…

指针和数组笔试题的透析

指针---进阶篇&#xff08;三&#xff09; 一、前言二、一维数组例题透析&#xff1a;三、指针笔试题1.例一&#xff1a;2.例二&#xff1a;3.例三&#xff1a;4.例四&#xff1a;5.例五&#xff1a;6.例六&#xff1a; 一、前言 那么好了好了&#xff0c;宝子们&#xff0c;从…

R语言绘制PCA双标图、碎石图、变量载荷图和变量贡献图

1、原论文数据双标图 代码&#xff1a; setwd("D:/Desktop/0000/R") #更改路径#导入数据 df <- read.table("Input data.csv", header T, sep ",")# ----------------------------------- #所需的包: packages <- c("ggplot2&quo…

Innodb 原理和日志

一、MySQL结构 客户端 server层 查询缓存&#xff08;5.7&#xff09; 连接器 分析器 优化器 执行器 引擎层 二、一条update操作mysql的流程 三、MySQL的日志 &#xff08;1&#xff09;redo log 保证MySQL 持久性的关键&#xff0c;如果MySQL宕机&#xff0c;buffer pool…

Java编程的精髓:深入理解JVM和性能优化

文章目录 Java虚拟机&#xff08;JVM&#xff09;的核心概念1. 类加载器&#xff08;Class Loader&#xff09;2. 内存区域3. 垃圾回收&#xff08;Garbage Collection&#xff09;4. 类型转换和多态 JVM性能调优1. JVM参数调整2. 内存管理3. 多线程优化4. 使用性能分析工具5. …

【【萌新的FPGA学习之按键控制LED实验】】

按键控制LED实验 在写这篇文章之前我必须对我的错误表示深刻的道歉 因为我之前的文章自己也是边看边学给大家带来了大的困扰 抱歉抱歉 我们这里讲述一下综合和仿真的关系 其实我们更多的是应该关注仿真下得到的波形情况 然后分析 对于综合&#xff0c;综合的最大的目的还是看功…

leetcode:2446. 判断两个事件是否存在冲突(python3解法)

难度&#xff1a;简单 给你两个字符串数组 event1 和 event2 &#xff0c;表示发生在同一天的两个闭区间时间段事件&#xff0c;其中&#xff1a; event1 [startTime1, endTime1] 且event2 [startTime2, endTime2] 事件的时间为有效的 24 小时制且按 HH:MM 格式给出。 当两个…

网站接入公网并配置域名访问【详细教程】

网站接入公网并配置域名访问【详细教程】 安装Nginx上传网页文件配置Nginx腾讯云配置域名映射接入公网备案流程 本教程将以腾讯云服务器和腾讯云域名为例&#xff0c;介绍如何快速将网站接入公网并配置域名访问。我们将使用xshell工具进行操作&#xff0c;并涵盖安装nginx、上传…