STL的sort()源码剖析

文章转载自http://www.cnblogs.com/imAkaka/articles/2407877.html
STL sort源码剖析
STL的sort()算法,数据量大时采用Quick Sort,分段递归排序,一旦分段后的数据量小于某个门槛,为避免Quick Sort的递归调用带来过大的额外负荷,就改用Insertion Sort。如果递归层次过深,还会改用Heap Sort。本文先分别介绍这个三个Sort,再整合分析STL sort算法(以上三种算法的综合) – Introspective Sorting(内省式排序)。

一、Insertion Sort
Insertion Sort是《算法导论》一开始就讨论的算法。它的基本原理是:将初始序列的第一个元素作为一个有序序列,然后将剩下的N-1个元素按关键字大小依次插入序列,并一直保持有序。这个算法的复杂度为O(N^2),最好情况下时间复杂度为O(N)。在数据量很少时,尤其还是在序列“几近排序但尚未完成”时,有着很不错的效果。

复制代码
// 默认以渐增方式排序
template
void __insertion_sort(RandomAccessIterator first,
RandomAccessIterator last)
{
if (first == last) return;
// — insertion sort 外循环 —
for (RandomAccessIterator i = first + 1; i != last; ++i)
__linear_insert(first, i, value_type(first));
// 以上,[first,i) 形成一个子区间
}

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;

    // --- insertion sort 内循环 ---
    // 注意,一旦不再出现逆转对(inversion),循环就可以结束了
    while (value < *next){    // 逆转对(inversion)存在
        *last = *next;        // 调整
        last = next;        // 调整迭代器    
        --next;            // 左移一个位置
    }
    *last = value;            // value 的正确落脚处
}

复制代码

上述函数之所以命名为unguarded_x是因为,一般的Insertion Sort在内循环原本需要做两次判断,判断是否相邻两元素是”逆转对“,同时也判断循环的行进是否超过边界。但由于上述所示的源代码会导致最小值必然在内循环子区间的边缘,所以两个判断可合为一个判断,所以称为unguarded_。省下一个判断操作,在大数据量的情况下,影响还是可观的。
二、Quick Sort
Quick Sort是目前已知最快的排序法,平均复杂度为O(NlogN),可是最坏情况下将达O(N^2)。
Quick Sort算法可以叙述如下。假设S代表将被处理的序列:
1、如果S的元素个数为0或1,结束。
2、取S中的任何一个元素,当做枢轴(pivot) v。
3、将S分割为L、R两段,使L内的每一个元素都小于或等于v,R内的每一个元素都大于或等于v。
4、对L、R递归执行Quick Sort。
Median-of-Three(三点中值)
因为任何元素都可以当做枢轴(pivot),为了避免元素输入时不够随机带来的恶化效应,最理想最稳当的方式就是取整个序列的投、尾、中央三个元素的中值(median)作为枢轴。这种做法称为median-of-three partitioning。

复制代码
// 返回 a,b,c之居中者
template
inline const T& __median(const T& a, const T& b, const T& c)
{
if (a < b)
if (b < c) // a < b < c
return b;
else if (a < c) // a < b, b >= c, a < c –> a < b <= c
return c;
else // a < b, b >= c, a >= c –> c <= a < b
return a;
else if (a < c) // c > a >= b
return a;
else if (b < c) // a >= b, a >= c, b < c –> b < c <= a
return c;
else // a >= b, a >= c, b >= c –> c<= b <= a
return b;
}
复制代码

Partitioning(分割)
分割方法有很多,以下叙述既简单又有良好成效的做法。令first向尾移动,last向头移动。当*first大于或等于pivot时停下来,当*last小于或等于pivot时也停下来,然后检验两个迭代器是否交错。未交错则元素互相,然后各自调整一个位置,再继续相同行为。若交错,则以此时first为轴将序列分为左右两半,左边值都小于或等于pivot,右边都大于等于pivot

复制代码
template

猜你喜欢

转载自blog.csdn.net/hhmy77/article/details/82532652