stl中的自定义比较函数详解, 各类自定义排序方法

标准库函数中的 set, map, priority_queue 等容器都可以保证一定的大小顺序, 并且可以覆盖默认的比较函数实现自定义的排序需求. 在各类博文中, 有大量不同方式自定义排序函数并实现相关需求的方式, 作为一名 c++ 小白, 决定将各种方式学习一下, 彻底弄懂自定义比较函数的相关问题.
我们以 set 为例详细说明. set 是c++标准库提供的一个容器, 他提供了一个保序的集合, 集合中的元素不可重复. 具体模板声明如下

template<
    class Key,
    class Compare = std::less<Key>,
    class Allocator = std::allocator<Key>
> class set;

其中 Key 是保存的元素类型; Compare 是比较器的类型, 也是本文讨论的重点; Allocator 是分配器的类型, 相关内容在此不做讨论.

比较器

比较器是 Compare 类型的一个实例(记为 comp), 并且满足

  • comp 有一个能接受两个 Key 对象作为参数, 并返回(或者可以转换为) bool 类型的函数对象. 注意, 这里并不要求接受的两个 Key 对象是传引用还是传值, 也不要求 const.
  • comp 定义了类型 Key 上的一个严格弱顺序(strict weak ordering).
    • comp(a, a) = false 对所有的 a 成立.
    • 如果 comp(a, b) = true, 那么comp(b, a) = false. (注意, 这个条件反过来不成立, 因为有可能 ab 相等).
    • 如果 comp(a, b) = truecomp(b, c) = true, 那么 comp(a, c) = true (传递性).
  • 定义 equiv(a, b) = !comp(a, b) && !comp(b, a), 那么
    • equiv(a, b) = true 对所有的 a 成立.
    • 如果 equiv(a, b) = true, 那么 equiv(b, a) = true.
    • 如果 equiv(a, b) = trueequiv(b, c) = true, 那么 equiv(a, c) = true.

也就是说, Compare 是比较器的类型, 而比较器是 Compare 类型的一个实例, 并且该实例满足上述条件. 如果比较器满足上面的条件, 那么比较器实际上就定义了一个 Key 上的顺序, 在这个顺序下, 我们就能够进行排序, 并按照顺序进行插入, 删除和去重等 set 上的常规操作. 那么比较器在什么时候传入呢, 自然是在构造函数中.


## 修改排序方式
下面我们看一下 `set` 的常用构造函数
```c++
set(): set(Compare());  // (1)
explicit set(const Compare& comp, const Allocator& alloc = Allocator()); // (2)
template<class InputIt>
set(InputIt first, InputIt last,
    const Compare& comp = Compare(),
    const Allocator& alloc = Allocator());    // (3)

在第一个构造函数中, 会实际调用第二个构造函数来进行初始化, 也就是使用 Compare 类型的默认构造函数来获得一个比较器. 由于 Compare 的默认值是 std::less<key>, 所以如果没有用户指定, 那么使用的比较器就是 std::less<Key> 的默认实例(实际上 std::less 也只有默认构造函数), 此时对应的顺序就是 Key 上的小于顺序 (对于内置类型, 就是自然的小于; 对于自定义类型, 需要有对小于号的重载, 并且这个对小于号的重载应该满足比较器的性质, 这点一般都能满足). 因此, 如果需要有把从小到大变成从大到小, 最简单的方式就是将模板中的 less<key> 换成 greater<key>.

int main() {
    set<pair<int, int>> s = {{1, 2}, {1, 3}, {2, 3}, {2, 5}};
    for (auto &[a, b] : s) {
        cout << a << '\t' << b << endl;
    }
    return 0;
}
/*输出为:
1       2
1       3
2       3
2       5
*/
int main() {
    set<pair<int, int>, greater<pair<int, int>>> s = {{1, 2}, {1, 3}, {2, 3}, {2, 5}};
    for (auto &[a, b] : s) {
        cout << a << '\t' << b << endl;
    }
    return 0;
}
/*输出为:
2       5
2       3
1       3
1       2
*/

不过有的时候我们需要对一个已经定义好的类型设置自定义的排序方式, 并且不能去修改或者添加相应的大于小于函数重载, 那么此时就需要运用其他的方法. 通过了解上述原理, 我们对下面的几种方法应该也不难理解了.
第一种方法是定义一个新的仿函数类, 在仿函数类中定义排序函数, 使用该排序函数实现比较器.

# 实现pair<int, int>的自定义排序, 按照第一个元素升序, 第二个元素降序.
struct PairComp {
    template <typename T1, typename T2>
    bool operator()(const pair<T1, T2> &left, const pair<T1, T2> &right) const {
        return left.first < right.first || (
            left.first == right.first && left.second > right.second);
    }
};


int main() {
    set<pair<int, int>, PairComp> s = {{1, 2}, {1, 3}, {2, 3}, {2, 5}};
    for (auto &[a, b] : s) {
        cout << a << '\t' << b << endl;
    }
    return 0;
}
/*输出
1       3
1       2
2       5
2       3
*/

第二种方法是自定义一个比较函数, 这样的话, 需要修改模板中的比较器类型, 同时传递比较器的值.

template <typename T1, typename T2>
bool comp(const pair<T1, T2> &left, const pair<T1, T2> &right) {
    return (left.first < right.first) || (
        left.first == right.first && left.second > right.second);
}



int main() {
    set<pair<int, int>, decltype(comp<int, int>)*> s({{1, 2}, {1, 3}, {2, 3}, {2, 5}}, comp);
    for (auto &[a, b] : s) {
        cout << a << '\t' << b << endl;
    }
    return 0;
}
/*输出
1       3
1       2
2       5
2       3
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值