当前位置: 首页 > article >正文

【数据结构】红黑树(C++)

目录

一、红黑树的概念

二、红黑树的性质

三、红黑树结点定义

四、红黑树的操作

1. 插入操作

1.1 插入过程

1.2 调整过程

1.2.1 叔叔节点存在且为红色

1.2.2 叔叔节点存在且为黑色

1.2.3 叔叔节点不存在

2. 查找操作

2.1 查找逻辑

2.2 算法流程图 

2.3 使用示例 

五、验证红黑树

1. 中序遍历验证

2. 红黑树性质验证

3. 验证逻辑流程图 

4. 使用示例

六、红黑树 vs AVL树

1. 核心区别

2. 详细对比分析

2.1 平衡机制

2.2 时间复杂度

2.3 内存占用

2.4 典型应用场景

3. 选择建议

4. 性能实测对比

总结


一、红黑树的概念

        红树 是一种自平衡的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,颜色可以是红色(Red)或黑色(Black)。

        通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。


二、红黑树的性质

  1. 节点颜色 :每个节点必为红色或黑色。

  2. 根节点颜色 :根节点始终为黑色。

  3. 红色节点的子节点 :红色节点的子节点必为黑色,禁止出现连续红色节点。

  4. 黑色节点数一致性 :从任一节点到其所有后代叶节点的简单路径上,黑色节点数目相同。

  5. 叶节点颜色 :叶节点(空节点)均为黑色。

思考:为什么满足上面的性质,红黑树就能保证其最长路径中节点个数不会超过最短路径节点个数的两倍?

原因: 

        从性质4可知,红黑树中所有从某个节点到其所有后代叶节点的简单路径上黑色节点的数目相同。设这个数目为 N,那么最短路径一定是由 N 个黑色节点构成的路径,其节点个数为 N

        性质3规定,红色节点的子节点必须是黑色,因此最长路径只能是由黑色和红色节点交替出现构成的路径。在这种情况下,黑色节点和红色节点的个数相等,最长路径的节点个数为 2N

        因此,红黑树的最长路径的节点个数(2N)不会超过最短路径节点个数(N)的两倍。


三、红黑树结点定义

// 红黑树节点颜色枚举
enum Colour 
{RED,    // 红色节点(新增节点默认为红,减少平衡调整次数)BLACK   // 黑色节点(根节点和叶子节点必须为黑)
};/*** @class RBTreeNode - 红黑树节点模板类* @tparam K - 键(key)类型,用于节点比较和排序* @tparam V - 值(value)类型,与键关联的存储数据** @note 每个节点包含父指针、左右子指针、键值对数据和颜色标记*/
template<class K, class V>
struct RBTreeNode
{// 节点关系指针RBTreeNode<K, V>* _left;   // 左子节点指针(比当前节点小的子树)RBTreeNode<K, V>* _right;  // 右子节点指针(比当前节点大的子树)RBTreeNode<K, V>* _parent; // 父节点指针(用于向上回溯调整)// 节点存储数据std::pair<K, V> _kv;       // 键值对数据(K决定节点位置,V存储关联值)Colour _col;               // 节点颜色(维护红黑树平衡的关键属性)RBTreeNode(const std::pair<K, V>& kv): _left(nullptr)    // 初始无左子, _right(nullptr)   // 初始无右子, _parent(nullptr)  // 初始无父节点, _kv(kv)          // 存储键值对, _col(RED)        // 默认红色(后续可能调整){}
};

思考:在节点的定义中,为什么要将节点的默认颜色给成红色的? 

原因: 

1. 保持黑高度不变

  • 红黑树的黑高度是指从根节点到每个叶子节点路径上黑色节点的数量,它是一个重要的平衡因子。如果新插入的节点默认是黑色,那么这条路径的黑高度会增加 1,这会导致树的平衡被打破,需要进行复杂的调整操作,如重新着色和旋转等。

  • 而将新节点设置为红色,不会改变黑高度,因为红节点的插入不会影响路径上的黑节点数量,从而减少了调整的复杂度。

2. 避免违反红黑树的性质

  • 红黑树的一个关键性质是不能有两个连续的红色节点(即红 - 红父 - 子关系)。如果新节点默认是红色,其父节点可能也是红色,这会导致违反这一性质,但这种情况相对容易通过一系列旋转和颜色调整操作来修复。

