为什么会有比较器呢
比较器(Comparator)的存在是为了解决一个核心问题:如何定义元素的顺序关系。

  1. 灵活性:支持自定义排序规则,对于结构体或类,没有天然的“大小”定义,必须通过比较器指定如何比较。
  2. 通用性:统一接口适配不同场景,像 sort、priority_queue 这样的模板函数/类需要适用于任意类型,但不同类型可能有不同的比较逻辑。比较器通过函数指针、函数对象或 Lambda 提供统一的接口。
    1. 数据结构的需求
      堆(Heap):priority_queue 需要比较器决定元素的优先级(大根堆或小根堆)。
      平衡树(如 std::map):红黑树需要通过比较器维护节点的有序性。
      二分查找:lower_bound 依赖有序性,而有序性由比较器定义。

理解C++比较器

C++比较器(comparator)是用于定义元素之间排序规则的函数或函数对象。

基本概念

比较器是一个可调用对象(函数、函数对象或lambda表达式),它接受两个参数并返回一个布尔值,表示第一个参数是否应该排在第二个参数前面。(这很关键,另外前面是指当前位置的左边)

举个例子:return a > b
如果返回 true:表示 a 应该排在 b 前面
如果返回 false:表示 a 不应该排在 b 前面(即 b 应该排在 a 前面或两者相等)

常见使用场景

  1. 标准库排序算法:如std::sort, std::stable_sort
  2. 关联容器:如std::set, std::map, std::priority_queue
  3. 二分查找:如std::lower_bound, std::upper_bound

比较器规则

比较器需要满足严格弱序关系:

  • 反自反性:comp(a, a)必须为false
  • 反对称性:如果comp(a, b)为true,则comp(b, a)必须为false
  • 传递性:如果comp(a, b)comp(b, c)都为true,则comp(a, c)必须为true

示例代码

1. 基本函数比较器

bool compareInt(int a, int b) {
return a > b; // 降序排序
}

std::vector<int> nums = {3, 1, 4, 1, 5};
std::sort(nums.begin(), nums.end(), compareInt);

2. 函数对象(Functor)

struct CompareInt {
bool operator()(int a, int b) const {
return a < b; // 升序排序
}
};

std::sort(nums.begin(), nums.end(), CompareInt());

3. Lambda表达式(C++11起)

std::sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b; // 降序排序
});

4. 自定义类型排序

struct Person {
std::string name;
int age;
};

// 按年龄升序排序
std::vector<Person> people = {{"Alice", 25}, {"Bob", 20}, {"Charlie", 30}};
std::sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
return a.age < b.age;
});

重点来了,注意比较器在堆里的应用,和前面的定义类似,个人理解在堆里第一个元素放在第二个元素的“下边”,而不是“前面”;

C++比较器与大根堆、小根堆的联系

堆的基本概念

完全二叉树是指深度为h的二叉树,除了第h层上,其他所有层的结点树都达到最大值,并且第h层的所有结点连续集中在左边;

堆是一种特殊的完全二叉树,分为两种:

  • **大根堆(大顶堆)**:每个父节点的值都大于或等于其子节点的值
  • **小根堆(小顶堆)**:每个父节点的值都小于或等于其子节点的值

比较器与堆的关系

在C++中,std::priority_queue默认实现的是大根堆,但我们可以通过比较器来改变这个行为。

1. 默认大根堆(降序)

// 默认是大根堆,相当于使用 less<int> 比较器
std::priority_queue<int> maxHeap;

这相当于使用比较器:

bool compare(int a, int b) {
return a < b; // 注意这里看起来像升序,但对堆来说是大根堆
}

为什么? 因为在堆的实现中,”优先级高”的元素会被放在堆顶。当使用a < b时,较大的元素会被视为优先级更高。

2. 小根堆(升序)

// 使用 greater<int> 比较器创建小根堆
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;

这相当于使用比较器:

bool compare(int a, int b) {
return a > b; // 看起来像降序,但对堆来说是小根堆
}

为什么? 因为当使用a > b时,较小的元素会被视为优先级更高,从而放在堆顶。

关键理解点

  1. 堆的比较器含义:回答”是否应该将a放在b的下方?”

    • 返回true:a应该放在b的下方(即b的优先级更高)
    • 返回false:a不应该放在b的下方(即a的优先级更高或相等)
  2. 与排序比较器的区别

    • 排序比较器:return a > b → 降序排序
    • 堆比较器:return a > b → 小根堆(因为较大的值会被放在下方)

示例对比

排序比较器(std::sort)

vector<int> nums = {3,1,4,2};

// 降序排序
sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b; // 3>1 → 3排在1前面 → [4,3,2,1]
});

堆比较器(priority_queue)

// 小根堆
priority_queue<int, vector<int>, function<bool(int,int)>> minHeap(
[](int a, int b) { return a > b; } // 3>1 → 1优先级高 → 堆顶是最小值
);
minHeap.push(3); minHeap.push(1); minHeap.push(4); minHeap.push(2);
// 弹出顺序:1,2,3,4

实际应用示例

// 自定义结构体的堆排序
struct Task {
int priority;
string name;
};

// 创建任务优先队列(优先级数字小的先执行)
auto cmp = [](const Task& a, const Task& b) {
return a.priority > b.priority; // 小根堆
};
priority_queue<Task, vector<Task>, decltype(cmp)> taskQueue(cmp);

这样当priority数字越小,任务优先级越高,会先被执行。