std::sort排序算法

std::sort的声明语法:

template <class RandomAccessIterator>

void sort ( RandomAccessIterator first,RandomAccessIterator last );

template <class RandomAccessIterator,class Compare>

void sort ( RandomAccessIterator first,RandomAccessIterator last, StrictWeakOrdering comp );

 

std::sort的标准实现综合了各家之长:

在数据量很大时采用正常的快速排序,此时效率为O(logN)。

一旦分段后的数据量小于某个阈值,就改用插入排序,因为此时这个分段是基本有序的,这时效率可达O(N)。

在递归过程中,如果递归层次过深,分割行为有恶化倾向时,它能够自动侦测出来,使用堆排序来处理,在此情况下,使其效率维持在堆排序的O(N logN),但这又比一开始使用堆排序好。

 

std::sort适合哪些容器?

这么高效的算法,是不是所有的容器都可以使用呢?我们常规数组是否也能使用?我们知道在STL中的容器可以大致分为:

序列式容器:vector, list, deque

关联式容器:set, map, multiset, multimap

配置器容器:queue, stack, priority_queue

无序关联式容器:unordered_set, unordered_map, unordered_multiset, unordered_multimap。这些是在C++ 11中引入的。

从上面的声明我们可以看出来,sort算法要求迭代器是随机迭代器,并且是可写的。如果迭代器不是随机的,那么排序在理论上将变得非常低效;如果迭代器是不可写,那么将无法进行排序,因为排序要求对迭代器指向的元素进行赋值操作。如此sort算法要求迭代器是可写的随机迭代器。这一点要求使得我们不能在std::set、std::list、std::map 等以结点形式存储的容器里使用std::sort,而只能用于vector、string、deque。

对于所有的关联式容器如map和set,由于它们底层是用红黑树实现,因此已经具有了自动排序功能,不需要std::sort。至于配置器容器,因为它们对出口和入口做了限制,比如说先进先出,先进后出,因此它们也禁止使用排序功能。

由于std::sort算法内部需要去取中间位置元素的值,为了能够让访问元素更迅速,因此它只接受有随机访问迭代器的容器。对于所有的无序关联式容器而言,它们只有前向迭代器,因而无法调用std::sort。但我认为更为重要的是,从它们名称来看,本身就是无序的,它们底层是用哈希表来实现。它们的作用像是字典,为的是根据key快速访问对应的元素,所以对其排序是没有意义的。

剩下的三种序列式容器中,vector和deque拥有随机访问迭代器,因此它们可以使用std::sort排序。而list只有双向迭代器,所以它无法使用std::sort,但好在它提供了自己的sort成员函数。

另外,我们最常使用的数组其实和vector一样,它的指针本质上就是一种迭代器,而且是随机访问迭代器,因此也可以使用std::sort。

 

此外在理论上,sort 算法接受的比较函数是一个“严格偏序”操作,其中最易被忽略的要求 comp(a, a)不能为true。

std::sort不保证相等元素的相对位置保持不变,可能恰好没有变,也可能恰好变了。std::stable_sort可以保证这一点,但是效率会低(内部采用归并排序)。

std::sort()在排序时,如果比较函数对相等的元素返回true,会导致程序coredump。

原因分析:std::sort()的排序分2种,当元素个数>16(_S_threshold)时选择快速排序,<=16个则选择插入排序(对象少时快排性能不理想)。按照快排原理,每次都是遍历所有值和一个中间值比较,小的放左边,大的放右边。从STL源代码可看出,std::sort()在遍历比较时,是没有边界保护的。如果比较相等的元素返回true,则在极端情况下(如所有元素相等,__pivot为最小|最大值时)会出现访问越界,导致coredump。

 

如果自写比较函数,永远让比较函数对相等的值返回false!

 

    原文作者:排序算法
    原文地址: https://blog.csdn.net/xijiacun/article/details/72902680
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