STL源码剖析(set/map)

SGI STL中set/map底层都是通过RB-tree实现的。

首先看看RB-tree结点的定义

 1 typedef bool __rb_tree_color_type;
 2 const __rb_tree_color_type __rb_tree_red = false;
 3 const __rb_tree_color_type __rb_tree_black = true;
 4
 5 // 结点的基类
 6 struct __rb_tree_node_base
 7 {
 8     typedef __rb_tree_color_type color_type;
 9     typedef __rb_tree_node_base* base_ptr;
10
11     // 关键的4个域
12     color_type color;
13     base_ptr parent;
14     base_ptr left;
15     base_ptr right;
16
17     // 返回极值
18     static base_ptr minimum(base_ptr x)
19    {
20         while (x->left != 0) x = x->left;
21         return x;
22     }
23
24     static base_ptr maximum(base_ptr x)
25     {
26         while (x->right != 0) x = x->right;
27         return x;
28      }
29 }
30
31 // 多了一个value域
32 template <class Value>
33 struct __rb_tree_node : public __rb_tree_node_base
34 {
35     typedef __rb_tree_node<Value>* link_type;
36     Value value_field;
37 };

下图是RB-tree结点跟其迭代器的关系

重点看看__rb_tree_iterator的operator++跟operator--,

实际是调用__rb_tree_base_iterator的increment跟decrement。

可以看出迭代器前移/后移的时候会按key的顺序找到下一个/上一个结点。

(set/map<...>::begin()会返回RB-tree中key最小的结点,因此使用operator++遍历会按key的顺序从小到大遍历结点)

 1 void increment()
 2 {
 3     if (node->right != 0) {
 4         node = node->right;
 5         while (node->left != 0)
 6             node = node->left;
 7     }
 8     else {
 9         base_ptr y = node->parent;
10         while (node == y->right) {
11             node = y;
12             y = y->parent;
13         }
14         if (node->right != y)
15             node = y;
16     }
17 }
18
19 void decrement()
20 {
21     if (node->color == __rb_tree_red &&
22         node->parent->parent == node)
23         node = node->right;
24     else if (node->left != 0) {
25         base_ptr y = node->left;
26         while (y->right != 0)
27             y = y->right;
28         node = y;
29     }
30     else {
31         base_ptr y = node->parent;
32         while (node == y->left) {
33             node = y;
34             y = y->parent;
35         }
36         node = y;
37     }
38 }

SGI STL中的rb_tree用了一个小trick,就是使用了一个header结点,用来代表整个rb_tree。

该结点与root结点互为父结点,该结点的left指向最左(key最小)的结点,right指向最右(key最大)的结点。

除此之外,该rb_tree的实现跟普通的红黑树类似,详情可以查看:http://www.cnblogs.com/runnyu/p/4679279.html。

 1 template <class Key, class Value, class KeyOfValue, class Compare,
 2           class Alloc = alloc>
 3 class rb_tree {
 4     // rb_tree的基本定义
 5 protected:
 6     typedef __rb_tree_node_base* base_ptr;
 7     typedef __rb_tree_node<Value> rb_tree_node;
 8     typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator;
 9 public:
10     typedef Key key_type;
11     typedef Value value_type;
12     typedef value_type* pointer;
13     typedef value_type& reference;
14     typedef rb_tree_node* link_type;
15     typedef size_t size_type;
16     typedef ptrdiff_t difference_type;
17
18     typedef __rb_tree_iterator<value_type, reference, pointer> iterator;
19
20     link_type header;
21     // ...
22
23     // 主要接口
24     iterator begin() { return leftmost(); } // 返回最左边的结点(最小key)
25     iterator end() { return header; }
26
27     iterator insert_equal(const value_type& x); // 插入元素 并允许键值相同
28     pair<iterator,bool> insert_unique(const value_type& x); // 插入元素 键值是独一无二的
29
30 };

set/multiset

有了rb_tree,set/multiset的实现也只是调用rb_tree的接口而已。

其中set跟multiset不一样的是,set插入的时候调用的是insert_unique(),而multiset调用的是insert_equal()。

下面是给出set的基本定义:

 1 template <class Key, class Compare = less<Key>, class Alloc = alloc>
 2 class set {
 3 public:
 4     typedef Key key_type;
 5     typedef Key value_type;  // 使用的value类型跟key一样
 6     typedef Compare key_compare;
 7     typedef Compare value_compare;
 8 private:
 9     typedef rb_tree<key_type, value_type,
10                   identity<value_type>, key_compare, Alloc> rep_type;
11     rep_type t;
12 public:
13     // 接口的实现只是对rb_tree的封装  不一一列举了
14     iterator begin() const { return t.begin(); }
15     iterator end() const { return t.end(); }
16     pair<iterator,bool> insert(const value_type& x) {
17         pair<typename rep_type::iterator, bool> p = t.insert_unique(x);
18         return pair<iterator, bool>(p.first, p.second);
19     }
20     // ...
21 };

map/multimap

map/mulitmap的实现也是通过调用rb_tree的接口。

map/mulitmap不一样的是,map插入的时候调用的是insert_unique(),而multimap调用的是insert_equal()。

