您的位置:首页 > 其它

STL源码分析之 sort算法

2015-06-13 14:46 253 查看
关于rotate算法

目的是将[first,middle)和[middle, last)间的元素互换,middle所指的元素会成为容器的第一个元素

比如{1, 2, 3,4, 5, 6}, 对元素3做rotate旋转操作,结果为{3, 4, 5, 6, 1, 2,}



首先要看一下关于reserve的算法:

template <classBidirectionalIterator>
void __reverse(BidirectionalIterator first, BidirectionalIterator last,
bidirectional_iterator_tag) {
while (true)
if (first == last || first == --last)
return;
else
iter_swap(first++, last);
}

template <classRandomAccessIterator>
void __reverse(RandomAccessIterator first, RandomAccessIterator last,
random_access_iterator_tag) {
while (first < last) iter_swap(first++, --last);
}

//调用的两种算法其实都是first和last指针swap再相互逼近,不同的地方在于
//randomiterator之间可以比较大小或者做减法,而双向迭代器之间则不行
template <class BidirectionalIterator>
inline void reverse(BidirectionalIterator first, BidirectionalIterator last) {
__reverse(first, last,iterator_category(first));
}


看完了reserve的算法,接着看rotate的算法:

//第一种:
template <class ForwardIterator, class Distance>
void __rotate(ForwardIteratorfirst, ForwardIterator middle,
ForwardIterator last, Distance*,forward_iterator_tag) {
//大致意思就是[first,middle)和[middle, first)先交换起来
//如果某一方先结束了,那么采取递归的思想,处理相同的问题
//比如 {1,2,3,4,5,6,7,8}, 交换{7,8}到前面
//在{1,2}和{7,8}交换完后成为了{7,8,3,4,5,6,1,2}
//接下来就是要把{3,4,5,6}和{1,2}进行交换,此时符合递归的思想
for (ForwardIterator i = middle; ;) {
iter_swap(first, i);
++first;
++i;
if (first == middle) {
if (i == last) return;
middle = i;
}
else if (i == last)
i = middle;
}
}


第二种
这个也不难理解,我贴一张图就明白了



template <classBidirectionalIterator, class Distance>
void__rotate(BidirectionalIterator first, BidirectionalIterator middle,
BidirectionalIterator last,Distance*,
bidirectional_iterator_tag) {
reverse(first, middle);
reverse(middle, last);
reverse(first, last);
}


第三种
//用辗转相除法求最大公约数
//关于辗转相除法:
/*两个数的最大公约数是指能同时整除它们的最大正整数。辗转相除法的基本原理是:两个数的最大公约数等于它们中较小的数和两数之差的最大公约数。例如,252和105的最大公约数是21(252 = 21 × 12;105= 21 × 5);因为252 − 105 = 147,所以147和105的最大公约数也是21。在这个过程中,较大的数缩小了,所以继续进行同样的计算可以不断缩小这两个数直至其中一个变成零。这时,所剩下的还没有变成零的数就是两数的最大公约数。*/
template <classEuclideanRingElement>
EuclideanRingElement__gcd(EuclideanRingElement m, EuclideanRingElement n)
{
while (n != 0) {
EuclideanRingElement t = m % n;
m = n;
n = t;
}
return m;
}

template <classRandomAccessIterator, class Distance, class T>
void__rotate_cycle(RandomAccessIterator first, RandomAccessIterator last,
RandomAccessIteratorinitial, Distance shift, T*) {
T value = *initial;
RandomAccessIterator ptr1 = initial;
RandomAccessIterator ptr2 = ptr1 + shift;
while (ptr2 != initial) {
*ptr1 = *ptr2;
ptr1 = ptr2;
if (last - ptr2 > shift)
ptr2 += shift;
else
ptr2 = first + (shift - (last - ptr2));
}
*ptr1 = value;
}

template <classRandomAccessIterator, class Distance>
void__rotate(RandomAccessIterator first, RandomAccessIterator middle,
RandomAccessIterator last,Distance*,
random_access_iterator_tag) {
Distance n = __gcd(last - first, middle -first);
while (n--)
__rotate_cycle(first, last, first + n,middle - first,
value_type(first));
}


//可以看出,rotate算法的实现分为了三种
template <class ForwardIterator>
inline voidrotate(ForwardIterator first, ForwardIterator middle,
ForwardIterator last) {
if (first == middle || middle == last) return;
__rotate(first, middle, last,distance_type(first),
iterator_category(first));
}


(关于这里的第三种对random iterator的处理方法我没有看懂。)

关于排序

STL的sort算法,数据量大时采用Quicksort,分段递归程序,一旦分段后的数据量小于某个门槛,为了避免Qucik sort的递归带来的过大的额外负担,就改用insertion sort。如果递归层次过深,还会改用heap sort。以下拆分来讲

Insertion sort(插入排序)

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

