C++ STL priority_queue 详解:从基础到自定义类型

一、优先队列基础:标准比较器的使用

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)插入元素xO(logn)
pop()移除顶部元素O(logn)
top()获取顶部元素O(1)
size()获取元素数量O(1)
empty()判断是否为空O(1)
注意事项:
  1. 禁止越界访问​:直接遍历priority_queue会破坏堆结构
  2. 空队列保护​:调用top()前必须检查!empty()
  3. 插入效率​:推荐使用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. 关键差异对比

特性运算符重载仿函数
是否修改原类
支持多条件排序需要修改类定义更灵活
代码耦合度高(与类绑定)低(独立比较器)
适用场景简单、通用的排序规则复杂、多变的排序需求

三、完整实战示例

需求描述

实现一个员工管理系统,要求:

  1. 工资高的员工优先级最高
  2. 工资相同时,入职时间早的员工优先
  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}); // 小顶堆自动取出最短路径

通过本文的学习,你应该已经掌握了:

  1. 标准比较器的使用场景
  2. 运算符重载与仿函数的差异
  3. 多条件排序的实现方法
  4. 常见错误规避技巧

建议实践​:尝试修改比较器中的比较符号,观察输出顺序的变化。例如将a.salary < b.salary改为a.salary > b.salary,体会大顶堆与小顶堆的区别。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

维维宝宝最可爱啦QWQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值