STL(十一)——sort

STL提供的各种算法里,sort()是最复杂庞大的一个。这个算法接受两个RandomAccessIterators(随机存取迭代器),然后将区间内的所有元素以渐增方式从小到大排序。

STL的sort算法,数据量大时采用Quick Sort,分段递归排序。一旦分段后的数据量小于某个门槛,为避免Quick Sort的递归调用带来过大的额外负荷(overhead),就改用插入排序。如果递归层次太深,还会改用Head Sort。这种排序组合算法称为introsort。

Insertion Sort

STL实现的插入排序,这种实现方法可以节省越界判断

template<class RandomAccessIterator>
void __insertion_sort(RandomAccessIterator first,
                      RandomAccessIterator last)
{
    if(first == last) return;
    for(RandomAccessIterator i = first + 1; i != last; ++i)
    {
        __linear_insert(first, i, value_type(first));
    }
}


template<class RandomAccessIterator, class T>
inline void __linear_insert(RandomAccessIterator first,
                            RandomAccessIterator last,
                            T*)
{
    T value = *last;
    if(value < *first)
    {
        copy_backward(first, last, last+1);
        *first = value;
    }
    else
    {
        __unguarded_linear_insert(last, value);
    }
}


template<class RandomAccessIterator, class T>
void __unguarded_linear_insert(
                                RandomAccessIterator last, T value)
{
    RandomAccessIterator next = last;
    --next;
    while(value < *next)
    {
        *last = *next;
        last = next;
        --next;
    }
    *last = value;
}

Quick Sort

注意,任何一个元素都可以被选来当作枢轴(pivot),但是其合适与否会影响QucikSort的效率。为了避免元素选择不当带来的恶化影响,最理想最稳妥的是取整个序列的头、尾、中央三个位置,以其中值来当枢轴。这种做法成为三点中值(median of three)。下面是SGI STL提供的三点中值决定函数:

template<class T>
inline const T& __median(const T& a, const T& b, const T& c)
{
    if(a<b)
        if(b<c)
            return b; //a<b<c
        else if(a<c)
            return c; //a<c<b
        else
            return a;
    else if(a<c)
        return a;
    else if(b<c)
        return c;
    else
        return b;
}

Partitioning(分割)

SGI STL提供的分割函数,返回的是右段的第一个位置。(为啥返回这么奇怪?)下面的源码:

template<class RandomAccessIterator, class T>
RandomAccessIterator __unguarded_partition(
                                            RandomAccessIterator first,
                                            RandomAccessIterator last,
                                            T pivot)
{
    while(true)
    {
        while(*first < pivot) ++first;
        --last;  //last指向的是end
        while(pivot < *last) --last;
        if(!(fist < last)) return first;
        iter_swap(first, last);
        ++first;
    }
}

threshold(阈值)

面对小量的数据,如果使用quick sort这样复杂而需要大量运算的排序法,可能会得不偿失。所以在小数量的情况下,会使用Insertion Sort。

final insertion sort

如果我们令某个大小以下的序列滞留在“接近排序完成但尚未完成”的状态,最后再以一次Insertion Sort来将这些序列做一次完整的排序,效果可能会更好。

introsort

不当的枢轴选择会导致Quick Sort恶化为O(n^2)。而这个算法(内省式排序),可以在恶化时改用堆排序,将最坏的时间复杂度维持在O(nlogn)。下面是SGI STL对introsort的实现:

template<class RandomAccessIterator>
inline void sort(RandomAccessIterator first, RandomAccessIterator last)
{
    if(first != last)
    {
        __introsort_loop(first, last, value_type(firsst), __lg(last-first)*2);
        __final_insertion_sort(first, last);
    }
}

//通过将n不断除以2得到对应的k值,效率高
template<class Size>
inline Size __lg(Size n)
{
    Size k;
    for(k=0;n>1;n>>=1)
        ++k;
    return k;
}

//当元素个数为40时,__introsort_loop()当最后一个参数是5*2,意思是最多分割10词=层。IntroSort算法如下:
template<class RandomAccessIterator, class T, class Size>
void __introsort_loop(RandomAccessIterator first, RandomAccessIterator last, T*,
                       Size depth_limit)
{
    while(last - first > __stl_threshold) //last - first > 16
    {
        if(depth_limit == 0)
        {
            partial_sort(first, last, last);
            return;
        }
    
    --depth_limit;
    RandomAccessIterator cut = __unguarded_partition(first, last, T(__median(*first, *(first + (last - first)/2, *(last-1)))));

    __introsort_loop(cut, last, value_type(first), depth_limit);
    last = cut;
    }
}


函数一开始检查元素个数和分割层数。如果分割层数超过指定值,就改用partial_sort()。当__introsort_loop结束后,调用__unguarded_partition找出分割点,然后针对左右段落进行introsort。

当__introsort_loop结束后,[first, last)内有多个"元素少于16"的子序列,回到主函数sort后,就会再次进入__final_insertion_sort()

template<class RandomAccessIterator>
void __final_insertion_sort(RandomAccessIterator first, 
                            RandomAccessIterator last)
{
    if(last - first > __stl_threshold)
    {
        __insertion_sort(first, first + __stl_threshold);
        __unguarded_insertion_sort(first + __stl_threshold, last);
    }
    else
    {
        __insertion_sort(first, last);
    }
}

此函数首先判断元素个数是否大于16.如果不大于,就调用插入排序法处理。如果大于,则将[first,last)分割成长度为16的一段子序列,和另一段剩余子序列,再对两个子序列分别调用。

猜你喜欢

转载自blog.csdn.net/Pokemon_Master/article/details/82683346