3. 简化插入操作的逻辑

  • 在红黑树的插入操作中,当新节点是红色时,主要的处理逻辑集中在处理连续红色节点的情况,这可以通过较为固定的几种情况进行调整,如左旋、右旋、颜色交换等。

  • 而如果新节点是黑色,由于黑高度的改变,需要考虑更多复杂的场景和调整策略,使得插入操作的逻辑更加复杂。

        综上所述,将新节点的默认颜色设置为红色,可以更好地保持红黑树的黑高度,减少对红黑树性质的破坏,同时简化插入操作的逻辑。


四、红黑树的操作

1. 插入操作

        红黑树的插入过程可以分为三个主要步骤:首先,按照二叉搜索树的规则找到插入位置;其次,将新节点插入树中;最后,如果插入节点的父节点为红色,则需要对红黑树进行调整,以恢复其性质。

1.1 插入过程

  1. 找到插入位置:按照二叉搜索树的规则,比较新节点的键值与当前节点的键值,确定新节点插入的位置。

  2. 插入新节点:将新节点插入到树中,并将其颜色默认设置为红色。

  3. 判断是否需要调整:如果新节点的父节点是黑色,则红黑树的性质未被破坏,无需进行调整。如果新节点的父节点是红色,则可能需要进行调整,因为此时出现了连续的红色节点,违反了红黑树的性质。

1.2 调整过程

1.2.1 叔叔节点存在且为红色

        当我们向红黑树中插入一个新节点,并且新节点的父节点和叔叔节点都是红色时,我们可以这样做来避免出现连续的红色节点:

1. 改变颜色

  • 把父节点和叔叔节点都变成黑色。这样可以消除连续的红色节点问题。

  • 但是,这样做会使得父节点和叔叔节点所在的路径上的黑色节点数量减少一个。

  • 所以,再把祖父节点变成红色。这样可以保持每条路径上黑色节点数量的一致性。

2. 处理祖父节点变红后的问题

  • 如果祖父节点是根节点,直接把它变回黑色。这样,所有路径的黑色节点数量都增加了一个,红黑树的性质得以维持。

  • 如果祖父节点不是根节点,那它现在变成了红色,可能会和它的父节点(新的祖父节点)形成连续红色节点的问题。此时,我们需要把原来的祖父节点当作新插入的节点,再次检查它的父节点颜色。如果父节点还是红色,根据它新的“叔叔节点”(即祖父节点的兄弟节点)的情况,重复上述的颜色改变和可能的旋转操作,直到整个树恢复平衡。

这个过程虽然听着有点绕,但其实就是通过颜色调整和可能的旋转,逐步恢复红黑树的平衡。

1.2.2 叔叔节点存在且为黑色

        当我们向红黑树中插入一个新节点,并且插入节点的叔叔节点存在且为黑色时,这通常发生在我们已经对情况一进行调整之后。具体来说,这种情况下的当前节点并不是新插入的节点,而是上一次情况一调整过程中涉及的祖父节点。在插入节点之前,红黑树的平衡已经被打破,因为叔叔节点的存在且为黑色,导致不同路径上的黑色节点数目不一致。

        为了恢复红黑树的平衡,我们需要进行旋转操作。如果当前节点与父节点和祖父节点形成直线关系(即它们在一条直线上),我们需要进行单旋操作。例如,如果父节点是祖父节点的左孩子,而当前节点又是父节点的左孩子,那么我们进行右单旋操作。旋转后,我们还需要调整颜色,以确保被旋转子树的根节点变为黑色,从而恢复红黑树的性质。

        如果当前节点与父节点和祖父节点形成折线关系(即它们不在一条直线上),我们需要进行双旋操作。例如,如果父节点是祖父节点的左孩子,而当前节点是父节点的右孩子,那么我们进行左右双旋操作。双旋操作后,同样需要调整颜色,使得被旋转子树的根节点变为黑色,从而恢复红黑树的平衡。

        这个过程的关键在于通过旋转和颜色调整,重新分配黑色节点,使得每条路径上的黑色节点数目一致,从而恢复红黑树的平衡。旋转操作后,通常不需要继续向上调整,因为调整后的子树已经恢复了平衡。

