排序一直是数据结构中的常用算法,STL提供的排序算法非常丰富,如何有效使用就值得探讨。在网上没有找到条款31的翻译,于是我自己翻译了。--Winter

如何进行排序?让我数数有几种方法。

一旦程序员需对容器元素进行排序,sort算法马上就会出现在他的脑海(可能有些程序员会想到qsort,但详细阅读条款46后,他们会放弃使用qsort的想法,转而使用sort算法)。

sort是一个非常优秀的算法,但并当你并不真正需要它的时候,其实就是一种浪费。有时你并不需要一个完整的排序(简称为全排序)。例如,你现有一 个包含Widget对象(Widget意为“小挂件”)的vector容器,并且你想把其中质量最好的20个Widget送给你最好的客户,那么你需做的 只是找出这20个质量最好的Widget元素,剩下的并不需关心它们的顺序。这时你需要的是部分排序(相对于全排序),恰好在算法中就有一个名副其实的部 分排序函数函数:partial_sort:

bool qualityCompare(const Widget& lhs, const Widget& rhs)
{
            // lhs的质量不比rhs的质量差时返回true,否则返回false
}

partial_sort (widgets.begin(),                         // 把质量最好的20元素
                    widgets.begin() + 20,                 // 顺序放入widgets容器中
                    widgets.end(),                             
                    qualityCompare);
…                                                                 // 使用 widgets...

通过调用partial_sort,容器中开始的20个元素就是你需要的质量最好的20个Widget,并按顺序排列,质量排第一的就是 widgets[0], 紧接着就是widgets[1],依次类推。这样你就可以把质量第一的Widget送给你最好的顾客,质量第二的Widget就可以送给下一个顾客,很方 便吧,更方便的还在后面呢。

如果你只是想把这20个质量最好的Widget礼物送给你最好的20位顾客,而不需要给他们一一对应,partial_sort在这里就有些显得大 材小用了。因为在这里,你只需要找出这20个元素,而不需要对它们本身进行排序。这时你需要的不是partial_sort,而是 nth_element。

nth_element排序算法只是对一个区间进行排序,一直到你指定的第n个位置上放上正确的元素为止,也就是说,和你进行全排序和 nth_element排序相比,其共同点就是第n个位置是同一个元素。当nth_element函数运行后,在全排序中应该在位置n之后的元素不会出现 在n的前面,应该在位置n前面的元素也不会出现在n的后面。听起来有些费解,主要是我不得不谨慎地使用我的语言来描述nth_element的功能。别着 急,呆会儿我会给你解释为什么,现在先让我们来看看nth_element是如何让质量最好的20个widget礼物排在vector容器的前面的:

nth_element (widgets.begin(),             // 把质量最好的20元素放在
                      widgets.begin() + 20,     // widgets容器的前面,
                      widgets.end(),                // 但并不关心这20个元素
                      qualityCompare);            //本身内部的顺序

你可以看出,调用nth_element函数和调用partial_sort函数在本质上没有区别,唯一的不同在于partial_sort把前 20个元素还进行排列了,而nth_element并不关系他们内部的顺序。两个算法都实现了同样的功能:把质量最好的20个元素放在vector容器的 开始部分。

这样引起了一个重要的问题:要是质量一样的元素,排序算法将会如何处理呢?假设有12个元素的质量都为1(最好等级),15个元素的质量等级为 2(质量次之),如果要选择20个最好的Widget,则先选12个质量为1的元素,然后从15个中选出8个质量为2的元素。到底nth_element 和partial_sort如何从15个中选出8个,依据何在?换句话说,当多个元素有同样的比较值时,排序算法如何决定谁先谁后?

对于partial_sort和nth_element算法来说,你无法控制它们,对于比较值相同的元素它们想怎么排就怎么排(查看条款19,了解 两个值相等的定义)。在我们上面的例子中,面对需要从15个等级为2的元素选出8个增加到top 20中去,他们会任意选取。这样做也有它的道理:如果你要求得到20个质量最好的Widget,同时有些Widget的质量又一样,当你得到20个元素至 少不比剩下的那些质量差,这已经达到你的要求,你就不能抱怨什么了。