下面是给出map的基本定义:

 1 template <class Key, class T, class Compare = less<Key>, class Alloc
 2 class map {
 3 public:
 4     typedef Key key_type;
 5     typedef T data_type;
 6     typedef T mapped_type;
 7     typedef pair<const Key, T> value_type; // 在rb_tree中value的类型是pair
 8     typedef Compare key_compare;
 9 private:
10     // select1st直接return T.first 用于rb_tree取到key进行比较大小
11     typedef rb_tree<key_type, value_type,
12                   select1st<value_type>, key_compare, Alloc> rep_type;
13     rep_type t;
14     // ...
15
16     // 接口只是对rb_tree的封装  就不一一列举了
17     iterator begin() { return t.begin(); }
18     iterator end() { return t.end(); }
19     pair<iterator,bool> insert(const value_type& x) { return t.insert_unique(x); }
20      // ...
21 }

另外STL中有未列入标准的hash_set/hash_map以及C++11中的unordered_set/map,底层是使用hashtable实现的。

相比于用rb_tree实现的set/map,它们的插入删除查找操作具有O(1)的时间复杂度(没有冲突情况下),但是它们的元素的顺序是无序的。

时间: 2024-10-08 16:55:07

STL源码剖析(set/map)的相关文章

重读STL源码剖析:map与set

map与set底层都是调用的RBTree 首先看RBTree RBTree 红黑树的特性: 1.根节点为黑色 2.新增节点一定是红色 3.节点只有红色或黑色两种颜色 4.两个节点颜色不能同为红 5.任意一条路径上的黑色节点个数相同 红黑树的节点设计: 1.表示节点颜色的变量color 2.链接左子树的left指针 3.链接右子树的right指针 4.链接父节点的parent指针 5.表示节点值的变量value_field;在map中为pair对,在set中只有key RBTree的迭代器: 1.

《STL源码剖析》---stl_pair.h阅读笔记

pair是STL中的模板类型,它可以存储两个元素,它也被称作"对组".在map中已经用到了它,pair其实就是一个struct结构,存有两个public的元素,重载了几个运算符,没有什么成员函数,源代码很简单. G++ 2.91.57,cygnus\cygwin-b20\include\g++\stl_pair.h 完整列表 /* * * Copyright (c) 1994 * Hewlett-Packard Company * * Permission to use, copy,

《STL源码剖析》---stl_tree.h阅读笔记

STL中,关联式容器的内部结构是一颗平衡二叉树,以便获得良好的搜索效率.红黑树是平衡二叉树的一种,它不像AVL树那样要求绝对平衡,降低了对旋转的要求,但是其性能并没有下降很多,它的搜索.插入.删除都能以O(nlogn)时间完成.平衡可以在一次或者两次旋转解决,是"性价比"很高的平衡二叉树. RB-tree(red black tree)红黑树是平衡二叉树.它满足一下规则 (1)每个节点不是红色就是黑色. (2)根节点是黑色. (3)如果节点为红色,则其子节点比为黑色. (4)任何一个节

STL&quot;源码&quot;剖析-重点知识总结

STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合套用: 容器(Containers):各种数据结构,如:vector.list.deque.set.map.用来存放数据.从实现的角度来看,STL容器是一种class template. 算法(algorithms):各种常用算法,如:sort.search.copy.erase.从实现的角度来看,STL算法

通读《STL源码剖析》之后的一点读书笔记

[QQ群: 189191838,对算法和C++感兴趣可以进来] 直接逼入正题. Standard Template Library简称STL.STL可分为容器(containers).迭代器(iterators).空间配置器(allocator).配接器(adaptors).算法(algorithms).仿函数(functors)六个部分. 迭代器和泛型编程的思想在这里几乎用到了极致.模板或者泛型编程其实就是算法实现时不指定具体类型,而由调用的时候指定类型,进行特化.在STL中,迭代器保证了ST

【转载】STL&quot;源码&quot;剖析-重点知识总结

原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合套用: 容器(Containers):各种数据结构,如:vector.list.deque.set.map.用来存放数据.从实现的角度来看,STL容器是一种class template. 算法(algorithms):各种常用算法,如:sort.se

《STL源码剖析》---stl_deque.h阅读笔记(1)

双端队列deque是容器的一种.它比vector更强大,vector只可以在尾端插入元素,deque不只是可以再尾端插入,也可以在队列头插入.下面借助<STL源代码剖析>的图片讲解. 1.deque的内存结构 vector是开辟一段连续的内存,deque可以在前端插入元素,如果像vector开辟一段连续的内存,向前面插入元素不易维护.例如如果只是开辟一段连续内存,那么前端到开辟内存的起始位置要空多少个位置?当这段内存满了时,拷贝到更大内存时,前端空留多少位置?显然难以维护.deque在STL中

《STL源码剖析》---stl_deque.h阅读笔记(2)

看完,<STL源码剖析>---stl_deque.h阅读笔记(1)后,再看代码: G++ 2.91.57,cygnus\cygwin-b20\include\g++\stl_deque.h 完整列表 /* * * Copyright (c) 1994 * Hewlett-Packard Company * * Permission to use, copy, modify, distribute and sell this software * and its documentation fo

《STL源码剖析》---stl_multimap.h阅读笔记

multimap和map的关系和multiset和set关系一样,multimap允许有重复的键值,它在使用底层数据结构红黑树用,插入操作用的是insert_equal,而不是insert_unique. G++ 2.91.57,cygnus\cygwin-b20\include\g++\stl_multimap.h 完整列表 /* * * Copyright (c) 1994 * Hewlett-Packard Company * * Permission to use, copy, modi