因为是“速记”,难免会有不完善的地方。这篇笔记咱日后应该还会进行补充。

关于sort的比较函数

void sort( RandomIt first, RandomIt last, Compare comp );

STL的algorithm库中的sort函数,可以接受一个comp函数作为第三个参数,用来指定排序的规则。

自定义sort比较函数

comp(a,b)函数的返回值是一个bool值,当返回值为true不改变元素顺序,反之则需要调换元素。

可以把其中的a看作序列中前一个位置的元素b看作后一个位置的元素:

  • 如果a < b的时候comp(a,b)=true,那么a就会被放在b前面,排序呈升序。
  • 如果a < b的时候comp(a,b)=false,那么b就会被放在a前面,排序呈降序。

也就是说如果a < b时有comp(a,b)=true成立,就是期待程序把小元素放在前面

#include <iostream>
#include <algorithm> using namespace std; bool ascending(int a, int b) // 升序排序,让小元素放在前面
{
// 把a看作序列中前一个元素,b看作后一个元素
return a < b; // 如果返回true就说明a<b成立,a是较小的元素
}; bool descending(int a, int b) // 降序排序,让大元素放在前面
{
// 把a看作序列中前一个元素,b看作后一个元素
return a > b; // 如果返回true就说明a>b成立,a是较大的元素
}; int main()
{
int test[10] = {9, 4, 2, 5, 1, 7, 3, 6, 8, 10};
sort(test, test + 10, ascending);
for (int i = 0; i < 10; i++)
cout << test[i] << " ";
// Ouput: 1 2 3 4 5 6 7 8 9 10
cout << "\n";
sort(test, test + 10, descending);
for (int i = 0; i < 10; i++)
cout << test[i] << " ";
// Ouput: 10 9 8 7 6 5 4 3 2 1
return 0;
}

缺省sort比较函数

默认情况下,sort函数会使用<运算符作比较。(也就是默认升序排序)

这个时候如果要自定义排序规则,可以重载<运算符

#include <iostream>
#include <algorithm> using namespace std; struct Node
{
int data;
bool operator<(const Node &obj)
{
// a>b的时候才返回true, 期待a是较大的元素。
// 把较大的元素放在前面,降序排序
return data > obj.data;
}
}; int main()
{
Node test[10] = { {1}, {4}, {2}, {5}, {9}, {7}, {3}, {6}, {8}, {10} };
sort(test, test + 10);
for (int i = 0; i < 10; i++)
cout << test[i].data << " ";
// Output: 10 9 8 7 6 5 4 3 2 1
return 0;
}

自定义容器内元素排序

priority_queuemapset这些元素有序的容器都可以自定义比较规则,常用的有以下两种途径:

  1. 自定义比较器类(Comparator Class)

  2. 重载运算符(缺省比较器类)

自定义比较器类

在cppreference页面可以看到,这类元素有序的容器都有个默认的Compare比较器类。比如priority_queue的声明:

template<
class T,
class Container = std::vector<T>,
class Compare = std::less<typename Container::value_type>
> class priority_queue;

多观察几个容器的声明,能发现默认的Compare比较器类都是std::less,定义大概是这样:

template<class T> struct less
{
// 这里其实是重载了()运算符,因此其对象可以像函数一样被调用
bool operator()(const T& lhs, const T& rhs) const
{
return lhs < rhs;
}
};

标准库中还有另一个比较器类std::greater,定义如下:

template<class T> struct greater
{
// 这里其实是重载了()运算符,因此其对象可以像函数一样被调用
bool operator()(const T& lhs, const T& rhs) const
{
return lhs > rhs;
}
};

数组排序的角度看,lhs就是序列中前一个位置的元素,rhs就是后一个位置的元素:

  1. std::less中,lhs < rhs成立的时候返回true,期待lhs是较小的元素,也就是前一个元素是较小的,因此是升序排序。

  2. std::greater中,lhs > rhs成立的时候才返回true,期待lhs是较大的元素,也就是前一个元素是较大的,因此是降序排序。

很显然,和sort函数的默认比较函数一样,有序容器都是默认升序排序(std:less)的。

因为重载了运算符(),我实际上可以把【比较器类】的对象作为比较函数传入sort方法:

#include <iostream>
#include <algorithm> using namespace std; int main()
{
int test[10] = {4, 1, 3, 7, 5, 8, 2, 9, 6, 10};
// 注意这里创建了greater对象,sort函数调用greater对象的()运算符重载方法
sort(test, test + 10, greater<int>());
for (int i = 0; i < 10; i++)
cout << test[i] << " ";
// Output: 10 9 8 7 6 5 4 3 2 1
return 0;
}

值得注意的是,这里首先调用的是greater类的默认构造方法,返回一个对象并传递给sort函数。sort函数内部调用对象(a,b)时调用的是对象的运算符()重载方法来进行比较。

