陈硕 (chenshuo.com)
2013-01-20
std::set/std::map (以下用 std::map 代表) 是常用的关联式容器,也是 ADT(抽象数据类型)。也就是说,其接口(不是 OO 意义下的 interface)不仅规定了操作的功能,还规定了操作的复杂度(代价/cost)。例如 set::insert(iterator first, iterator last) 在通常情况下是 O(N log N),N 是区间的长度;但是如果 [first, last) 已经排好序(在 key_compare 意义下),那么复杂度将会是 O(N)。
尽管 C++ 标准没有强求 std::map 底层的数据结构,但是根据其规定的时间复杂度,现在所有的 STL 实现都采用平衡二叉树来实现 std::map,而且用的都是红黑树。《算法导论(第 2 版)》第 12、13 章介绍了二叉搜索树和红黑树的原理、性质、伪代码,侯捷先生的《STL 源码剖析》第 5 章详细剖析了 SGI STL 的对应实现。本文对 STL 中红黑树(rb_tree)的实现问了几个稍微深入一点的问题,并给出了我的理解。本文剖析的是 G++ 4.7 自带的这一份 STL 实现及其特定行为,与《STL 源码剖析》的版本略有区别。为了便于阅读,文中的变量名和 class 名都略有改写(例如 _Rb_tree_node 改为 rb_tree_node)。本文不谈红黑树的平衡算法,在我看来这属于“旁枝末节”(见陈硕《谈一谈网络编程学习经验》对此的定义),因此也就不关心节点的具体颜色了。
数据结构回顾
先回顾一下数据结构教材上讲的二叉搜索树的结构,节点(Node)一般有三个数据成员(left、right、data),树(Tree)有一到两个成员(root 和 node_count)。
用 Python 表示: class Node: def __init__(self, data): self.left = None self.right = None self.data = data class Tree: def __init__(self): self.root = None self.node_count = 0
而实际上 STL rb_tree 的结构比这个要略微复杂一些,我整理的代码见 https://gist.github.com/4574621#file-tree-structure-cc 。
节点
Node 有 5 个成员,除了 left、right、data,还有 color 和 parent。
C++实现,位于bits/stl_tree.h /** * Non-template code **/ enum rb_tree_color { kRed, kBlack }; struct rb_tree_node_base { rb_tree_color color_; rb_tree_node_base* parent_; rb_tree_node_base* left_; rb_tree_node_base* right_; }; /** * template code **/ template<typename Value> struct rb_tree_node : public rb_tree_node_base { Value value_field_; };
见下图。
color 的存在很好理解,红黑树每个节点非红即黑,需要保存其颜色(颜色只需要 1-bit 数据,一种节省内存的优化措施是把颜色嵌入到某个指针的最高位或最低位,Linux 内核里的 rbtree 是嵌入到 parent 的最低位);parent 的存在使得非递归遍历成为可能,后面还将再谈到这一点。
树
Tree 有更多的成员,它包含一个完整的 rb_tree_node_base(color/parent/left/right),还有 node_count 和 key_compare 这两个额外的成员。
这里省略了一些默认模板参数,如 key_compare 和 allocator。 template<typename Key, typename Value> // key_compare and allocator class rb_tree { public: typedef std::less<Key> key_compare; typedef rb_tree_iterator<Value> iterator; protected: struct rb_tree_impl // : public node_allocator { key_compare key_compare_; rb_tree_node_base header_; size_t node_count_; }; rb_tree_impl impl_; }; template<typename Key, typename T> // key_compare and allocator class map { public: typedef std::pair<const Key, T> value_type; private: typedef rb_tree<Key, value_type&