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

本文深入探讨了C++中less和greater比较器的功能,解释了它们如何影响排序和优先级队列的行为。文章详细分析了比较规则、自定义比较函数以及在数组和优先级队列中的应用。


一、结论

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

<think>好的,用户想了解如何为C++std::map设置自定义排序规则。首先,我需要回忆std::map的基本知识。std::map是一个关联容器,通常用红黑树实现,自动根据键排序。默认情况下,它使用std::less比较键,也就是按升序排列。但如果用户想要自定义排序规则,就需要提供自己的比较函数。 接下来,我需要确定如何实现自定义比较函数。根据引用的资料,特别是引用[3]和引用[1],有两种方法:重载结构体的小于运算符或者在定义map时传入一个比较类或函数对象。引用[3]提到可以通过重载operator<或者在外部定义比较函数。而引用[1]中的例子展示了如何自定义比较函数,比如不区分大小写的字符串比较。 然后,我需要考虑不同情况下的应用。比如,当键是自定义结构体或类时,必须定义比较方式,否则编译会出错。另外,当需要降序排列时,可以使用std::greater作为模板参数。这时候需要确保键类型支持相应的比较操作。 还要注意,比较函数必须满足严格弱序的条件,否则map的行为会不可预测。例如,比较函数必须保证如果a < b为真,那么b < a必须为假,且传递性等条件。 然后,用户可能需要具体的代码示例。例如,使用仿函数作为比较类,或者在定义map时指定比较函数。例如,引用[3]中的例子展示了在结构体内部重载operator<,或者在外部定义比较函数。而引用[1]中的例子则使用了自定义的Compare类。 还需要提到,当使用自定义比较函数时,map的类型声明中需要显式指定比较类型,因为模板参数需要知道比较函数的类型。比如,std::map<KeyType, ValueType, Compare>,其中Compare是自定义比较类。 另外,引用[4]指出,map默认按键排序,无法直接按值排序。所以如果用户想按值排序,可能需要将map的内容复制到vector中再进行排序,但这是另一个问题,不过用户当前的问题是关于键的排序,所以可以暂时不提。 总结步骤的话,应该是: 1. 确定排序规则的需求,比如升序、降序或自定义结构体的排序。 2. 创建比较函数或仿函数,满足严格弱序。 3. 在声明std::map时,将比较函数作为第三个模板参数传入。 4. 注意比较函数的正确性,避免逻辑错误。 需要检查是否有遗漏的情况,比如lambda表达式是否可用。不过,由于lambda在C++中作为模板参数时需要是无状态的,可能无法直接使用,但可以封装成函数对象。不过通常使用仿函数更常见。 可能还需要提醒用户,当使用自定义比较函数时,两个不同的map实例如果比较函数不同,它们的类型也不同,不能互相赋值或交换。 最后,根据用户的问题,需要给出清晰的步骤和示例代码,确保用户能够理解并正确应用。例如,展示如何为内置类型(如int降序)和自定义类型(如结构体按特定字段排序)设置比较函数。</think>### 如何为std::map设置自定义排序规则C++中,`std::map`默认按键的升序排列(使用`std::less<Key>`)。若要自定义排序规则,需通过**模板参数**指定比较函数。以下是具体实现方法: --- ### 方法一:使用仿函数函数对象) 通过定义一个包含`operator()`的类或结构体,实现自定义比较逻辑。 #### 示例1:按整型键降序排列 ```cpp #include <map> #include <functional> struct CompareIntDesc { bool operator()(int a, int b) const { return a > b; // 降序规则 } }; std::map<int, std::string, CompareIntDesc> myMap; ``` 此时`myMap`会按`int`键从大到小排序[^1]。 --- #### 示例2:自定义结构体作为键 若键为自定义结构体,需定义比较逻辑: ```cpp struct Person { std::string name; int age; }; struct ComparePerson { bool operator()(const Person& p1, const Person& p2) const { // 按年龄升序,若年龄相同则按姓名升序 if (p1.age != p2.age) return p1.age < p2.age; return p1.name < p2.name; } }; std::map<Person, int, ComparePerson> personMap; ``` 此处的`personMap`会先按年龄升序,再按姓名升序排列[^3]。 --- ### 方法二:重载运算符 为自定义类型直接重载`operator<`,适用于仅需一种默认排序规则的场景: ```cpp struct Point { int x, y; bool operator<(const Point& other) const { return (x*x + y*y) < (other.x*other.x + other.y*other.y); // 按与原点的距离升序 } }; std::map<Point, int> pointMap; ``` 此时`pointMap`会按点的模长升序排列[^3]。 --- ### 方法三:使用Lambda表达式(C++11+) 通过模板参数传递Lambda表达式,但需注意Lambda必须是无状态的(需转换为函数指针或包装为`std::function`): ```cpp auto compare = [](const std::string& a, const std::string& b) { return a.length() < b.length(); // 按字符串长度升序 }; std::map<std::string, int, decltype(compare)> stringMap(compare); ``` --- ### 注意事项 1. **严格弱序**:比较函数必须满足严格弱序条件(例如,若`a < b`为`true`,则`b < a`必须为`false`)[^2]。 2. **类型一致性**:若使用自定义比较函数,不同比较规则的`std::map`属于不同类型,无法直接赋值或交换。 3. **性能影响**:自定义比较函数的复杂度会影响`std::map`的插入、查找效率(默认红黑树操作时间复杂度为$O(\log n)$)[^2]。 --- ### 常见问题 1. **如何按值排序?** `std::map`无法直接按值排序,需将键值对复制到`std::vector`后用`std::sort`处理[^4]。 2. **如何实现不区分大小写的字符串排序?** 定义比较函数时将字符串转换为统一大小写后比较: ```cpp struct CaseInsensitiveCompare { bool operator()(const std::string& a, const std::string& b) const { return std::lexicographical_compare( a.begin(), a.end(), b.begin(), b.end(), [](char c1, char c2) { return tolower(c1) < tolower(c2); } ); } }; ``` ---
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值