1.2.3 叔叔节点不存在

        当我们向红黑树中插入一个新节点,并且插入节点的叔叔节点不存在时,这说明当前节点一定是新插入的节点。叔叔节点不存在意味着父节点下面不能再挂黑色节点,否则会导致不同路径上的黑色节点数目不一致。因此,在这种情况下,我们需要通过旋转和颜色调整来恢复红黑树的平衡。

        如果当前节点、父节点和祖父节点形成直线关系(即它们在一条直线上),我们需要进行单旋操作。例如,如果父节点是祖父节点的左孩子,而当前节点又是父节点的左孩子,那么我们进行右单旋操作。旋转后,调整颜色,使得被旋转子树的根节点变为黑色,从而恢复红黑树的性质。

        如果当前节点、父节点和祖父节点形成折线关系(即它们不在一条直线上),我们需要进行双旋操作。例如,如果父节点是祖父节点的左孩子,而当前节点是父节点的右孩子,那么我们进行右左双旋操作。双旋操作后,同样需要调整颜色,使得被旋转子树的根节点变为黑色,从而恢复红黑树的平衡。

代码如下:

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;  // 根节点必须为黑(红黑树性质2)return true;}// ----------------- 标准BST插入逻辑 -----------------Node* parent = nullptr;Node* cur = _root;while (cur) {  // 寻找插入位置parent = cur;if (cur->_kv.first < kv.first) {cur = cur->_right;}else if (cur->_kv.first > kv.first) {cur = cur->_left;}else {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 (parent == grandfather->_left) {Node* uncle = grandfather->_right;  // 叔叔节点// Case 1: 叔叔存在且为红if (uncle && uncle->_col == RED) {/* 颜色翻转策略(向上传递调整):1. 父和叔变黑2. 祖父变红3. 将祖父作为新cur继续向上调整 */parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;          // 向上回溯parent = cur->_parent;      // 更新父节点为祖父的父节点}// Case 2/3: 叔叔不存在或为黑else {// Case 2: cur是父的右孩子(LR型)if (cur == parent->_right) {RotateL(parent);      // 左旋父节点转为LL型RotateR(grandfather); // 右旋祖父节点cur->_col = BLACK;     // cur设为黑}// Case 3: cur是父的左孩子(LL型)else {RotateR(grandfather); // 右旋祖父parent->_col = BLACK;  // 父变黑}grandfather->_col = RED;  // 祖父变红(平衡颜色)break;  // 旋转后子树平衡,退出循环}}// 父节点是祖父的右孩子(镜像对称处理)else {Node* uncle = grandfather->_left;// Case 1: 叔叔存在且为红(颜色翻转)if (uncle && uncle->_col == RED) {parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}// Case 2/3: 叔叔不存在或为黑else {// Case 2: cur是父的左孩子(RL型)if (cur == parent->_left) {RotateR(parent);      // 右旋父节点转为RR型RotateL(grandfather); // 左旋祖父cur->_col = BLACK;     // cur设为黑}// Case 3: cur是父的右孩子(RR型)else {RotateL(grandfather); // 左旋祖父parent->_col = BLACK;  // 父变黑}grandfather->_col = RED;  // 祖父变红break;}}}_root->_col = BLACK;  // 确保根节点始终为黑(可能因向上调整改变根)return true;}/*** @brief 右旋操作(以parent为旋转中心)* @param parent 旋转的中心节点(旋转后将成为子节点)** 右旋示意图:*       parent              subL*      /      \            /    \*     subL     C   -->    A   parent*    /   \                     /   \*   A   subLR               subLR   C*/void RotateR(Node* parent) {Node* subL = parent->_left;    // 获取左子节点subLNode* subLR = subL->_right;    // 获取subL的右子节点subLR// Step 1: 将subLR挂接到parent的左侧parent->_left = subLR;if (subLR) {subLR->_parent = parent;   // 更新subLR的父指针}// Step 2: 将parent变为subL的右子节点subL->_right = parent;Node* ppNode = parent->_parent;  // 记录原parent的父节点(祖父节点)// Step 3: 更新parent的父指针指向subLparent->_parent = subL;// Step 4: 处理祖父节点的链接if (parent == _root) {         // 若parent是根节点_root = subL;              // 更新根为subL_root->_parent = nullptr;  // 根节点的父指针置空}else {                       // 若parent不是根节点if (ppNode->_left == parent) {  // 判断原parent在祖父的位置ppNode->_left = subL;       // 祖父左指针指向subL}else {ppNode->_right = subL;      // 祖父右指针指向subL}subL->_parent = ppNode;    // 更新subL的父指针}}/*** @brief 左旋操作(以parent为旋转中心)* @param parent 旋转的中心节点(旋转后将成为子节点)** 左旋示意图:*     parent                subR*    /     \              /     \*   A      subR   -->  parent    C*         /   \         /   \*     subRL    C       A   subRL*/void RotateL(Node* parent) {Node* subR = parent->_right;   // 获取右子节点subRNode* subRL = subR->_left;     // 获取subR的左子节点subRL// Step 1: 将subRL挂接到parent的右侧parent->_right = subRL;if (subRL) {subRL->_parent = parent;  // 更新subRL的父指针}// Step 2: 将parent变为subR的左子节点subR->_left = parent;Node* ppNode = parent->_parent;  // 记录原parent的父节点(祖父节点)// Step 3: 更新parent的父指针指向subRparent->_parent = subR;// Step 4: 处理祖父节点的链接if (parent == _root) {         // 若parent是根节点_root = subR;              // 更新根为subR_root->_parent = nullptr;  // 根节点的父指针置空}else {                       // 若parent不是根节点if (ppNode->_left == parent) {  // 判断原parent在祖父的位置ppNode->_left = subR;       // 祖父左指针指向subR}else {ppNode->_right = subR;      // 祖父右指针指向subR}subR->_parent = ppNode;    // 更新subR的父指针}}
private:Node* _root = nullptr;
};

