STL之priority_queue(揭秘C++优先队列:高效管理数据优先级)

priority_queue的介绍:

 

 翻译:

优先队列是一种容器适配器(Container Adaptor),其设计遵循严格弱序(Strict Weak Ordering)原则,确保队列的首个元素始终是所有元素中的最大值。

这一特性与堆(Heap)数据结构高度相似:元素可以随时插入队列,而只能检索到最大元素(即优先队列顶部的元素)。

优先队列通过封装特定容器类的对象作为底层容器(Underlying Container)来实现,并提供一组特定的成员函数访问元素。元素从底层容器的 "尾部" 弹出,该位置对应优先队列的顶部。

底层容器可以是任何标准容器类模板或其他专门设计的容器类,但必须满足以下条件:

  1. 支持随机访问迭代器(Random Access Iterators)
  2. 提供以下操作:
    • empty ():判断容器是否为空
    • size ():返回容器大小
    • front ():访问首个元素
    • push_back ():在尾部插入元素
    • pop_back ():删除尾部元素

标准容器 vector 和 deque 完全满足这些要求。默认情况下,如果未显式指定底层容器,优先队列将使用 vector 作为其底层实现。

优先队列通过随机访问迭代器在内部维护一个堆结构。当需要调整堆结构时,容器适配器会自动调用以下算法函数:

  • make_heap ():构造堆结构
  • push_heap ():插入元素后调整堆
  • pop_heap ():删除元素前调整堆

这种设计确保优先队列在插入和删除操作后,始终保持堆的性质,从而高效地维护元素的优先级顺序。

可以看到模板第一个class T,是数据类型

第二个class Container=vactor<T>,这是一个默认以vector为底层容器

第三个class Compare=less<>,这是一个默认以系统实现的仿函数(后面讲解)(默认大堆)

priority_queue的使用

 成员函数:

top():返回堆顶数据

pop():删除堆顶数据 

其他通过前面的学习也知道

理解仿函数:

通过观察,可以发现默认是一个大堆 

我们可以改掉仿函数,因为默认使用了less

此时是一个小堆,堆顶是最小的数据(如果没有学过堆,可以去看我数据结构一栏的讲解)

接下来我们讲解仿函数less和greater 

这两个函数在<functional>的头文件中

可以发现这就是一个类

也就是less是一个类名,它重载了一个operator(),注意返回的是x<y (第一个小于第二个就返true)

如果这是一个类,你是否可以这样使用less<int> funless;funless(1,2),显然这是可以的 

 

为什么叫仿函数???

如果单独看funless,你是以为它是一个函数,还是一个类对象呢???

所以仿函数:在 C++ 中,仿函数(Functor) 也称为 函数对象(Function Object),是一种行为类似函数的对象。它通过重载 operator() 运算符,使得对象可以像函数一样被调用。 、

那greater又是怎么一回事???(相信已经可以联想到了)

返回的是x>y(只要x>y就返回true) 

是否可以自己动手写一个仿函数后传进去???

显然这是可以的

 

如何理解priority_queue中的仿函数调用??? 

 再次回顾参数,可以看到class Compare=less<typename Container::value_type>

现在重新看是否理解不一样,less<这里面是底层容器的数据类型>

Compare默认使用less

所以你在模拟实现的时候可以先创建一个仿函数类的成员,或者你可以直接构建一个类对象

比如你在成员那里直接Compare _com;或者每次调用的地方在创建类对象也行

 比如这种直接设成成员,那你后面用的时候可以直接_com(),传两个参数即可

_com就是类对象,当你调用_com()的时候会调类对象的operator()

模拟实现priority_queue

June: 这里包含我的c++和Linux及数据结构https://gitee.com/taifanshu/day2.git已上传gitee,需要可以自己取

总结:

 适配器都不支持迭代器遍历,因为需要保持自身的一些特性

但是底层容器可以支持,只是适配器不对外设接口

优先级队列底层就是一个堆,在解决堆问题时可以用优先级队列

<think>我们正在讨论C++ STL中的priority_queue如何支持自定义数据类型和比较函数。根据引用[1][2][4][5],priority_queue是一个容器适配器,它默认使用vector作为底层容器,并使用堆算法来维护元素的优先级priority_queue有三个模板参数: - T: 元素类型 - Container: 底层容器类型,默认为vector<T> - Compare: 比较函数对象类型,默认为less<typename Container::value_type>(即默认最大堆) 要使用自定义数据类型和比较函数,我们需要提供比较规则,通常有两种方式: 1. 在自定义类型中重载小于运算符(<),这样默认的less就可以使用。 2. 自定义一个函数对象(仿函数)并作为第三个模板参数传递。 下面详细说明: ### 1. 自定义数据类型 假设我们有一个自定义类型`Person`,它包含姓名和年龄。我们想要根据年龄来建立优先级队列(年龄大的优先级高,即最大堆)。 方法一:重载小于运算符(<) 在自定义类型中重载<运算符,使得在堆中比较时,年龄大的被视为“大”。注意:默认的priority_queue是最大堆,它使用less,而less会调用<运算符。如果我们希望年龄大的在堆顶,那么应该定义:当a的年龄小于b的年龄时,a<b成立。这样,堆顶就是最大的元素(年龄最大)。 方法二:使用自定义比较仿函数 我们可以定义一个函数对象(即重载了operator()的类),然后将这个类作为priority_queue的第三个模板参数。 ### 2. 实现方法 #### 方法一:重载<运算符 ```cpp #include <iostream> #include <queue> #include <string> #include <vector> using namespace std; class Person { public: string name; int age; Person(string n, int a) : name(n), age(a) {} // 重载<运算符:注意,在最大堆中,我们希望年龄大的排在前面,所以这里定义的是:当a的年龄小于b的年龄时,a<b bool operator<(const Person& other) const { return this->age < other.age; } }; int main() { // 默认使用less<Person>,而less<Person>会调用Person的<运算符 priority_queue<Person> pq; pq.push(Person("Alice", 30)); pq.push(Person("Bob", 20)); pq.push(Person("Charlie", 40)); // 顶部应该是Charlie,因为年龄最大 cout << "Top: " << pq.top().name << " (age: " << pq.top().age << ")" << endl; return 0; } ``` #### 方法二:自定义仿函数(函数对象) 如果我们不想重载<运算符,或者希望有多个不同的比较规则(比如有时按年龄最小堆,有时按最大堆),那么可以自定义仿函数。 例如,我们定义一个按年龄从小到大排序的比较规则(最小堆): ```cpp #include <iostream> #include <queue> #include <string> #include <vector> using namespace std; class Person { public: string name; int age; Person(string n, int a) : name(n), age(a) {} }; // 自定义比较仿函数:最小堆(年龄小的优先级高) struct CompareByAgeAsc { bool operator()(const Person& a, const Person& b) const { // 注意:在优先队列中,我们比较的是“优先级”,优先级高的在堆顶。 // 但是,在最小堆中,我们定义:当a的年龄大于b的年龄时,a的优先级低于b,所以返回true(表示a应该在b的下面) // 因为STL优先队列默认是最大堆,而我们通过这个比较器反转了顺序。 return a.age > b.age; } }; int main() { // 指定第三个模板参数为CompareByAgeAsc,并且由于我们改变了比较器,所以第二个参数(底层容器)也需要显式指定 priority_queue<Person, vector<Person>, CompareByAgeAsc> pq; pq.push(Person("Alice", 30)); pq.push(Person("Bob", 20)); pq.push(Person("Charlie", 40)); // 顶部应该是Bob,因为年龄最小 cout << "Top: " << pq.top().name << " (age: " << pq.top().age << ")" << endl; return 0; } ``` 注意:在自定义仿函数时,我们返回的是`a.age > b.age`,这是因为在STL优先队列中,比较函数的意义是:如果第一个参数应该排在第二个参数的后面(即优先级更低),则返回true。在最小堆中,我们想要年龄小的在堆顶(优先级高),所以当a.age大于b.age时,a的优先级低于b,所以返回true。 ### 3. 使用lambda表达式作为比较器(C++11以后) 在C++11中,我们也可以使用lambda表达式,但是lambda表达式类型需要通过decltype获取,并且需要将lambda作为构造函数的参数。由于priority_queue的模板参数需要类型,而lambda的类型是一个匿名类型,所以我们可以使用以下方法: ```cpp // 使用lambda表达式作为比较器 auto cmp = [](const Person& a, const Person& b) { return a.age > b.age; // 最小堆 }; // 注意:我们需要将lambda的类型作为模板参数,并将lambda对象作为构造函数的参数 // 由于lambda表达式默认是匿名类型,我们需要用decltype(cmp)来获取类型 priority_queue<Person, vector<Person>, decltype(cmp)> pq(cmp); ``` 但是需要注意的是,如果lambda捕获了变量,那么就不能直接使用,因为优先队列要求比较器必须是可拷贝构造的。无捕获的lambda可以转换为函数指针,但是这里我们使用decltype来推导类型,所以无捕获的lambda可以这样用。 ### 总结 - 对于自定义数据类型,我们可以通过重载<运算符来使用默认的最大堆。 - 如果需要更灵活的比较方式,可以自定义仿函数(函数对象)并作为priority_queue的第三个模板参数。 - 在C++11中,可以使用lambda表达式,但需要显式指定类型(通过decltype)并将lambda对象传给构造函数。 这些方法使得STLpriority_queue可以灵活地处理自定义数据类型和不同的排序规则。 [^1][^2][^4][^5] ### 相关问题 1. 如何在priority_queue中存储自定义数据类型的指针,并自定义比较函数? 2. 使用lambda表达式作为比较器时,如果lambda捕获了变量,该如何处理? 3. priority_queue的底层实现原理是什么?它如何保证插入和删除操作的时间复杂度为$O(\log n)$? 4. 除了自定义仿函数,还有哪些方法可以改变priority_queue的排序顺序(如最小堆)? </think> ### C++ STLpriority_queue支持自定义数据类型和比较函数的方法 在C++ STL中,`priority_queue`通过模板参数和仿函数(函数对象)支持自定义数据类型和比较规则。以下是具体实现方法: --- #### 1. **自定义数据类型的实现** **核心步骤**: 1. 定义自定义数据类型(结构体/类) 2. 提供比较规则(重载运算符或自定义仿函数) 3. 声明`priority_queue`时指定比较规则 **示例代码**: ```cpp #include <iostream> #include <queue> #include <string> // 自定义数据类型 struct Person { std::string name; int age; Person(std::string n, int a) : name(n), age(a) {} }; // 方法1:重载<运算符(最大堆) bool operator<(const Person& p1, const Person& p2) { return p1.age < p2.age; // 按年龄降序 } int main() { // 使用重载运算符 std::priority_queue<Person> pq1; pq1.push(Person("Alice", 30)); pq1.push(Person("Bob", 25)); std::cout << pq1.top().name; // 输出Alice(年龄最大) } ``` --- #### 2. **自定义比较函数的实现** **方法1:使用仿函数(推荐)** ```cpp // 自定义比较仿函数(最小堆) struct CompareAge { bool operator()(const Person& a, const Person& b) { return a.age > b.age; // 注意:>号实现最小堆 } }; int main() { // 声明优先队列时指定仿函数 std::priority_queue<Person, std::vector<Person>, CompareAge> pq2; pq2.push(Person("Alice", 30)); pq2.push(Person("Bob", 25)); std::cout << pq2.top().name; // 输出Bob(年龄最小) } ``` **方法2:使用Lambda表达式(C++11+)** ```cpp auto cmp = [](const Person& a, const Person& b) { return a.name > b.name; // 按名字母序降序 }; int main() { // 需显式指定容器类型和比较器类型 std::priority_queue<Person, std::vector<Person>, decltype(cmp)> pq3(cmp); pq3.push(Person("Alice", 30)); pq3.push(Person("Bob", 25)); std::cout << pq3.top().name; // 输出Bob(B在A前) } ``` --- #### 3. **关键注意事项** 1. **比较函数语义**: - 返回`true`表示第一个参数优先级**低于**第二个参数 - 最大堆使用`<`(默认),最小堆使用`>`[^4][^5] 2. **模板参数顺序**: ```cpp priority_queue<T, Container, Compare> // T: 元素类型 // Container: 底层容器(默认vector) // Compare: 比较器类型(默认less<T>) ``` 3. **性能特性**: - 插入/删除时间复杂度:$O(\log n)$ - 构建堆时间复杂度:$O(n)$ --- #### 4. **完整示例:按名字母序排序** ```cpp #include <iostream> #include <queue> #include <vector> #include <algorithm> struct Student { std::string id; double gpa; Student(std::string i, double g) : id(i), gpa(g) {} }; // 自定义比较器:按GPA升序(最小堆) struct CompareGPA { bool operator()(const Student& a, const Student& b) { return a.gpa > b.gpa; } }; int main() { std::priority_queue<Student, std::vector<Student>, CompareGPA> pq; pq.push(Student("S001", 3.7)); pq.push(Student("S002", 4.0)); pq.push(Student("S003", 3.5)); // 输出:S003(3.5) -> S001(3.7) -> S002(4.0) while (!pq.empty()) { auto s = pq.top(); std::cout << s.id << "(" << s.gpa << ") "; pq.pop(); } } ``` --- ### 总结 1. **自定义数据类型**:需提供比较规则(运算符重载或仿函数) 2. **比较器实现**: - 仿函数通过`operator()`定义比较逻辑 - Lambda表达式需结合`decltype`使用 3. **堆类型控制**: - 最大堆:`less<T>` 或 `operator<` - 最小堆:`greater<T>` 或 仿函数中使用`>` 此机制使`priority_queue`能灵活处理各类场景,如任务调度(按优先级)、图算法(如Dijkstra)等[^2]。 --- ### 相关问题 1. 如何在`priority_queue`中存储智能指针并自定义比较逻辑? 2. 使用Lambda比较器时出现捕获列表错误该如何解决? 3. `priority_queue`的底层堆实现如何保证$O(\log n)$的插入/删除效率? 4. 除了仿函数,还有哪些方法实现自定义比较规则(如函数指针)?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值