STL sort快排
1.1 所有sort演算法介紹
所有的sort演算法的引數都需要輸入一個範圍,[begin, end)。這裡使用的迭代器(iterator)都需是隨機迭代器(RadomAccessIterator), 也就是說可以隨機訪問的迭代器,如:it+n什麼的。(partition 和stable_partition 除外)如果你需要自己定義比較函式,你可以把你定義好的仿函式(functor)作為引數傳入。每種演算法都支援傳入比較函式。以下是所有STL sort演算法函式的名字列表:
函式名 | 功能描述 |
---|---|
sort | 對給定區間所有元素進行排序 |
stable_sort | 對給定區間所有元素進行穩定排序 |
partial_sort | 對給定區間所有元素部分排序 |
partial_sort_copy | 對給定區間複製並排序 |
nth_element | 找出給定區間的某個位置對應的元素 |
is_sorted | 判斷一個區間是否已經排好序 |
partition | 使得符合某個條件的元素放在前面 |
stable_partition | 相對穩定的使得符合某個條件的元素放在前面 |
1.2 sort 中的比較函式
當你需要按照某種特定方式進行排序時,你需要給sort指定比較函式,否則程式會自動提供給你一個比較函式。vector < int上述例子中系統自己為sort提供了less仿函式。在STL中還提供了其他仿函式,以下是仿函式列表:> vect; //... sort(vect.begin(), vect.end()); //此時相當於呼叫 sort(vect.begin(), vect.end(), less<int>() );
名稱 | 功能描述 |
---|---|
equal_to | 相等 |
not_equal_to | 不相等 |
less | 小於 |
greater | 大於 |
less_equal | 小於等於 |
greater_equal | 大於等於 |
less<int>() greater<int>()
#include <QtCore/QCoreApplication> #include <iostream> #include <algorithm> #include <vector> #include <functional> using namespace std; class myclass { public: myclass(int a, int b):first(a),second(b){} int first; int second; bool operator < (const myclass &m)const { return first < m.first; } }; bool less_second(const myclass &m1, const myclass &m2) { return m1.second < m2.second; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); vector< myclass > vect; for(int i = 0; i < 10; i++) { myclass my(10 - i, i * 3); vect.push_back(my); } cout<<"原始資料:\n"; for(int i = 0; i < vect.size(); i++) { cout<<"("<<vect[i].first<<","<<vect[i].second<<")\n"; } sort(vect.begin(), vect.end()); cout<<"第一次排序後(sort預設從小到大):\n"<<endl; for(int i = 0 ; i < vect.size(); i ++) { cout<<"("<<vect[i].first<<","<<vect[i].second<<")\n"; } cout<<"第二次排序後(要想從到小排序就要過載):\n"<<endl; sort(vect.begin(), vect.end(), less_second); for(int i = 0 ; i < vect.size(); i ++) { cout<<"("<<vect[i].first<<","<<vect[i].second<<")\n"; } cout<<"end"<<endl; return a.exec(); } 執行結果:
Starting /home/jz/test/test...
原始資料:
(10,0)
(9,3)
(8,6)
(7,9)
(6,12)
(5,15)
(4,18)
(3,21)
(2,24)
(1,27)
第一次排序後(sort預設從小到大):
(1,27)
(2,24)
(3,21)
(4,18)
(5,15)
(6,12)
(7,9)
(8,6)
(9,3)
(10,0)
第二次排序後(要想從到小排序就要過載):
(10,0)
(9,3)
(8,6)
(7,9)
(6,12)
(5,15)
(4,18)
(3,21)
(2,24)
(1,27)
end
1.3 sort 的穩定性你發現有sort和stable_sort,還有 partition 和stable_partition, 感到奇怪吧。其中的區別是,帶有stable的函式可保證相等元素的原本相對次序在排序後保持不變。或許你會問,既然相等,你還管他相對位置呢,也分不清楚誰是誰了?這裡需要弄清楚一個問題,這裡的相等,是指你提供的函式表示兩個元素相等,並不一定是一摸一樣的元素。
例如,如果你寫一個比較函式:
bool less_len(const string &str1, const string &str2) { return str1.length() < str2.length(); }此時,"apple" 和 "winter" 就是相等的,如果在"apple" 出現在"winter"前面,用帶stable的函式排序後,他們的次序一定不變,如果你使用的是不帶"stable"的函式排序,那麼排序完後,"Winter"有可能在"apple"的前面。
1.4 全排序
全排序即把所給定範圍所有的元素按照大小關係順序排列。用於全排序的函式有template <class RandomAccessIterator> void sort(RandomAccessIterator first, RandomAccessIterator last); template <class RandomAccessIterator, class StrictWeakOrdering> void sort(RandomAccessIterator first, RandomAccessIterator last, StrictWeakOrdering comp); template <class RandomAccessIterator> void stable_sort(RandomAccessIterator first, RandomAccessIterator last); template <class RandomAccessIterator, class StrictWeakOrdering> void stable_sort(RandomAccessIterator first, RandomAccessIterator last, StrictWeakOrdering comp);在第1,3種形式中,sort 和 stable_sort都沒有指定比較函式,系統會預設使用operator< 對區間[first,last)內的所有元素進行排序, 因此,如果你使用的型別義軍已經過載了operator<函式,那麼你可以省心了。第2, 4種形式,你可以隨意指定比較函式,應用更為靈活一些。來看看實際應用:
班上有10個學生,我想知道他們的成績排名。
#include <iostream> #include <algorithm> #include <functional> #include <vector> #include <string> using namespace std; class student{ public: student(const string &a, int b):name(a), score(b){} string name; int score; bool operator < (const student &m)const { return score< m.score; } }; int main() { vector< student> vect; student st1("Tom", 74); vect.push_back(st1); st1.name="Jimy"; st1.score=56; vect.push_back(st1); st1.name="Mary"; st1.score=92; vect.push_back(st1); st1.name="Jessy"; st1.score=85; vect.push_back(st1); st1.name="Jone"; st1.score=56; vect.push_back(st1); st1.name="Bush"; st1.score=52; vect.push_back(st1); st1.name="Winter"; st1.score=77; vect.push_back(st1); st1.name="Andyer"; st1.score=63; vect.push_back(st1); st1.name="Lily"; st1.score=76; vect.push_back(st1); st1.name="Maryia"; st1.score=89; vect.push_back(st1); cout<<"------before sort..."<<endl; for(int i = 0 ; i < vect.size(); i ++) cout<<vect[i].name<<":\t"<<vect[i].score<<endl; stable_sort(vect.begin(), vect.end(),less<student>()); cout <<"-----after sort ...."<<endl; for(int i = 0 ; i < vect.size(); i ++) cout<<vect[i].name<<":\t"<<vect[i].score<<endl; return 0 ; }
sort採用的是成熟的"快速排序演算法"(目前大部分STL版本已經不是採用簡單的快速排序,而是結合內插排序演算法)。注1,可以保證很好的平均效能、複雜度為n*log(n),由於單純的快速排序在理論上有最差的情況,效能很低,其演算法複雜度為n*n,但目前大部分的STL版本都已經在這方面做了優化,因此你可以放心使用。stable_sort採用的是"歸併排序",分派足夠記憶體是,其演算法複雜度為n*log(n), 否則其複雜度為n*log(n)*log(n),其優點是會保持相等元素之間的相對位置在排序前後保持一致。
1.5 區域性排序
區域性排序其實是為了減少不必要的操作而提供的排序方式。其函式原型為:template <class RandomAccessIterator> void partial_sort(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last); template <class RandomAccessIterator, class StrictWeakOrdering> void partial_sort(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, StrictWeakOrdering comp); template <class InputIterator, class RandomAccessIterator> RandomAccessIterator partial_sort_copy(InputIterator first, InputIterator last, RandomAccessIterator result_first, RandomAccessIterator result_last); template <class InputIterator, class RandomAccessIterator, class StrictWeakOrdering> RandomAccessIterator partial_sort_copy(InputIterator first, InputIterator last, RandomAccessIterator result_first, RandomAccessIterator result_last, Compare comp);理解了sort 和stable_sort後,再來理解partial_sort 就比較容易了。先看看其用途: 班上有10個學生,我想知道分數最低的5名是哪些人。如果沒有partial_sort,你就需要用sort把所有人排好序,然後再取前5個。現在你只需要對分數最低5名排序,把上面的程式做如下修改:
stable_sort(vect.begin(), vect.end(),less<student>()); 替換為: partial_sort(vect.begin(), vect.begin()+5, vect.end(),less<student>());這樣的好處知道了嗎?當資料量小的時候可能看不出優勢,如果是100萬學生,我想找分數最少的5個人......
partial_sort採用的堆排序(heapsort),它在任何情況下的複雜度都是n*log(n). 如果你希望用partial_sort來實現全排序,你只要讓middle=last就可以了。
partial_sort_copy其實是copy和partial_sort的組合。被排序(被複制)的數量是[first, last)和[result_first, result_last)中區間較小的那個。如果[result_first, result_last)區間大於[first, last)區間,那麼partial_sort相當於copy和sort的組合。
1.6 nth_element 指定元素排序
nth_element一個容易看懂但解釋比較麻煩的排序。用例子說會更方便:班上有10個學生,我想知道分數排在倒數第4名的學生。
如果要滿足上述需求,可以用sort排好序,然後取第4位(因為是由小到大排), 更聰明的朋友會用partial_sort, 只排前4位,然後得到第4位。其實這是你還是浪費,因為前兩位你根本沒有必要排序,此時,你就需要nth_element:
template <class RandomAccessIterator> void nth_element(RandomAccessIterator first, RandomAccessIterator nth, RandomAccessIterator last); template <class RandomAccessIterator, class StrictWeakOrdering> void nth_element(RandomAccessIterator first, RandomAccessIterator nth, RandomAccessIterator last, StrictWeakOrdering comp);對於上述例項需求,你只需要按下面要求修改1.4中的程式:
stable_sort(vect.begin(), vect.end(),less<student>()); 替換為: nth_element(vect.begin(), vect.begin()+3, vect.end(),less<student>());第四個是誰?Andyer,這個倒黴的傢伙。為什麼是begin()+3而不是+4? 我開始寫這篇文章的時候也沒有在意,後來在ilovevc 的提醒下,發現了這個問題。begin()是第一個,begin()+1是第二個,... begin()+3當然就是第四個了。
1.7 partition 和stable_partition
好像這兩個函式並不是用來排序的,'分類'演算法,會更加貼切一些。partition就是把一個區間中的元素按照某個條件分成兩類。其函式原型為:template <class ForwardIterator, class Predicate> ForwardIterator partition(ForwardIterator first, ForwardIterator last, Predicate pred) template <class ForwardIterator, class Predicate> ForwardIterator stable_partition(ForwardIterator first, ForwardIterator last, Predicate pred);看看應用吧:班上10個學生,計算所有沒有及格(低於60分)的學生。你只需要按照下面格式替換1.4中的程式:
stable_sort(vect.begin(), vect.end(),less<student>());
替換為:
student exam("pass", 60);
stable_partition(vect.begin(), vect.end(), bind2nd(less<student>(), exam));
使用的是stable_partition, 元素之間的相對次序是沒有變.
2 Sort 和容器
STL中標準容器主要vector, list, deque, string, set, multiset, map, multimay, 其中set, multiset, map, multimap都是以樹結構的方式儲存其元素詳細內容請參看:學習STL map, STL set之資料結構基礎. 因此在這些容器中,元素一直是有序的。這些容器的迭代器型別並不是隨機型迭代器,因此,上述的那些排序函式,對於這些容器是不可用的。上述sort函式對於下列容器是可用的:
- vector
- string
- deque
對於list容器,list自帶一個sort成員函式list::sort(). 它和演算法函式中的sort差不多,但是list::sort是基於指標的方式排序,也就是說,所有的資料移動和比較都是此用指標的方式實現,因此排序後的迭代器一直保持有效(vector中sort後的迭代器會失效).
3 選擇合適的排序函式
如果你以前有用過C語言中的qsort, 想知道qsort和他們的比較,那我告訴你,qsort和sort是一樣的,因為他們採用的都是快速排序。從效率上看,以下幾種sort演算法的是一個排序,效率由高到低(耗時由小變大):
- partion
- stable_partition
- nth_element
- partial_sort
- sort
- stable_sort