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

红黑树算法笔记

文章目录

    • 红黑树 (Red-Black Tree) 学习笔记
      • 0. 节点结构与哨兵节点
      • 1. 什么是红黑树?
      • 2. 红黑树的五个核心性质
      • 3. 为什么需要红黑树?
      • 4. 红黑树的基本操作
        • a. 查找 (Search)
        • b. 插入 (Insert)
        • c. 删除 (Delete)
      • 5. 维护平衡的关键操作
        • a. 变色 (Recoloring)
        • b. 旋转 (Rotations)
      • 6. 红黑树的优缺点
        • 优点:
        • 缺点:
      • 7. 应用场景

红黑树 (Red-Black Tree) 学习笔记

0. 节点结构与哨兵节点

在深入之前,我们先定义红黑树的节点结构,并引入一个重要的概念:哨兵节点 (Sentinel Node)

  • 颜色 (Color): REDBLACK
  • 键 (Key): 节点存储的值
  • 左孩子 (Left): 指向左子节点
  • 右孩子 (Right): 指向右子节点
  • 父节点 (Parent): 指向父节点

哨兵节点 (NIL):
为了简化红黑树操作的边界条件处理,我们通常使用一个特殊的哨兵节点 NIL 来代表所有的叶子节点(外部节点)以及根节点的父节点。

  • NIL 节点的颜色总是 黑色
  • NIL 节点的 key, left, right, parent 字段可以不关心或指向自身。
  • 所有“真正的”叶子节点的 leftright 指针都指向这个唯一的 NIL 哨兵节点。
enum Color { RED, BLACK };struct Node {int key;Color color;Node *parent;Node *left;Node *right;// Constructor for a new node (usually red)Node(int k, Color c = RED, Node* p = nullptr, Node* l = nullptr, Node* r = nullptr): key(k), color(c), parent(p), left(l), right(r) {}
};// Global sentinel node
Node* NIL; // Should be initialized once, e.g., in the RBT class constructor// NIL = new Node(0, BLACK); NIL->parent = NIL; NIL->left = NIL; NIL->right = NIL;// Or make it a static member of a RBT class.

在后续伪代码中,当提到叶节点或空指针时,通常指的就是这个 NIL 哨兵节点。

1. 什么是红黑树?

红黑树是一种自平衡的二叉查找树(Self-Balancing Binary Search Tree)。它通过在每个节点上增加一个额外的颜色属性(红色或黑色)并遵循一组特定的规则来确保树在插入和删除操作后保持大致平衡,从而保证了在最坏情况下的操作时间复杂度为 O(log N),其中 N 是树中节点的数量。这种平衡不是绝对的(像 AVL 树那样左右子树高度差最多为1),而是通过性质保证最长路径不超过最短路径的两倍。

2. 红黑树的五个核心性质

红黑树必须始终满足以下五个性质:

  1. 性质1 (颜色): 每个节点要么是红色,要么是黑色。
  2. 性质2 (根节点): 根节点是黑色的。
  3. 性质3 (叶节点): 所有叶节点(NIL 节点,即空节点或外部节点)都是黑色的。在实现中,这些通常是哨兵节点 NIL
  4. 性质4 (红色节点): 如果一个节点是红色的,则它的两个子节点都是黑色的。(即,不能有两个连续的红色节点,从根到叶的路径上红色节点不相邻)。
  5. 性质5 (黑色高度): 对每个节点,从该节点到其所有后代叶节点(NIL节点)的简单路径上,均包含相同数目的黑色节点。这个数目被称为节点的“黑色高度 (black-height)”。

推论:

  • 从根到叶子的最长路径(红黑交替)最多是最短路径(全黑)的两倍长。
  • 一棵有 n 个内部节点的红黑树的高度 h <= 2 * log2(n+1)

3. 为什么需要红黑树?

  • 普通二叉查找树 (BST) 的问题: 在极端情况下(例如,按顺序插入已排序的数据),BST 可能退化成链表,导致查找、插入、删除操作的时间复杂度变为 O(N)。这使得 BST 在动态数据集上的性能不可靠。
  • 红黑树的保证: 红黑树通过上述五个性质,确保树的高度始终保持在对数级别,即 O(log N)。这使得即使在最坏情况下,其各项操作(查找、插入、删除)也能保持高效。
  • 与 AVL 树的比较:
    • 平衡性: AVL 树是更严格平衡的(左右子树高度差不超过1),红黑树则相对宽松(最长路径不超过最短路径两倍)。
    • 操作效率:
      • 查找: AVL 树通常更快,因为它更平衡,树高更低。
      • 插入/删除: 红黑树通常更快。AVL 树为了维持严格平衡可能需要进行多次旋转(最多 O(log N) 次),而红黑树的插入/删除操作中,旋转次数是常数级的(插入最多2次,删除最多3次),变色操作可能是 O(log N) 次。因此,在写操作频繁的场景下,红黑树可能更有优势。
    • 实现复杂度: 两者都比较复杂。红黑树的修复情况(cases)比 AVL 树多,但每次修复涉及的旋转次数少。

4. 红黑树的基本操作

a. 查找 (Search)

与普通二叉查找树的查找操作完全相同。从根节点开始,比较目标值与当前节点键值,决定向左子树还是右子树继续查找,直到找到目标节点或到达 NIL 节点。

时间复杂度:O(log N),因为树高是对数级别的。

Node* search(Node* root, int key) {Node* current = root;while (current != NIL && current->key != key) {if (key < current->key) {current = current->left;} else {current = current->right;}}return current; // Returns NIL if not found
}
b. 插入 (Insert)

