C++中利用sort进行排序
C++中利用sort进行排序
编写C++经常需要使用sort进行排序,有可能是简单的数组、数字vector或者是复杂一点的存放对象的vector。
C++为了满足用户的需求,在algorithm里面封装了sort泛型算法。所以使用时,必须#include < algorithm>
template <class RandomAccessIterator>
void sort (RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class Compare>
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
可以看见,sort原型分为两个,区别在于第一个函数有两个参数,第一个函数有三个参数。
其中两个函数都有的是 RandomAccessIterator 是随机访问迭代器,first是初始位置,last是末尾位置,默认使用迭代器引用的 operator < 进行排序。
第二个函数,前两个参数一样,也是用来说明从哪儿到哪儿排序。第三个参数是Compare,意思是使用comp这个“方法”对对象进行排序。comp可以是函数对象或者是函数指针,具体使用请看后面。
两个参数排序
使用两个参数这应该是最普遍也是最简单的情景,如果只有两个参数,默认使用 operator < 对数组排序,结果为升序。
对数组排序:
int arr[] = { 9,8,7,6,5,4,3,2,1 };
sort(arr, arr + 9);
需要注意的是,这里传入的是迭代器,所以要传入头指针和末尾指针(最后一个待排元素的后一个位置)
对vector排序:
vector<int> arr;
for (int i = 9; i > 0; i--)
arr.push_back(i);
sort(arr.begin(), arr.end());
这里直接传入vector的begin和end两个迭代器就对整个vector完成了排序。
对象排序
如果只使用两个参数的话,要对对象排序,那么只能依靠重载运算符来实现。而且必须重载的是 < 关系运算符。
class Test {
public:
int value;
Test() : value(0) {};
Test(int x) : value(x) {};
// 重载运算符
bool operator < (const Test& t) {
if (value < t.value)
return true;
return false;
}
};
...
vector<Test> arr;
for (int i = 9; i > 0; i--) {
arr.push_back(Test(i));
}
sort(arr.begin(), arr.end());
这样,就根据Test类中value的值来升序排对象的顺序了。
使用三个参数排序
那么有读者肯定会问,如果我想要降序排列,或者我有我自己的比大小规则,或者对于对象我不想重载运算符怎么办呢?
那么这里就要用到我们sort的第三个参数了,之前提到了,第三个参数是决定以什么“方法”进行排序的。可以传入函数对象和函数指针。那么这个参数如何填入,如何编写便成了最主要的问题。
使用自定义函数
假如我们还是存的之前那个类,唯一的区别是,我不再重载运算符了,那要怎么办呢?
class Test {
public:
int value;
Test() : value(0) {};
Test(int x) : value(x) {};
};
那么,我们可以自己自定义编写比较函数,来决定两个元素是如何比较的。
bool cmp_func(const Test& t1, const Test& t2) {
if (t1.value < t2.value)
return true;
return false;
}
vector<Test> arr;
for (int i = 9; i > 0; i--) {
arr.push_back(Test(i));
}
sort(arr.begin(), arr.end(), cmp_func);
如上,定义了一个cmp_func,直接将sort的第三个参数指定为cmp_func就可以对迭代器引用区的元素进行排序。例如这里可以实现根据Test中value的值来实现升序排列这些对象了。
当然,也可以通过修改比较函数来实现降序。
bool cmp_func(const Test& t1, const Test& t2) {
if (t1.value > t2.value)
return true;
return false;
}
使用函数对象
函数对象其实很简单,可以很好的代替函数指针,但它理解起来非常简单,重载了()运算符的对象就叫函数对象。之所以叫函数对象,是因为它的调用可以和函数看起来一样。
class Test {
public:
int value;
Test() : value(0) {};
Test(int x) : value(x) {};
// 重载运算符
bool operator () (const Test& t1, const Test& t2) {
if (t1.value < t2.value)
return true;
return false;
}
};
vector<Test> arr;
for (int i = 9; i > 0; i--) {
arr.push_back(Test(i));
}
sort(arr.begin(), arr.end(), Test());
这个原理其实就是第三个参数,临时创建了一个Test对象,假设这个对象为t。
那么代码就是 sort(arr.begin(), arr.end(), t)
然后会把比较的两个元素,通过函数对象的调用 t(t1, t2)来进行比较,所以他会正常工作。
sort排序利用的是 快排/堆排/插排,所以并不稳定,如果对稳定性有需求,请使用stable_sort()
究竟怎么比?
那么,其实读到这里,细心的读者会想问,我引入自定义函数和函数对象后,什么叫“大”?什么叫“小”?我要怎么得到理想的排列顺序?
例如,为什么降序就是那么自定义函数,返回值是true又发生了什么?。
实际调用sort时,第三个参数Compare comp对应的是一个函数对象或者函数的名字。comp对应的功能,是教sort怎么排序的,我们可以称之为“比较器”。
其实,前面提到了,sort算法比较元素,默认是通过 < 运算符进行的,达到的效果是它天然就是要升序排列。那我们可以理解为,它其实默认写了一个comp是这样的。
bool comp(const T& t1, const T& t2) {
if (t1.value < t2.value)
return true;
return false;
}
如果把传入的第一个元素比第二个元素小成立(返回值为true),那么会把第一个元素往前挪,第二个元素往后挪。
那么我们现在把小改为任意比较 @。如果第一个元素@第二个元素成立,那么会把第一个元素往前挪,第二个元素往后挪。
所以把@改成>,就实现了降序排列,因为如果第一个元素比第二个大,那么大的都往前挪,小的都往后挪,自然就是降序了。
学术一点来说,STL关联容器中的元素是从小到大排序的。使用关联容器时,可以用自定义的“比较器”来取代 < 运算符,以规定元素之间的大小关系。所以以下三者是等价的:
- x < y
- bool comp(x, y)的返回值为true
- y > x
也就是说,我如果从逻辑上遵循了这个规则,那么会达到升序的效果。
那么把小于改成任意关系运算符@,在自定义比较关系时 bool comp(x, y),以下三条是等价的
- x@y
- bool comp(x, y)的返回值为true
- y !@ x
(!@表示@的逆关系)
例如把这个@替换为 > ,如何规定自己的比较函数和函数对象就显而易见了。