《stl源码剖析》:容器、算法

关联容器set和map有自己的find算法,它可以利用红黑树的特性达到log(n)的复杂度。

我开始的疑问是,它们同时也可以调用标准STL的find算法,这样只是循序遍历。那这样说来红黑树中已经排好序了,因此使用迭代器的++操作可以实现遍历,没有问题。疑问解决了。

 

multiset与set的不同之处在于它允许重复元素,唯一的区别就是插入元素时调用红黑树底层的insert_equal(),而后者是调用insert_unique()

multimap与map的差别也在于此。

 

线性探测、二次探测、开链(separate chaining)。

 

hastable有一个模板参数class ExtractKey,从节点取出键值的方法(函数或仿函数)。对应的它有一个该类型的成员:

ExtractKey get_key

但是在初始化的时候get_key被默认初始化为ExtractKey():

public:
    //There is no default ctor
  hashtable(size_type n, //------------(1)
            const HashFcn&    hf,
            const EqualKey&   eql,
            const ExtractKey& ext)
    : hash(hf), equals(eql), get_key(ext), num_elements(0)
  {
    initialize_buckets(n);
  }
  hashtable(size_type n, //------------(2)
        const HashFcn&    hf,
        const EqualKey&   eql)
: hash(hf), equals(eql), get_key(ExtractKey()), num_elements(0)
  {
    initialize_buckets(n);
  }
//...

 

remove将不符合移除条件的元素复制到前端,而后面的元素不动,即形成了“残余数据”,返回的有效数据的下一个位置,因此可结合erase将残余数据彻底删除

array不能用remove和remove_if,因为它无法缩小尺寸,因此残余数据会永远存在。

remove_copy将结果复制到一个容器,以result为起始位置。

 

rotate算法将[first,middle)内的元素和[middle,last)内的元素互换。

rotate算法每次互换两个区间中较少元素个数的元素,直到将所有元素换置妥当,因此在它的其中一个版本(迭代器随机访问时)里面用到了求最大公约数(利用辗转相除法)。

rotate的三个版本:

1、前向迭代器:每次互换两个区间中较少元素个数的元素,直到将所有元素换置妥当。

2、双向迭代器:分别倒转两个区间中元素,然后将整个区间元素倒转。

// rotate 的 bidirectional iterator 版  
template <class BidirectionalIterator, class Distance>  
void __rotate(BidirectionalIterator first, BidirectionalIterator middle,  
    BidirectionalIterator last, Distance*,  
    bidirectional_iterator_tag) {  
    reverse(first, middle);  
    reverse(middle, last);  
    reverse(first, last);  
}  

此版本是三步翻转法,下图说明了运算过程:

3、随机访问迭代器:循环移位法,采用了上面所说的求最大公约数。(具体暂时不研究了)

此版本是循环移位法,此法利用了辗转相除法和数论中的一个定理:若有两个正整数m、n,且gcd(m,n)=d,那么序列{m%n,2m%n, 3m%n,..., nm%n}一定是{0, d, 2d,..., n-d}的某个排列并重复出现d次,其中%号代表求模操作。比如若m=6,n=8,d=gcd(m,n)=2,那么{6%8, 12%8,18%8,..., 48%8}即为{0,2,4,6}的某个排列并重复两次,事实上也正是{6,4,2,0, 6,4,2, 0}。特别地,若m、n互素,d=1,那么序列{m%n,2m%n,3m%n,...,(n-1)m%n}实际上就是{1, 2,3,..., n-1}的某个排列。

了解这个定理后,此版本也就容易看懂了。每一次__rotate_cycle只能将t/n的元素正确的左移,其中t为容器内元素个数,n为last-first和middle-first的最大公约数,而这些被移动的元素是以n为等间距的,所以循环n次,并分别以串的前n个元素为起点进行__rotate_cycle操作,就能保证将所有的元素都移动到正确的位置上。

 

经常看到STL的源码中使用value_type(first)之类的形式,因此感觉它是迭代器的成员函数。但是不对,如果是成员函数,应该这样调用first.value_type().???

使用了iterator_traits而不直接使用typename I::value_type是因为可以进行偏特化:

template <class T>  //对iterator_traits的偏特化  
struct iterator_traits<T*>  
{  
    typedef ... iterator_category;  
    typedef T value_type;  
    typedef ptrdiff_t difference_type;  
    typedef T* pointer;  
    typedef T& reference;  
}  

template <class T>  //对iterator_traits的偏特化  
struct iterator_traits<const T*>  
{  
    typedef ... iterator_category;  
    typedef T value_type;  
    typedef ptrdiff_t difference_type;  
    typedef const T* pointer;  
    typedef const T& reference;  
}  

这样可以使用它提取int*等内置类型的value_type了。同时注意到上面的偏特化分为普通指针与const 指针两个版本(如果只对T*进行偏特化,iterator_traits<const int*>::value_type的类型就是const int。这一般不会是我们想要的,所以必须对const I*也进行特化。)。

 

partition将[first,last)元素重新排列,被一元条件运算pred判定为true的放在前段,false放在后段。

partiial_sort排序前N个元素。

 

快速排序时枢轴(pivot)选择头、尾、中央三个值的中间值,将这种称为medium-of-three-QuickSort。为了快速取出中间值,迭代器需要能随机定位,因此必须是随机访问迭代器

SGI STl的QuickSort采用的是Introspective Sorting(內省式排序),简称IntroSort。它在分割恶化时,会适时采用heapsort(设置了最多分层数)。最后,当序列大致有序时,采用InsertionSort最后排好序。

 

inplace_merge将连在一起的序列[first,middle)和[middle,last)(这两个序列已经排好序)组合成单一序列并保持有序。

 

posted @ 2017-02-06 18:33  IT由零开始  阅读(282)  评论(0编辑  收藏  举报