使用STL算法排序
一、简介
C++标准库(STL)提供了丰富的排序算法供开发者使用。除了常见的全范围排序外,STL还支持对部分范围进行排序、检查排序状态等操作。同时Boost库也提供了一些高效的排序算法供选择。本文将介绍STL和Boost中常用的排序相关函数及其使用场景。
一起来看看STL和Boost在这方面能做些什么。
二、对整个范围进行排序
对整个范围进行排序的标准函数是std::sort
。它使用快速排序(Quicksort)算法,时间复杂度是O(n*log(n))
以内,并直接对传递的范围应用排序。默认情况下,使用的比较是operator<
,也可以提供自定义比较器以按不同顺序排序。
template<class RandomIt>
void sort(RandomIt first, RandomIt last);
template<class RandomIt, class Compare>
void sort(RandomIt first, RandomIt last, Compare comp);
参数:
first
:指向排序范围的起始位置的迭代器。last
:指向排序范围的结束位置的迭代器(不包含在排序范围内)。comp
(可选):排序时用于比较元素的二元谓词函数对象,默认使用operator<
进行比较。
std::sort
不能保证保持相等元素之间的相对顺序。即几个相等的元素在排序之后可能会以不同于其原始顺序的顺序出现。如果需要这种保证,使用 std::stable_sort
,用于对指定范围内的元素进行稳定排序。它使用归并排序(Mergesort)算法,算法复杂度略慢:如果有足够多的内存可用,它可以保持 O(n*log(n))
的复杂度,否则可能会达到 O(n*log(n)²)
的复杂度。但是,除非是性能问题,性能分析器显示程序的瓶颈出现在 std::stable_sort
中,否则这并不是一个大问题。
template<class RandomIt>
void stable_sort(RandomIt first, RandomIt last);
template<class RandomIt, class Compare>
void stable_sort(RandomIt first, RandomIt last, Compare comp);
如果需要检查一个范围是否已排序,只需使用std::is_sorted
。
template<class ForwardIt>
bool is_sorted(ForwardIt first, ForwardIt last);
template<class ForwardIt, class Compare>
bool is_sorted(ForwardIt first, ForwardIt last, Compare comp);
std::is_sorted
函数用于检查指定范围内的元素是否已按照指定的排序准则进行排序。
三、对范围的开始进行排序
如果不希望或不需要对范围进行完全排序,可以使用std::partial_sort
对范围进行排序,直至指定的点。
template< typename RandomIt >
void partial_sort(RandomIt first, RandomIt middle, RandomIt last);
middle
表示在进行了部分排序之后,元素被排序到的位置(不包括中间位置)。所有位于该位置之后的元素都保证比位于该位置之前的元素大,但它们的相对顺序未作规定。其复杂度大约为(last - first)*log(middle - first)
。
std::partial_sort
不保证保持等价元素的相对顺序,即使是在范围中已排序的部分。也不存在“partial_stable_sort”这样的算法。
注意,std::partial_sort_copy
做的事情与std::partial_sort
相同,但它在另一个范围内输出结果,使传递给算法的范围保持不变。函数原型:
template<class InputIt, class RandomIt>
RandomIt partial_sort_copy(InputIt first, InputIt last,
RandomIt d_first, RandomIt d_last);
template<class InputIt, class RandomIt, class Compare>
RandomIt partial_sort_copy(InputIt first, InputIt last,
RandomIt d_first, RandomIt d_last,
Compare comp);
std::partial_sort_copy
函数从输入范围 [first, last)
中选择最小的 std::distance(d_first, d_last)
个元素,并将其按照升序(默认情况下)或自定义的比较准则复制到目标范围 [d_first, d_last)
中。
参数:
first
:指向输入范围的起始位置的迭代器。last
:指向输入范围的结束位置的迭代器(不包含在输入范围内)。d_first
:指向目标范围的起始位置的迭代器。d_last
:指向目标范围的结束位置的迭代器(不包含在目标范围内)。comp
(可选):排序时用于比较元素的二元谓词函数对象,默认使用operator<
进行比较。
返回值:返回一个指向目标范围的末尾位置的迭代器。
std::partial_sort_copy
函数的时间复杂度为O(NlogM),其中N是输入范围的大小,M是目标范围的大小。该函数对于需要在保持相对顺序的情况下选择最小的元素的问题非常有用。
如果需要检查范围是否部分排序,可以使用std::is_sorted_until
。与std::is_sorted
返回bool类型不同,std::is_sorted_until
返回一个迭代器,该迭代器指向范围内元素被排序到的最后一个元素(不包括此元素)。函数原型:
template< typename ForwardIt >
ForwardIt is_sorted_until(ForwardIt first, ForwardIt last);
四、仅对范围中的一个元素排序
选择一个范围,并将该范围内的元素按照排序后的位置进行放置。这可以通过 std::nth_element
函数来实现。
template< typename RandomIt >
void nth_element(RandomIt first, RandomIt nth, RandomIt last);
在第n个位置之前的所有元素的相对顺序是不确定的,但它们都小于或等于第n个位置之后的元素的相对顺序,而后者的相对顺序也是不确定的。
std::nth_element
的时间复杂度为O (n)。
通过使用std::partial_sort
和std::nth_element
的组合,可以对集合的子范围进行排序,而不是从集合的开头开始。为此,可以在子范围的开头应用std::nth_element
,然后从该点到子范围的末尾应用std::partial_sort
。
五、Boost中的一个附加排序函数
Boost提供了另一种比 Quicksort 更快的排序算法:spreadsort(Boost::sort::spreadsort::spreadsort)
。扩展排序是比较排序和Radix-based
排序的组合。
5.1、比较排序和Radix-based排序
经典的排序算法(如Quicksort)基于元素比较(使用operator<
或自定义等价)。因此,它们被称为基于比较的算法。
基数排序的操作方式不同。本质上,基数排序算法按最高有效位数对所有元素进行分组。然后在具有相同最高有效数字的每一组中,将元素按第二位最高有效数字分组,以此类推。这是递归操作,并有效地对集合进行排序。这种类型的基数排序称为 MSD,即最高有效位数;LSD(最低有效位数)则相反。
5.2、Boost spreadsort:两者的混合
对于较大的集合(>=1000个元素),基数排序往往比比较排序更有效。spreadsort
混合了基数排序和比较排序,使其成为一种混合排序算法。它从基数排序开始,直到组太小而不能有效地进行基数排序,这时,它在组内执行比较排序。
对于小型集合(<1000个元素),spreadsort
就和std::sort
一样了。
spreadsort
应该比std::sort
更有效,或者至少对较小的集合同样有效。Boost支持整数、浮点数和std::string
的扩展排序。
六、总结
C++标准库提供了多种灵活的排序算法,可以满足各种场景下的排序需求。对于大型集合,Boost的spreadsort
也是一个不错的选择。