插入操作分为两步:

  1. 标准 BST 插入: 像在普通二叉查找树中一样找到新节点的插入位置,并插入新节点。
  2. 着色和修复:
    • 着色: 新插入的节点 z 总是被初始化为 红色
      • 原因:
        • 如果设为黑色,几乎肯定会违反性质5 (黑色高度),因为会增加一条路径上的黑色节点数,而其他路径不变。修复性质5通常比修复性质4复杂。
        • 设为红色,性质5天然满足(新红节点不改变任何路径的黑色节点数)。可能违反的性质是:
          • 性质2:如果 z 是根节点且为红色 (简单修复:将其变黑)。
          • 性质4:如果 z 的父节点 z->parent 也是红色 (需要更复杂的修复)。
    • 修复 (Fixup): 调用一个修复过程 insert_fixup(z),通过一系列的变色 (Recoloring)旋转 (Rotations) 操作来恢复红黑树的性质。修复过程从新插入的节点 z 开始,向上回溯,直到所有性质都满足。

插入修复 (insert_fixup(z)) 逻辑:
循环条件:只要 z 不是根节点,且 z 的颜色是红色,并且 z->parent 的颜色也是红色 (违反性质4)。

p = z->parent (父节点),g = p->parent (祖父节点,此时 p 为红色,g 必然存在且为黑色,否则在 p 插入时就已违反性质4)。
uz 的叔叔节点 (u = (p == g->left) ? g->right : g->left)。

  • Case 1: z 的叔叔 u 是红色。

    • 动作:
      1. p (父) 设为黑色。
      2. u (叔) 设为黑色。
      3. g (祖父) 设为红色。
      4. 将当前节点 z 设为 g (z = g)。
    • 解释: 通过将 pu 变黑,g 变红,我们将问题向上推移了两层。g 变成红色可能会与 g 的父节点形成新的红-红冲突,所以需要继续对 g 进行检查。这条路径的黑色高度不变。
          G(B)                 G(R) <-- z moves here for next iteration/   \                /   \P(R)   U(R)  --->   P(B)   U(B)/                    /z(R)                 z(R) (original z)
    
  • Case 2: z 的叔叔 u 是黑色 (或 NIL),且 zp 的内侧孩子 (Zig-Zag 情况)。
    (例如, pg 的左孩子, zp 的右孩子;或者 pg 的右孩子, zp 的左孩子)

    • 动作:
      1. 如果 z == p->rightp == g->left (左-右情况): 对 p 左旋。然后 z 变成原来的 p,原来的 p 变成 z 的左孩子。
      2. 如果 z == p->leftp == g->right (右-左情况): 对 p 右旋。然后 z 变成原来的 p,原来的 p 变成 z 的右孩子。
      3. 在旋转后,zp 交换了角色。将 z 指向新的父节点 (原来的 p)。
    • 解释: 这一步是为了将 Zig-Zag 情况转换为 Zig-Zig 情况 (Case 3),方便后续处理。旋转后,新的 z (原来的 p) 和其父节点 (原来的 z) 以及祖父节点 g 会形成一条直线。
    • 现在 z 是外侧孩子,进入 Case 3。
    (Example: p is left child, z is right child of p)G(B)                   G(B)/   \                  /   \P(R)   U(B)  --L_ROT(P)--> z(R)  U(B)  <-- old p becomes child of z/  \                     /  \             <-- z becomes new p for Case 3z(R)                 P(R)/(old z's left child if any)
    
  • Case 3: z 的叔叔 u 是黑色 (或 NIL),且 zp 的外侧孩子 (Zig-Zig 情况)。
    (例如, pg 的左孩子, zp 的左孩子;或者 pg 的右孩子, zp 的右孩子)

    • 动作:
      1. p (父) 设为黑色。
      2. g (祖父) 设为红色。
      3. 如果 z == p->left (左-左情况): 对 g 右旋。
      4. 如果 z == p->right (右-右情况): 对 g 左旋。
    • 解释: 通过变色和一次旋转,红-红冲突被解决。旋转后,原来的 p 成为子树的根,颜色为黑,其子节点 z (原当前节点) 和 g (原祖父) 均为红色,满足性质4。由于 p 替代了原来黑色的 g 的位置,路径的黑色高度也得以保持。调整完成。
    (Example: p is left child, z is left child of p - Left-Left)G(B)                   P(B)/   \                  /   \P(R)   U(B)  --R_ROT(G)--> z(R) G(R)/                          \z(R)                         U(B)
    

循环结束后:
最后,无论如何,将根节点 root 设为黑色 (满足性质2)。

// In RBT class
// Node* root; (initialized to NIL in constructor)
// Node* NIL;  (global or static member, initialized)void left_rotate(Node* x);  // See section 5
void right_rotate(Node* x); // See section 5void insert_fixup(Node* z) {Node* y; // Unclewhile (z->parent->color == RED) { // Parent is red (z is also red initially)if (z->parent == z->parent->parent->left) { // Parent is a left childy = z->parent->parent->right; // Uncleif (y->color == RED) { // Case 1: Uncle is REDz->parent->color = BLACK;y->color = BLACK;z->parent->parent->color = RED;z = z->parent->parent; // Move z up to grandparent} else { // Uncle is BLACKif (z == z->parent->right) { // Case 2: z is a right child (Zig-Zag LR)z = z->parent;left_rotate(z);}// Case 3: z is a left child (Zig-Zig LL, or after Case 2 transform)z->parent->color = BLACK;z->parent->parent->color = RED;right_rotate(z->parent->parent);}} else { // Parent is a right child (symmetric to above)y = z->parent->parent->left; // Uncleif (y->color == RED) { // Case 1: Uncle is REDz->parent->color = BLACK;y->color = BLACK;z->parent->parent->color = RED;z = z->parent->parent;} else { // Uncle is BLACKif (z == z->parent->left) { // Case 2: z is a left child (Zig-Zag RL)z = z->parent;right_rotate(z);}// Case 3: z is a right child (Zig-Zig RR, or after Case 2 transform)z->parent->color = BLACK;z->parent->parent->color = RED;left_rotate(z->parent->parent);}}}root->color = BLACK; // Ensure root is black
}void insert_node(int key) {Node* z = new Node(key, RED, NIL, NIL, NIL); // New node is REDNode* y = NIL;    // trailing pointerNode* x = root; // current pointerwhile (x != NIL) {y = x;if (z->key < x->key) {x = x->left;} else {x = x->right;}}z->parent = y;if (y == NIL) { // Tree was emptyroot = z;} else if (z->key < y->key) {y->left = z;} else {y->right = z;}// z->left and z->right are already NIL from constructorinsert_fixup(z);
}
c. 删除 (Delete)

