自定义排序辨析

yuanheci 2024年12月29日 30次浏览

C++ 和 Python 在自定义排序方面的一些区别:

返回值类型:

  • Python 的 cmp 函数返回 -1、1 或 0 来表示顺序关系。
  • C++ 的 cmp 函数或 operator< 重载通常返回 bool 类型,表明第一个元素是否小于第二个元素。

C++中通过 重载< / 重载> 或者 写一个比较函数cmp,即可实现自定义排序。
但是注意,std::sort 的自定义逻辑和 priority_queue 的自定义逻辑有区别(本质是底层实现逻辑不同)!


1. std::sort

std::sort 的逻辑基础:
  std::sort 是基于比较函数的结果来确定元素顺序的,它遵循 < 运算符的语义。这意味着当比较函数(通常是 operator< 或自定义比较函数)返回 true 时,std::sort 认为第一个元素小于第二个元素,因此第一个元素应该排在第二个元素之前。

正常的 return a < b 逻辑:
当使用 return a < b 时,例如在自定义结构体的 operator< 中:

struct Person {
    std::string name;
    int age;
    bool operator<(const Person& other) const {
        return age < other.age;
    }
};

这里的 return age < other.age 遵循正常的 < 逻辑。假设我们比较 Person A 与 Person B,如果 A.age < B.age 为 true,std::sort 会将 Person A 放在 Person B 之前,最终结果是按照年龄升序排序。

return a > b 如何反转逻辑:

当使用 return a > b 时,如:

struct Person {
    std::string name;
    int age;
    bool operator<(const Person& other) const {
        return age > other.age;
    }
};

  现在,假设我们比较 Person A 与 Person B,如果 A.age > B.age 为 true,根据 operator< 的实现,它会返回 true。但这里的 true 对于 std::sort 来说意味着 Person A 小于 Person B,因为 std::sort 只看到 operator< 的返回结果,而不关心内部的实际比较逻辑。
  然而,因为 A.age > B.age 为 true 意味着 A 的年龄更大,按照正常逻辑,A 应该排在 B 之后。但由于 operator< 告诉 std::sort 它们的顺序是 A 小于 B,std::sort 会将 A 放在 B 的前面。
  所以,通过使用 return a > b,我们实际上欺骗了 std::sort,让它将较大的元素(age 较大的 Person)认为是小于较小的元素,从而将较大元素排在前面,实现了降序排序。


2. std::priority_queue

  无论是 less<T> 比较器还是 greater<T> 比较器,都可以实现大根堆 / 小根堆的效果,通过利用比较逻辑反转即可。

priority_queue默认使用的是less<T>作为比较器。

核心比较逻辑(以 std::priority_queue 大根堆为例):
  在 std::priority_queue 中,虽然也经常重载 operator<,但它的逻辑与 std::sort 有所不同。对于 std::priority_queue 来说,a < b 为 true 意味着 a 的优先级低于 b,因此b会优先出队!。所以,如果要实现大根堆,你会在 operator< 的实现中使用小于逻辑(例如 return a.age < b.age;),这样年龄大的元素会被认为具有更高的优先级,先被弹出。

  如果使用 greater<T> 作为 priority_queue 的比较器,并且存储的是自定义类型,通常需要重载 operator> 运算符。
如:priority_queue<Seg, vector<Seg>, greater<Seg>> heap

  因为 greater<T> 比较器使用 operator> 来确定元素的顺序,它会调用 operator> 来比较元素。当 x > y 为 true 时,x的优先级更低,所以让y先出队,这符合小根堆的逻辑,即较小的元素排在堆的前面。


3. 总结

  总的来说,对于 std::priority_queue 实现大根堆和 std::sort 从大到小排序,虽然都可能涉及 operator< 的重载,但 operator< 的逻辑是相反的,因为 std::priority_queue 关心的是元素的优先级,而 std::sort 关心的是元素的位置。同时,两者都可以使用自定义比较函数或运算符重载来实现自定义排序逻辑。


4. cmp

cmp在 std::sort中充当的是比较器,也就是之前提到的 less<T>greater<T> 这类角色。

cmp 用在 std::sortstd::priority 时,同样需要遵循其底层比较逻辑。

比较器的概念:
比较器是一个更广泛的概念,在 C++ 中,它可以是以下几种形式:

  • 函数指针:像 cmp 函数这样的函数指针可以作为比较器。
  • 函数对象(仿函数):通过重载 operator() 实现的类或结构体,例如:
#include <iostream>
#include <vector>
#include <algorithm>

struct Cmp {
    bool operator()(int a, int b) const {
        return a > b;
    }
};

int main() {
    std::vector<int> v = {3, 1, 2};
    std::sort(v.begin(), v.end(), Cmp());
    for (int i : v) {
        std::cout << i << " ";
    }
    return 0;
}

std::priority_queue 中自定义排序如何用 cmp

以下是在 std::priority_queue 中使用 cmp 函数的解决方案:

1、定义一个 cmp 函数,它将作为比较器。
2、创建 std::priority_queue 时,使用自定义的 cmp 函数作为比较器。由于 std::priority_queue 的构造函数不直接接受函数指针作为比较器,我们需要将 cmp 函数封装在一个结构体或类中,通过重载 operator() 实现。如下所示:

#include <iostream>
#include <queue>

// 定义 cmp 函数,这里实现大元素优先级高
bool cmp(int a, int b) {
    return a < b;
}

// 结构体,用于封装 cmp 函数
struct Cmp {
    bool operator()(int a, int b) const {
        return cmp(a, b);
    }
};

int main() {
    std::priority_queue<int, std::vector<int>, Cmp> pq;
    pq.push(3);
    pq.push(1);
    pq.push(2);
    while (!pq.empty()) {
        std::cout << pq.top() << " ";
        pq.pop();
    }
    return 0;
}