〇、前言
在刷leetcode,sort函数是一个比较常用的方法,在之前都没有系统总结过用法,导致每次写代码都不够顺畅,拉低了解题速度,因此决定小结一下sort() 函数的用法,做下笔记,加深自己的印象
一、sort函数定义(2种)
- 包含于#include < algorithm >头文件中,使用之前记得加上
// version1,对 [first, last) 区域内的元素进行默认的升序排序,即使用 < 运算符
template <class RandomAccessIterator>
void sort ( RandomAccessIterator first, RandomAccessIterator last );
// version2,按照指定的 mycomp 排序规则,对 [first, last) 区域内的元素进行排序
// 在这里,mycomp 可以是 C++ STL 标准库提供的排序规则(比如 std::greater<T>),也可以是自定义的排序规则。
template <class RandomAccessIterator, class Compare>
void sort ( RandomAccessIterator first, RandomAccessIterator last, CompareFunction mycomp );
二、 sort函数的特点
- 适用对象:支持随机访问的容器,即只支持序列式容器(vector, deque, array)。
- 排序范围:左闭右开,即 [ )。
- 在第 2 种版本定义中,comp 可以是 C++ STL 标准库提供的排序规则(比如 std::greater< T >),也可以是自定义的排序规则。
- 关于自定义的参数comp的设计原则:comp带两个同类型的参数,如果第一个参数排在第二个参数前面,返回true,否则返回false。
- 返回值:无,因为它直接通过迭代器(指针)改变容器。
- 默认进行升序排序。
- 不稳定的排序:不能保证相同元素的相对顺序不变,sort() 函数是基于快速排序实现的。stable_sort()才是稳定的。
三、调用sort()函数需满足的条件
- 容器支持的迭代器类型必须为随机访问迭代器,即vector、deque、array。
- sort() 函数受到底层实现方式的限制,它仅适用于普通数组和部分类型的容器。
- 如果对容器中指定区域的元素做默认升序排序,则元素类型必须支持<小于运算符;同样,如果选用标准库提供的其它排序规则,元素类型也必须支持该规则底层实现所用的比较运算符;
- sort() 函数在实现排序时,需要交换容器中元素的存储位置。这种情况下,如果容器中存储的是自定义的类对象,则该类的内部必须提供移动构造函数和移动赋值运算符。
四、标准库提供的其他排序规则
- less(小于)
- greater(大于)
- equal_to(等于)
- not_equal_to(不相等)
- less_equal(小于等于)
- greater_equal(大于等于)
- 注意:这些均是函数对象 / 仿函数(function object),传入的时候注意使用的格式,要加模版参数(由于是模板类)和后面的(),例如:less< int >()
五、自定义排序规则
- sort函数传入的自定义比较规则 mycmp 可以是两种类型
函数指针
函数对象 or 仿函数
函数对象(Function Object),是重载了operator()函数的类(或结构体)实例化出来的对象,使用起来像函数,又叫仿函数。
5.1 利用普通函数实现自定义排序
- sort中的比较函数compare要声明为静态成员函数或全局函数,不能作为普通成员函数,即不可以写在类中;如果写在类中,也需要加static关键字,否则会报错。
原因:1)非静态成员函数只能由具体的类对象来调用;2)std::sort这类函数是全局的,因此无法在sort中调用非静态成员函数。
使用指导:静态成员函数或者全局函数是不依赖于具体对象的,可以独立访问,无须创建任何对象实例就可以访问。同时,静态成员函数不可以调用类的非静态成员。
简言之:
- 类内:使用static关键字修饰;
- 类外:不用static关键字,直接在类外定义,使之成为全局函数。
5.1.1 类内定义函数–static关键字
- 注意点:在第9行中,因为不能改变引用形参的值,因此将其声明为常量引用(使用const修饰),终于不再为这点烦恼hhhh
#include <algorithm>
using namespace std;
class Solution {
public:
// 自定义排序准则,类内
// 假设待比较的元素类型是vector<int>
// 因为这里不能改变引用形参的值,因此将其声明为常量引用,终于不再为这点烦恼hhhh
static bool cmp(const vector<int>& a, const vector<int>& b) {
return a[0] > b[0] || (a[0] == b[0] && a[1] < b[1]);
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
// 调用排序准则
sort(people.begin(), people.end(), cmp);
vector<vector<int>> ans;
for (const vector<int>& person: people) {
ans.insert(ans.begin() + person[1], person);
}
return ans;
}
};
5.1.2 类外定义函数–全局
#include <algorithm>
using namespace std;
// 类外定义全局的排序准则
bool cmp(const vector<int>& a, const vector<int>& b) {
return a[0] > b[0] || (a[0] == b[0] && a[1] < b[1]);
}
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
// 调用排序准则
sort(people.begin(), people.end(), cmp);
vector<vector<int>> ans;
for (const vector<int>& person: people) {
ans.insert(ans.begin() + person[1], person);
}
return ans;
}
};
5.1.3 利用lambda表达式直接在sort函数中定义—这操作很秀!!
使用lambda表达式实现
[] 表示需要作用域的哪些参数传入,这里为空,表示不需要传入任何参数
() 表示形参列表
{} 表示函数体
但是只适合于一次使用的情况
#include <algorithm>
using namespace std;
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
// 直接在sort函数内部定义并调用排序准则
sort(people.begin(), people.end(), [](const vector<int>& u, const vector<int>& v) {
return u[0] > v[0] || (u[0] == v[0] && u[1] < v[1]);
});
vector<vector<int>> ans;
for (const vector<int>& person: people) {
ans.insert(ans.begin() + person[1], person);
}
return ans;
}
};
5.2 利用仿函数 or 函数对象实现自定义排序
#include <algorithm>
using namespace std;
// 以仿函数的方式实现自定义的排序规则
// 定义函数对象类
class Cmp {
public:
// 重载 () 运算符
bool operator() (const vector<int>& a, const vector<int>& b) {
return a[0] > b[0] || (a[0] == b[0] && a[1] < b[1]);
}
}mycmp; // 定义时,自动创建一个类对象,后面我们会解释这样定义的妙处!
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
// 调用排序准则
// sort(people.begin(), people.end(), Cmp); // 报错(mycopm does not refer to a value),需要添加括号
sort(people.begin(), people.end(), Cmp()) // 正确,生成一个临时的函数对象/仿函数
sort(people.begin(), people.end(), mycmp) // 正确,直接省略了创建临时仿函数的步骤
vector<vector<int>> ans;
for (const vector<int>& person: people) {
ans.insert(ans.begin() + person[1], person);
}
return ans;
}
};
此外,C++ 中的 struct 和 class 非常类似,struct也可以包含成员变量和成员函数。因此上面程序中,函数对象类 cmp 也可以使用 struct 关键字创建:
struct Cmp {
public:
// 重载 () 运算符
bool operator() (const vector<int>& a, const vector<int>& b) {
return a[0] > b[0] || (a[0] == b[0] && a[1] < b[1]);
}
}mycmp;
在定义函数对象类时,也可以将其定义为模板类
注意,此方式必须保证 T 类型元素可以直接使用关系运算符(比如这里的 < 运算符)做比较。
调用时要实例化模板,指定具体的类型
template <typename T>
class Cmp {
public:
// 重载 () 运算符
bool operator() (const T& a, const T& b) {
return a[0] > b[0] || (a[0] == b[0] && a[1] < b[1]);
}
}mycmp;
// 调用部分的代码
myComp<vector<int>> my;
sort(people.begin(), people.end(), my);
波折
不知道为什么,合并起来简化写进去就是不行,老是出错
sort(people.begin(), people.end(), myComp()<vector<int>>);
// Line 29: Char 44: error: no viable constructor or deduction guide for deduction of template arguments of 'myComp'
// sort(people.begin(), people.end(), myComp()<vector<int>>);
// ^
// Line 7: Char 7: note: candidate template ignored: couldn't infer template argument 'T'
// class myComp {
// ^
// Line 7: Char 7: note: candidate function template not viable: requires 1 argument, but 0 were provided
报错信息分析:缺少类模板的实例化,类模板要实例化才能调用,但是我已经有了啊(痛苦面具)
// 下面这一句也是怎么试验都是不行,只有分开实例化才能成功编译
sort(people.begin(), people.end(), myComp<vector<int>>);
// 编译报错信息:Line 29: Char 63: error: expected '(' for function-style cast or type construction
// sort(people.begin(), people.end(), myComp<vector<int>>);
// ~~~~~~~~~~~~~~~~~~~^
// 1 error generated.
报错信息分析:类模板的实例化有是有了,但是有提示缺少类的实例化的括号
柳暗花明
也不知道是怎么回事,脑中突然灵光一现,会不会是类实例化和模板实例化的顺序不对,果然,一试便知!!!(终于解决)
终于知道为什么了会出错了:类的实例化和模板实例化的顺序很重要,类模板的实例化先于类的实例化才正确!!!!
sort(people.begin(), people.end(), myComp<vector<int>>());
5.3 重载关系运算符实现自定义排序(三种方式)
当关联式容器中存储的数据类型为自定义的结构体变量或者类对象时,通过对现有排序规则中所用的关系运算符进行重载,也能实现自定义排序规则的目的。
对于这种方法,一般应用于自定义数据类型,因为sort排序函数是应用基本运算符实现功能的。
注意注意!!针对自定义结构类型的,也就是结构体struct等
- 全局
- 类内成员函数
- 友元函数
bool operator <运算符符号>(<参数表>)
{
// 比较逻辑
<函数体>
}
具体用法详见:
C++ STL关联式容器自定义排序规则(2种方法)
C++函数对象详解(附带实例)
STL priority_queue自定义排序实现方法详解