一、红黑树的基本概念与规则红黑树是一种满足特定性质的二叉搜索树。与普通的二叉搜索树不同红黑树的每个结点都附加了一个颜色属性且通过一些规则保证了树的平衡性。红黑树的四条基本规则如下每个节点的颜色要么是红色要么是黑色。根节点是黑色的。如果一个节点是红色的那么它的两个子节点必须是黑色的。这就意味着红色节点不能有连续的红色节点。从任意一个节点到其所有叶子节点的路径上必须包含相同数量的黑色节点。这条规则保证了树的平衡性防止树的某些路径过长。红黑树的这些规则通过“颜色约束”来间接实现自平衡因此每次进行插入、删除操作时都需要确保这些规则被满足。以上均为红黑树示例。二、红黑树的高度与效率红黑树的高度是它的关键特性之一。红黑树的自平衡机制确保了它的高度不会太大这对于树的查找、插入和删除操作的效率至关重要。理论上红黑树的高度 h 与黑色高度 bh从根到叶子节点的路径上黑色节点的数量之间存在如下关系最短路径只有黑色节点长度为 bh。最长路径交替的红色和黑色节点长度为 2 * bh。因此红黑树的最大高度为 2 * bh而最小高度为 bh。因此红黑树的高度 h 满足 h≤2×bhh \leq 2 \times bhh≤2×bh并且由于红黑树的黑色高度是相同的所以可以得出红黑树的高度是 O(log N)其中 N 是树中结点的数量。由于红黑树的高度被控制在对数级别它能够保证查找、插入、删除操作在最坏情况下的时间复杂度均为 O(log N)。三、红黑树的结构红黑树的基本节点结构包括以下几个部分代码语言javascriptAI代码解释enum Colour { RED, BLACK }; templateclass K, class V struct RBTreeNode { pairK, V _kv; // 存储键值对 RBTreeNodeK, V* _left; // 左子树 RBTreeNodeK, V* _right; // 右子树 RBTreeNodeK, V* _parent; // 父节点 Colour _col; // 颜色属性 RBTreeNode(const pairK, V kv) : _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED) {} };每个节点不仅包含键值对 (_kv)还包含指向左子树、右子树和父节点的指针。此外节点的颜色 (_col) 用于维护红黑树的平衡。四、红黑树的插入操作红黑树的插入操作需要遵循以下步骤按照二叉搜索树的规则插入新节点。这是插入操作的第一步我们首先将新节点插入到树的适当位置。将新节点的颜色设置为红色。新插入的节点默认是红色的这有助于避免违反红黑树的黑色节点数平衡。调整颜色和结构。插入新节点后可能会破坏红黑树的平衡。具体的修复操作包括情况1变色操作。如果父节点和叔叔节点都是红色则将父节点和叔叔节点都变为黑色祖父节点变为红色并继续往上处理。情况2旋转和变色。如果父节点是红色且叔叔节点是黑色或不存在则需要进行旋转操作单旋或双旋并相应地调整颜色。我们来看一段插入代码的实现代码语言javascriptAI代码解释bool Insert(const pairK, 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); 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) { parent-_col uncle-_col BLACK; grandfather-_col RED; cur grandfather; parent cur-_parent; } else { 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) { parent-_col uncle-_col BLACK; grandfather-_col RED; cur grandfather; parent cur-_parent; } else { 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; return true; }这段代码展示了红黑树的插入操作包括节点插入、颜色变更和旋转操作。关键的操作是通过循环和条件判断来确保红黑树的规则不被破坏。五、红黑树的查找操作红黑树的查找操作和普通的二叉搜索树是类似的我们依然按照二叉搜索树的规则进行查找。由于红黑树的平衡性查找操作的时间复杂度为 O(log N)。代码语言javascriptAI代码解释Node* Find(const K key) { Node* cur _root; while (cur) { if (cur-_kv.first key) { cur cur-_right; } else if (cur-_kv.first key) { cur cur-_left; } else { return cur; } } return nullptr; }上述代码展示了查找操作的实现我们根据节点的键值逐步向左或向右子树移动直到找到目标节点或树为空。六、红黑树的删除操作删除操作在红黑树中比插入操作更为复杂。删除一个节点后可能会破坏红黑树的平衡特别是当删除的节点是黑色时需要特别注意。因此删除操作需要执行旋转和变色操作来恢复平衡。尽管删除操作复杂但其时间复杂度同样是 O(log N)。由于删除操作较为复杂本文不深入讨论其实现。如果有兴趣可以参考《算法导论》或《STL源码剖析》中的相关章节。七、红黑树的验证为了验证红黑树的正确性我们可以检查它是否满足四条基本规则。可以通过前序遍历树的每一条路径检查节点颜色和黑色节点数量是否符合要求。