template <classRandomAccessIterator, 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 <classRandomAccessIterator, 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, 但是其合适与否却影响着Quick Sort的效率。为了避免“元素当初输入时不够随机”带来的恶化效应,最理想最稳当的方式就是取整个序列的头,尾,中央三个位置的元素,以其中值(median)作为pivot,这种做法称为median-of-three partitioning, 而为了能够快速取出中央位置的元素,显然迭代器必须能够随机定位,即必须是random iterator

以下即为stl提供的三点中值决定函数:

median-of-three-QuickSort

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


得到中值后,我们需要将整个序列分为两部分,一部分完全小于pivot, 一部分完全大于pivot

Partitioning

template <classRandomAccessIterator, class T>
RandomAccessIterator__unguarded_partition(RandomAccessIterator first,
RandomAccessIterator last,
Tpivot) {
while (true) {
while (*first < pivot) ++first;
--last;
while (pivot < *last) --last;
if (!(first < last)) return first;
iter_swap(first, last);
++first;
}
}


与我们之前的了解相同,很好理解

Threshold(阈值)

面对只有十几个元素的小型序列,使用Quick sort这样复杂的需要大量运算的排序是不值得的,因为其会为了极小的子序列产生许多的函数递归调用

鉴于此情况,适度的评估序列的大小,然后决定采用Quick还是insertion是值得采用的优化措施

Final insertionsort(何时使用插入排序)

如果我们令某个大小以下的序列滞留在“几近排序但尚未完成”的状态,最后再以一次insertion sort将所有这些“几近排序但尚未成功”的子序列做一次完整的排序,其效率一般会认为比“将所有子序列彻底”更好,这事因为insertion sort在处理“几近排序”的序列时,有更好的表现。

Introsort(内省式排序)

不当的pivot导致不当的分割,导致Quciksort恶化为O(N^2),于是有人提出了内省式排序。其行为在大部分情况下几乎与median-of-three Qucik sort完全相同,但是当分割行为有恶化为二次行为的倾向时,能够自我侦测,转而使用heap sort,使效率维持在heap sort的O(N(logN)), 又比一开始就使用heap sort来的好

下面我们就来看看源代码:

template <classRandomAccessIterator>
inline voidsort(RandomAccessIterator first, RandomAccessIterator last) {
if (first != last) {
__introsort_loop(first, last,value_type(first), __lg(last - first) * 2);
__final_insertion_sort(first, last);
}
}


//其中的第一个函数
//当元素个数是40时,此函数最后一个参数将是5*2,意思是最多允许分割10层
template <classRandomAccessIterator, class T, class Size>
void__introsort_loop(RandomAccessIterator first,
RandomAccessIteratorlast, T*,
Size depth_limit) {
//__stl_threshold为全局常数,稍早定义为 const int 16
while (last - first > __stl_threshold) {
if (depth_limit == 0) {
partial_sort(first, last, last);
return;
}
--depth_limit;
//以下是我们上面讲到的 median-of-three partitioning,找到适当的median
//并对整个序列进行调整
RandomAccessIterator cut =__unguarded_partition
(first, last, T(__median(*first, *(first+ (last - first)/2),
*(last - 1))));
//对右半段进行递归sort
__introsort_loop(cut, last,value_type(first), depth_limit);
last = cut;
//现在又回到while循环,准备处理左半段
//书上说这种写法可读性较差,效率并没有比较好
//RW STL对左右两段直观的进行递归,较易阅读
//在我自己看到这个的时候觉得不错,能省掉一些递归
}
}


可以看到,函数一开始就与__stl_threshold进行比较;

之后进行层次检查,如果分割层次超过了指定值,就改用partial_sort(以heap_sort实现)

检验完所有后,就进入了Quick sort

进行过几次分割后,整个序列可能分为多个“元素个数少于16”的子序列,每个子序列都有相当程度的排序,但未完全排序,于是回到母函数,进入__final_insertion_sort函数了

//上面函数最后一个参数是用来控制分割恶化的情况,在sort函数中传入的就是 __lg 函数返回值
//找出2^k <= n 的最大值k。例如:n==7,得k=2;n=20,k=4;n=8,k=3
template <class Size>
inline Size __lg(Size n) {
Size k;
for (k = 0; n != 1; n >>= 1) ++k;
return k;
}


//其中的第二个函数
template <classRandomAccessIterator>
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,否的话说明是在分割的过程因为个数太少导致退出的,直接调用__insertion_sort函数处理

如果个数大于16,那么将【first, last)分割为两部分,前部分调用的函数前面接触了,下面看看后半部分使用的函数:

template <classRandomAccessIterator>
inline void__unguarded_insertion_sort(RandomAccessIterator first,
RandomAccessIterator last) {
__unguarded_insertion_sort_aux(first, last,value_type(first));
}

template <class RandomAccessIterator,class T>
void__unguarded_insertion_sort_aux(RandomAccessIterator first,
RandomAccessIterator last, T*) {
for (RandomAccessIterator i = first; i != last; ++i)
//走到这里我们发现这个函数就是前面insertion中使用的
__unguarded_linear_insert(i, T(*i));
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: