STL中的排序算法一览[By ACM郭老师]

数据结构课程 这篇文章我很喜欢,是郭老师的新作!希望大家喜欢! 详细的从算法的效率方面来说明了排序算法! STL中有多种排序算法,各有各的适用范围,下面听我一一道来: I、完全排序 sort() 首先要隆重推出的当然是最最常用的sort了,sort有两种形式,第一种形式有两个迭代器参数,构成一个前开后闭的区间,按照元素的 less 关系排序;第二种形式多加一个指定排序准则的谓词。sort基本是最通用的排序函数,它使用快速排序算法,并且在递归过程中,当元素数目小于一个阈值(一般是16,我的试验是24)时,转成直接插入排序。伟大的数学家Knuth已经证明,在平均意义上,快速排序是最快的了;当然,最坏复杂性比较差。sort要求随机迭代器,因此对于很多编译器来说,对于前向迭代器(如list)使用sort是一个编译错误。(不过,在vc2005里面,这个错误信息实在很糟糕) sort的基本使用方式如下: view plaincopy to clipboardprint? C++: #include #include #include #include using namespace std; void func1() { vector ar; //向数组里面插入一些随机数 generate_n(back_inserter(ar), 100, rand); //按从小到大排序 sort(ar.begin(), ar.end()); } C++:#include #include #include #include using namespace std;void func1(){ vector ar; //向数组里面插入一些随机数 generate_n(back_inserter(ar), 100, rand); //按从小到大排序 sort(ar.begin(), ar.end());} 经常有人问如何从大到小逆排序,这个其实有很多中方式实现,如下面的例子: view plaincopy to clipboardprint? C++: void func2() { vector ar; //向数组里面插入一些随机数 generate_n(back_inserter(ar), 100, rand); //方法1:使用函数作为谓词 sort(ar.begin(), ar.end(), GreateThan); //方法2:使用仿函数作为谓词 //注意下面两种方法都需要有个括号,实际上是要产生一个临时对象 sort(ar.begin(), ar.end(), CompareInt()); //方法3:使用预定义的Adapter, 定义在 中 sort(ar.begin(), ar.end(), greater ()); //方法4:正常排序,然后翻转过来 sort(ar.begin(), ar.end()); reverse(ar.begin(), ar.end()); //方法5:使用逆迭代器 sort(ar.rbegin(), ar.rend()); } C++:void func2(){ vector ar; //向数组里面插入一些随机数 generate_n(back_inserter(ar), 100, rand); //方法1:使用函数作为谓词 sort(ar.begin(), ar.end(), GreateThan); //方法2:使用仿函数作为谓词 //注意下面两种方法都需要有个括号,实际上是要产生一个临时对象 sort(ar.begin(), ar.end(), CompareInt()); //方法3:使用预定义的Adapter, 定义在 中 sort(ar.begin(), ar.end(), greater ()); //方法4:正常排序,然后翻转过来 sort(ar.begin(), ar.end()); reverse(ar.begin(), ar.end()); //方法5:使用逆迭代器 sort(ar.rbegin(), ar.rend());} 最后一种方法是我比较欣赏的,可以不能直接对原生数组使用,也就是说,如果ar的定义是int ar[MAXN],上面其他的排序算法都可以简单的改成sort(ar, ar+MAXN, ...),但最后一个不行,要用另外一种比较丑陋的方式: view plaincopy to clipboardprint? C++: #include void func3(){ int ax[5]={1,3,4,5,2}; sort(reverse_iterator (ax+5), reverse_iterator (ax+0)); } C++:#include void func3(){ int ax[5]={1,3,4,5,2}; sort(reverse_iterator (ax+5), reverse_iterator (ax+0));} stable_sort sort优点一大堆,一个缺点就是它不是一种稳定的排序。什么是排序的稳定性,就是如果出现两个元素相等时,要求排序之后他们之间保持原来的次序(比如我们先按学号排序,然后按成绩排序,这时就希望成绩相同的还是按照学号的次序排)。很可惜,快速排序算法就不是稳定的,要追求这个,只好用stable_sort了。 在各种排序算法中,合并排序是稳定的,但一般的合并排序需要额外的O(N)的存储空间,而这个条件不是一定能够满足的(可能是比较奢侈的)。所以在stable_sort内部,首先判断是否有足够的额外空间(如vecotr中的cap-size()部分),有的话就使用普通合并函数,总的时间复杂性和快速排序一个数量级,都是O(N*logN)。如果没有额外空间,使用了一个merge_without_buffer的关键函数进行就地合并(如何实现是比较有技巧的,完全可以专门谈一谈),这个合并过程不需要额外的存储空间,但时间复杂度变成O(N*logN),这种情况下,总的stable_sort时间复杂度是O(N*logN*logN)。 总之,stable_sort稍微慢一点儿,但能够保证稳定,使用方法和sort一样。但很多时候可以不用这种方式和这个函数,比如上面的例子,完全可以在排序比较准则中写入成绩和学号两个条件就OK了 view plaincopy to clipboardprint? C++: class CStudent { public: CStudent(); //注意这个比较函数中的const bool operator<(const CStudent& rhs) const { if (m_score != rhs.m_score) return (m_score arStu; sort(arStu.begin(), arStu.end()); } C++:class CStudent{public: CStudent(); //注意这个比较函数中的const bool operator<(const CStudent& rhs) const { if (m_score != rhs.m_score) return (m_score arStu; sort(arStu.begin(), arStu.end());} sort_heap 堆排序也是一种快速的排序算法,复杂度也是O(N*logN)。STL中有一些和堆相关的函数,能够构造堆,如果在构造好的堆上每次取出来根节点放在尾部,所有元素循环一遍,最后的结果也就有序了。这就是sort_heap了。它的使用要求区间前面已经构造成堆,如: view plaincopy to clipboardprint? C++: void func5() { vector ar; generate_n(back_inserter(ar), 100, rand); make_heap(ar.begin(), ar.end()); sort_heap(ar.begin(), ar.end()); } C++:void func5(){ vector ar; generate_n(back_inserter(ar), 100, rand); make_heap(ar.begin(), ar.end()); sort_heap(ar.begin(), ar.end());} list.sort 对于list容器,是不能直接使用sort的(包括stable_sort),从技术的角度来说是由于sort要求随机迭代器;从算法的角度来说,list这种链表结构就不适合用快速排序。因此,list容器内部实现了专门的sort算法,这个算法采用的是合并排序,应该是稳定的(不确定)。 其他 优先队列(priority_queue)每次弹出的都是max值。实际上就是heap的一个容器方式的包装。 关联式容器自身就必须是有序的(针对key),对其迭代时,key是递增的。 II、部分排序 这些部分排序功能能够完成一段数据(而不是所有)的排序,在适当的适合使用可以节省计算量。不过用的人不多。 partial_sort(), partial_sort_copy() 这两个函数能够将整个区间中给定数目的元素进行排序,也就是说,结果中只有最小的M个元素是有序的。你当然也可以使用sort,区别就在于效率。如果M显著地小于N,时间就比较短;当然M太小了也不好,那还不如挨个找最小值了。 partial_sort接受三个参数,分别是区间的头,中间和结尾。执行后,将前面M(M=中间-头)个元素有序地放在前面,后面的元素肯定是比前面的大,但他们内部的次序没有保证。partial_sort_copy的区别在于把结果放到另外指定的迭代器区间中: view plaincopy to clipboardprint? C++: void func6() { int ar[12]={69,23,80,42,17,15,26,51,19,12,35,8}; //只排序前7个数据 partial_sort(ar, ar+7, ar+12); //结果是 8 12 15 17 19 23 26 80 69 51 42 35,后5个数据不定 vector res(7); //前7项排序后放入res partial_sort_copy(ar, ar+7, res.begin(), res.end(), greater () ); } C++:void func6(){ int ar[12]={69,23,80,42,17,15,26,51,19,12,35,8}; //只排序前7个数据 partial_sort(ar, ar+7, ar+12); //结果是 8 12 15 17 19 23 26 80 69 51 42 35,后5个数据不定 vector res(7); //前7项排序后放入res partial_sort_copy(ar, ar+7, res.begin(), res.end(), greater () );} 这两个函数的实现使用的是堆的方法,先将前M个元素构造成堆,然后挨个检查后面的元素,看看是否小于堆的最大值,是的话就彼此交换,然后重排堆;最后将前面已经是最小的M个元素构成的堆作一次sort_heap就可以了。算法的复杂度差不多是O(N*logM) nth_element 这个函数只真正排序出一个元素来,就是第n个。函数有三个迭代器的输入(当然还可以加上一个谓词),执行完毕后,中间位置指向的元素保证和完全排序后这个位置的元素一致,前面区间的元素都小于(精确地说,是不大于)后面区间的元素。 熟悉快速排序的马上就能发现,这实际上是一个按位置划分的算法。STL的规范中要求此函数的平均复杂度是线性的,和快速排序一样,这种算法的最坏复杂度比较差。在一般的实现(如SGI)中,采用三种取1的方法寻找划分元素,最坏复杂度是O(N^N)。虽然理论上有一些算法可以保证最坏线性复杂度,但算法过于复杂,STL一般也不采用。 III、排序辅助功能 partition, stable_partition merge, inplace_merge IV、有序区间操作 这个准备单独写一篇 引用 让我们继续期待郭老师的补充! 郭老师的boke地址 www.skywind.name/blog
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值