这里重载了()运算符其实是构造了一个“伪函数”,也就是可以把类的对象作为函数来使用。

模仿greaterless模板类的定义,我们也可以自己定义比较器类:

#include <iostream>
#include <algorithm> using namespace std; struct Node
{
int data;
}; struct MyGreater // 自定义比较器类
{
// a是序列中前一个元素,b则是后一个元素
bool operator()(const Node &a, const Node &b) const
{
return a.data > b.data; // 前一个元素 > 后一个元素时才返回true
}
}; int main()
{
Node test[10] = { {4}, {1}, {3}, {7}, {5}, {8}, {2}, {9}, {6}, {10} };
sort(test, test + 10, MyGreater());
for (int i = 0; i < 10; i++)
cout << test[i].data << " ";
// Output: 10 9 8 7 6 5 4 3 2 1
return 0;
}

优先队列的比较器类

在这几种STL容器中,优先队列priority_queue的元素比较规则是略显“另类”的:

  1. 默认情况下(std::less类),优先队列中的元素出队后是呈降序排列的,即大的元素在队头,是一个大根堆

  2. 如果使用std::greater类,则优先队列中的元素出队后是呈升序排列的,即小的元素在队头,是一个小根堆

这里和之前的排序不同的地方就在于:比较方法中形参的意义不同

std::greater举例:

template<class T> struct greater
{
// 这里其实是重载了()运算符,因此在使用的时候可以像函数一样调用
bool operator()(const T& lhs, const T& rhs) const
{
return lhs > rhs;
}
};
  • sort函数、mapset容器中,lhs代表序列中前一个元素rhs代表序列中后一个元素

  • 而在priority_queue中,lhs代表新插入的节点rhs代表这个节点的父节点

    lhs>rhs时就是期望子节点大于父节点,即构成小根堆,因此堆顶元素总是堆中最小的,所以从优先队列中取出的元素是从小到大的,即升序排列。

    • 注:往堆中插入新节点时是插入在最后一个叶子节点的位置的。

重载运算符

sort函数中可以缺省排序函数。在创建容器对象时,我们也可以缺省比较器类。

在缺省比较器类的情况下,STL容器默认采用std::less模板类来进行比较:

  1. 默认升序排列。

  2. 对于优先队列来说,默认出队元素呈降序排列。

std::less类的重载方法中一样也是调用了对象的<运算符进行比较,因此我们也可以重载<运算符来实现自定义的比较规则。

#include <iostream>
#include <algorithm> using namespace std; struct Node
{
int data;
// std::less中调用了这里的<运算符重载方法
bool operator<(const Node &b) const
{
return data > b.data; // a>b时返回true,期待前一个元素更大,即降序排列
}
}; int main()
{
Node test[10] = { {4}, {1}, {3}, {7}, {5}, {8}, {2}, {9}, {6}, {10} };
sort(test, test + 10);
for (int i = 0; i < 10; i++)
cout << test[i].data << " ";
// Output: 10 9 8 7 6 5 4 3 2 1
return 0;
}

总结

  1. 把比较函数的形参(a,b)中的a看作序列中前一个位置的元素b看作序列中后一个位置的元素,方便理解。

  2. 无论是sort函数的比较函数comp(a,b)还是比较器类的重载方法operator()(a,b):

    • 返回true时,不会改变a和b的顺序
    • 而返回false时,会改变ab的顺序。

    比如在默认情况下实现升序排列,a在序列中的位置小于b且满足条件a<b时,返回true,不会改变a和b的顺序;而当a在序列中的位置大于b时则不满足条件,会改变a和b的顺序。

  3. 在缺省比较函数/比较器类的时候,可以活用待比较对象运算符重载方法。具体重载哪个运算符需要根据具体的实现来确定。

    比如容器默认采用比较器类std::less,其内部调用待比较对象<运算符。

  4. 优先队列容器priority_queue的比较方法的形参含义是不同的,重载方法operator()(a,b)中前一个元素a指的是新插入的元素,而后一个元素b指的是这个元素的父节点

    • 这里仍然是比较方法返回true时不会改变元素顺序。

    比如在默认情况下,维持的是大根堆的堆序性。若新插入的元素a的值小于父节点b的值,满足条件a<b,则返回true,不会改变a和b的位置;而当新插入的元素a的值大于父节点b的值,不满足条件,会改变a和b的位置。

参考文章

https://blog.csdn.net/sandalphon4869/article/details/105419706