删除操作是红黑树中最复杂的操作。

  1. 标准 BST 删除:
    • 首先找到要删除的节点 z
    • 如果 z 最多只有一个孩子:直接删除 z,用其唯一的孩子(或 NIL)替换它。
    • 如果 z 有两个孩子:找到 z 的中序后继 y (即 z 右子树中的最小节点)。用 y 的键值替换 z 的键值,然后问题转化为删除节点 y。此时 y 最多只有一个右孩子 (因为 y 是其子树中最小的,所以它没有左孩子)。
  2. 实际被物理删除的节点 y 和其替代者 x
    • 经过上述步骤,我们总能确定一个节点 y,它是实际要从树中物理移除的节点。y 最多只有一个非 NIL 的子节点。
    • xy 的那个唯一的孩子(如果存在),或者 NIL(如果 y 没有孩子)。x 将会取代 y 在树中的位置。
  3. 修复 (delete_fixup(x)):
    • 如果被删除的节点 y 的颜色是 红色
      • 删除红色节点 y 不会违反任何红黑树性质。红色节点不影响黑色高度,删除它也不会造成红-红冲突(因为它的子节点 x 必然是黑色,根据性质4)。所以操作完成。
    • 如果被删除的节点 y 的颜色是 黑色
      • 删除黑色节点 y 会导致经过 y 的路径上的黑色节点数量减少1,从而违反性质5 (黑色高度)。
      • 同时,如果 x (替代者) 是红色且 x 的父节点 (原来 y 的父节点) 也是红色,则会违反性质4。
      • 为了修复,我们视 x 为一个“额外”的黑色。如果 x 原本是红色,它现在就变成了“红加黑”,即一个普通的黑色节点,修复完成。
      • 如果 x 原本是黑色 (包括 NIL 节点),它现在就变成了“双重黑色 (doubly black)”。这意味着 x 所在位置需要一个额外的黑色来弥补被删除的 y 的黑色。这时就需要调用 delete_fixup(x)

删除修复 (delete_fixup(x)) 逻辑:
循环条件:只要 x 不是根节点,并且 x 的颜色是黑色 (表示它仍带有“双黑”属性)。
p = x->parent。令 sx 的兄弟节点。

  • Case 1: x 的兄弟 s 是红色。

    • 动作:
      1. s 设为黑色。
      2. p (父) 设为红色。
      3. 如果 xp 的左孩子,对 p 左旋;否则对 p 右旋。
      4. 更新 sx 的新兄弟节点 (它现在是原来 s 的一个孩子,并且是黑色的)。
    • 解释: 这个操作将 Case 1 转换为 Case 2、3 或 4 中的一种,即 x 的兄弟是黑色的情况。旋转后 x 的新兄弟是黑色的,并且 x 的父节点 p 变成了红色。路径的黑色高度不变。
    (x is left child, s is red right child)P(B/R)                   S(B)/     \                  /   \x(DB)   S(R)  --L_ROT(P)--> P(R)  S_child_R(B/R)/   \              /   \S_L(B) S_R(B)      x(DB) S_L(B)  <-- s is now S_L
    
  • Case 2: x 的兄弟 s 是黑色,且 s 的两个孩子都是黑色。

    • 动作:
      1. s (兄弟) 设为红色。
      2. 将当前节点 x 设为 p (父) (x = p)。
    • 解释:s 变红,这样 xs 子树的黑色高度都减少了1。这个“双黑”问题就被推给了父节点 p。如果 p 原本是红色,现在变黑,问题解决。如果 p 原本是黑色,它现在变成了新的“双黑”节点,继续循环。
         P(B/R)                       P(was B -> DB, was R -> B)/     \                      /     \x(DB)   S(B)      --->       x(B)    S(R)/   \                        /   \SL(B) SR(B)                  SL(B) SR(B)
    
  • Case 3: x 的兄弟 s 是黑色,s 的靠近 x 的孩子是红色,s 的远离 x 的孩子是黑色。
    (例如, x 是左孩子, s->left 是红色, s->right 是黑色)

    • 动作:
      1. s->left (或 s->right,如果 x 是右孩子) 设为黑色。
      2. s 设为红色。
      3. 如果 xp 的左孩子,对 s 右旋;否则对 s 左旋。
      4. 更新 sx 的新兄弟节点 (它现在是原来 s->lefts->right,并且是黑色的)。
    • 解释: 这个操作将 Case 3 转换为 Case 4。旋转后,x 的新兄弟节点是黑色的,并且其远离 x 的孩子是红色的。
    (x is left child)P(B/R)                         P(B/R)/     \                        /     \x(DB)   S(B)       --->        x(DB)   SL(B) <-- new S/   \                                \SL(R) SR(B)                             S(R)\SR(B)
    
  • Case 4: x 的兄弟 s 是黑色,且 s 的远离 x 的孩子是红色。
    (例如, x 是左孩子, s->right 是红色)

    • 动作:
      1. s 的颜色设为 p (父) 的颜色。
      2. p (父) 设为黑色。
      3. s 的远离 x 的孩子 (例如 s->right) 设为黑色。
      4. 如果 xp 的左孩子,对 p 左旋;否则对 p 右旋。
      5. x 设为根节点 (这会终止循环)。
    • 解释: 这是最终解决“双黑”问题的步骤。通过旋转和变色,额外的黑色被消除,并且所有红黑树性质都得到恢复。p 的旋转将兄弟 s 提升,其红色子节点用于平衡黑色高度。
    (x is left child, s->right is red)P(ColorP)                     S(ColorP)/       \                     /       \x(DB)     S(B)     --L_ROT(P)--> P(B)      SR(B)/   \                  /   \SL(B/R) SR(R)          x(B)  SL(B/R)
    

循环结束后:
如果 x 不是 NIL,将 x 的颜色设为黑色。这可以安全地吸收任何剩余的“双黑” (如果 x 是根) 或确保性质。

