C++:std::greater()、std::less()、自定义比较函数的规则


一、结论

1.排序和建堆的效果

  • 排序:
    less<T>变成升序(从左到右遍历下标时,数组元素是从小到大
    greater<T>变成降序(从左到右遍历下标时,数组元素是从大到小
  • 建堆:
    less<T>变成大顶堆(从上层到下层,堆元素是从大到小,同层之间随便)
    greater<T>变成小顶堆(从上层到下层,堆元素是从小到大,同层之间随便)

可以看到排序和建队时,lessgreater并不是直接对应汉语意思,不能统一。其实是真正的意思是两个要比较的元素,第一个元素是否比第二个元素更小less还是更大greater。

2.解释结论

  • 排序
    上面的例子应该就看懂了吧。less就是让前一个比后一个更小;greater就是让前一个比后一个更大。谁会是a,谁会是b,是按照排序算法的。

  • 建堆
    顶堆插入一个新元素时,就是插入到最后一个叶子。在这里插入图片描述
    然后这时候整理堆内元素让堆重新满足大小顶堆。关键让新插入的结点和它的父结点进行比较,comp(新插入,它的父结点)
    大顶堆就是让父比子大,即符合less让新插入的比父结点更小;
    小顶堆就是父比子小,即符合greater让新插入的比父结点更大。

二、解析

1.比较规则:strict weak ordering

std::greater()std::less()、自定义比较函数,这些都其实是用作比较的,要遵从c++制定的比较规则。
在这里插入图片描述
需要满足三种特性要求,否则使用中会报错:

  • 反自反性:false
  • true的互斥性:truefalse(但不要求false则怎么样)
  • 传递性:truetruetrue

2.less和greater其实是什么

比如less

template <class T> struct less {
  bool operator() (const T& x, const T& y) const {return x<y;}
  typedef T first_argument_type;
  typedef T second_argument_type;
  typedef bool result_type;
};

可以看到关键就是bool operator()return x<y;

  • bool operator():要的就是这个返回值bool决定比较是否要交换。这个结果用在排序和建堆中就表示是否要交换。
  • return x<y;:可以看到其实就是使用<之类的操作符重载,这就是怎么排序的规则
    PS:但这产生了限制,基本的元素int之类的,自然可以直接比较;但复杂类型如自定义一个类,里面有多个数据,我们就还得定义重载操作符比较,要不然编译器不知道该比较什么。

3.bool返回值和比较操作符

(1)规则

bool comp(a, b)意思是:返回的值指示作为第一个参数传递的元素是否被视为在其定义的特定严格弱排序中位于第二个参数之前。

  • 返回true:表示ab(ab前)
  • 返回false:表示ba(ab`后)
op1 op 22 op 12 op 2结果
<
less
true,ab,不交换false,ba,交换false,ba,交换数组升序、大顶堆
>
greater
false,ba,交换true,ab,不交换false,ba,交换数组降序、小顶堆
<=true,ab,不交换false,ba,交换true,ab,不交换代价更小的数组升序、大顶堆
>=false,ba,交换true,ab,不交换true,ab,不交换代价更小的数组降序、小顶堆
==false,ba,交换false,ba,交换true,ab,不交换意义不明
!=true,ab,不交换true,ab,不交换false,ba,交换意义不明

(2)并不是想当然的位置交换

comp(a, b)虽然会交换ab,但你不能想当然地认为位置就该怎么样,到底数组中谁会是a,谁会是b这要看调用的算法的

比如,[a < b][6 < 1] : 0,其实是算法调用时a是6,b是1,而非看到数组中原来的顺序就想当然的a是1,b是6。

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

// 重写排序方法
// 常引用const T &xxx
bool comp(const int &a, const int &b)
{
    printf("[a < b][%d < %d] : %d\n", a, b , a<b);
    return a < b;
}

int main()
{
    vector<int> v = {2, 3, 1, 6, 2, 5, 4};
    sort(v.begin(), v.end(), comp);
    for (int i = 0; i < v.size(); i++)
    {
        cout << v[i] << " ";
    }
    cout << endl;
    return 0;
}
/*
[a < b][3 < 2] : 0
[a < b][3 < 2] : 0
[a < b][1 < 2] : 1
[a < b][6 < 1] : 0
[a < b][6 < 3] : 0
[a < b][2 < 1] : 0
[a < b][2 < 6] : 1
[a < b][2 < 3] : 1
[a < b][2 < 2] : 0
[a < b][5 < 1] : 0
[a < b][5 < 6] : 1
[a < b][5 < 3] : 0
[a < b][4 < 1] : 0
[a < b][4 < 6] : 1
[a < b][4 < 5] : 1
[a < b][4 < 3] : 0
1 2 2 3 4 5 6
*/

(3)<和<=代价证明

大致测试了一下,好像0的次数(则要交换的次数)更少,应该<=<更好吧。
同理>=>

PS:还没有证明和具体的排序算法是否有关系,应该没吧。o( ̄▽ ̄)o

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

// 重写排序方法
// 常引用const T &xxx
bool comp(const int &a, const int &b)
{
    printf("[a <= b][%d <= %d] : %d\n", a, b, a <= b);
    return a <= b;
}

int main()
{
    vector<int> v = {2, 3, 1, 6, 2, 5, 4};
    sort(v.begin(), v.end(), comp);
    for (int i = 0; i < v.size(); i++)
    {
        cout << v[i] << " ";
    }
    cout << endl;
    return 0;
}
/*
[a <= b][3 <= 2] : 0
[a <= b][3 <= 2] : 0
[a <= b][1 <= 2] : 1
[a <= b][6 <= 1] : 0
[a <= b][6 <= 3] : 0
[a <= b][2 <= 1] : 0
[a <= b][2 <= 6] : 1
[a <= b][2 <= 3] : 1
[a <= b][2 <= 2] : 1
[a <= b][2 <= 1] : 0
[a <= b][5 <= 1] : 0
[a <= b][5 <= 6] : 1
[a <= b][5 <= 3] : 0
[a <= b][4 <= 1] : 0
[a <= b][4 <= 6] : 1
[a <= b][4 <= 5] : 1
[a <= b][4 <= 3] : 0
1 2 2 3 4 5 6
*/

三、自定义

符合两个条件:

  • bool:返回值bool
  • return x<y;:重载<之类的操作符,并且要决定比较什么元素。
  • PS:建议还要常引用,保险,禁止发生修改要比较的元素可能。

1.数组

  • 函数:使用时不加括号,加了报错
  • 类的对象:注意,排序时的类必须使用类的对象才对,直接使用类报错。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

// 重写排序函数
bool cmpfunc(const int &a, const int &b)
{
    return a < b;
    // < 升序; > 降序
}

// 模仿less、greater构建类
struct cmpClass
{
    bool operator()(const int &i, const int &j)
    {
        return (i < j);
    }
}cmpClassObject;		// 注意,排序时的类必须使用类的对象才对,使用类报错。

int main()
{
	// 使用函数
    vector<int> v1 = {2, 3, 1, 6, 2, 5, 4};
    // 使用时不加括号,加了报错
    sort(v1.begin(), v1.end(), cmpfunc);
    for (int i = 0; i < v1.size(); i++)
    {
        cout << v1[i] << " ";
    }
    cout << endl;
    // 1 2 2 3 4 5 6
    
    // 使用类的对象
    vector<int> v2 = {2, 3, 1, 6, 2, 5, 4};
    sort(v2.begin(), v2.end(), cmpClassObject);
    for (int i = 0; i < v2.size(); i++)
    {
        cout << v2[i] << " ";
    }
    cout << endl;
    // 1 2 2 3 4 5 6
    return 0;
}

2.优先级队列

  • 定义类时同时定义操作符重载函数:操作符重载函数,必须是具体的操作符<之类的,写()报错
  • 自定义类,自定义比较函数:操作符重载函数,必须是具体的操作符<之类的,写()报错
  • 自定义类,自定义包含比较函数的结构体:操作符重载函数,必须是写()
#include <iostream>
#include <queue>
using namespace std;

/******** 定义类时同时定义操作符重载函数 ********/
struct Node1
{
    // 要比较的元素
    int x;
    // 构造函数
    Node1(int x) { this->x = x; }
    // 操作符重载函数,必须是具体的操作符<之类的,写()报错
    bool operator<(const Node1 &b) const
    {
        // 实现less中需要的<,大顶堆
        return x < b.x;
    }
};

/******** 自定义类,自定义比较函数 ********/
struct Node2
{
    // 要比较的元素
    int x;
    // 构造函数
    Node2(int x) { this->x = x; }
};

// 操作符重载函数,必须是具体的操作符<之类的,写()报错
bool operator<(const Node2 &a, const Node2 &b)
{
    // less,大顶堆
    return a.x < b.x;
}

/******** 自定义类,自定义包含比较函数的结构体 ********/
struct Node3
{
    // 要比较的元素
    int x;
    // 构造函数
    Node3(int x) { this->x = x; }
};

struct cmpClass
{
    // 操作符重载函数,必须是写()
    bool operator()(const Node3 &a, const Node3 &b)
    {
        // less,大顶堆
        return a.x < b.x;
    }
};

int main()
{
    /******** 初始化优先级队列的对象p ********/
    // Node1类型,默认使用vector,小顶堆,同 priority_queue<Node1, vector<Node1>, less<Node1> > p;
    priority_queue<Node1> p;

    // 乱序入队
    p.emplace(1);
    p.emplace(3);
    p.emplace(2);

    // 弹出队首
    while (!p.empty())
    {
        cout << p.top().x << " ";
        p.pop();
    }
    cout << endl;
    // 3 2 1

    /******** 初始化优先级队列的对象q ********/
    // 同 priority_queue<Node2> q;
    priority_queue<Node2, vector<Node2>, less<Node2>> q;

    // 乱序入队
    q.emplace(1);
    q.emplace(3);
    q.emplace(2);

    // 弹出队首
    while (!q.empty())
    {
        cout << q.top().x << " ";
        q.pop();
    }
    cout << endl;
    // 3 2 1

    /******** 初始化优先级队列的对象r ********/
    priority_queue<Node3, vector<Node3>, cmpClass> r;

    // 乱序入队
    r.emplace(1);
    r.emplace(3);
    r.emplace(2);

    // 弹出队首
    while (!r.empty())
    {
        cout << r.top().x << " ";
        r.pop();
    }
    cout << endl;
    // 3 2 1
    return 0;
}

C++ std::优先级队列priority_queue


Reference

一个std::sort 自定义比较排序函数 crash的分析过程
关于STL中的greater()和less()
C++官网:less
C++ std::优先级队列priority_queue

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值