0
点赞
收藏
分享

微信扫一扫

深入理解C++中的priority_queue(优先队列)

一、priority_queue简介

priority_queue(优先队列)是C++标准模板库(STL)中用于管理元素优先级的容器适配器。与普通队列(FIFO)不同,优先队列每次从队列中取出的元素总是优先级最高的元素(默认是最大值),这使得它在需要频繁获取极值的场景中非常有用,例如堆排序、Dijkstra算法、Prim算法等。

二、核心特性

1. 底层实现

  • 数据结构:优先队列的底层通过堆(Heap)实现,通常是用数组存储的二叉堆(大顶堆或小顶堆)。
  • 堆的性质
  • 大顶堆:父节点的值大于等于子节点的值,根节点为最大值。
  • 小顶堆:父节点的值小于等于子节点的值,根节点为最小值。

2. 访问方式

  • 只能通过top()访问堆顶元素(优先级最高的元素)。
  • 无法直接访问队列中间的元素,必须通过pop()移除堆顶元素后,新的堆顶元素才会暴露出来。

3. 常用操作

操作

描述

empty()

判断队列是否为空,空则返回true,否则返回false

size()

返回队列中元素的个数。

top()

返回堆顶元素(优先级最高的元素),若队列为空,行为未定义。

push()

向队列中插入新元素,并调整堆结构以维持堆的性质。

pop()

移除堆顶元素,并调整堆结构。注意:该函数不返回元素值,需配合top()使用。

三、基础用法示例

1. 基本使用(大顶堆,默认情况)

#include <iostream>
#include <queue>

using namespace std;

int main() {
    priority_queue<int> pq; // 大顶堆,默认比较方式

    // 插入元素
    pq.push(30);
    pq.push(10);
    pq.push(20);

    cout << "堆顶元素(最大值):" << pq.top() << endl; // 输出30

    // 弹出堆顶元素
    pq.pop();
    cout << "新堆顶元素:" << pq.top() << endl; // 输出20

    return 0;
}

2. 自定义小顶堆

通过修改比较函数,将默认的大顶堆改为小顶堆。

#include <queue>

// 方式1:使用greater<int>(内置类型适用)
priority_queue<int, vector<int>, greater<int>> min_heap;

// 方式2:自定义比较类(可用于自定义类型)
struct Compare {
    bool operator()(int a, int b) {
        return a > b; // 小顶堆:a的优先级低于b时,a排在后面
    }
};
priority_queue<int, vector<int>, Compare> min_heap;

3. 自定义类型的优先队列

#include <string>
#include <queue>

struct Student {
    string name;
    int score;
    Student(string n, int s) : name(n), score(s) {}
};

// 按分数从高到低排序(大顶堆)
struct StudentCompare {
    bool operator()(const Student& s1, const Student& s2) {
        return s1.score < s2.score; // 注意:priority_queue使用的是“大于”来构建大顶堆
    }
};

int main() {
    priority_queue<Student, vector<Student>, StudentCompare> pq;
    pq.push(Student("Alice", 90));
    pq.push(Student("Bob", 85));
    pq.push(Student("Charlie", 95));

    while (!pq.empty()) {
        Student s = pq.top();
        cout << s.name << ": " << s.score << endl; // 输出顺序:Charlie, Alice, Bob
        pq.pop();
    }
    return 0;
}

四、原理剖析:堆的实现与调整

1. 堆的存储方式

二叉堆通常用数组存储,下标从0或1开始(STL中从0开始)。对于下标为i的元素:

  • 左子节点下标:2*i + 1
  • 右子节点下标:2*i + 2
  • 父节点下标:(i - 1) / 2

2. 关键操作

  • 插入(push)
  1. 将元素添加到数组末尾。
  2. 执行向上调整( percolate up):比较当前元素与父节点,若优先级更高则交换,直到堆性质恢复。
  • 删除堆顶(pop)
  1. 将堆顶元素与最后一个元素交换,删除最后一个元素。
  2. 执行向下调整(percolate down):比较当前元素与子节点,若优先级低于子节点则交换,直到堆性质恢复。

五、应用场景

1. 堆排序

通过优先队列实现升序排序(利用大顶堆):

vector<int> heapSort(vector<int>& nums) {
    priority_queue<int> pq(nums.begin(), nums.end());
    vector<int> result;
    while (!pq.empty()) {
        result.push_back(pq.top());
        pq.pop();
    }
    reverse(result.begin(), result.end()); // 转为升序
    return result;
}

2. 寻找前K大元素

使用小顶堆维护一个大小为K的堆,堆顶为第K大元素:

vector<int> findTopK(vector<int>& nums, int k) {
    priority_queue<int, vector<int>, greater<int>> min_heap;
    for (int num : nums) {
        if (min_heap.size() < k) {
            min_heap.push(num);
        } else if (num > min_heap.top()) {
            min_heap.pop();
            min_heap.push(num);
        }
    }
    vector<int> result;
    while (!min_heap.empty()) {
        result.push_back(min_heap.top());
        min_heap.pop();
    }
    return result;
}

3. 任务调度(贪心算法)

例如,在操作系统中调度具有优先级的任务,每次选择优先级最高的任务执行。

六、注意事项

  1. 访问空队列:调用top()pop()对空队列操作会导致未定义行为,需先用empty()判断。
  2. 底层容器priority_queue默认使用vector作为底层容器,也可指定deque,但必须满足随机访问和push_back()/pop_back()操作。
  3. 稳定性:优先队列不保证相同优先级元素的相对顺序,属于不稳定的数据结构。
  4. 迭代器支持priority_queue不提供迭代器,无法直接遍历队列中的元素。

七、与其他容器的对比

容器

特点

适用场景

priority_queue

基于堆,每次取极值,不支持遍历。

极值查找、贪心算法

vector

动态数组,支持随机访问和迭代器。

顺序存储、频繁访问中间元素

set/multiset

基于红黑树,自动排序,支持迭代器和有序遍历。

有序集合、去重

八、总结

priority_queue是C++中处理优先级问题的高效工具,其核心在于利用堆结构实现快速的极值获取。通过自定义比较函数,它可以灵活适应各种优先级规则。在使用时,需注意其底层实现的特性(如不支持遍历、默认大顶堆等),并结合具体场景选择合适的容器。熟练掌握优先队列,能有效提升算法效率,尤其是在贪心、图论等问题中。

举报

相关推荐

0 条评论