假如对于全排序,你倒是可以得到更多的控制权。一些排序算法是“稳定的”(stable),在一个“稳定”的排序算法中,如果两个元素有相同的值, 它们的相对位置在排序后也会保持不变。例如:如果在未排序时Widget A在Widget B之前,而且都有相同的质量等级,那么“稳定”排序算法就可以保证在排序之后,Widget A仍然在Widget B之前。而非“稳定”排序算法就不能保证这一点。

partial_sort和nth_element都不是“稳定”排序算法,真正的“稳定”排序算法是stable_sort,从名字上看就知道它 是“稳定”的。如果你在排序的时候需要保证相同元素的相对位置,你最好使用stable_sort,在STL中没有为partial_sort和 nth_element算法提供对应的“稳定”版本。

说到nth_element,名字确实很怪,但是功能却是不少,除了让你找到无关顺序的top n个元素外,它还能找到某个范围的中值,或者找到在某个特定百分点的值。

vector<Widget>::iterator begin(widgets.begin());     // widgets的第一个
    vector<Widget>::iterator end(widgets.end());           //和最后一个迭代器
                                                                                      // 
    vector<Widget>::iterator goalPosition;                      // 需要定位的那个迭代器

//以下代码用来得到质量排在中间的那个元素的迭代器
    goalPosition = begin + widgets.size() / 2;             // 要找的那个元素应该
                                                                                 //在vector的中部。
    nth_element(begin, goalPosition, end,         // 找到容器widgets元素的中值
                        qualityCompare);                      //
    …                                                                     // goalPosition现在指向中值元素 
    
    //以下代码用来得到质量排在75%的元素
    vector<Widget>::size_type goalOffset =               // 计算出要找的值
                                        0.25 * widgets.size();         //离begin迭代器的距离。 
                                                                                  // 
    nth_element( begin, begin + goalOffset, end,       // 得到质量排在75%的元素
                            qualityCompare);                           //

…                                                 // goalPosition 现在指向质量排在75%的元素。

当你需要把一个集合由无序变成有序时,可选用sort, stable_sort或partial_sort,当你只需得到top n或者在某个特定位置的元素,你就可以使用nth_element。或许有时你的需求比nth_element提供的还要少,例如:你并不需要得到质量最 好的前20个Widget,而只需要识别那些质量等级为1或者等级为2的Widget。当然,你可以对整个vector按照Widget的质量等级进行全 排序,然后查找到第一个质量等级低于等级2的元素。

问题就在于全排序太耗费资源,而且大部分工作都是无用功。这种情况下最好选择partition算法,partition只是给你确定一个区间,把 符合特定条件的元素放到这个区间中。举例来说,要把质量等级好于等于等级2的Widget的元素放在widget容器的前端,我们可以定义一个用于识别 Widget质量等级的函数:

bool hasAcceptableQuality(const Widget& w)
    {
        //如果w的质量等于或好于2,返回true,否则返回false
    }

然后把这个判断函数传递给partion算法:
    vector<Widget>::iterator goodEnd =   // 把所有满足hasAcceptableQuality 
                    partition(widgets.begin(),     // 条件的放在widgets容器的前面,
                    widgets.end(),                     // 返回第一个不满足条件的
                    hasAcceptableQuality);       //元素的位置

这样一来,所有在迭代器widgets.begin()和迭代器goodEnd之间的元素都是满足需求的元素:其质量等级好于或等于2。而在 goodEnd 到 widgets.end() 之间的元素的质量等级都会低于质量等级2。如果你对质量等级相同元素的相对位置很关心的话,你可以选择stable_partition算法来代替 partition。

需要注意的是sort, stable_sort, partial_sort, 和nth_element算法都需要以随机迭代器(random access
iterators)为参数,因此这些算法能只能用于vector, string, deque, 和array等容器,对于标准的关联容器map、set、multmap、multset等,这些算法就有必要用了,这些容器本身的比较函数使得容器内所 有元素一直都是有序排列的。对于容器list,看似可以用这些排序算法,其实也是不可用的(其iterator的类型并不是随机迭代器),不过在需要的时 候可以使用list自带的排序函数sort(有趣的是list::sort函数和一个“稳定”排序函数的效果一样)。如果你想对一个list容器使用 partial_sort或nth_element,你只能间接使用。一个可选的方法是把list中的元素拷贝到带有随机迭代器的容器中,然后再使用这些 算法;另一种是生成一个包含list::iterator的容器,直接对容器内的list::iterator进行排序,然后通过 list::iterator得到所指的元素;第三种方法,借助一个包含iterator的有序容器,然后反复把list中的元素连接到你想要链接的位 置。看见了吧,你可以选择的方式还是比较多的。

