一、优先队列基础:标准比较器的使用
1. 什么是优先队列?
优先队列是一种特殊的容器,它的特点是每次取出的元素都是优先级最高的。
就像医院急诊室的叫号系统:重伤患者(高优先级)会优先于普通患者(低优先级)被处理。
2. 模板参数解析
cpp
priority_queue<T, Container, Comparator>
T
:存储的数据类型(如int、Person)Container
:底层容器(必须是数组形式,常用vector)Comparator
:比较规则(决定元素优先级)
3. 标准比较器
C++标准库提供了两种内置比较器:
- **
less<T>
**:大顶堆(数值大的优先级高) - **
greater<T>
**:小顶堆(数值小的优先级高)
示例代码:数字优先队列
cpp
#include <iostream>
#include <queue>
using namespace std;
int main() {
// 大顶堆(默认就是less<int>)
priority_queue<int> max_heap;
// 小顶堆(需要显式指定greater<int>)
priority_queue<int, vector<int>, greater<int>> min_heap;
// 插入元素
max_heap.push(3); max_heap.push(1); max_heap.push(4);
min_heap.push(3); min_heap.push(1); min_heap.push(4);
// 输出结果对比
cout << "大顶堆顶部: " << max_heap.top() << endl; // 输出4
cout << "小顶堆顶部: " << min_heap.top() << endl; // 输出1
}
4. 基本操作
操作 | 说明 | 时间复杂度 |
---|---|---|
push(x) | 插入元素x | O(logn) |
pop() | 移除顶部元素 | O(logn) |
top() | 获取顶部元素 | O(1) |
size() | 获取元素数量 | O(1) |
empty() | 判断是否为空 | O(1) |
注意事项:
- 禁止越界访问:直接遍历priority_queue会破坏堆结构
- 空队列保护:调用
top()
前必须检查!empty()
- 插入效率:推荐使用
emplace
代替push
(减少临时对象构造)
二、自定义比较规则:运算符重载与仿函数
1. 为什么需要自定义比较?
当处理自定义类型(如Person类)时,编译器无法自动判断元素优先级。
示例需求:按年龄从大到小排序,年龄相同按姓名字母序排序。
2. 两种实现方式对比
方式一:运算符重载(修改类定义)
cpp
class Person {
public:
string name;
int age;
// 重载<运算符(影响默认排序规则)
bool operator<(const Person& other) const {
if (age == other.age) {
return name > other.name; // 年龄相同按名字升序
}
return age < other.age; // 年龄降序排列
}
};
// 使用方式
priority_queue<Person> pq; // 自动使用operator<规则
方式二:自定义仿函数(推荐)
cpp
// 定义比较器(不修改原类)
struct ComparePerson {
bool operator()(const Person& a, const Person& b) {
if (a.age == b.age) {
return a.name < b.name; // 注意:这里要用>才能实现名字升序
}
return a.age < b.age; // 年龄降序
}
};
// 使用方式
priority_queue<Person, vector<Person>, ComparePerson> pq;
3. 关键差异对比
特性 | 运算符重载 | 仿函数 |
---|---|---|
是否修改原类 | 是 | 否 |
支持多条件排序 | 需要修改类定义 | 更灵活 |
代码耦合度 | 高(与类绑定) | 低(独立比较器) |
适用场景 | 简单、通用的排序规则 | 复杂、多变的排序需求 |
三、完整实战示例
需求描述
实现一个员工管理系统,要求:
- 工资高的员工优先级最高
- 工资相同时,入职时间早的员工优先
- 支持动态添加和删除员工
代码实现
cpp
#include <iostream>
#include <queue>
#include <string>
#include <vector>
using namespace std;
class Employee {
public:
string name;
double salary;
int hire_date; // 入职日期(数值越小越早)
Employee(string n, double s, int h)
: name(n), salary(s), hire_date(h) {}
};
// 自定义比较器(工资降序,日期升序)
struct CompareEmployee {
bool operator()(const Employee& a, const Employee& b) {
if (a.salary != b.salary) {
return a.salary < b.salary; // 工资高的优先
}
return a.hire_date > b.hire_date; // 日期早的优先
}
};
int main() {
priority_queue<Employee, vector<Employee>, CompareEmployee> pq;
// 添加员工
pq.push(Employee("张三", 15000, 2020));
pq.push(Employee("李四", 20000, 2019));
pq.push(Employee("王五", 15000, 2018));
// 处理最高优先级员工
while (!pq.empty()) {
Employee emp = pq.top();
pq.pop();
cout << "处理员工: " << emp.name
<< " 薪资: " << emp.salary
<< " 入职年份: " << emp.hire_date << endl;
}
}
/* 输出结果:
处理员工: 李四 薪资: 20000 入职年份: 2019
处理员工: 王五 薪资: 15000 入职年份: 2018
处理员工: 张三 薪资: 15000 入职年份: 2020
*/
四、必须避开的7个坑
1. 比较逻辑颠倒
cpp
// 错误示例:想实现大顶堆但写反了比较符
struct BadComparator {
bool operator()(int a, int b) {
return a > b; // 实际会形成小顶堆!
}
};
2. 忘记处理相等条件
cpp
// 错误示例:未处理age相等的情况
struct ComparePerson {
bool operator()(const Person& a, const Person& b) {
return a.age < b.age; // 相同年龄时会随机排序
}
};
3. 在比较器中修改对象
cpp
// 危险示例:比较器中修改了对象状态
struct DangerousComparator {
bool operator()(Person& a, Person& b) {
a.salary += 1000; // 违反比较器应具有"不变性"原则
return a.salary < b.salary;
}
};
4. 使用非随机访问容器
cpp
// 编译错误:list不支持随机访问
priority_queue<int, list<int>> pq;
5. 忘记包含必要头文件
cpp
// 编译错误:未包含functional头文件
#include <queue>
priority_queue<int, vector<int>, greater<int>> pq;
6. 多线程竞争问题
cpp
// 多线程环境下需要加锁
priority_queue<int> pq;
// 线程1
pq.push(10);
// 线程2(可能同时访问导致数据竞争)
pq.pop();
7. 预分配内存不当
cpp
// 正确做法:预分配足够空间
vector<int> buffer(1000);
priority_queue<int, vector<int>> pq(buffer);
五、性能优化技巧
1. 移动语义(C++11+)
cpp
pq.push(Employee("赵六", 30000, 2022)); // 拷贝构造
pq.emplace("钱七", 35000, 2023); // 直接构造(快2-3倍)
2. 自定义容器适配器
对于海量数据处理可以:
cpp
// 使用deque替代vector(插入删除更快)
priority_queue<int, deque<int>> pq;
// 自定义内存池
MemoryPool<int> pool(1000000);
priority_queue<int, vector<int>, less<int>, MemoryPool<int>> pq(pool);
六、拓展应用场景
1. 任务调度系统
cpp
struct Task {
int priority;
string task_name;
};
struct CompareTask {
bool operator()(const Task& a, const Task& b) {
// 数值小的优先级反而高(紧急任务)
return a.priority > b.priority;
}
};
2. Dijkstra算法实现
cpp
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
pq.push({distance, node_id}); // 小顶堆自动取出最短路径
通过本文的学习,你应该已经掌握了:
- 标准比较器的使用场景
- 运算符重载与仿函数的差异
- 多条件排序的实现方法
- 常见错误规避技巧
建议实践:尝试修改比较器中的比较符号,观察输出顺序的变化。例如将a.salary < b.salary
改为a.salary > b.salary
,体会大顶堆与小顶堆的区别。