带你深入浅出c++中的优先队列(Priority Queue),算法竞赛必学考点!入门看我这篇就够啦!

制作不易,欢迎各位点赞收藏,如有不足,欢迎在评论区补充

引入        

        STL作为c++重要特性之一,为各位的编程过程中带来不少便利,其中首当其冲要掌握的便是其中的优先队列(Priority Queue)。他与普通的队列相比,最重要的特点就是不是按照进入顺序进行所谓的“先进先出原则”,而是根据元素的优先级进行排列,而后根据元素的优先级进行弹出和插入操作,这里的优先级可以有很多定义,既可以是大小,也可以是根据类的定义不同,自行规定优先级的排列规则,如“奶牛的食量”,“Acmer的编程水平”等等,进行由高到低或者由低到高的排序。

Priority Queue的底层实现

        在c++的STL中,优先队列(priority_queue)的底层默认是用来实现的,并通常是用vector容器来储存堆中的元素。而提到堆,就不得不提到堆的两大性质:完全二叉树性与堆序性。前者(完全二叉数性)只允许二叉树中的最后一行不为空,且最后一行节点尽量靠左(必须从左往右排序,最后一行元素间不可以有间隔)。后者(堆序性)是对于最大堆,堆序性要求每个父节点的值都大于或等于其子节点的值;对于最小堆,每个父节点的值都小于或等于其子节点的值。

 堆序性和完全二叉树性是实现优先队列底层的关键

原因如下:
堆序性
 
        - 快速确定优先级:堆序性规定了父节点和子节点的大小关系。这使得优先队列能在O(1)时间内通过堆顶元素确定最高或最低优先级元素,能高效地实现 top 操作,快速返回优先队列中优先级最高或最低的元素。
        - 高效调整:插入和删除元素时,依据堆序性通过上浮或下沉操作,能以O(\log n)的时间复杂度重新平衡堆,保持堆的特性,确保优先队列操作的高效性。比如插入新元素时,将其与父节点比较并根据堆序性调整位置,直至满足堆序。


完全二叉树性
 
        - 空间效率高:完全二叉树可以用数组紧凑存储,节点在数组中的位置有规律可循,能有效利用存储空间。对于具有n个节点的完全二叉树,数组下标从1开始存储时,节点i的左子节点是2i,右子节点是2i + 1,无需额外空间存储节点间的指针关系。
        - 操作高效:完全二叉树的结构特点使堆的调整操作更高效。由于其结构规整,在进行上浮或下沉操作时,能快速定位节点及其子节点和父节点,减少比较和移动次数。而且这种结构保证了堆在插入和删除元素后,能通过简单的计算和有限次调整,迅速恢复为满足堆序性的完全二叉树,维持优先队列的性质。

C++中优先队列的具体用法

模版介绍与常用操作

        在C++中,优先队列被C++标准模版库(STL)封装成了priority_queue类(注意要带“_”)。它的模板参数有三个,分别是 T 、 Container 和 Compare 。

priority_queue<T, Container, Comparator>

以下是具体介绍:

         T :表示优先队列中存储元素的数据类型,可以是内置数据类型(如 int 、 double 等),也可以是自定义的数据类型(如结构体、类)。

         Container :用于指定优先队列的底层容器,默认是 vector ,也可以使用 deque 等其他满足要求的序列容器。这个容器用于存储优先队列中的元素,并提供随机访问和在末尾插入/删除元素的功能。

         Compare :是一个比较函数对象类型,用于定义元素之间的比较规则,以确定元素在优先队列中的优先级。默认情况下,对于数值类型,使用 less<T> ,即大的元素优先级高,形成最大堆;若要使用最小堆,可指定为 greater<T> 。对于自定义类型,需要用户自定义比较函数或函数对象。

        同时,其在调用之前必须要在main函数前带上#include <queue>的头文件,同时与他搭配的,还有以下操作

操作标识符功能时间复杂度
插入元素push将一个新元素及其优先级插入到优先队列中。O(log n)
删除元素pop删除并返回优先队列中优先级最高(对于最大堆)或最低(对于最小堆)的元素。(注意不是返回被删除元素)O(log n)
访问队首元素top或front返回优先队列中优先级最高或最低的元素,但不删除该元素。O(1)
 判断优先队列是否为空empty检查优先队列中是否包含任何元素。O(1)
获取优先队列中元素的个数size返回优先队列中元素的数量O(1)

         需要注意的一点就是,优先队列本身是不支持迭代器访问的,所以 无法通过迭代器直接遍历和删除指定元素,那我们该如何删除元素呢?通常方式有两个:

        1. 删除队首元素:因为优先队列默认情况下只能访问和操作队首(优先级最高或最低)的元素,所以可以使用 pop 操作来删除队首元素。

样例代码如下:

#include <iostream>
#include <queue>
using namespace std;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    priority_queue<int> pq;
    pq.push(5);
    pq.push(7);
    pq.push(3);
    pq.push(1);
    // 删除队首元素(优先级最高的元素)
    //默认大根堆
    pq.pop();//删除大根堆的堆顶 7
    
    while(!pq.empty())
    {
        cout<<pq.top()<<endl;
        pq.pop();
    }
    
    return 0;
}