辅助函数 transplant(u, v):
用节点 v 替换子树 u。它负责更新 u 的父节点指向 v,以及 v 的父节点指向 u 的父节点。

void transplant(Node* u, Node* v) {if (u->parent == NIL) {root = v;} else if (u == u->parent->left) {u->parent->left = v;} else {u->parent->right = v;}v->parent = u->parent; // Even if v is NIL, NIL->parent should be set
}Node* tree_minimum(Node* node) {while (node->left != NIL) {node = node->left;}return node;
}void delete_fixup(Node* x) {Node* s; // Siblingwhile (x != root && x->color == BLACK) {if (x == x->parent->left) { // x is left childs = x->parent->right;if (s->color == RED) { // Case 1: Sibling s is reds->color = BLACK;x->parent->color = RED;left_rotate(x->parent);s = x->parent->right; // Update sibling}// s is now guaranteed blackif (s->left->color == BLACK && s->right->color == BLACK) { // Case 2: Sibling's children are both blacks->color = RED;x = x->parent; // Move double blackness up} else {if (s->right->color == BLACK) { // Case 3: Sibling's near child red, far child blacks->left->color = BLACK;s->color = RED;right_rotate(s);s = x->parent->right; // Update sibling}// Case 4: Sibling's far child is reds->color = x->parent->color;x->parent->color = BLACK;s->right->color = BLACK;left_rotate(x->parent);x = root; // Problem solved, exit loop}} else { // x is right child (symmetric cases)s = x->parent->left;if (s->color == RED) { // Case 1s->color = BLACK;x->parent->color = RED;right_rotate(x->parent);s = x->parent->left;}if (s->right->color == BLACK && s->left->color == BLACK) { // Case 2s->color = RED;x = x->parent;} else {if (s->left->color == BLACK) { // Case 3s->right->color = BLACK;s->color = RED;left_rotate(s);s = x->parent->left;}// Case 4s->color = x->parent->color;x->parent->color = BLACK;s->left->color = BLACK;right_rotate(x->parent);x = root;}}}if (x != NIL) x->color = BLACK; // Ensure x is black (e.g. if it became root and was red)
}void delete_node_val(int key) {Node* z = search(root, key);if (z == NIL) return; // Node not foundNode* y = z; // y is the node to be physically removed or movedNode* x;     // x is the child that replaces yColor y_original_color = y->color;if (z->left == NIL) {x = z->right;transplant(z, z->right);} else if (z->right == NIL) {x = z->left;transplant(z, z->left);} else {y = tree_minimum(z->right); // y is z's successory_original_color = y->color;x = y->right; // x is y's right child (y's left child is NIL)if (y->parent == z) {x->parent = y; // Important if x is NIL} else {transplant(y, y->right);y->right = z->right;y->right->parent = y;}transplant(z, y);y->left = z->left;y->left->parent = y;y->color = z->color;}// delete z; // Actual memory deallocation for z if it was a two-child case,// or if y == z. If y was successor, y got z's data and z is effectively// now where y was, and needs to be handled.// Proper memory management requires careful thought. For now, assume z// points to the node structure that is no longer part of the tree logic.if (y_original_color == BLACK) {delete_fixup(x);}// Don't forget to `delete` the node that was removed from the tree structure// For example, if y was z, then 'delete z' after transplant.// If y was z's successor, 'delete node_that_was_originally_y'// The logic above reuses y's node structure to replace z. The node physically// removed is the one that y was pointing to if y != z, or z itself if y == z.// A common implementation detail: the node to be deallocated is the one whose// content was overwritten (if successor copied) or the one directly removed.
}

5. 维护平衡的关键操作

a. 变色 (Recoloring)

改变节点的颜色(红色变黑色,黑色变红色)。这是最简单的操作,用于调整节点颜色以满足红黑树的性质,通常不改变树的结构,或者作为旋转操作的一部分。变色本身非常快,是 O(1) 操作。

b. 旋转 (Rotations)

旋转操作用于改变树的局部结构,重新组织节点间的父子关系,同时保持二叉查找树的性质(即中序遍历顺序不变)。旋转的目的是在不破坏 BST 性质的前提下,调整节点间的父子关系,以帮助恢复红黑树的性质(特别是性质4 - 无连续红节点,和性质5 - 黑色高度)。旋转是 O(1) 操作。

  • 左旋 (Left Rotation) on node x:
    假设 x 有一个右孩子 y (y 不能是 NIL)。左旋使 y 成为新的子树根,x 成为 y 的左孩子。y 原来的左孩子 B 成为 x 的右孩子。

      Parent            Parent|                 |x                 y/ \               / \A   y    --Left_Rotate(x)-->   x   C/ \                       / \B   C                     A   B
    

    步骤:

    1. y = x->right
    2. x->right = y->left (将 B 过继给 x)
    3. 如果 y->left != NIL, 则 y->left->parent = x
    4. y->parent = x->parent ( y 连接到 x 的原父节点)
    5. 如果 x->parent == NIL ( x 是根), 则 root = y
    6. 否则,如果 x == x->parent->left, 则 x->parent->left = y
    7. 否则 (x == x->parent->right), 则 x->parent->right = y
    8. y->left = x
    9. x->parent = y
  • 右旋 (Right Rotation) on node x:
    假设 x 有一个左孩子 y (y 不能是 NIL)。右旋使 y 成为新的子树根,x 成为 y 的右孩子。y 原来的右孩子 B 成为 x 的左孩子。

        Parent            Parent|                 |x                 y/ \               / \y   C  --Right_Rotate(x)--> A   x/ \                           / \A   B                         B   C
    

    步骤 (对称于左旋):

    1. y = x->left
    2. x->left = y->right (将 B 过继给 x)
    3. 如果 y->right != NIL, 则 y->right->parent = x
    4. y->parent = x->parent
    5. 如果 x->parent == NIL, 则 root = y
    6. 否则,如果 x == x->parent->right, 则 x->parent->right = y
    7. 否则 (x == x->parent->left), 则 x->parent->left = y
    8. y->right = x
    9. x->parent = y