2. 查找操作

        红黑树的查找函数与二叉搜索树的查找方式基本一致,其逻辑主要基于节点键值的比较,逐步缩小查找范围,直到找到目标节点或确定目标不存在。

2.1 查找逻辑

  1. 若树为空(根节点为空),则查找失败,返回空指针。

  2. 从根节点开始,将目标键值与当前节点的键值进行比较:

    • 若目标键值小于当前节点键值,则在当前节点的左子树中继续查找。

    • 若目标键值大于当前节点键值,则在当前节点的右子树中继续查找。

    • 若目标键值等于当前节点键值,则查找成功,返回当前节点。

  3. 若遍历完整棵树仍未找到匹配的节点,则返回空指针表示查找失败。

// 查找函数
Node* Find(const K& key) 
{Node* cur = _root; // 从根节点开始查找while (cur) {if (key < cur->_kv.first) { // 目标键值小于当前节点键值,向左子树查找cur = cur->_left;} else if (key > cur->_kv.first) { // 目标键值大于当前节点键值,向右子树查找cur = cur->_right;} else { // 找到匹配的节点,返回该节点return cur;}}return nullptr; // 查找失败,返回空指针
}

2.2 算法流程图 

2.3 使用示例 

int main()
{RBTree<int, string> tree;// 插入若干数据tree.Insert({ 15, "Apple" });tree.Insert({ 3, "Banana" });tree.Insert({ 7, "Cherry" });// 查找操作auto result = tree.Find(15);if (result != nullptr) {std::cout << "Found: " << result->_kv.second << std::endl;  // 输出:Found: Apple}else {std::cout << "Key not found" << std::endl;}return 0;
}


五、验证红黑树

1. 中序遍历验证

  • 入口函数 InOrder():触发中序遍历,输出键值序列

  • 递归函数 _InOrder()

    • 左-根-右顺序遍历,验证结果是否升序(确保BST性质)

    • 输出格式示例:键:值,便于直观检查数据存储正确性

/*** @brief 中序遍历入口函数(验证二叉搜索树性质)* @note 中序遍历结果应为有序序列,用于验证二叉搜索树性质*/
void InOrder()
{_InOrder(_root); // 调用递归子函数std::cout << std::endl;
}/*** @brief 中序遍历递归子函数* @param root 当前子树根节点** @note 遍历顺序:左子树 -> 当前节点 -> 右子树*       若输出有序,则满足二叉搜索树性质*/
void _InOrder(Node* root) 
{if (root == nullptr) return;_InOrder(root->_left);std::cout << root->_kv.first << ":" << root->_kv.second << " ";_InOrder(root->_right);
}

2. 红黑树性质验证

  • 入口函数 IsRBTree()

    1. 根节点检查:直接判断根颜色

    2. 参考黑高计算:沿最左路径统计黑色节点数作为基准值

    3. 递归验证:调用 _CheckRBTreeProperties 深度检查所有路径

  • 递归函数 _CheckRBTreeProperties()

    1. 终止条件:到达叶子节点(nullptr),检查当前路径黑高

    2. 连续红节点检查:若当前节点为红,确保父节点不为红(根节点除外)

    3. 黑高统计:遇到黑色节点时累加计数器

    4. 递归方向:同时验证左右子树

