sort与stable_sort踩坑
先说坑,stable_sort()的比较函数的参数不能使用引用(可以用常引用或者常量或者值传递)。
sort()和stable_sort()两个排序算法均可加入自定义的比较符来进行排序。
template <class RandomAccessIterator, class Compare> void stable_sort ( RandomAccessIterator first, RandomAccessIterator last, Compare comp ); template <class RandomAccessIterator, class Compare> void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
sort()在数据小于16的时候采用插入排序,大于16且划分均匀(靠一个划分元素数目的定值判断)时采用快速排序,在划分不均匀的时候采用堆排序。
stable_sort()为了保证数据有序性采用归并排序,归并排序利用inplace_merge实现(一个序列的前一部分和后一部分分别有序,然后将其整个排序)而inplace_merge又分两种情况,有缓冲区和无缓冲区。无缓冲区的排序比较复杂,STL源码剖析也没写。所以只考虑有缓冲区。
template <class BidirectionalIterator, class Compare> void inplace_merge (BidirectionalIterator first, BidirectionalIterator middle, BidirectionalIterator last, Compare comp);
有缓冲区的情况下,根据缓冲区的大小分三种情况:
template <class InputIterator1, class InputIterator2, class OutputIterator, class Compare> OutputIterator merge (InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result, Compare comp);
①缓冲区仅够容纳一个子序列,这时将足够放入缓冲区的那个子序列放入缓冲区,然后两个子序列以原序列的尾部为起点使用merge_backword。这也没什么坑。
②缓冲区不够容纳任何子序列,这时需要递归切割子序列,这里递归分割的方法很巧妙。首先将较长的子序列长度减半,然后以次序列剩余的子序列的第一个元素的upper_bound()作为第一个子序列和第二个子序列(这里是两个新的子序列)的分割点,即upper_bound的作用是将第一个序列能安插到第二个序列后的部分分割出来。此时新的第二个次序列是部分有序的,此时则根据缓冲区的大小来使用类似rotate的算法来使子序列2有序。(查看源码在第一个子序列较长时会使用lower_bound来切割第二个子序列)
可以使用upper是为了分割之前的第一个子序列的一部分可以分割给新的第二个子序列,而且不用从新排序(保证了有序性和稳定性)只用旋转一次即可,如果也将第一个子序列分为一半,则第一个子序列和第二个子序列的剩余部分还需要一次排序而且不是在同一序列的相邻位置,不能使用inplace_merge,而使用其他merge,则又需要缓冲区,不符合初衷。


而stable_sort的坑就在于这个算法精彩的部分,使用了upper_bound或者lower_bound,这个函数的模板和源码如下:
template <class ForwardIterator, class T, class Compare> ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last, const T& val, Compare comp);
1 template<class _FwdIt, 2 class _Ty, 3 class _Pr> 4 _NODISCARD inline _FwdIt lower_bound(_FwdIt _First, const _FwdIt _Last, 5 const _Ty& _Val, _Pr _Pred) 6 { // find first element not before _Val, using _Pred 7 _Adl_verify_range(_First, _Last); 8 auto _UFirst = _Get_unwrapped(_First); 9 _Iter_diff_t<_FwdIt> _Count = _STD distance(_UFirst, _Get_unwrapped(_Last)); 10 11 while (0 < _Count) 12 { // divide and conquer, find half that contains answer 13 const _Iter_diff_t<_FwdIt> _Count2 = _Count >> 1; // TRANSITION, VSO#433486 14 const auto _UMid = _STD next(_UFirst, _Count2); 15 if (_Pred(*_UMid, _Val)) 16 { // try top half 17 _UFirst = _Next_iter(_UMid); 18 _Count -= _Count2 + 1; 19 } 20 else 21 { 22 _Count = _Count2; 23 } 24 } 25 26 _Seek_wrapped(_First, _UFirst); 27 return (_First); 28 }
注意源码第15行,使用迭代器元素_Umid的元素和目标值_Val作为参数应用于自定义的比较函数,而从第14行可以知道,这个迭代器是个常量迭代器。所以我们自定义的compare函数第一个参数就不能是引用了,因为一个常量不能作为引用的参数(权限降低了)。
总结:这个坑真是绝了,sort里面没有使用upper_bound和lower_bound就不会出现这样的问题。

浙公网安备 33010602011771号