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的一段子序列,和另一段剩余子序列,再对两个子序列分别调用。