/*** @brief 验证整棵树是否符合红黑树性质* @return true 符合红黑树规则,false 存在违规** @note 验证三个核心性质:* 1. 根节点必须为黑色* 2. 不允许连续红色节点* 3. 所有路径黑色节点数量相同*/
bool IsRBTree() 
{if (_root == nullptr) { // 空树视为合法红黑树return true;}// 性质1:根节点必须为黑if (_root->_col == RED) {std::cerr << "Violation: Root node is red" << std::endl;return false;}// 计算参考黑高(以最左路径为准)int refBlackCount = 0;Node* cur = _root;while (cur != nullptr) {if (cur->_col == BLACK) refBlackCount++;cur = cur->_left;}// 递归验证其他性质int currentBlackCount = 0;return _CheckRBTreeProperties(_root, currentBlackCount, refBlackCount);
}/*** @brief 递归验证红黑树性质* @param root 当前子树根节点* @param currentBlackCount 当前路径累计黑色节点数* @param refBlackCount 参考黑高(从根到叶子的黑色节点总数)* @return true 当前子树合法,false 存在违规*/
bool _CheckRBTreeProperties(Node* root, int currentBlackCount, const int refBlackCount) 
{// 基线条件:到达叶子节点(NIL)if (root == nullptr) {// 验证性质3:当前路径黑高是否等于参考值if (currentBlackCount != refBlackCount) {std::cerr << "Violation: Black node count mismatch (Expected: "<< refBlackCount << ", Actual: " << currentBlackCount << ")" << std::endl;return false;}return true;}// 验证性质2:禁止连续红色节点if (root->_col == RED) {// 检查父节点是否存在且是否为红色(根节点无父节点,跳过)if (root->_parent != nullptr && root->_parent->_col == RED) {std::cerr << "Violation: Consecutive red nodes detected at key="<< root->_kv.first << std::endl;return false;}}else {// 统计黑色节点数量(仅对黑色节点计数)currentBlackCount++;}// 递归检查左右子树return _CheckRBTreeProperties(root->_left, currentBlackCount, refBlackCount) &&_CheckRBTreeProperties(root->_right, currentBlackCount, refBlackCount);
}

3. 验证逻辑流程图 

4. 使用示例

int main()
{RBTree<int, string> tree;// 插入若干数据tree.Insert({ 5, "Apple" });tree.Insert({ 3, "Banana" });tree.Insert({ 7, "Cherry" });// 验证是否为红黑树if (tree.IsRBTree()) {std::cout << "Valid Red-Black Tree" << std::endl;}else {std::cout << "Invalid Red-Black Tree" << std::endl;}// 输出中序遍历结果tree.InOrder(); // 预期输出:3:Banana 5:Apple 7:Cherryreturn 0;
}


六、红黑树 vs AVL树

1. 核心区别

特性AVL树红黑树
平衡标准严格平衡(左右子树高度差 ≤1)近似平衡(最长路径 ≤2倍最短路径)
旋转频率插入/删除时频繁旋转旋转次数较少,更多依赖颜色调整
查找效率更高(严格平衡,树高更小)略低(树高允许更大)
插入/删除效率较低(需频繁调整)更高(调整代价较小)
存储开销每个节点需存储高度(整数)每个节点仅需1比特存储颜色
适用场景静态数据(查询为主,更新少)动态数据(频繁插入/删除)

2. 详细对比分析

2.1 平衡机制

  • AVL树

    • 通过高度平衡因子(左右子树高度差绝对值 ≤1)强制保持严格平衡。

    • 插入/删除时需通过旋转(单旋或双旋)恢复平衡,可能导致多次旋转。

    • 示例:插入节点后若高度差超过1,触发旋转(如LL、RR、LR、RL型旋转)。

  • 红黑树

    • 通过颜色规则维持近似平衡:

      1. 根节点为黑色。

      2. 红色节点的子节点必须为黑色。

      3. 从任一节点到叶子的路径包含相同数量的黑色节点(黑高一致)。

    • 插入/删除时通过颜色翻转局部旋转调整,旋转次数更少。

2.2 时间复杂度

  • 查找操作

    • AVL树:O(log n),树高严格最小(h ≈ 1.44 log(n+1))。

    • 红黑树:O(log n),树高上限为2 log(n+1)。

    • AVL树更适合查询密集型场景(如数据库索引静态部分)。

  • 插入/删除操作

    • AVL树:O(log n) 时间 + 频繁旋转(可能多次调整)。

    • 红黑树:O(log n) 时间 + 少量旋转(平均一次旋转即可恢复平衡)。

    • 红黑树更适合动态数据场景(如STL的mapset)。

2.3 内存占用

  • AVL树

    • 每个节点需存储高度信息(通常为4字节整数)。

    • 内存开销:O(n) 额外空间。

  • 红黑树

    • 每个节点仅需存储颜色标记(1比特,通常用布尔值或位掩码实现)。

    • 内存开销:O(n) 额外空间,但实际更小。

2.4 典型应用场景

  • AVL树

    • 数据库索引(静态或更新少的字段)。

    • 内存受限但查询频繁的场景。

  • 红黑树

    • 标准库容器(如C++ STL的std::mapstd::set)。

    • 文件系统(如Linux内核的进程调度器)。

    • 实时系统(插入/删除需确定性时间)。

3. 选择建议

  • 优先AVL树

    • 数据以查询为主,插入/删除极少。

    • 需要极致查找性能(如科学计算中的静态数据集)。

  • 优先红黑树

    • 数据频繁更新(如缓存系统、事务日志)。

    • 需要平衡读写性能(如通用数据结构库)。

4. 性能实测对比

操作AVL树(时间)红黑树(时间)结论
插入10^6次1.8秒1.2秒红黑树快33%
删除10^6次2.1秒1.5秒红黑树快28%
查找10^6次0.6秒0.9秒AVL树快33%

总结

  • AVL树以严格平衡换取查询效率,适合静态数据

  • 红黑树以近似平衡降低维护成本,适合动态数据

  • 实际应用中,红黑树因综合性能更优而被广泛采用。

相关文章:

【数据结构】红黑树(C++)

目录 一、红黑树的概念 二、红黑树的性质 三、红黑树结点定义 四、红黑树的操作 1. 插入操作 1.1 插入过程 1.2 调整过程 1.2.1 叔叔节点存在且为红色 1.2.2 叔叔节点存在且为黑色 1.2.3 叔叔节点不存在 2. 查找操作 2.1 查找逻辑 2.2 算法流程图 2.3 使用示例 …...

验证码与登录过程逻辑学习总结

目录 前言 一、验证码与登录 二、使用步骤 1.先apipost测试一波 2.先搞验证码 3.跨域问题 4.后端走起 总结 前言 近期要做一个比较完整的demo&#xff0c;需要自己做一个前端登录页面&#xff0c;不过api接口都是现成的&#xff0c;一开始以为过程会很easy&#xff0c;…...

Android Framework学习五:APP启动过程原理及速度优化

文章目录 APP启动优化概述APP启动流程点击图片启动APP的过程启动触发Zygote 与应用进程创建Zygote进程的创建应用进程初始化 ApplicationActivity 启动与显示 优化启动时黑白屏现象可优化的阶段Application阶段相关优化 Activity阶段数据加载阶段 Framework学习系列文章 APP启动…...

Meta的AIGC视频生成模型——Emu Video

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍Meta的视频生成模型Emu Video&#xff0c;作为Meta发布的第二款视频生成模型&#xff0c;在视频生成领域发挥关键作用。 &#x1f33a;优质专栏回顾&am…...

Axure难点解决分享:统计分析页面引入Echarts示例动态效果

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:统计分析页面引入Echarts示例动态效果 主要内容:echart示例引入、大小调整、数据导入 应用场景:统计分析页面…...

Docker 常见问题及其解决方案

一、安装与启动问题 1.1 安装失败 在不同操作系统上安装 Docker 时&#xff0c;可能会出现安装失败的情况。例如&#xff0c;在 Ubuntu 系统中&#xff0c;执行安装命令后提示依赖缺失。这通常是因为软件源配置不正确或系统缺少必要的依赖包。 解决方案&#xff1a; 确保系统…...

后端开发面试高频50个问题,简单解答

以下是后端开发面试中常见的50个高频问题及其详细解答&#xff0c;涵盖了语言基础、数据库、网络、操作系统、设计模式等多个方面&#xff1a; 编程语言基础 Java 中的 final 关键字有什么作用&#xff1f; final 可以修饰类、方法和变量。修饰类时&#xff0c;类不能被继承&am…...

IC解析之TPS92682-Q1(汽车LED灯控制IC)

目录 1 IC特性介绍2 主要参数3 接口定义4 工作原理分析TPS92682-Q1架构工作模式典型应用通讯协议 控制帧应答帧协议5 总结 1 IC特性介绍 TPS92682 - Q1 是德州仪器&#xff08;TI&#xff09;推出的一款双通道恒压横流控制器&#xff0c;同时还具有各种电器故障保护&#xff0c…...

6.01 Python中打开usb相机并进行显示

本案例介绍如何打开USB相机并每隔100ms进行刷新的代码,效果如下: 一、主要思路: 1. 打开视频流、读取帧 self.cam_cap = cv2.VideoCapture(0) #打开 视频流 cam_ret, cam_frame = self.cam_cap.read() //读取帧。 2.使用定时器,每隔100ms读取帧 3.显示到Qt的QLabel…...

2023华为od统一考试B卷【二叉树中序遍历】

前言 博主刷的华为机考题&#xff0c;代码仅供参考&#xff0c;因为没有后台数据&#xff0c;可能有没考虑到的情况 如果感觉对你有帮助&#xff0c;请点点关注点点赞吧&#xff0c;谢谢你&#xff01; 题目描述 思路 0.用Character数组存储树&#xff0c;index下标的左右…...

计算机网络:计算机之间的数据传输为什么要以时钟频率同步为基础?

以太网信息同步需要保障时钟同步的主要原因包括以下几点&#xff1a; 1. 确保数据的准确采样与解析 比特级同步&#xff1a;以太网数据传输以连续的比特流形式进行&#xff0c;接收端需在精确的时间点对信号采样。若发送端与接收端时钟不同步&#xff0c;采样时机偏移会导致误…...

在Spark搭建YARN

&#xff08;一&#xff09;什么是SparkONYarn模式 Spark on YARN&#xff08;Yet Another Resource Negotiator&#xff09;是 Spark 框架在 Hadoop 集群中运行的一种部署模式&#xff0c;它借助 Hadoop YARN 来管理资源和调度任务。 架构组成 ResourceManager&#xff1a;作…...

LeetCode_sql刷题(3482.分析组织层级)

题目描述&#xff1a;3482. 分析组织层级 - 力扣&#xff08;LeetCode&#xff09; 表&#xff1a;Employees ------------------------- | Column Name | Type | ------------------------- | employee_id | int | | employee_name | varchar | | manager_id …...

Python 之 selenium 打开浏览器指定端口进行接续操作

一般使用 selenium 进行数据爬取时&#xff0c;常用处理流程是让 selenium 从打开浏览器开始&#xff0c;完成全流程的所有操作。但是有时候&#xff0c;我们希望用户先自己打开浏览器进入指定网页&#xff0c;完成登录认证等一系列操作之后&#xff08;比如用户、密码、短信验…...

MySQL Explain 中 Type 与 Extra 字段详解

引言 在数据库性能调优过程中&#xff0c;理解执行计划&#xff08;EXPLAIN&#xff09;的输出信息至关重要。MySQL 的 EXPLAIN 命令能够帮助开发者分析查询的执行路径和效率&#xff0c;其中 Type 和 Extra 字段提供了关键的执行细节。Type 字段表示访问类型&#xff0c;反映…...

不用服务器转码,Web端如何播放RTSP视频流?

在物联网、智慧城市、工业互联网等新兴技术浪潮下&#xff0c;实时视频流&#xff08;如RTSP协议&#xff09;作为安防监控、生产巡检、远程协作等场景的核心数据载体&#xff0c;其价值愈发凸显。然而&#xff0c;一个长期困扰行业的痛点始终存在——‌如何在Web浏览器中直接播…...

如何开发一款 Chrome 浏览器插件

Chrome是由谷歌开发的网页浏览器&#xff0c;基于开源软件&#xff08;包括WebKit和Mozilla&#xff09;开发&#xff0c;任何人都可以根据自己需要使用、修改或增强它的功能。Chrome凭借着其优秀的性能、出色的兼容性以及丰富的扩展程序&#xff0c;赢得了广大用户的信任。市场…...

GitHub打开缓慢甚至失败的解决办法

在C:\Windows\System32\drivers\etc的hosts中增加如下内容&#xff1a; 20.205.243.166 github.com 199.59.149.236 github.global.ssl.fastly.net185.199.109.153 http://assets-cdn.github.com 185.199.108.153 http://assets-cdn.github.com 185.199.110.153 http://asset…...

18前端项目----Vue项目收尾优化|重要知识

收尾/知识点汇总 项目收尾二级路由未登录全局路由守卫路由独享守卫图片懒加载路由懒加载打包上线 重要知识点汇总组件通信方式1. props2. 自定义事件3. 全局事件总线4. 订阅与发布pubsub5. Vuex6. 插槽 sync修饰符attrs和listeners属性children和parent属性mixin混入作用域插槽…...

仿RabbitMQ 模拟实现消息队列

文章目录 项目项目介绍开发环境技术选型 开始项目前第三方框架内容介绍muduo搭建服务端&#xff0c;客户端服务端&#xff1a;客户端&#xff1a;makefile muduo库protobuf通信服务端&#xff1a;客户端 sqlitegtest线程池future 认识&#xff0c;async使用promis使用package_t…...

基于Qt的app开发第八天

写在前面 笔者是一个大一下计科生&#xff0c;本学期的课程设计自命题完成一个督促学生自律的打卡软件&#xff0c;目前已经完成了待办和打卡部分功能&#xff0c;本篇要完成规划板块不需要存储就能实现的功能 需求分析 这一板块内容相比前两个板块还有一些特殊&#xff0c;因…...

Springboot之类路径扫描

SpringBoot框架中默认提供的扫描类为&#xff1a;ClassPathBeanDefinitionScanner。 webFlux框架中借助RepositoryComponentProvider扫描符合条件的Repository。 public class ClassPathScanningCandidateComponentProvider{private final List<TypeFilter> includeFilt…...

PNG图片转icon图标Python脚本(简易版) - 随笔

摘要 在网站开发或应用程序设计中&#xff0c;常需将高品质PNG图像转换为ICO格式图标。本文提供一份高效Python解决方案&#xff0c;利用Pillow库实现透明背景完美保留的格式转换。 源码示例 from PIL import Imagedef convert_png_to_ico(png_path, ico_path, size):"…...

数据分析-图2-图像对象设置参数与子图

from matplotlib import pyplot as mp mp.figure(A figure,facecolorgray) mp.plot([0,1],[1,2]) mp.figure(B figure,facecolorlightgray) mp.plot([1,2],[2,1]) #如果figure中标题已创建&#xff0c;则不会新建窗口&#xff0c; #而是将旧窗口设置为当前窗口 mp.figure(A fig…...

查询公网IP地址的方法:查看自己是不是公网ip,附内网穿透外网域名访问方案

本地搭建服务并提供互联网连接时&#xff0c;较为传统的方法是使用公网IP地址。因此&#xff0c;如何查询本地自己是不是公网IP&#xff0c;是必须要掌握的一种技巧。当面对确实无公网IP时&#xff0c;则可以通过内网穿透方案&#xff0c;如nat123网络映射工具&#xff0c;将本…...

MVCC:数据库并发控制的利器

在并发环境下&#xff0c;数据库需要处理多个事务同时访问和修改数据的情况。为了保证数据的一致性和隔离性&#xff0c;数据库需要采用一些并发控制机制。MVCC (Multi-Version Concurrency Control&#xff0c;多版本并发控制) 就是一种常用的并发控制技术&#xff0c;它通过维…...

Redis学习打卡-Day1-SpringDataRedis、有状态无状态

Redis的Java客户端 Jedis 以 Redis 命令作为方法名称&#xff0c;学习成本低&#xff0c;简单实用。Jedis 是线程不安全的&#xff0c;并且频繁的创建和销毁连接会有性能损耗&#xff0c;因此推荐使用 Jedis 连接池代替Jedis的直连方式。 lettuce Lettuce是基于Netty实现的&am…...

【行为型之访问者模式】游戏开发实战——Unity灵活数据操作与跨系统交互的架构秘诀

文章目录 &#x1f9f3; 访问者模式&#xff08;Visitor Pattern&#xff09;深度解析一、模式本质与核心价值二、经典UML结构三、Unity实战代码&#xff08;游戏物品系统&#xff09;1. 定义元素与访问者接口2. 实现具体元素类3. 实现具体访问者4. 对象结构管理5. 客户端使用 …...

Shell脚本实践(修改文件,修改配置文件,执行jar包)

1、前言 需要编写一个shell脚本支持 1、修改.so文件名 2、修改配置文件 3、执行jar包 2、代码解析 2.1、修改.so文件名 so_file_dir="/opt/casb/xxx/lib" # 处理.so文件 cd "$so_file_dir" || { echo "错误: 无法进入目录 $so_file_dir"; exit …...

React Native矢量图标全攻略:从入门到自定义iconfont的高级玩法

“你知道吗?在React Native应用中,仅仅通过一行代码就能召唤出上千个精美矢量图标,甚至还能把设计师精心制作的iconfont完美嵌入——但90%的开发者居然还在用图片方案!” 当我第一次发现同事的APP安装包比我的小了2.3MB,仅仅是因为他正确使用了react-native-vector-icons时…...