首页 > 代码库 > STL中排序算法的选择

STL中排序算法的选择



当大多数程序员需要对一组对象进行排序的时候,首先想到的一个算法是sort。sort是一个非常不错的算法,但它也并非在任何场合下都是完美无缺的。有时候我们并不需要一个完全的排序操作。比如说,如果我们有一个存放Widget的矢量,而我们希望将质量最好的20个Widget送给最重要的顾客,按照顾客的重要程度送上不同质量的Widget,那么只需要排序出前20个最好的Widget,其他的Widget可以不用排序。在这种情况下,需要的是一种部分排序的功能,而有一个名为partial_sort的算法正好可以完成这样的任务:
template <class RandomAccessIterator>
inline void partial_sort(RandomAccessIterator first,RandomAccessIterator middle,
RandomAccessIterator last)
{
 _partial_sort(first,middle,last,value_type(first));
}

template <class RandomAccessIterator,class T>
void _partial_sort(RandomAccessIterator first,RandomAccessIterator middle,
RandomAccessIterator last,T*)
{
 make_heap(first,middle);
 for (RandomAccessIterator i = middle;i < last;++i)
  if (*i < *first)
   _pop_heap(first,middle,i,T(*i),distance_type(first));
 sort_heap(first,middle);
}

该算法接收一个middle迭代器(位于序列[first,last)之内),然后重新安排[first,last),
使序列中的middle-first个最小元素以递增顺序排序,置于[first,middle)内。其余last-middle个元素安置于[middle,last)中,不保证有任何特定顺序。选择partial_sort而非sort的唯一理由是效率。是的,如果只是挑出前N个最小元素来排序,当然比对整个序列排序快上许多。

如果只是要将最好的20个Widget送给最重要的20位顾客,而不关心哪个Widget送给哪位顾客,那么partial_sort就不是最合适的选择了,因为只需要找到最好的20个Widget,这20个Widget可以以任意顺序排序。STL有一个算法可以恰好完成这样的任务:
template <class RandAccessIterator>
inline void nth_element(RandomAccessIterator first,RandomAccessIterator nth,
RandomAccessIterator last)
{
 _nth_element(first,nth,last,value_type(first));
}

template <class RandomAccessIterator,class T>
void _nth_element(RandomAccessIterator first,RandomAccessIterator nth,
RandomAccessIterator last,T*)
{
 while (last-first > 3)
 {
  RandomAccessIterator cut = _unguarded_partition(first,last,
   T(_median(*first,*(first+(last-first)/2),*(last-1))));
  if (cut <= nth)
   first = cut;
  else
   last = cut;
 }
 _insertion_sort(first,last);
}

这个算法会重新排列[first,last),使迭代器nth所指的元素,与“整个[first,last)完整排序后,同一位置的元素”同值。此外并保证[nth,last)内没有任何一个元素小于(更精确地说是不大于)[first,nth)内的元素,但对于[first,nth)和[nth,last)两个子区间内的元素次序则无任何保证——这一点也是它与partial_sort很大的不同处。
nth_element除了可以用来找到排名在前的n个元素以外,还有其他一些功能。比如,nth_element可以用来找到一个区间的中间值或者找到某个特定百分比上的值:
vector<int> ints;
vector<int>::iterator begin(ints.begin());
vector<int>::iterator end(ints.end());
vector<int>::iterator goalPosition;
//下面的代码找到具有中间级别的值
goalPosition = begin + ints.size()/2;
nth_element(begin,goalPosition,end);
//下面的代码找到区间中具有75%级别的元素
vector<int>::size_type poalOffset = 0.25*ints.size();
nth_element(begin,begin + goalOffset,end);

假如,你需要的不是质量最好的20个Widget而是所有的一级品和二级品。当然,我们可以先对整个区间进行排序,然后找到一个质量值比二级还差的元素的位置,于是,从起始处到这个位置之间的元素正是你所需要的。然而,完全排序意味着需要大量的比较和交换工作,对于上述任务,做这么多工作是不必要的。一种更好的策略是使用partition算法。partition算法可以把所有满足某个特定条件的元素放在区间的前部。
template <class BidirectionalIterator,class Predicate>
BiderectionalIterator partition(BidirectionalIterator first,BidirectionalIterator last,
Predicate pred)
{
 while(true)
        {
  while(true)
  {
   if (first == last)
    return first;
   else if (pred(*first))
    ++first;
   else
    break;
  }
  --last;
  while(true)
  {
   if (first == last)
    return first;
   else if (!pred(*last))
    --last;
   else
    break;
  }
  iter_swap(first,last);
  ++first;
 }
}

partition会将区间[first,last)中的元素重新排列。所有被一元条件运算pred判定为true的元素,都会被放在区间的前段,被判定为false的元素,都会被放在区间的后段。这个算法并不保证保留元素的原始相对位置。
总结:
(1)如果需要对vector、string、deque或者数组中的元素执行一次完全排序,那么可以使用sort或者stable_sort。
(2)如果有一个vector、string、deque或者数组,并且只需要对等价性最前面的元素进行排序,那么可以使用partial_sort。
(3)如果有一个vector、string、deque或者数组,并且需要找到第n个位置上的元素,或者,需要找到等价性最前面的n个元素又不必对这n个元素进行排序,那么nth_element正是所需要的函数。
(4)如果需要将一个标准序列容器中的元素按照是否满足某个特定的条件区分开来,那么,partition和stable_partition可能正是所需要的。
(5)如果数据在一个list中,那么仍然可以直接调用partition和stable_partition算法;可以用
list::sort来代替sort和stable_sort算法。但是,如果需要获得partial_sort或nth_element算法的效果,那么,可以采用下面的途径来完成这项任务:(list不提供随机访问迭代器,而sort、stable_sort、partial_sort和nth_element都要求随机访问迭代器)
①将list中的元素拷贝到一个提供随机访问迭代器的容器中,然后对该容器执行所期望的算法。
②先创建一个list::iterator的容器,再对该容器执行相应的算法,然后通过其中的迭代器访问list的元素。
③利用一个包含迭代器的有序容器中的信息,通过反复地调用splice成员函数,将list中的元素调整到期望的目标位置。