C++之因此獲得這麼多人的喜歡,是由於它既具備面向對象的概念,又保持了C語言高效的特色。STL 排序算法一樣須要保持高效。所以,對於不一樣的需求,STL提供的不一樣的函數,不一樣的函數,實現的算法又不盡相同。php
全部的sort算法的參數都須要輸入一個範圍,[begin, end)。這裏使用的迭代器(iterator)都需是隨機迭代器(RadomAccessIterator), 也就是說能夠隨機訪問的迭代器,如:it+n什麼的。(partition 和stable_partition 除外)html
若是你須要本身定義比較函數,你能夠把你定義好的仿函數(functor)做爲參數傳入。每種算法都支持傳入比較函數。如下是全部STL sort算法函數的名字列表:ios
函數名 | 功能描述 |
---|---|
sort | 對給定區間全部元素進行排序 |
stable_sort | 對給定區間全部元素進行穩定排序 |
partial_sort | 對給定區間全部元素部分排序 |
partial_sort_copy | 對給定區間複製並排序 |
nth_element | 找出給定區間的某個位置對應的元素 |
is_sorted | 判斷一個區間是否已經排好序 |
partition | 使得符合某個條件的元素放在前面 |
stable_partition | 相對穩定的使得符合某個條件的元素放在前面 |
其中nth_element 是最不易理解的,實際上,這個函數是用來找出第幾個。例如:找出包含7個元素的數組中排在中間那個數的值,此時,我可能不關心前面,也不關心後面,我只關心排在第四位的元素值是多少。web
當你須要按照某種特定方式進行排序時,你須要給sort指定比較函數,不然程序會自動提供給你一個比較函數。面試
vector < int > vect; //... sort(vect.begin(), vect.end()); //此時至關於調用 sort(vect.begin(), vect.end(), less<int>() );
上述例子中系統本身爲sort提供了less仿函數。在STL中還提供了其餘仿函數,如下是仿函數列表:算法
名稱 | 功能描述 |
---|---|
equal_to | 相等 |
not_equal_to | 不相等 |
less | 小於 |
greater | 大於 |
less_equal | 小於等於 |
greater_equal | 大於等於 |
須要注意的是,這些函數不是都能適用於你的sort算法,如何選擇,決定於你的應用。另外,不能直接寫入仿函數的名字,而是要寫其重載的()函數:數組
less<int>() greater<int>()
當你的容器中元素時一些標準類型(int float char)或者string時,你能夠直接使用這些函數模板。但若是你時本身定義的類型或者你須要按照其餘方式排序,你能夠有兩種方法來達到效果:一種是本身寫比較函數。另外一種是重載類型的'<'操做賦。數據結構
#include <iostream> #include <algorithm> #include <functional> #include <vector> 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() { vector< myclass > vect; for(int i = 0 ; i < 10 ; i ++){ myclass my(10-i, i*3); vect.push_back(my); } for(int i = 0 ; i < vect.size(); i ++) cout<<"("<<vect[i].first<<","<<vect[i].second<<")\n"; sort(vect.begin(), vect.end()); cout<<"after sorted by first:"<<endl; for(int i = 0 ; i < vect.size(); i ++) cout<<"("<<vect[i].first<<","<<vect[i].second<<")\n"; cout<<"after sorted by second:"<<endl; sort(vect.begin(), vect.end(), less_second); for(int i = 0 ; i < vect.size(); i ++) cout<<"("<<vect[i].first<<","<<vect[i].second<<")\n"; return 0 ; }
知道其輸出結果是什麼了吧:app
(10,0) (9,3) (8,6) (7,9) (6,12) (5,15) (4,18) (3,21) (2,24) (1,27) after sorted by first: (1,27) (2,24) (3,21) (4,18) (5,15) (6,12) (7,9) (8,6) (9,3) (10,0) after sorted by second: (10,0) (9,3) (8,6) (7,9) (6,12) (5,15) (4,18) (3,21) (2,24) (1,27)
你發現有sort和stable_sort,還有 partition 和stable_partition, 感到奇怪吧。其中的區別是,帶有stable的函數可保證相等元素的本來相對次序在排序後保持不變。或許你會問,既然相等,你還管他相對位置呢,也分不清楚誰是誰了?這裏須要弄清楚一個問題,這裏的相等,是指你提供的函數表示兩個元素相等,並不必定是一摸同樣的元素。less
例如,若是你寫一個比較函數:
bool less_len(const string &str1, const string &str2) { return str1.length() < str2.length(); }
此時,"apple" 和 "winter" 就是相等的,若是在"apple" 出如今"winter"前面,用帶stable的函數排序後,他們的次序必定不變,若是你使用的是不帶"stable"的函數排序,那麼排序完後,"Winter"有可能在"apple"的前面。
全排序即把所給定範圍全部的元素按照大小關係順序排列。用於全排序的函數有
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 ; }
其輸出是:
------before sort... Tom: 74 Jimy: 56 Mary: 92 Jessy: 85 Jone: 56 Bush: 52 Winter: 77 Andyer: 63 Lily: 76 Maryia: 89 -----after sort .... Bush: 52 Jimy: 56 Jone: 56 Andyer: 63 Tom: 74 Lily: 76 Winter: 77 Jessy: 85 Maryia: 89 Mary: 92
sort採用的是成熟的"快速排序算法"(目前大部分STL版本已經不是採用簡單的快速排序,而是結合內插排序算法)。注1,能夠保證很好的平均性能、複雜度爲n*log(n),因爲單純的快速排序在理論上有最差的狀況,性能很低,其算法複雜度爲n*n,但目前大部分的STL版本都已經在這方面作了優化,所以你能夠放心使用。stable_sort採用的是"歸併排序",分派足夠內存是,其算法複雜度爲n*log(n), 不然其複雜度爲n*log(n)*log(n),其優勢是會保持相等元素之間的相對位置在排序先後保持一致。
局部排序實際上是爲了減小沒必要要的操做而提供的排序方式。其函數原型爲:
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>());
輸出結果爲:
------before sort... Tom: 74 Jimy: 56 Mary: 92 Jessy: 85 Jone: 56 Bush: 52 Winter: 77 Andyer: 63 Lily: 76 Maryia: 89 -----after sort .... Bush: 52 Jimy: 56 Jone: 56 Andyer: 63 Tom: 74 Mary: 92 Jessy: 85 Winter: 77 Lily: 76 Maryia: 89
這樣的好處知道了嗎?當數據量小的時候可能看不出優點,若是是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的組合。
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>());
運行結果爲:
------before sort... Tom: 74 Jimy: 56 Mary: 92 Jessy: 85 Jone: 56 Bush: 52 Winter: 77 Andyer: 63 Lily: 76 Maryia: 89 -----after sort .... Jone: 56 Bush: 52 Jimy: 56 Andyer: 63 Jessy: 85 Mary: 92 Winter: 77 Tom: 74 Lily: 76 Maryia: 89
第四個是誰?Andyer,這個倒黴的傢伙。爲何是begin()+3而不是+4? 我開始寫這篇文章的時候也沒有在乎,後來在ilovevc 的提醒下,發現了這個問題。begin()是第一個,begin()+1是第二個,... begin()+3固然就是第四個了。
好像這兩個函數並非用來排序的,'分類'算法,會更加貼切一些。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));
其輸出結果爲:
------before sort... Tom: 74 Jimy: 56 Mary: 92 Jessy: 85 Jone: 56 Bush: 52 Winter: 77 Andyer: 63 Lily: 76 Maryia: 89 -----after sort .... Jimy: 56 Jone: 56 Bush: 52 Tom: 74 Mary: 92 Jessy: 85 Winter: 77 Andyer: 63 Lily: 76 Maryia: 89
看見了嗎,Jimy,Jone, Bush(難怪說美國總統比較笨 )都沒有及格。並且使用的是stable_partition, 元素之間的相對次序是沒有變.
STL中標準容器主要vector, list, deque, string, set, multiset, map, multimay, 其中set, multiset, map, multimap都是以樹結構的方式存儲其元素詳細內容請參看:學習STL map, STL set之數據結構基礎. 所以在這些容器中,元素一直是有序的。
這些容器的迭代器類型並非隨機型迭代器,所以,上述的那些排序函數,對於這些容器是不可用的。上述sort函數對於下列容器是可用的:
若是你本身定義的容器也支持隨機型迭代器,那麼使用排序算法是沒有任何問題的。
對於list容器,list自帶一個sort成員函數list::sort(). 它和算法函數中的sort差很少,可是list::sort是基於指針的方式排序,也就是說,全部的數據移動和比較都是此用指針的方式實現,所以排序後的迭代器一直保持有效(vector中sort後的迭代器會失效).
爲何要選擇合適的排序函數?可能你並不關心效率(這裏的效率指的是程序運行時間), 或者說你的數據量很小, 所以你以爲隨便用哪一個函數都可有可無。
其實否則,即便你不關心效率,若是你選擇合適的排序函數,你會讓你的代碼更容易讓人明白,你會讓你的代碼更有擴充性,逐漸養成一個良好的習慣,很重要吧 。
若是你之前有用過C語言中的qsort, 想知道qsort和他們的比較,那我告訴你,qsort和sort是同樣的,由於他們採用的都是快速排序。從效率上看,如下幾種sort算法的是一個排序,效率由高到低(耗時由小變大):
記得,之前翻譯過Effective STL的文章,其中對如何選擇排序函數總結的很好:
總之記住一句話: 若是你想節約時間,不要走彎路, 也不要走多餘的路!
討論技術就像個無底洞,常常容易由一點能夠引伸另外無數個技術點。所以須要從全局的角度來觀察問題,就像觀察STL中的sort算法同樣。其實在STL還有make_heap, sort_heap等排序算法。本文章沒有提到。本文以實例的方式,解釋了STL中排序算法的特性,並總結了在實際狀況下應如何選擇合適的算法。
假設有這樣一個擁有3個操做的隊列:
最左邊的數字表明元素在棧中的位置。當3入棧時,它就是最小元素,所以在輔助棧中與它位置相同的地方,保存它以前的最大位置(-1或0皆可),4入棧的時候,考慮到3和help[0]相等,所以4對應的輔助棧元素是0,2入棧時,考慮到4大於stack[help[1]],所以2對應的輔助棧元素仍然是0,1入棧時,因爲2比stack[help[2]]小,所以1對應的輔助棧元素是2的位置,即2.依次類推。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
template
<
typename
T>
class
MinStack {
private
:
T *stack;
T *min;
size_t
top;
T MinValue;
size_t
MaxElement;
public
:
MinStack();
~MinStack();
void
push(T t);
void
pop();
T GetTop();
T GetMin();
};
template
<
typename
T> MinStack<T>::MinStack()
{
MaxElement = 20;
stack =
new
T[MaxElement];
min =
new
T[MaxElement];
top = -1;
}
template
<
typename
T> MinStack<T>::~MinStack()
{
delete
[] stack;
delete
[] min;
}
template
<
typename
T>
void
MinStack<T>::push(T t)
{
if
(top == MaxElement)
{
cout<<
"It's full"
;
return
;
}
if
(top == -1)
{
stack[++top] = t;
min[top] = 0;
MinValue = t;
}
else
{
stack[++top] = t;
min[top] = stack[top-1]>stack[min[top-1]]?min[top-1]:top-1;
MinValue = t>stack[min[top]]?stack[min[top]]:t;
}
}
template
<
typename
T>
void
MinStack<T>::pop()
{
if
(top==-1)
{
cout<<
"It's empty"
;
return
;
}
if
(top == 0)
{
--top;
}
else
{
--top;
MinValue = stack[min[top+1]];
}
}
template
<
typename
T> T MinStack<T>::GetTop()
{
if
(top==-1)
{
cout<<
"It's empty stack"
;
exit
(0);
}
return
stack[top];
}
template
<
typename
T> T MinStack<T>::GetMin()
{
if
(top==-1)
{
cout<<
"It's empty stack"
;
exit
(0);
}
return
MinValue;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
template
<
typename
T>
class
Stack
{
public
:
T *s;
size_t
top;
size_t
Max;
Stack();
~Stack();
void
push(T t);
void
pop();
T GetTop();
};
template
<
typename
T>
class
Queue
{
private
:
Stack<T> En;
Stack<T> De;
public
:
void
EnQueue(T t);
T DeQueue();
};
template
<
typename
T> Stack<T>::Stack()
{
Max = 20;
s =
new
T[Max];
top = -1;
}
template
<
typename
T> Stack<T>::~Stack()
{
delete
[] s;
}
template
<
typename
T>
void
Stack<T>::push(T t)
{
if
(top==Max)
{
cout<<
"It's full stack"
;
return
;
}
s[++top] = t;
}
template
<
typename
T>
void
Stack<T>::pop()
{
if
(top == -1)
{
cout<<
"It's empty stack"
;
return
;
}
--top;
}
template
<
typename
T> T Stack<T>::GetTop()
{
return
s[top];
}
template
<
typename
T>
void
Queue<T>::EnQueue(T t)
{
En.push(t);
}
template
<
typename
T> T Queue<T>::DeQueue()
{
if
(De.top==-1)
{
while
(En.top+1>0)
{
De.push(En.GetTop());
En.pop();
}
}
if
(De.top==-1)
{
cout<<
"It's empty queue"
;
}
T temp = De.GetTop();
De.pop();
return
temp;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
template
<
typename
T>
class
Queue
{
private
:
MinStack<T> En;
MinStack<T> De;
public
:
void
EnQueue(T t);
T DeQueue();
T MinValue();
};
template
<
typename
T> T Queue<T>::MinValue()
{
if
(De.top==-1&&En.top==-1)
{
cout<<
"It's a empty queue"
;
exit
(0);
}
else
if
(De.top == -1)
{
return
En.MinValue;
}
else
if
(En.top == -1)
{
return
De.MinValue;
}
else
{
return
En.MinValue<De.MinValue?En.MinValue:De.MinValue;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
//問題:設計一個隊列可以在O(1)時間內取得隊列的最大值
#include <stdio.h>
#include <queue>
#include <stack>
//O(1)的速度取出棧中的最大值
template
<
typename
T>
class
MaxStack
{
public
:
//入棧
void
Push(
const
T& value)
{
data_.push(value);
if
(max_element_.empty())
{
max_element_.push(value);
}
else
if
(value >= max_element_.top())
{
max_element_.push(value);
}
}
//返回棧頂元素
T Top()
{
return
data_.top();
}
//出棧
void
Pop()
{
if
(data_.top() == max_element_.top())
{
max_element_.pop();
}
data_.pop();
}
//判斷是否爲空
bool
Empty()
{
return
data_.empty();
}
//取出最大值
T Max()
{
if
(!max_element_.empty())
{
return
max_element_.top();
}
}
private
:
std::stack<T> data_;
std::stack<T> max_element_;
};
//O(1)的速度取出隊列中的最大值
template
<
typename
T>
class
MaxQueue
{
public
:
//入隊操做!!!!
void
Push(
const
T& value)
{
push_stack_.Push(value);
}
//取隊首元素
T Front()
{
if
(pop_stack_.empty())
{
while
(!push_stack_.Empty())
{
pop_stack_.Push(push_stack_.Top());
push_stack_.Pop();
}
}
return
pop_stack_.Top();
}
//出隊操做!!!!
void
Pop()
{
if
(pop_stack_.Empty())
{
while
(!push_stack_.Empty())
{
pop_stack_.Push(push_stack_.Top());
push_stack_.Pop();
}
}
pop_stack_.Pop();
}
//判空操做!!!!!
bool
IsEmpty()
{
return
push_stack_.Empty() && pop_stack_.Empty();
}
//取出最大值
T Max()
{
if
(!push_stack_.Empty() && !pop_stack_.Empty())
{
return
push_stack_.Max() > pop_stack_.Max() ? push_stack_.Max() : pop_stack_.Max();
}
else
if
(push_stack_.Empty() && !pop_stack_.Empty())
{
return
pop_stack_.Max();
}
else
if
(!push_stack_.Empty() && pop_stack_.Empty())
{
return
push_stack_.Max();
}
else
{
throw
RUNTIME_ERROR;
}
}
private
:
MaxStack<T> push_stack_;
MaxStack<T> pop_stack_;
};
//測試用例
int
main(
int
argc,
char
** argv)
{
MaxQueue<
int
> max_queue;
max_queue.Push(1);
max_queue.Push(2);
max_queue.Push(6);
max_queue.Push(4);
max_queue.Push(5);
max_queue.Push(2);
printf
(
"max %d\n"
, max_queue.Max());
max_queue.Pop();
printf
(
"max %d\n"
, max_queue.Max());
max_queue.Pop();
printf
(
"max %d\n"
, max_queue.Max());
max_queue.Pop();
printf
(
"max %d\n"
, max_queue.Max());
max_queue.Pop();
printf
(
"max %d\n"
, max_queue.Max());
max_queue.Pop();
printf
(
"max %d\n"
, max_queue.Max());
}
|