输出如下:

5
3
1

        2.重新构建优先队列:如果要删除特定的元素,一种间接的方法是将优先队列中的元素逐个取出,跳过要删除的元素,然后再将其余元素重新插入到一个新的优先队列中。这种方法的时间复杂度较高,因为需要遍历原优先队列中的所有元素并进行插入操作。

样例代码如下:

#include <iostream>
#include <queue>
#include <vector>
using namespace std;

signed main() {
    priority_queue<int> pq;
    pq.push(5);
    pq.push(3);
    pq.push(8);

    int elementToDelete = 3;
    vector<int> temp;
    while (!pq.empty()) {
        //如果队头不是被删元素,将其
        if (pq.top() != elementToDelete) {
            temp.push_back(pq.top());
        }
        pq.pop();
    }

    // 重新构建优先队列
    for (int num : temp) {
        pq.push(num);
    }

    while (!pq.empty()) {
        cout << pq.top() << endl;
        pq.pop();
    }
    return 0;
}

输出如下:

8
5

自定义类型优先级比较写法与演示

        上文提到,在C++中, priority_queue 默认使用 less 比较函数(对于数值类型,大的元素优先级高,形成最大堆)。然而当需要自定义类型(如结构体或类)的优先级比较规则时,可以通过两种常见的方式来实现,以下是详细的写法与演示:

1.使用函数对象(仿函数)

        我们可以自行构造仿函数,在定义priority_queue类时直接写到模版当中,从而从底层改变优先级的排列方式。代码与注释如下:

#include <iostream>
#include <queue>
#include <string>

using namespace std;

// 定义自定义类型结构体
struct Student {
    string name;
    int score;
    Student(const string& n, int s) : name(n), score(s) {}
};

// 自定义比较函数对象(仿函数),分数高的学生优先级高
struct CompareStudent {
    bool operator()(const Student& s1, const Student& s2) {
        return s1.score < s2.score;
    }
};

int main() {
    // 创建优先队列,元素类型为Student,底层容器为vector,比较函数为CompareStudent
    priority_queue<Student, vector<Student>, CompareStudent> studentQueue;

    // 插入学生信息
    studentQueue.push(Student("Alice", 85));
    studentQueue.push(Student("Bob", 90));
    studentQueue.push(Student("Charlie", 80));

    // 依次取出并输出学生信息,分数高的先输出
    while (!studentQueue.empty()) {
        Student currentStudent = studentQueue.top();
        studentQueue.pop();
        cout << "学生姓名: " << currentStudent.name << ", 分数: " << currentStudent.score << endl;
    }

    return 0;
}

输出如下:

学生姓名: Bob, 分数: 90
学生姓名: Alice, 分数: 85
学生姓名: Charlie, 分数: 80

2.重载 operator<

        利用结构体或类的重载,定义比较方式,使得编译器会判断自定义类型的优先级大小。

#include <iostream>
#include <queue>
#include <string>

using namespace std;

// 定义自定义类型结构体
struct Employee {
    string name;
    int age;
    Employee(const string& n, int a) : name(n), age(a) {}
    // 重载operator<,年龄大的员工优先级高
    bool operator<(const Employee&e1)const
    {
        return age<e1.age;
    }
};


int main() {
    // 创建优先队列,元素类型为Employee,使用默认的vector作为底层容器和默认的比较方式(因为Employee重载了operator<)
    priority_queue<Employee> employeeQueue;

    // 插入员工信息
    employeeQueue.push(Employee("Tom", 30));
    employeeQueue.push(Employee("Jerry", 35));
    employeeQueue.push(Employee("Lucy", 28));

    // 依次取出并输出员工信息,年龄大的先输出
    while (!employeeQueue.empty()) {
        Employee currentEmployee = employeeQueue.top();
        employeeQueue.pop();
        cout << "员工姓名: " << currentEmployee.name << ", 年龄: " << currentEmployee.age << endl;
    }

    return 0;
}

输出:

员工姓名: Jerry, 年龄: 35
员工姓名: Tom, 年龄: 30
员工姓名: Lucy, 年龄: 28

以下内容当做了解

        如果我们想从小到大排序非自定义类型,在 C++ 中, priority_queue<int, vector<int>, greater<int> > q; 最后的 int> 与 > 之间必须空一个格。这是因为 >> 在 C++ 中是右移运算符,如果不空格,编译器会将其解析为右移运算符,导致编译错误。所以为了让编译器正确识别为模板参数的结束,需要在 int> 和 > 之间添加一个空格。

        然而,在C++11标准以后,引入了尖括号匹配规则的改进,编译器能够更好地识别模板参数列表的结束,所以即使 >> 之间没有空格,也能正确解析为两个连续的右尖括号,而不是右移运算符。这使得代码在书写上更加灵活,也提高了代码的可读性和可维护性。

结语

        相信如果你看到这了,一定对优先队列有了更深度的了解,然而他的美妙之处更在于与算法的结合(堆排序,Dijkstra算法,Huffman编码),这些就要慢慢探索了。

