標籤:stl 排序演算法 sort partial_sort nth_element
當大多數程式員需要對一組對象進行排序的時候,首先想到的一個演算法是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中的元素調整到期望的目標位置。