// In RBT class
// Node* root;
// Node* NIL;void left_rotate(Node* x) {Node* y = x->right; // Set yx->right = y->left; // Turn y's left subtree into x's right subtreeif (y->left != NIL) {y->left->parent = x;}y->parent = x->parent; // Link x's parent to yif (x->parent == NIL) {root = y;} else if (x == x->parent->left) {x->parent->left = y;} else {x->parent->right = y;}y->left = x; // Put x on y's leftx->parent = y;
}void right_rotate(Node* x) {Node* y = x->left; // Set yx->left = y->right; // Turn y's right subtree into x's left subtreeif (y->right != NIL) {y->right->parent = x;}y->parent = x->parent; // Link x's parent to yif (x->parent == NIL) {root = y;} else if (x == x->parent->right) {x->parent->right = y;} else {x->parent->left = y;}y->right = x; // Put x on y's rightx->parent = y;
}

6. 红黑树的优缺点

优点:
  • 性能保证: 所有基本操作(查找、插入、删除)在最坏情况下的时间复杂度都是 O(log N)。这是其最重要的特性。
  • 相对高效的插入/删除: 相较于 AVL 树(另一种自平衡 BST),红黑树在插入和删除时需要的旋转次数较少(AVL 树可能需要 O(log N) 次旋转,红黑树插入最多2次,删除最多3次旋转)。变色操作虽然可能沿着路径向上传播,但总体上写操作的平均常量因子较低。
  • 广泛应用: 由于其稳定的性能和相对 AVL 树更优的写操作效率,许多标准库和系统组件都采用红黑树。
  • 空间开销小: 每个节点只需要额外存储一位颜色信息(1 bit)。
缺点:
  • 实现复杂: 相较于普通 BST,红黑树的插入和删除操作涉及到多种情况的判断、变色和旋转,实现起来要复杂得多,容易出错。删除操作尤其复杂。
  • 查找性能略逊于 AVL 树: 由于红黑树的平衡性不如 AVL 树严格(红黑树的最长路径可以是最短路径的2倍,AVL树是高度差最多1),其树高可能略大于 AVL 树。因此,在纯查找密集型应用中,AVL 树理论上可能略快一点(但两者都是 O(log N) 级别,实际差异通常不大)。
  • 理解难度: 各种修复情况的逻辑和它们如何维持红黑性质的证明比较微妙,学习曲线较陡峭。

7. 应用场景

红黑树因其高效和稳定的性能,在需要频繁进行查找、插入和删除操作的动态数据集合中得到广泛应用:

  • 关联数组/映射表 (Associative Arrays/Maps):
    • C++ STL: std::map, std::multimap, std::set, std::multiset
    • Java: java.util.TreeMap, java.util.TreeSet
  • 进程调度:
    • Linux 内核的 Completely Fair Scheduler (CFS) 使用红黑树来管理任务队列,确保进程按虚拟运行时间公平地获得 CPU。
  • 内存管理:
    • 在某些操作系统的内核或用户态内存分配器中,用于管理空闲内存块,可以快速找到合适大小的块或合并相邻块。
  • IO 多路复用:
    • 如 Linux 的 epoll 在内核中可能使用红黑树来管理被监控的文件描述符集合,以便高效地添加、删除和查找事件。
  • 数据库索引:
    • 虽然 B树及其变种在磁盘存储的数据库中更常见,但某些内存数据库或特定类型的索引可能会使用红黑树或其变种。
  • 几何计算:
    • 例如,计算几何中的区间树 (Interval Tree) 可以基于红黑树构建。

相关文章:

红黑树算法笔记

文章目录 红黑树 (Red-Black Tree) 学习笔记0. 节点结构与哨兵节点1. 什么是红黑树&#xff1f;2. 红黑树的五个核心性质3. 为什么需要红黑树&#xff1f;4. 红黑树的基本操作a. 查找 (Search)b. 插入 (Insert)c. 删除 (Delete) 5. 维护平衡的关键操作a. 变色 (Recoloring)b. 旋…...

【Axios】解决Axios下载二进制文件返回空对象的问题

【Axios】解决Axios下载二进制文件返回空对象的问题 问题背景 在一个基于Vue 3的项目中,我们使用Axios下载Excel文件,但遇到了一个奇怪的问题:文件能成功下载下来,但打开时显示内容为[object Object]无法使用。 当我们执行下载代码: const response = await downloadT…...

2.MySQL数据库操作

一.MySQL数据库介绍 数据库目前标准指令集是SQL&#xff0c;即结构化查询语言。SQL语言主要由以下几部分组成 DDL&#xff08;数据定义语言&#xff09;&#xff1a;用来建立数据库、数据库对象和定义字段&#xff0c;如create、alter、drop。 DML&#xff08;数据操纵语言&…...

01.three官方示例+编辑器+AI快速学习webgl_animation_keyframes

实例&#xff1a;examples/webgl_animation_keyframes.html 在这里插入图片描述 重点关注&#xff1a; AnimationMixer&#xff1a;管理模型的所有动画AnimationClip&#xff1a;表示一个完整的动画ClipAction&#xff1a;控制动画的播放状态&#xff08;播放、暂停、速度等&am…...

在 Spring Boot 中实现动态线程池的全面指南

动态线程池是一种线程池管理方案&#xff0c;允许在运行时根据业务需求动态调整线程池参数&#xff08;如核心线程数、最大线程数、队列容量等&#xff09;&#xff0c;以优化资源利用率和系统性能。在 Spring Boot 中&#xff0c;动态线程池可以通过 Java 的 ThreadPoolExecut…...

餐饮行业新风口:上门厨师服务系统的技术实现路径

上门做饭正在成为下一个万亿级风口&#xff01;当外卖平台被预制菜攻陷&#xff0c;当年轻人对着料理包无可奈何&#xff0c;一个全新的餐饮模式正在悄然崛起。 我们的市场调研显示&#xff0c;83%的消费者无法分辨外卖是否使用预制菜&#xff0c;76%的年轻人愿意为透明烹饪过程…...

