分享
 
 
 

Effective STL 条款31

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

条款31:了解你的排序选择

How can I sort thee? Let me count the ways.

当很多程序员想到排序对象时,只有一个算法出现在脑海:sort。(有些程序员想到qsort,但一旦他们看了条款46,他们会放弃qsort的想法并用sort的想法取代之。)

现在,sort是一个令人称赞的算法,但如果你不需要你就没有必要浪费表情。有时候你不需要完全排序。比如,如果你有一个Widget的vector,你想选择20个质量最高的Widget发送给你最忠实的客户,你需要做的只是排序以鉴别出20个最好的Widget,剩下的可以保持无序。你需要的是部分排序,有一个算法叫做partial_sort,它能准确地完成它的名字所透露的事情:

bool qualityCompare(const Widget& lhs, const Widget& rhs)

{

// 返回lhs的质量是不是比rhs的质量好

}

...

partial_sort(widgets.begin(), // 把最好的20个元素

widgets.begin() + 20, // (按顺序)放在widgets的前端

widgets.end(),

qualityCompare);

... // 使用widgets...

调用完partial_sort后,widgets的前20个元素是容器中最好的而且它们按顺序排列,也就是,质量最高的Widget是widgets[0],第二高的是widgets[1]等。这就是你很容易把最好的Widget发送给你最好的客户,第二好的Widget给你第二好的客户等。

如果你关心的只是能把20个最好的Widget给你的20个最好的客户,但你不关心哪个Widget给哪个客户,partial_sort就给了你多于需要的东西。在那种情况下,你需要的只是任意顺序的20个最好的Widget。STL有一个算法精确的完成了你需要的,虽然名字不大可能从你的脑中迸出。它叫做nth_element。

nth_element排序一个区间,在ri位置(你指定的)的元素是如果区间被完全排序后会出现在那儿的元素。另外,当nth_element返回时,在n以上的元素没有在排序顺序上在位置n的元素之后的,而且在n以下的元素没有在排序顺序上在位置n的元素之前的。如果这听起来很复杂,那只是因为我必须仔细地选择我的语言。一会儿我会解释为什么,但首先让我们看看怎么使用nth_element来保证最好的20个Widget在widgets vector的前端:

nth_element (widgets.begin(), // 把最好的20个元素

widgets.begin() + 20, // 放在widgets前端,

widgets.end(), // 但不用担心

qualityCompare); // 它们的顺序

正如你所见,调用nth_element本质上等价于调用partial_sort。它们结果的唯一区别是partial_sort排序了在位置1-20的元素,而nth_element不。但是两个算法都把20个质量最高的Widget移动到vector前端。

那引出一个重要的问题。当有元素有同样质量的时候这些算法怎么办?比如假设有12个元素质量是1级(可能是最好的),15个元素质量是2级(第二好的)。在这种情况下,选择20个最好的Widget就是选择12个1级的和15个中的8个2级的。partial_sort和nth_element怎么判断15个中的哪些要放到最好的20个中?对于这个问题,当多个元素有等价的值时sort怎么判断元素的顺序?

partial_sort和nth_element以任何它们喜欢的方式排序值等价的元素,而且你不能控制它们在这方面行为。(条款19可以告诉你什么是两个值等价。)在我们的例子中,当需要把质量都是2级的Widget选出最后8个放到vector的前20个时,它们可以选择它们想要的任何一个。这不是没有理由的。如果你需要20个最好的Widget和一些也很好的Widget,你不该抱怨你取回的20个至少和你没有取回的一样好。

对于完整的排序,你有稍微多一些的控制权。有些排序算法是稳定的。在稳定排序中,如果一个区间中的两个元素有等价的值,它们的相对位置在排序后不改变。因此,如果在(未排序的)widgets vector中Widget A在Widget B之前,而且两者都有相同的质量等级,那么稳定排序算法会保证在这个vector排序后,Widget A仍然在Widget B之前。不稳定的算法没做这个保证。

partial_sort是不稳定的。nth_element、sort也没有提供稳定性,但是有一个算法——stable_sort——它完成了它的名字所透露的。如果当你排序的时候你需要稳定性,你可能要使用stable_sort。STL并不包含partial_sort和nth_element的稳定版本。

现在谈谈nth_element,这个名字奇怪的算法是个引人注目的多面手。除了能帮你找到区间顶部的n个元素,它也可以用于找到区间的中值或者找到在指定百分点的元素:

vector<Widget>::iterator begin(widgets.begin()); // 方便地表示widgets的

vector<Widget>::iterator end(widgets.end()); // 起点和终点

// 迭代器的变量

vector<Widget>::iterator goalPosition; // 这个迭代器指示了