partition 和stable_partition函数与sort、stable_sort、partial_sort、nth_element不一样,要求没有那么严 格,输入参数只需是双向迭代器(bidirectional iterator)。因此你可以对所有的标准序列容器使用partition和stable_partition算法。

让我们来总结一下你的排序操作:

若需对vector, string, deque, 或 array容器进行全排序,你可选择sort或stable_sort;

若只需对vector, string, deque, 或 array容器中取得top n的元素,部分排序partial_sort是首选.

若对于vector, string, deque, 或array容器,你需要找到第n个位置的元素或者你需要得到top n且不关系top n中的内部顺序,nth_element是最理想的;

若你需要从标准序列容器或者array中把满足某个条件或者不满足某个条件的元素分开,你最好使用partition或stable_partition;

若使用的list容器,你可以直接使用partition和stable_partition算法,你可以使用list::sort代替sort和 stable_sort排序。若你需要得到partial_sort或nth_element的排序效果,你必须间接使用。正如上面介绍的有几种方式可以 选择。

另外,你可以使用标准的关联容器来保证容器中所有元素在操作过程中一直有序。你还可考虑非标准的STL容器priority_queue,它同样可 以保证其元素在所有操作过程中一直有序(priority_queue在传统意义上属于STL的一部分,但根据“STL”定义,需要STL容器支持迭代 器,而priority_queue并不支持迭代器,故不能能称为标准STL容器)。

这时你或许会问:“性能如何?”非常好的问题。广义的讲,算法做的工作越多,花的时间越长,“稳定”性排序算法比“非稳定”性排序算法要耗时。我们可以按照其消耗的资源(时间和空间)的多少,对本文中讨论的排序算法作个排序,消耗资源越少的排在前面:

1. partition

2. stable_partition

3. nth_element

4. partial_sort

5. sort

6. stable_sort

选择这些算法的依据是你的需求而不是它们的性能。若你能选择一个算法恰好满足你的需求(如用部分排序代替全排序),不仅会清晰表达你的意图,而且能高效的使用STL。

附上一个小例子:

#include<iostream>
#include<vector>
#include<iterator>
#include<cstdlib>
#include<algorithm> 
#include<functional>

using namespace std;

bool less5(int a)
{
return a<5;
}

int main()
{
    const size_t n=10;
    int a[10]={3,6,9,2,5,8,1,4,7,0};
    vector<int> ive(a,a+n);
    cout<<"After sort;\n";
    sort(a,a+n);
    for(int i=0;i<n;++i) cout<<a[i]<<" ";
    cout<<endl;
    
    cout<<"The top 4 number: "<<endl;
    partial_sort(ive.begin(),ive.begin()+4,ive.end(),greater<int>());
    copy(ive.begin(),ive.begin()+4,ostream_iterator<int>(cout," "));
    
    cout<<"\nThe 4th number: "<<endl;
    nth_element(ive.begin(),ive.begin()+3,ive.end(),greater<int>());   //注意是 ive.begin()+3
    cout<<ive[3];
    
    cout<<"\nThe top 4 number: "<<endl;
    copy(ive.begin(),ive.begin()+4,ostream_iterator<int>(cout," "));
    
    cout<<"\nThe numbers are divided less or greater 5:"<<endl;
    partition(ive.begin(),ive.end(),less5);
    copy(ive.begin(),ive.end(),ostream_iterator<int>(cout," "));
    cout<<endl;
    
    system("pause");
    return 0;
}