odoo-049 Pycharm 中 git stash 后有pyc 文件,如何删除pyc文件

文章目录 问题描述解决思路正确的去除 git 跟踪 pyc文件的做法 问题描述 查看本地 stash 列表 stash 后有很多 pyc 文件都被 git 追踪了&#xff0c;这样不合理&#xff0c;而且等 unstash 的时候就会有问题 解决思路 尝试方法&#xff1a; 递归地删除指定文件夹及其子目录中…...

线程同步机制

synchronized 实现线程同步的关键字&#xff0c;用来防止多个线程同时访问某个代码块或方法&#xff0c;避免并发冲突和数据不一致。通过持有一把唯一的对象锁&#xff0c;谁拿到了谁就能执行&#xff0c;谁没拿到只能等待锁释放。 1. 修饰实例方法&#xff08;锁当前实例&…...

YOLO目标检测算法

文章目录 前言一、目标检测算法简介1、传统目标检测算法&#xff08;1&#xff09;R-CNN算法简介&#xff08;2&#xff09;Fast R-CNN算法简介&#xff08;3&#xff09;Faster R-CNN算法简介 2、目标检测中的算法设计范式&#xff08;1&#xff09;one-stage&#xff08;2&am…...

【官方题解】StarryCoding 入门教育赛 2 | acm | 蓝桥杯 | 新手入门

比赛传送门&#xff1a; 本场比赛开始时题面存在一些问题&#xff0c;私密马赛&#xff01; A.池化【入门教育赛】 根据题目所给公式计算即可。 #include "bits/stdc.h"signed main() {int t; std::cin >> t;while (t --) {int l, k, s, p; std::cin >&…...

《让歌声跨越山海:Flutter借助Agora SDK实现高质量连麦合唱》

对于Flutter开发者而言&#xff0c;借助Agora SDK实现这一功能&#xff0c;不仅能为用户带来前所未有的社交体验&#xff0c;更是在激烈的市场竞争中脱颖而出的关键。 Agora SDK作为实时通信领域的佼佼者&#xff0c;拥有一系列令人瞩目的特性&#xff0c;使其成为实现高质量连…...

1.3.2 linux音频PulseAudio详细介绍

PulseAudio 是一个在 Linux 及其他类 Unix 操作系统中广泛使用的声音服务器&#xff08;Sound Server&#xff09;&#xff0c;它为不同的音频应用程序提供了一种中间层&#xff0c;以方便管理和控制音频流。下面将详细介绍 PulseAudio 的相关内容&#xff0c;包括其基本概念、…...

8.1.Kubernetes进阶

目录 一、Kubernetes核心原理深度解析 架构设计精髓 • 控制平面组件&#xff08;API Server、etcd、Controller Manager、Scheduler&#xff09;协作流程 • 数据平面&#xff08;kubelet、容器运行时、CNI/CSI插件&#xff09;核心工作机制 API对象与声明式模型 • CRD&…...

electron 结合 react(cra创建的) 创建桌面应用和打包桌面应用

我说一下 react 结合 electron 如果打包和使用&#xff0c;以及其中可能会遇到的问题&#xff0c;这里只做简单功能的演示 我们先通过 cra 创建一个 react 项目&#xff0c;然后安装相关依赖&#xff0c;之后启动 npx create-react-app react_electron cd react_electron np…...

C++23 views::chunk_by (P2443R1) 详解

文章目录 引言C23 范围库概述范围视图&#xff08;Range Views&#xff09;范围算法&#xff08;Range Algorithms&#xff09;范围适配器&#xff08;Range Adapters&#xff09; std::views::chunk_by 介绍基本概念特性使用场景 示例代码简单示例自定义谓词示例 总结 引言 在…...

MySQL核心内容【持续更新中】

MySQL核心内容 文章目录 MySQL核心内容1.MySQL核心内容目录2.MySQL知识面扩展3.MySQL安装4.MySQL配置目录介绍Mysql配置远程ip连接 5.MySQL基础1.MySQL数据类型1.数值类型2.字符串类型3.日期和时间类型4.enum和set 2.MySQL运算符1.算数运算符2.逻辑运算符3.比较运算符 3.MySQL完…...

【高级IO】多路转接之单线程Reactor

这里写目录标题 一.Epoll的两种工作模式二.单线程Reactor1.Connection模块2.Reactor服务器模块2.1初始化Init2.2启动循环服务器Loop2.3事件派发Dispatcher2.4连接管理器Accepter2.5事件管理器Receiver2.6发送管理器Sender 3.上层业务模块定制协议业务处理 代码 一.Epoll的两种工…...

基于设备指纹识别的反爬虫技术:给设备办 “身份证”

传统的封禁 IP、验证码等反爬虫手段已逐渐失效&#xff0c;基于设备指纹识别的反爬虫技术应运而生&#xff0c;成为守护数据安全的新防线。它如同给每个设备办一张独一无二的 “身份证”&#xff0c;精准区分正常用户与爬虫工具。 一、基础参数采集&#xff1a;构建设备指纹的…...

公开模型一切,优于DeepSeek-R1,英伟达开源Llama-Nemotron家族

在大模型飞速发展的今天&#xff0c;推理能力作为衡量模型智能的关键指标&#xff0c;更是各家 AI 企业竞相追逐的焦点。 但近年来&#xff0c;推理效率已成为模型部署和性能的关键限制因素。 基于此&#xff0c;英伟达推出了 Llama-Nemotron 系列模型&#xff08;基于 Meta …...

CI/CD面试题及答案

一、CI/CD 基础概念 1. 什么是 CI/CD&#xff1f;CI 和 CD 的区别是什么&#xff1f; 答案&#xff1a; CI&#xff08;持续集成&#xff09;&#xff1a;开发人员提交代码后&#xff0c;自动构建并运行测试&#xff0c;确保代码集成无冲突。CD&#xff08;持续交付 / 部署&am…...

解决 Ubuntu DNS 无法解析问题(适用于虚拟机 长期使用)

