别再死记硬背了!用C++手搓一个二叉搜索树,理解STL map/set的底层就这么简单

张开发
2026/4/21 1:58:00 15 分钟阅读

分享文章

别再死记硬背了!用C++手搓一个二叉搜索树,理解STL map/set的底层就这么简单
从零构建二叉搜索树解密STL map/set的底层奥秘在C标准库中map和set作为关联容器的代表其底层实现往往让初学者望而生畏。红黑树、AVL树这些名词听起来就让人头疼但它们的本质其实都源于一个更基础的数据结构——二叉搜索树BST。本文将带你从零开始实现一个完整的BST逐步揭示STL关联容器的设计哲学。1. 二叉搜索树的核心概念二叉搜索树是一种特殊的二叉树结构它满足以下性质左子树所有节点的值小于根节点的值右子树所有节点的值大于根节点的值左右子树也分别是二叉搜索树这种结构天然支持高效查找操作平均时间复杂度为O(log n)。STL中的map和set正是基于这种结构的变体——平衡二叉搜索树实现的。template typename T struct BSTNode { T data; BSTNode* left; BSTNode* right; BSTNode(const T val) : data(val), left(nullptr), right(nullptr) {} };2. BST的基本操作实现2.1 插入操作BST的插入遵循小左大右的原则从根节点开始比较小于当前节点则向左子树移动大于当前节点则向右子树移动找到空位后插入新节点template typename T class BST { public: bool insert(const T value) { if (!root) { root new BSTNodeT(value); return true; } BSTNodeT* current root; BSTNodeT* parent nullptr; while (current) { parent current; if (value current-data) { current current-left; } else if (value current-data) { current current-right; } else { return false; // 值已存在 } } if (value parent-data) { parent-left new BSTNodeT(value); } else { parent-right new BSTNodeT(value); } return true; } private: BSTNodeT* root nullptr; };2.2 查找操作查找操作与插入类似通过比较值的大小决定搜索路径bool contains(const T value) const { BSTNodeT* current root; while (current) { if (value current-data) { current current-left; } else if (value current-data) { current current-right; } else { return true; } } return false; }2.3 删除操作的三种情况删除节点是BST中最复杂的操作需要处理三种不同情况无子节点直接删除有一个子节点用子节点替代被删节点有两个子节点找到右子树的最小节点替代bool remove(const T value) { // 查找要删除的节点 BSTNodeT* current root; BSTNodeT* parent nullptr; while (current current-data ! value) { parent current; if (value current-data) { current current-left; } else { current current-right; } } if (!current) return false; // 未找到 // 情况1有两个子节点 if (current-left current-right) { BSTNodeT* successor current-right; BSTNodeT* successorParent current; while (successor-left) { successorParent successor; successor successor-left; } current-data successor-data; current successor; parent successorParent; } // 情况2和3最多一个子节点 BSTNodeT* child current-left ? current-left : current-right; if (!parent) { root child; // 删除的是根节点 } else if (parent-left current) { parent-left child; } else { parent-right child; } delete current; return true; }3. BST的遍历方式中序遍历BST会得到一个有序序列这是BST的重要特性void inorderTraversal() const { _inorder(root); std::cout std::endl; } private: void _inorder(BSTNodeT* node) const { if (!node) return; _inorder(node-left); std::cout node-data ; _inorder(node-right); }4. 从BST到STL容器的跨越4.1 BST的局限性普通BST存在一个致命缺陷——可能退化为链表。当插入有序数据时BST会失去平衡查找效率降为O(n)。这就是STL选择平衡二叉搜索树如红黑树的原因。操作平衡BST普通BST(最坏)查找O(log n)O(n)插入O(log n)O(n)删除O(log n)O(n)4.2 STL map/set的设计哲学STL的map和set基于红黑树实现红黑树通过以下规则保持平衡每个节点非红即黑根节点是黑色红色节点的子节点必须是黑色从任一节点到其叶子的所有路径包含相同数目的黑色节点这些规则确保了树的高度始终保持在O(log n)级别。// STL map的简化接口示例 template typename Key, typename T class map { public: typedef pairconst Key, T value_type; // 插入操作 pairiterator, bool insert(const value_type x); // 查找操作 iterator find(const Key key); // 删除操作 size_type erase(const Key key); };4.3 键值对存储的实现STL map存储的是键值对我们可以扩展BST来支持类似功能template typename Key, typename Value class BSTMap { struct Node { Key key; Value value; Node* left; Node* right; }; public: Value operator[](const Key key) { Node* current root; Node* parent nullptr; while (current) { if (key current-key) { parent current; current current-left; } else if (key current-key) { parent current; current current-right; } else { return current-value; } } Node* newNode new Node{key, Value(), nullptr, nullptr}; if (!parent) { root newNode; } else if (key parent-key) { parent-left newNode; } else { parent-right newNode; } return newNode-value; } private: Node* root nullptr; };5. 性能优化与实践建议5.1 内存管理优化实际工程中我们可以使用内存池来优化节点分配template typename T class BST { public: BST() { // 初始化内存池 pool new BSTNodeT[POOL_SIZE]; freeList pool; for (int i 0; i POOL_SIZE - 1; i) { pool[i].next pool[i1]; } pool[POOL_SIZE-1].next nullptr; } ~BST() { delete[] pool; } private: static const int POOL_SIZE 1000; BSTNodeT* pool; BSTNodeT* freeList; };5.2 迭代器实现为BST实现迭代器可以更方便地遍历template typename T class BST { public: class iterator { public: iterator(BSTNodeT* node) : current(node) {} T operator*() { return current-data; } iterator operator() { if (current-right) { current current-right; while (current-left) { current current-left; } } else { // 实现父指针回溯 } return *this; } bool operator!(const iterator other) { return current ! other.current; } private: BSTNodeT* current; }; iterator begin() { BSTNodeT* node root; while (node node-left) { node node-left; } return iterator(node); } iterator end() { return iterator(nullptr); } };理解二叉搜索树是掌握STL关联容器的关键第一步。通过亲手实现BST我们不仅理解了map/set的底层原理也为学习更复杂的平衡树结构打下了坚实基础。在实际项目中虽然我们很少需要自己实现这些数据结构但理解它们的原理能帮助我们更好地使用标准库提供的工具。

更多文章