第23课-C++-红黑树的插入与旋转
🌇前言
红黑树是一种自平衡的二叉搜索树,因其出色的性能,广泛应用于实际中。Linux 内核中的 CFS 调度器便是一个使用红黑树的例子,这足以说明它的重要性。红黑树的实现通过红黑两种颜色的控制来维持平衡,并在必要时使用旋转操作来降低树的高度,使之保持平衡。
🏙️正文
1. 认识红黑树
红黑树最初由德国慕尼黑大学的 Rudolf Bayer 教授于1978年发明,后来由 Leo J. Guibas 和 Robert Sedgewick 修改为我们今天所熟悉的红黑树。红黑树在二叉搜索树的基础上,增加了颜色属性,通过一些规则降低树的高度。
与 AVL 树的严格平衡策略不同,红黑树仅在需要时进行旋转,从而减少了旋转操作的开销。虽然 AVL 树在极端情况下可能比红黑树快一倍,但红黑树在插入、删除、修改操作中性能更优。
1.1、红黑树的定义
红黑树 也是 三叉链 结构,不过它没有 平衡因子,取而代之的是 颜色。
红黑树节点的定义
// 节点的颜色
enum Color{RED, BLACK};
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{
RBTreeNode(const ValueType& data = ValueType(),Color color = RED)
: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
, _data(data), _color(color)
{}
RBTreeNode<ValueType>* _pLeft; // 节点的左孩子
RBTreeNode<ValueType>* _pRight; // 节点的右孩子
RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给
出该字段)
ValueType _data; // 节点的值域
Color _color; // 节点的颜色
}
注意: 定义新节点时,颜色可以为 红 也可以为 黑,推荐为 红色,具体原因后面解释。
1.2 红黑树的性质
红黑树有以下五条性质:
- 每个节点不是红色就是黑色。
- 根节点是黑色的。
- 如果一个节点是红色,那么它的两个子节点必须是黑色(不能有连续的红节点)。
- 从任一节点到其所有后代的叶子节点的路径中,都包含相同数量的黑色节点。
- 每个叶子节点都是黑色的空节点。
1.3 红黑树的特点
红黑树在插入节点时,默认将新节点颜色设为红色。这种设计简化了调整,因为红色新节点仅在特定条件下会触发旋转调整。
红黑树的路径特点如下:
- 最长路径:红黑相间
- 最短路径:全是黑节点
红黑树在极端情况下最长路径为最短路径的两倍,但这种情况很少见。因此在实际应用中,红黑树的查询性能与 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;elseparent->_left = cur;cur->_parent = parent;//判断是否需要 染色、旋转while (parent && parent->_col == RED){Node* grandfather = parent->_parent; //祖父节点//……}return true;
}
红黑树 如何调整取决于 叔叔,即父亲的兄弟节点 。并且如果父亲为黑,直接插入就行了,不必调整 如果父亲为红并且叔叔也为红,可以只通过染色解决当前问题,然后向上走,继续判断是否需要调整,如果父亲为红并且叔叔为黑或者叔叔不存在,此时需要 旋转 + 染色,根据当前节点与父亲的位置关系,选择 单旋 或 双旋,值得一提的是旋转 + 染色 后,不必再向上判断,可以直接结束调整 关于旋转的具体实现,这里不再展开叙述,可以复用 AVL 中的旋转代码,并且最后不需要调整平衡因子 《C++【AVL树】》 注意:红黑树的调整可以分为 右半区 和 左半区 两个方向(根据 grandfather 与 parent 的位置关系而定),每个方向中都包含三种情况:单纯染色、单旋+染色、双旋+染色,逐一讲解费时费力,并且两个大方向的代码重复度极高,因此 下面的旋转操作基于 右半区 左半区 的操作和 右半区 基本没啥区别,可以去完整代码中求证。
2.3、单纯染色
如果父亲为黑色,则不需要调整,不讨论这种情况,下面三种情况基本要求都是:父亲为红
当新节点插入后,如果 叔叔 节点也为 红色,那么可以通过将 祖父 节点的黑色素下放给 父亲和叔叔,祖父节点 变为 红色,这样调整仍可确保 每条路径中的黑色节点数目相同
单次染色还不够,需要从 grandfather 处继续向上判断是否需要 调整,单纯染色后,向上判断可能会变成其他情况,这是不确定的,具体情况具体分析
单纯染色 的操作如下:
注意:c 表示当前节点,p 表示父亲节点,u 表示叔叔节点,g 表示祖父节点
修正: 动图中语句修正为 “父亲为红,叔叔也为红,直接染色即可” 当 单次染色 结束后,更新 cur 至 grandfather 的位置,并同步更新 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 往上提即可,并保证不违背 红黑树 的性质
红黑树的检验主要是为了确保在插入或删除节点后,红黑树的性质依旧得到了满足。我们可以通过以下几个方面来验证红黑树的合法性:
4.红黑树的性质回顾
为了检验红黑树的合法性,需要检查以下五条性质是否都被满足:
- 每个节点不是红色就是黑色。
- 根节点是黑色。
- 如果一个节点是红色,那么它的两个子节点必须是黑色(即不能有连续的红色节点)。
- 从任一节点到其所有后代叶子节点的路径上,必须包含相同数量的黑色节点。
- 每个叶子节点的
nullptr
视为黑色。
检验步骤
我们可以通过编写检查函数,递归地验证红黑树的合法性。
1. 验证根节点是否为黑色
红黑树的根节点必须是黑色的。如果根节点不是黑色,就说明红黑树不合法。这是一个简单的检查。
if (_root->_col != BLACK)
{std::cerr << "根节点不是黑色,违反红黑树性质二" << std::endl;return false;
}
2. 检查是否出现连续的红色节点
如果一个节点是红色,那么它的子节点必须是黑色。因此,当遍历树时,可以检查每个红色节点的子节点是否都是黑色。如果出现连续的红色节点,则树不合法。
bool CheckRedNodeRule(Node* node)
{if (node == nullptr)return true;if (node->_col == RED){// 如果当前节点是红色,检查左右子节点if ((node->_left && node->_left->_col == RED) || (node->_right && node->_right->_col == RED)){std::cerr << "出现连续的红色节点,违反红黑树性质三" << std::endl;return false;}}// 递归检查左右子树return CheckRedNodeRule(node->_left) && CheckRedNodeRule(node->_right);
}
3. 验证从根节点到每个叶子节点路径的黑色节点数量是否一致
每一条路径从根节点到叶子节点所包含的黑色节点数量必须一致。这个数量可以称为基准黑色节点数。
首先,从根节点到最左边叶子节点的路径中统计黑色节点数量作为基准值。然后,递归检查其他路径是否符合这个基准值。
// 计算左侧路径上的黑色节点数量作为基准
int GetBlackHeight(Node* node)
{int blackCount = 0;while (node != nullptr){if (node->_col == BLACK)blackCount++;node = node->_left;}return blackCount;
}// 检查每条路径的黑色节点数是否一致
bool CheckBlackHeight(Node* node, int blackCount, int benchMark)
{if (node == nullptr){// 到达叶子节点,检查是否等于基准值return blackCount == benchMark;}if (node->_col == BLACK)blackCount++;return CheckBlackHeight(node->_left, blackCount, benchMark) &&CheckBlackHeight(node->_right, blackCount, benchMark);
}
4. 汇总检验
结合以上的检查点,我们可以编写一个 IsRBTree()
函数,逐步验证红黑树的合法性
// 计算左侧路径上的黑色节点数量作为基准
int GetBlackHeight(Node* node)
{int blackCount = 0;while (node != nullptr){if (node->_col == BLACK)blackCount++;node = node->_left;}return blackCount;
}// 检查每条路径的黑色节点数是否一致
bool CheckBlackHeight(Node* node, int blackCount, int benchMark)
{if (node == nullptr){// 到达叶子节点,检查是否等于基准值return blackCount == benchMark;}if (node->_col == BLACK)blackCount++;return CheckBlackHeight(node->_left, blackCount, benchMark) &&CheckBlackHeight(node->_right, blackCount, benchMark);
}
5.调试和验证
当代码中的某些操作可能会破坏红黑树的性质时,可以调用 IsRBTree()
来验证整个树的合法性,输出不满足性质的详细信息。这些检查帮助确保红黑树操作在每次插入、删除或其他变动后仍符合红黑树的要求。
5. AVL和红黑树的性能比较
AVL树和红黑树是两种经典的自平衡二叉搜索树,它们在不同应用场景中有各自的优势和劣势。下面从几个方面对它们的性能进行对比,包括平衡机制、插入/删除性能、查询性能和实际应用场景。
1. 平衡机制对比
- AVL树:AVL树严格保持高度平衡,每个节点的左右子树高度差(称为平衡因子)最多为1。为了维持这种严格的平衡,AVL树在插入或删除节点时,几乎每次都需要进行旋转操作以确保平衡。
- 红黑树:红黑树通过颜色标记(红和黑)以及特定的旋转规则来维持大致的平衡,它的平衡策略相对宽松。红黑树允许路径长度差达到2倍,因此在插入和删除时,它会尽可能避免旋转,除非触发特定条件。
2. 插入性能对比
- AVL树:由于AVL树保持严格的平衡性,它在插入时往往需要频繁旋转以重新平衡树。插入操作的平均和最差时间复杂度都是 O(logN)O(\log N)O(logN),但旋转操作的频率较高,尤其在接近完全平衡的情况下,这会导致插入操作时间增加。
- 红黑树:红黑树在插入时通常只需要少量旋转,且更多情况下可以通过染色来维持平衡,而不进行旋转。插入操作的时间复杂度也是 O(logN)O(\log N)O(logN),但由于旋转较少,因此在插入密集的应用场景中,红黑树往往表现得更好。
3. 删除性能对比
- AVL树:AVL树删除节点后会执行严格的平衡操作,频繁旋转调整确保树高度平衡,因此在删除操作时会消耗更多时间。
- 红黑树:红黑树的删除操作复杂度相对较低,因为其平衡策略宽松。删除时,红黑树往往可以通过染色操作或少量旋转恢复平衡,不会像AVL树那样频繁旋转。因此在删除操作密集的场景中,红黑树性能通常优于AVL树。
4. 查询性能对比
在查询性能上,二者的时间复杂度都是 O(logN)O(\log N)O(logN),但由于它们的平衡策略不同,有以下几点差异:
- AVL树:因为严格平衡,AVL树的高度始终接近 logN\log NlogN,这使得查找路径较短。理论上,AVL树的查找性能略优于红黑树,特别是在数据分布接近最差情况时,AVL树会比红黑树更快。
- 红黑树:红黑树允许路径长度差达到2倍,意味着它的高度可能比AVL树稍大一些。因此在某些场景下,AVL树的查询可能略微快于红黑树。然而,在实际应用中,这种差距通常微乎其微。
5. 内存占用对比
- AVL树:AVL树每个节点除了存储左右子节点,还要存储平衡因子。因此内存消耗会稍微多一点。
- 红黑树:红黑树每个节点只需一个颜色标记(红或黑),因此在内存开销上相对较小。
6. 实际应用场景对比
- AVL树适用场景:AVL树适合频繁查找、不频繁更新(插入/删除)的场景。例如,适合用于静态数据库或对性能要求较高的读取密集型场景。
- 红黑树适用场景:红黑树适合需要频繁插入和删除操作的场景,特别是在数据动态变化的情况下。红黑树的宽松平衡机制在插入、删除密集的情况下能够减少旋转,从而提升性能。这使得红黑树广泛应用于系统中的许多集合操作,例如C++ STL的
map
、set
以及Linux内核中的调度器等。
7. 性能测试示例
假设插入和删除大量数据的性能测试,结果通常会显示:
- 插入/删除:红黑树的插入和删除操作时间较短,尤其在大量数据的随机插入/删除测试中,红黑树的性能比AVL树好。
- 查询:查询操作性能上,两者差距不大,AVL树可能在极端情况下稍快,但在实际应用中效果差别不大。
总结
属性 | AVL树 | 红黑树 |
---|---|---|
平衡机制 | 严格平衡,更多旋转 | 宽松平衡,染色+少量旋转 |
插入性能 | 较多旋转,插入稍慢 | 较少旋转,插入较快 |
删除性能 | 较多旋转,删除稍慢 | 较少旋转,删除较快 |
查询性能 | 高度接近完全平衡,稍快 | 高度略大于AVL,性能接近 |
内存开销 | 较高(存储平衡因子) | 较低(存储颜色标记) |
适用场景 | 查找频繁、更新较少的场景 | 插入/删除频繁的动态场景 |
总体而言,红黑树在插入和删除密集的动态应用场景中表现更好,而AVL树在静态或查找频繁的场景中性能稍优。
完整代码:
#pragma once
#include<iostream>
#include<map>
#include<assert.h>
#include<vector>
using namespace std;enum Color
{RED,BREAK
};template<class K, class V>
struct RBTreeNode
{struct RBTreeNode<K, V>* _left;struct RBTreeNode<K, V>* _right;struct RBTreeNode<K, V>* _parent;pair<K, V> _kv;int _color;RBTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_color(RED){}
};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->_color = BREAK;return true;}else{Node* cur = _root;Node* parent = _root;while (cur)//比较Frist{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 false;//必须退出}}cur = new Node(kv);if (cur->_kv.first < parent->_kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}while (parent && parent->_color == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_right)//父亲为右{Node* Uncle = grandfather->_left;if (Uncle && Uncle->_color == RED){parent->_color = Uncle->_color = BREAK;grandfather->_color = RED;cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){RotateL(grandfather);grandfather->_color = RED;parent->_color = BREAK;}else//交叉{RotateRL(grandfather);grandfather->_color = RED;cur->_color = BREAK;}}}else//父亲在左;{Node* Uncle = grandfather->_right;if (Uncle && Uncle ->_color== RED){parent->_color = Uncle->_color = BREAK;grandfather->_color = RED;cur = grandfather;parent = cur->_parent;}else//叔叔不存在,或者等于黑色,我直线或者弯曲{if (cur == cur->_left){RotateR(grandfather);parent->_color = BREAK;grandfather->_color = RED;}else{RotateLR(grandfather);cur->_color = BREAK;grandfather->_color = RED;}}}_root->_color = BREAK;}return true;}}void RotateL(Node * parent)//穿的是问题节点,{Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;cur->_left = parent;if (curleft){curleft->_parent = parent;}Node* pphead = parent->_parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (pphead->_left == parent){pphead->_left = cur;}else if (pphead->_right == parent){pphead->_right = cur;}cur->_parent = pphead;} }void RotateR(Node * parent){Node* cur = parent->_left;Node* curright = cur->_right;cur->_right = parent;parent->_left = curright;if (curright){curright->_parent = parent;}Node* phead = parent->_parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (phead->_left == parent){phead->_left = cur;}else{phead->_right = cur;}cur->_parent = phead;}}void RotateRL(Node * parent)//传谁谁父亲下去{Node* cur = parent->_right;Node* curleft = cur->_left;RotateR(parent->_right);RotateL(parent);}void RotateLR(Node * parent){Node* cur = parent->_left;Node* curright = cur->_right;RotateL(cur);RotateR(parent);}bool IsBalanceTree(){return IsBalanceTree(_root);}bool IsBalanceTree(Node* root){if (root == nullptr){return true;}if (root->_color != BREAK){return false;}int benchmark = 0;Node* cur = root;while (cur){if (cur->_color == BREAK){benchmark++;}cur = cur->_left;}return CheckColour(root, 0, benchmark);}bool CheckColour(Node* root, int blacknum, int benchmark){if (root == nullptr){if (blacknum != benchmark){return false;}return true;}if (root->_color == BREAK){blacknum++;}if (root->_color == RED && root->_parent && root->_parent->_color == RED){cout << root->_kv.first << "出现连续红节点" << endl;}return CheckColour(root->_left, blacknum, benchmark);}private:Node* _root = nullptr;
};
相关文章:

第23课-C++-红黑树的插入与旋转
🌇前言 红黑树是一种自平衡的二叉搜索树,因其出色的性能,广泛应用于实际中。Linux 内核中的 CFS 调度器便是一个使用红黑树的例子,这足以说明它的重要性。红黑树的实现通过红黑两种颜色的控制来维持平衡,并在必要时使…...

【C#】C#编程入门指南:构建你的.NET开发基础
文章目录 前言:1. C# 开发环境 VS的基本熟悉2. 解决方案与项目的关系3. 编辑、编译、链接、运行4. 托管代码和CLR4.1 CLR:4.2 C# 代码第编译过程(两次编译的) 5. 命名空间6. 类的组成与分析7. C# 的数据类型7.1 值类型7.2 引用类型…...
[系统安全] PE文件知识在免杀中的应用
0x1 PE文件与免杀思路 基于PE文件结构知识的免杀技术主要用于对抗启发式扫描。 通过修改PE文件中的一些关键点来达到欺骗反病毒软件的目的。 修改区段名 1.1 移动PE文件头位置免杀 工具:PeClean SizeOfOptionalHeader字段来描述扩展头的大小,恒定值为…...

相机标定原理
相机标定原理 什么是相机标定相机畸变 什么是相机标定 为了确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,需建立相机成像的几何模型,几何模型参数即为相机参数,求解相机参数的过程就是相机标定。 坐标系 **世界坐标…...

Linux基础开发工具使用
目录 1. 软件包管理器yum 1.1 概念介绍 1.2 更换镜像源(可选) 1.3 工具的搜索/查看/安装/卸载 1.4 优势 2. vim编辑器 2.1 vi和vim 2.2 三种常用模式和操作 2.3 配置vim 3. Linux编译器-gcc/g 4. Linux调试器-gdb 5. make和Makefile 6.…...
蓝牙PBAP协议及Android实现
文章目录 前言一、什么是PBAP协议?PBAP的关键功能 二、PBAP的工作流程PBAP流程 三、PBAP在Android实现关键步骤:1. 检查设备是否支持 PBAP 服务 2. 创建 PBAP 连接3. 发送 OBEX 请求4. 解析 vCard 数据数据存储与展示6. 性能优化建议7. 完整示例…...
Py之pymupdf:基于langchain框架结合pymupdf库实现输出每个PDF页面的文本内容、元数据等
Py之pymupdf:基于langchain框架结合pymupdf库实现输出每个PDF页面的文本内容、元数据等 目录 PyMuPDFLoader类 初始化 属性 方法 __init__(file_path, *, headers=None, extract_images=False, **kwargs) lazy_load() aload() alazy_load() load(**kwargs) load_and…...
LeetCode题解:17.电话号码的数字组合【Python题解超详细,回溯法、多叉树】,知识拓展:深度优先搜索与广度优先搜索
题目描述 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 示例 1: 输入:digits "23" 输出…...

《JVM第10课》内存溢出(OOM)排查过程
文章目录 常用命令1. jps2. jconsole3. jstat4. jmap 工具1.jvisualvm 排查OOM的方法其实很简单很简单。 如果能找到拋OOM的日志,可以在日志里看到是哪一行抛出的OOM异常。如果找不到日志,那么处理方式是导出Java进程的内存快照,然后用工具查…...

Thinkphp6视图介绍
一.MVC MVC 软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller) ThinkPHP6 是一个典型的 MVC 架构 控制器—控制器,用于将用户请求转发给相应的Model进行处理&a…...
躺平成长-人工智能进行编程-(12)
躺平成长: 让每一个人在科技(开源的网络/智能科技对于生活琐事的处理)的帮助下,实现养生反卷,躺平成长。 开源竞争: 当你无法彻底掌握技术的时候,你就开源这个技术,形成技术依赖&a…...

计算机网络中的域名系统(DNS)及其优化技术
💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 计算机网络中的域名系统(DNS)及其优化技术 计算机网络中的域名系统(DNS)及其优化…...
Matplotlib库中show()函数的用法
在Matplotlib库中使用show()函数是用于显示绘制的图形的函数。它将图形显示在屏幕上或保存到文件中。show()函数通常在绘制完图形后调用。 Matplotlib是一个用于绘制2D图形的Python库,它提供了丰富的绘图工具和函数,可以用于创建各种类型的图表…...
C#中object和dynamic
在C#中,object和dynamic都是用于存储不同类型值的类型,但它们之间存在一些关键的区别: object object是C#中的基元类型之一,是所有其他类型的最终基类。当你将一个值赋给object类型的变量时,编译器会执行装箱操作&am…...

Spring Cloud Eureka 服务注册与发现
Spring Cloud Eureka 服务注册与发现 一、Eureka基础知识概述1.Eureka两个核心组件2.Eureka 服务注册与发现 二、Eureka单机搭建三、Eureka集群搭建四、心跳续约五、Eureka自我保护机制 一、Eureka基础知识概述 1.Eureka两个核心组件 Eureka Server :服务注册中心…...

【WPF】Prism学习(三)
Prism Commands 1.复合命令(Composite Commanding) 这段内容主要介绍了在应用程序中如何使用复合命令(Composite Commands)来实现多个视图模型(ViewModels)上的命令。以下是对这段内容的解释: …...

1+X应急响应(网络)系统加固:
系统加固: 数据库的重要性: 数据库面临的风险: 数据库加固: 业务系统加固: 安全设备加固: 网络设备加固:...

使用 Grafana api 查询 Datasource 数据
一、使用grafana 的api 接口 官方API 二、生成Api key 点击 Administration -》Users and accss -》Service accounts 进入页面 点击Add service account 创建 service account 点击Add service account token 点击 Generate token , 就可以生成 api key 了 三、进入grafana…...

【电子设计】按键LED控制与FreeRTOS
1. 安装Keilv5 打开野火资料,寻找软件包 解压后得到的信息 百度网盘 请输入提取码 提取码:gfpp 安装526或者533版本都可以 下载需要的 F1、F4、F7、H7 名字的 DFP pack 芯片包 安装完 keil 后直接双击安装 注册操作,解压注册文件夹后根据里面的图示步骤操作 打开说明 STM…...

JMeter中添加请求头
在JMeter中添加请求头的步骤如下: 1.打开HTTP信息头管理器 : 首先,你需要进入JMeter的HTTP请求组件。这可以通过在HTTP请求测试元素上右键点击,然后选择“添加 > 配置元件 > HTTP信息头管理器”来完成。 2.添加新的请求头…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...