解决 Ubuntu DNS 无法解析问题 在使用 Ubuntu 虚拟机&#xff08;尤其是在国内&#xff09;时&#xff0c;经常会遇到这样的错误&#xff1a; Temporary failure resolving cn.archive.ubuntu.com但是此时又能成功 ping 通 IP&#xff0c;这说明网络是正常的&#xff0c;问题…...

如何通过C# 获取Excel单元格的数据类型

在处理 Excel 文件时&#xff0c;了解单元格的数据类型有助于我们正确地解析和处理数据。Free Spire.XLS 是一款功能强大且免费的.NET 组件&#xff0c;支持高效地操作 Excel 文件&#xff0c;包括读取单元格类型。本文将详细介绍如何使用 Free Spire.XLS 来获取 Excel 单元格的…...

Spring Boot初级教程:从零搭建企业级Java应用

一、Spring Boot是什么?为什么学它? 定义:Spring Boot是Spring框架的轻量级快速开发工具,基于“约定优于配置”原则,简化Spring应用的搭建与部署。核心优势: 零配置起步:内置Tomcat/Jetty,无需手动部署Web服务器。自动装配:自动扫描依赖、注入Bean,减少XML/注解冗余代…...

IBM BAW(原BPM升级版)使用教程第六讲

一、事件&#xff1a;Undercover Agent 在 IBM Business Automation Workflow (BAW) 中&#xff0c;Undercover Agent (UCA) 是一个非常独特和强大的概念&#xff0c;旨在实现跨流程或系统的事件处理和触发机制。Undercover Agent 主要用于 事件驱动的流程自动化&#xff0c;它…...

[250509] x-cmd 发布 v0.5.11 beta:x ping 优化、AI 模型新增支持和语言变量调整

目录 X-CMD 发布 v0.5.11 beta&#x1f4c3;Changelog&#x1f9e9; ping&#x1f9e9; openai&#x1f9e9; gemini&#x1f9e9; asdf&#x1f9e9; mac✅ 升级指南 X-CMD 发布 v0.5.11 beta &#x1f4c3;Changelog &#x1f9e9; ping 调整 x ping 默认参数为 bing.com&a…...

Web前端VSCode如何解决打开html页面中文乱码的问题(方法2)

Web前端—VSCode如何解决打开html页面中文乱码的问题&#xff08;方法2&#xff09; 1.打开VScode后&#xff0c;依次点击 文件 >> 首选项 >> 设置 2.打开设置后&#xff0c;依次点击 文本编辑器 >> 文件&#xff08;或在搜索框直接搜索“files.autoGuessEnc…...

打造专属AI好友:小智AI聊天机器人详解

打造专属AI好友&#xff1a;小智AI聊天机器人详解 在当下的科技热潮中&#xff0c;AI正迅速改变着我们的生活&#xff0c;成为了科技领域的新宠。而今&#xff0c;借助开源项目的力量&#xff0c;你可以亲手打造一个智能小助手——小智AI聊天机器人。它不仅是一个技术探索的窗…...

Spring,SpringMVC,SpringBoot,SpringCloud的区别

Spring Spring 是一个基础框架&#xff0c;为 Java 应用提供了 IoC&#xff08;控制反转&#xff09;和 AOP&#xff08;面向切面编程&#xff09;功能。其主要特点如下&#xff1a; IoC 容器&#xff1a;借助依赖注入&#xff0c;降低了组件间的耦合度。AOP 支持&#xff1a…...

从投入产出、效率、上手难易度等角度综合对比 pytest 和 unittest 框架

对于选择python作为测试脚本开发的同学来说&#xff0c;pytest和python unittest是必需了解的两个框架。那么他们有什么区别&#xff1f;我们该怎么选&#xff1f;让我们一起来了解一下吧&#xff01; 我们从投入产出、效率、上手难易度等角度综合对比 pytest 和 unittest 框架…...

无人机电池储存与操作指南

一、正确储存方式 1. 储存电量 保持电池在 40%-60% 电量&#xff08;单片电压约3.8V-3.85V&#xff09;存放&#xff0c;避免满电或空电长期储存。 满电存放会加速电解液分解&#xff0c;导致鼓包&#xff1b;**空电**存放可能引发过放&#xff08;电压低于3.0V/片会永久…...

CSS实现图片垂直居中方法

html <div class"footer border-top-row"><div class"footer-row"><span class"footer-row-col01">制单人&#xff1a;{{ printData[pageIndex - 1].rkMaster.makerName}}<img :src"getPersonSignImgSrc(printData[pa…...

多账号管理与自动化中的浏览器指纹对抗方案

多账号管理与自动化中的浏览器指纹对抗方案 在日常的开发工作中&#xff0c;如果你曾涉及自动化脚本、多账号运营、数据抓取&#xff0c;或是在安全研究方向摸爬滚打过&#xff0c;应该对“浏览器指纹识别”这几个字不会陌生。 指纹识别&#xff1a;不是你以为的那种“指纹”…...