<think>嗯,用户想了解蓝桥杯C++考试的考点和解析。首先,我需要回想一下蓝桥杯的常见考点,可能包括基础语法、数据结构算法等。用户提到的是大学C组,可能难度适中,但具体内容需要确认。 我记得蓝桥杯常考的有循环、条件语句、数组、字符串处理这些基础内容。比如质数判断、斐波那契数列这些经典题目。然后数据结构方面,栈、队列、链表、树、图这些应该都是重点,特别是树的遍历和图的最短路径,比如DFS、BFS、Dijkstra算法。 接下来是算法部分,排序算法如快速排序、归并排序,还有贪心、动态规划、回溯这些。动态规划可能会考背包问题,最长子序列之类的。数学题方面,最大公约数、快速幂、排列组合这些也是常出现的。 STL库的应用也很重要,比如vector、map、set的使用,还有字符串处理的函数。另外,文件操作和输入输出处理也不能忽视,特别是大数处理和高精度计算,蓝桥杯有时候会有这类题目。 然后,我需要参考用户提供的引用,里面提到的是个人题解和思路,可能存在一些错误,但可以作为参考。例如,引用中提到动态规划在2024年考试中的重要性,可能需要强调这一点。 另外,用户可能想知道如何高效备考,比如推荐书籍或在线资源。常见的书籍有《算法竞赛入门经典》,在线平台如洛谷、LeetCode。还有注意时间复杂度的优化,避免超时,以及代码调试技巧。 可能还需要提醒用户注意蓝桥杯的评分标准,部分分和优化策略。比如,即使无法完全解决题目,也要尽量写出能拿部分分的代码。 最后,确保回答结构清晰,分点列出考点,每个考点下给出例子和注意事项,并附上相关问题帮助用户深入思考。同时,检查是否符合格式要求,数学公式用$...$,独立公式用$$...$$,正确使用LaTeX语法,引用标识自然添加。</think>### 蓝桥杯 C++ 考试重点与常见考点解析 #### 一、基础语法与编程能力 1. **输入输出与数据类型** - 蓝桥杯常涉及大整数运算(如 $10^{18}$ 范围)和高精度计算,需熟练使用 `long long`、字符串处理或自定义高精度结构。 - 示例:`scanf("%lld", &n)` 处理长整型输入;字符串反转实现高精度加法。 2. **循环与条件语句** - 嵌套循环常出现在质数筛法、矩阵操作等场景,例如埃拉托斯特尼筛法求质数: ```cpp for (int i=2; i*i<=n; i++) if (!st[i]) for (int j=i*i; j<=n; j+=i) st[j]=true; ``` 3. **函数与递归** - 递归应用广泛,如斐波那契数列、全排列生成。需注意递归深度限制(蓝桥杯栈空间默认约 1MB)。 --- #### 二、数据结构算法核心考点 1. **线性数据结构** - **数组/字符串**:前缀和、差分数组(区间修改优化)。 - **链表**:双向链表实现 LRU 缓存(近年真题)。 - **栈/队列**:单调栈解决“下一个更大元素”问题,队列用于 BFS。 2. **树与图论** - **二叉树**:遍历(先序/中序/后序)、最近公共祖先(LCA)。 - **图论**:最短路径(Dijkstra、Floyd)、最小生成树(Kruskal)。 示例代码(Dijkstra 核心逻辑): ```cpp priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq; pq.emplace(0, start); while (!pq.empty()) { auto [dist, u] = pq.top(); pq.pop(); if (visited[u]) continue; visited[u] = true; for (auto [v, w] : graph[u]) { if (dist + w < dis[v]) { dis[v] = dist + w; pq.emplace(dis[v], v); } } } ``` 3. **动态规划(重点!)** - **经典模型**:背包问题(01背包、完全背包)、最长公共子序列(LCS)。 - **优化技巧**:状态压缩(如滚动数组)、斜率优化。 - 真题示例:2024年C组题目中出现“分割数组使子数组和差值最小”,需结合前缀和与 DP 状态设计[^1]。 --- #### 三、数学与计算思维 1. **数论基础** - 快速幂算法(计算 $a^b \mod p$,时间复杂度 $O(\log b)$): ```cpp long long qpow(long long a, long long b, long long p) { long long res = 1; while (b) { if (b & 1) res = res * a % p; a = a * a % p; b >>= 1; } return res; } ``` - 扩展欧几里得算法解线性同余方程。 2. **组合数学** - 排列组合公式(如 $C(n,k) = \frac{n!}{k!(n-k)!}$)的实际应用,常需配合动态规划或预处理阶乘逆元。 --- #### 四、实战技巧与备赛建议 1. **代码模板准备** - 提前整理高频算法模板(如并查集、快速排序),考试时直接修改参数适配题目。 2. **调试与优化** - 使用 `freopen("input.txt", "r", stdin)` 重定向输入测试用例。 - 避免超时:优先使用 $O(n \log n)$ 算法(如排序代替双重循环)。 3. **真题训练方向** - 参考近三年C组真题,重点练习“模拟题”(如日期计算、图形输出)和“贪心+动态规划”组合题。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值