标签:
C++之所以得到这么多人的喜欢,是因为它既具有面向对象的概念,又保持了C语言高效的特点。STL 排序算法同样需要保持高效。因此,对于不同的需求,STL提供的不同的函数,不同的函数,实现的算法又不尽相同。
所有的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 | 相对稳定的使得符合某个条件的元素放在前面 |
其中nth_element 是最不易理解的,实际上,这个函数是用来找出第几个。例如:找出包含7个元素的数组中排在中间那个数的值,此时,我可能不关心前面,也不关心后面,我只关心排在第四位的元素值是多少。
当你需要按照某种特定方式进行排序时,你需要给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 ; }
知道其输出结果是什么了吧:
(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的函数可保证相等元素的原本相对次序在排序后保持不变。或许你会问,既然相等,你还管他相对位置呢,也分不清楚谁是谁了?这里需要弄清楚一个问题,这里的相等,是指你提供的函数表示两个元素相等,并不一定是一摸一样的元素。
例如,如果你写一个比较函数:
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()); } |
标签:
原文地址:http://www.cnblogs.com/lijia0511/p/4936850.html