[6-1] TIM定时中断 江协科技学习笔记(45个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 TRGO是“Trigger Output”的缩写&#xff0c;中文意思是“触发输出”。在STM32微控制器中&#xff0c;TRGO是一个非常重要的功能&#xff0c;它允许定时器&#xff08;Timer&#xff09;在特定事件发生时输出一个触发信号。这个触发信号可以用…...

Flutter 3.29.3 花屏问题记录

文章目录 Flutter 3.29.3 花屏问题记录问题记录解决尝试解决 Flutter 3.29.3 花屏问题记录 问题记录 flutter版本3.29.3&#xff0c;代码大致为&#xff1a; ShaderMask(shaderCallback: (Rect bounds) {return LinearGradient(begin: Alignment.topCenter,end: Alignment.bo…...

[Windows] 希捷(Seagate)硬盘官方检测工具 - SeaTools(1.4.0.7)

[Windows] 希捷&#xff08;Seagate&#xff09;硬盘官方检测工具 - SeaTools 链接&#xff1a;https://pan.xunlei.com/s/VOPpN9A3Tn_rVktEMu6Lg9q9A1?pwdh8rz# 希望能修复好硬盘...

YOLOv8目标检测性能优化:损失函数改进的深度剖析

文章目录 YOLOv8 简介损失函数在 YOLOv8 中的关键作用SlideLoss 的原理与应用原理代码实例 FocalLoss 分类损失函数的优化原理代码实例 SlideLoss 与 FocalLoss 在 YOLOv8 中的协同作用实验结果与分析 YOLOv8 简介 YOLO&#xff08;You Only Look Once&#xff09;系列目标检测…...

docker 日志暴露方案 (带权限 还 免费 版本)

接到了一个需求&#xff0c;需求的内容是需要将测试环境的容器暴露给我们的 外包同事&#xff0c;但是又不能将所有的容器都暴露给他们。 一开始&#xff0c;我分别找了 Portainer log-pilot dpanel 它们都拥有非常良好的界面和容器情况可视化。 但&#xff0c;缺点是&am…...

水印云:AI赋能,让图像处理变得简单高效

水印云是一款基于超强AI技术的图像处理工具&#xff0c;提供丰富的图像编辑功能&#xff0c;将复杂的图像处理极简化&#xff0c;真正实现简单高效的图像处理。无论是去除水印、智能抠图、添加水印&#xff0c;还是提升画质&#xff0c;水印云都能轻松应对&#xff0c;满足不同…...

使用 ECharts GL 实现交互式 3D 饼图:技术解析与实践

一、效果概览 本文基于 Vue 3 和 ECharts GL&#xff0c;实现了一个具有以下特性的 3D 饼图&#xff1a; 立体视觉效果&#xff1a;通过参数方程构建 3D 扇形与底座动态交互&#xff1a;支持点击选中&#xff08;位移效果&#xff09;和悬停高亮&#xff08;放大效果&#xff…...

allure生成测试报告(搭配Pytest、allure-pytest)

文章目录 前言allure简介allure安装软件下载安装配置环境变量安装成功验证 allure运行流程allure装饰器函数基本说明装饰器函数使用allure.attach 命令行运行利用allure-pytest生成中间结果json 查看测试报告总览页面每个tab页的说明类别页面测试套图表页面时间刻度功能页面包 …...

一场陟遐自迩的 SwiftUI + CoreData 性能优化之旅(下)

概述 自从 SwiftUI 诞生那天起&#xff0c;我们秃头码农们就仿佛打开了一个全新的撸码世界&#xff0c;再辅以 CoreData 框架的鼎力相助&#xff0c;打造一款持久存储支持的 App 就像探囊取物般的 Easy。 话虽如此&#xff0c;不过 CoreData 虽好&#xff0c;稍不留神也可能会…...

java的输入输出模板(ACM模式)

文章目录 1、前置准备2、普通输入输出API①、输入API②、输出API 3、快速输入输出API①、BufferedReader②、BufferedWriter 案例题目描述代码 面试有时候要acm模式&#xff0c;刷惯leetcode可能会手生不会acm模式&#xff0c;该文直接通过几个题来熟悉java的输入输出模板&…...

浏览器自动化与网络爬虫实战:工具对比与选型指南

浏览器自动化与网络爬虫实战&#xff1a;工具对比与选型指南 摘要 在当今数字化时代&#xff0c;浏览器自动化和网络爬虫技术已成为数据收集与测试的重要工具。本文深入剖析了多种主流浏览器自动化工具和爬虫框架的特点、优缺点及其适用场景&#xff0c;包括 Selenium、Puppe…...

“双非” “退伍” “材料” “学验证” 拿到Dream Offer

大家好&#xff0c;我是2024年路科验证V2X春季班的学员。在春季班的课上完后&#xff0c;觉得自己的基础大部分已经被路科给弥补了&#xff0c;但是很多课程中关于框架的搭建和一些细节还是不够扎实&#xff0c;有所欠缺&#xff0c;于是又重修了秋季班的课程。这两次课程给我的…...

python 上海新闻爬虫, 上观新闻 + 腾讯新闻

1. 起因&#xff0c; 目的: 继续爬上海新闻&#xff0c; 增加新闻来源。昨天写了&#xff1a; 东方网 澎湃新闻今天增加2个来源&#xff1a; 上观新闻 腾讯新闻此时有4个来源&#xff0c;我觉得已经差不多了。 2. 先看效果 3. 过程: 代码 1, 上观新闻 这里也有一个有趣的…...

【LUT技术专题】ECLUT代码解读

目录 原文概要 1. 训练 2. 转表 3. 测试 本文是对ECLUT技术的代码解读&#xff0c;原文解读请看ECLUT。 原文概要 ECLUT通过EC模块增大网络感受野&#xff0c;提升超分效果&#xff0c;实现SRLUT的改进&#xff0c;主要是2个创新点&#xff1a; 提出了一个扩展卷积&…...

Wsl2 网络模式介绍

每个模式说明参考下面连接 使用 WSL 访问网络应用程序 | Microsoft Learn...

项目高压生存指南:科学重构身体与认知系统的抗压算法

引言&#xff1a;压力重构的工程学思维 在项目管理的高压熔炉中&#xff0c;优秀从业者与普通执行者的核心差异不在于抗压能力的高低&#xff0c;而在于是否掌握压力管理的系统化算法。本文摒弃传统的鸡汤式减压建议&#xff0c;从人体工程学、神经科学和认知心理学角度&#…...

Java设计模式之工厂方法模式:从入门到精通

1. 工厂方法模式概述 1.1 定义与核心思想 工厂方法模式(Factory Method Pattern) **定义:**是一种创建型设计模式,它定义了一个用于创建对象的接口,但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。 **核心思想:**工厂模式的核心思想是将对象的创建…...

生成自定义的androidjar文件具体操作

在Androidsdk目录下的platform找到对应的api的android源码包路径&#xff0c;如android-32拷贝里面的android.jar文件到目录&#xff0c;如 C:\Users\xxxxxxx\Desktop\android\new_android_jar&#xff0c;然后解压android.jar到目录new_android_jar下。在编译后的aosp源码中找…...