// 下面代码要找的

// 中等质量等级的Widget

// 的位置

goalPosition = begin + widgets.size() / 2; // 兴趣的Widget

// 会是有序的vector的中间

nth_element(begin, goalPosition, end, // 找到widgets中中等

qualityCompare); // 质量等级的值

... // goalPosition现在指向

// 中等质量等级的Widget

// 下面的代码能找到

// 质量等级为75%的Widget

vector<Widget>::size_type goalOffset = // 指出兴趣的Widget

0.25 * widgets.size(); // 离开始有多远

nth_element(begin, begin + goalOffset, end, // 找到质量值为

qualityCompare); // 75%的Widget

... // goalPosition现在指向

// 质量等级为75%的Widget

如果你真的需要把东西按顺序放置,sort、stable_sort和partial_sort都很优秀,当你需要鉴别出顶部的n个元素或在一个指定位置的元素时nth_element就会买单。但有时候你需要类似nth_element的东西,但不是完全相同。比如假设,你不需要鉴别出20个质量最高的Widget。取而代之的是,你需要鉴别出所有质量等级为1或2的。当然你可以按照质量排序这个vector,然后搜索第一个质量等级比2差的。那就可以鉴别出质量差的Widget的区间起点。

但是完全排序需要很多工作,而且对于这个任务做了很多不必要的工作。一个更好的策略是使用partition算法,它重排区间中的元素以使所有满足某个标准的元素都在区间的开头。

比如,移动所有质量等级为2或更好的Widget到widgets前端,我们定义了一个函数来鉴别哪个Widget是这个级别。

bool hasAcceptableQuality(const Widget& w)

{

// 返回w质量等级是否是2或更高;

}

传这个函数给partition:

vector<Widget>::iterator goodEnd = // 把所有满足hasAcceptableQuality

partition(widgets.begin(), // 的widgets移动到widgets前端,

widgets.end(), // 并且返回一个指向第一个

hasAcceptableQuality); // 不满足的widget的迭代器

此调用完成后,从widgets.begin()到goodEnd的区间容纳了所有质量是1或2的Widget,从goodEnd到widgets.end()的区间包含了所有质量等级更低的Widget。如果在分割时保持同样质量等级的Widget的相对位置很重要,我们自然会用stable_partition来代替partition。

算法sort、stable_sort、partial_sort和nth_element需要随机访问迭代器,所以它们可能只能用于vector、string、deque和数组。对标准关联容器排序元素没有意义,因为这样的容器使用它们的比较函数来在任何时候保持有序。唯一我们可能会但不能使用sort、stable_sort、partial_sort或nth_element的容器是list,list通过提供sort成员函数做了一些补偿。(有趣的是,list::sort提供了稳定排序。)所以,如果你想要排序一个list,你可以,但如果你想要对list中的对象进行partial_sort或nth_element,你必须间接完成。一个间接的方法是把元素拷贝到一个支持随机访问迭代器的容器中,然后对它应用需要的算法。另一个方法是建立一个list::iterator的容器,对那个容器使用算法,然后通过迭代器访问list元素。第三种方法是使用有序的迭代器容器的信息来迭代地把list的元素接合到你想让它们所处的位置。正如你所见,有很多选择。

partition和stable_partition与sort、stable_sort、partial_sort和nth_element不同,它们只需要双向迭代器。因此你可以在任何标准序列迭代器上使用partition和stable_partition。

我们总结一下你的排序选择:

如果你需要在vector、string、deque或数组上进行完全排序,你可以使用sort或stable_sort。

如果你有一个vector、string、deque或数组,你只需要排序前n个元素,应该用partial_sort。

如果你有一个vector、string、deque或数组,你需要鉴别出第n个元素或你需要鉴别出最前的n个元素,而不用知道它们的顺序,nth_element是你应该注意和调用的。

如果你需要把标准序列容器的元素或数组分隔为满足和不满足某个标准,你大概就要找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并没有迭代器。)

“但性能怎么样?”,你想知道。这是极好的问题。一般来说,做更多工作的算法比做得少的要花更长时间,而必须稳定排序的算法比忽略稳定性的算法要花更长时间。我们可以把我们在本条款讨论的算法排序如下,需要更少资源(时间和空间)的算法列在需要更多的前面:

1. partition

4. partial_sort

2. stable_partition

5. sort

3. nth_element

6. stable_sort

我对于在这些排序算法之间作选择的建议是让你的选择基于你需要完成的任务上,而不是考虑性能。如果你选择的算法只完成了你需要的(比如用partition代替完全排序),你能得到的不仅是可以最清楚地表达了你要做的代码,而且是使用STL最高效的方法来完成它。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有