【速记】C++ STL自定义排序的更多相关文章

  1. stl 自定义排序与删除重复元素

    转: STL—vector删除重复元素 STL提供了很多实用的算法,这里主要讲解sort和unique算法. 删除重复元素,首先将vector排序. sort( vecSrc.begin(), vec ...

  2. 【转】c++中Vector等STL容器的自定义排序

    如果要自己定义STL容器的元素类最好满足STL容器对元素的要求    必须要求:     1.Copy构造函数     2.赋值=操作符     3.能够销毁对象的析构函数    另外:     1. ...

  3. STL中vector的赋值,遍历,查找,删除,自定义排序——sort,push_back,find,erase

    今天学习网络编程,那个程序中利用了STL中的sort,push_back,erase,自己没有接触过,今天学习一下,写了一个简单的学习程序.编译环境是VC6.0         这个程序使用了vect ...

  4. [LeetCode] Custom Sort String 自定义排序的字符串

    S and T are strings composed of lowercase letters. In S, no letter occurs more than once. S was sort ...

  5. map的默认排序和自定义排序

    STL的容器map为我们处理有序key-value形式数据提供了非常大的便利,由于内部红黑树结构的存储,查找的时间复杂度为O(log2N). 一般而言,使用map的时候直接采取map<typen ...

  6. Java集合框架实现自定义排序

    Java集合框架针对不同的数据结构提供了多种排序的方法,虽然很多时候我们可以自己实现排序,比如数组等,但是灵活的使用JDK提供的排序方法,可以提高开发效率,而且通常JDK的实现要比自己造的轮子性能更优 ...

  7. DataTable自定义排序

    使用JQ DataTable 的时候,希望某列数据可以进行自定义排序,操作如下:(以中文排序和百分比排序为例) 1:定义排序类型: //百分率排序 jQuery.fn.dataTableExt.oSo ...

  8. 干货之UICollectionViewFlowLayout自定义排序和拖拽手势

    使用UICollectionView,需要使用UICollectionViewLayout控制UICollectionViewCell布局,虽然UICollectionViewLayout提供了高度自 ...

  9. DataGridView 绑定List集合后实现自定义排序

    这里只贴主要代码,dataList是已添加数据的全局变量,绑定数据源 datagridview1.DataSource = dataList,以下是核心代码. 实现点击列表头实现自定义排序 priva ...

  10. mysql如何用order by 自定义排序

    mysql如何用order by 自定义排序 id name roleId aaa bbb ccc ddd eee ,MySQL可以通过field()函数自定义排序,格式:field(value,st ...

随机推荐

  1. Vue-购物车实战

    computed 计算属性 正则 css部分 [v-cloak] { display : none ; } table{ border : lpx solid #e9e9e9 ; border- co ...

  2. [APIO2019] 路灯 题解

    LG5445 把询问 \(x,y\) 看作平面上的点 记当前时刻 \(t\),\(l\) 是与 \(i\) 连通的最左端,\(r\) 是与 \(i+1\) 连通的最右端,可以通过 set 维护断边找到 ...

  3. 【CMake系列】09-cmake install

    本节我们来学习,cmake的安装,将我们构建的目标安装到指定位置,为了模拟一般情况,实现了 可执行文件.静态库.动态库的安装,以及一般的 file 安装.代码的安装 本专栏的实践代码全部放在 gith ...

  4. Homebrew 卸载 Wireshark 报错

    我在使用 Homebrew 安装 Wireshark 的时候,Homebrew 要求我输入密码.此时我又不想安转 Wireshark 了,于是我没有输入密码并且按下了 Ctrl + C.后来,我又尝试 ...

  5. OpenCV开发笔记(八十):基于特征点匹配实现全景图片拼接

    前言   一个摄像头视野不大的时候,我们希望进行两个视野合并,这样让正视的视野增大,从而可以看到更广阔的标准视野.拼接的方法分为两条路,第一条路是Sticher类,第二条思路是特征点匹配.  本篇使用 ...

  6. 开源项目管理工具 Plane 安装和使用教程

    说到项目管理工具,很多人脑海中第一个蹦出来的可能就是 Jira 了.没错,Jira 确实很强大,但是...它也有点太强大了,既复杂又昂贵,而且目前也不再提供私有化部署版本了. 再说说飞书,作为国产之光 ...

  7. C# WinForm避免程序重复启动,限制程序只能运行一个实例【转】

    记录一下,原文:https://blog.csdn.net/xggbest/article/details/104231935 禁止多个进程运行,当重复运行时激活以前的进程 Program.cs: u ...

  8. php自定义函数访问其他地方定义的变量

    新捣鼓php,很多地方有.net的思维好难改过来. 在Connection/config.php 自定义了数据库连接字符串 然后在外部页面,自定义了一个function,对数据库进行操作 然后死活都给 ...

  9. Kubelet证书自动续签(为 kubelet 配置证书轮换)

    1.概述 Kubelet 使用证书进行 Kubernetes API 的认证. 默认情况下,这些证书的签发期限为一年,所以不需要太频繁地进行更新. Kubernetes 包含特性 Kubelet 证书 ...

  10. CSS – 实战 Font

    前言 这篇想整理一下在网页开发中, 字体是如果被处理的. 先看完: 平面设计 – 字体 CSS – Font / Text 属性 CSS – Font Family CSS – word-break, ...