理解你的排序操作(stable_sort,sort,partial_sort,nth_element,stable_partition,partition)相关推荐

  1. Python入门--列表元素的排序操作,sort(),sorted()

    #列表元素的排序操作 #sort()-->默认将列表元素从小到达排序,指定reverse=True,进行降序排序,不产生新的列表对象 #sorted()-->对列表进行排序,将产生一个新的 ...

  2. C++中std::sort/std::stable_sort/std::partial_sort的区别及使用

    某些算法会重排容器中元素的顺序,如std::sort.调用sort会重排输入序列中的元素,使之有序,它默认是利用元素类型的<运算符来实现排序的.也可以重载sort的默认排序,即通过sort的第三 ...

  3. linux环境下使用sort命令完成常见排序操作

    文章目录 前言 命令作用 常见选项 数据文件 核心参数 用法展示 按照指定列排序 将排序结果存入指定文件 查看文件是否已经排序好 去掉排序结果中的重复行 按照数值结果进行排序 反向排序 自定义分割字符 ...

  4. 【C++】C++11 STL算法(三):分隔操作(Partitioning operations)、排序操作(Sorting operations)

    目录 分隔操作(Partitioning operations) 一.is_partitioned 1.原型: 2.说明: 3.官网demo 二.partition 1.原型: 2.说明: 3.官方d ...

  5. python中排序英文单词怎么写_Python实现对文件进行单词划分并去重排序操作示例...

    本文实例讲述了Python实现对文件进行单词划分并去重排序操作.,具体如下: 文件名:test1.txt 文件内容: But soft what light through yonder window ...

  6. oracle如何升序,oracle排序操作

    查询排序最多的SQL语句: WITH sql_workarea AS (SELECT sql_id || '_' || child_number sql_id_child, operation_typ ...

  7. Java中使用Jedis连接Redis对SortedSet进行排序操作

    场景 Centos中Redis的下载编译与安装(超详细): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/103967334 Re ...

  8. linux查询字段排序,Linux 操作命令 sort

    1.简介 sort 命令能够帮助我们对文本文件和 stdin 进行排序操作:通常 会 结合其他命令来生成所需要的输出 2.用法/命令格式 sort [选项] [文件名] 3.常用参数解析 参数 描述 ...

  9. antd table 时间搜索_antd table按表格里的日期去排序操作

    表格内容 根据票据日期升序(这里是已经排序后的效果) 上代码 代码中data的内容如下 根据paper_date排序,因为目前这种格式不支持比较,需要先转换成时间戳 new Date(aTimeStr ...

最新文章

  1. stat用法:获取文件对应权限的数字
  2. python之父叫什么-Python之父谈Python的未来形式
  3. 阻止浏览器关闭 区分刷新和关闭 自试IE可用
  4. 数据结构 --- 堆
  5. JAXB –新手的观点,第2部分
  6. 剑指offer 26.数字的子结构
  7. 总结vue几种页面刷新方法
  8. 【电路仿真】基于matlab Simulink光伏电池仿真模型【含Matlab源码 486期】
  9. RMI(Remote Method Invocation)原理浅析
  10. 安全站点导航(感谢backlion整理)
  11. worldpress小工具自定义html,WorldPress Grid html代码设置浅析,代码非常简洁
  12. 对于计算机等级考试的建议
  13. 单片机计数器实验代码c语言,单片机计数器功能实验程序
  14. ROS机器人平台发展趋势
  15. lotus 2k 测试网 多签钱包改为单签
  16. Linux下网络服务的安全设置
  17. Mysql将某一列值统一拼接一个字符串
  18. 猎豹移动逆势在美扩张业务 未来2年员工大幅增长
  19. Python正则表达式学习心得及总结
  20. Django计算机毕业设计超市会员积分管理系统(程序+LW)Python

热门文章

  1. redis过期key监听事件
  2. 修改Mathon为默认浏览器
  3. 安全漏洞中的倚天剑——XSS跨站脚本攻击
  4. nuxt百度地图引入基础使用,百度地图初始化,行政区划划分
  5. 半年学好英语,一辈子都能受益。
  6. OpenEuler安装Docker及OpenGuass最全教程
  7. 即时通讯软件:改变企业沟通方式
  8. 深刻理解空间(线性空间,度量空间,赋范空间,线性赋范空间,内积空间,巴拿赫空间以及希尔伯特空间)
  9. [BZOJ3698]XWW的难题(有源汇上下界最大流+讲解)
  10. redis list操作leftpop