【C++入门到精通】C++入门 —— priority_queue(STL)优先队列

简介: 本文介绍了C++的STL组件`std::priority_queue`,它是一个容器适配器,实现优先队列数据结构,通常基于堆实现。文章讨论了优先队列的基本概念、特点和底层堆结构,强调了其自动排序和优先级最高元素的访问。还展示了如何定义、插入、访问和移除元素,以及自定义比较函数。此外,提供了模拟实现`priority_queue`的代码示例,探讨了仿函数的作用,包括默认的`std::less`和自定义比较函数。文章鼓励读者进一步探索C++的优先队列及其应用。

@​​TOC​
前言
⭕文章绑定了VS平台下std::priority_queue的源码,大家可以下载了解一下😍
前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的命名空间的一些知识点以及关于C++的缺省参数、函数重载,引用 和 内联函数也认识了什么是类和对象以及怎么去new一个 ‘对象’ ,以及学习了几个STL的结构也相信大家都掌握的不错,接下来博主将会带领大家继续学习有关C++比较重要的知识点—— priority_queue(STL)优先队列。下面话不多说坐稳扶好咱们要开车了😍
一、priority_queue简介
⭕​​官方文档​​

  1. 概念
    ​​priority_queue​​是C++标准库中的一个容器适配器(container adapter),用于实现优先队列(priority queue)的数据结构。优先队列是一种特殊的队列,其中的元素按照一定的优先级进行排序,每次取出的元素都是优先级最高的。它的底层实现通常使用堆(heap)数据结构。
    在C++中,​​priority_queue​​​模板类定义在​​​​头文件中,可以通过指定元素类型和比较函数来创建不同类型的优先队列。比较函数用于确定元素的优先级,可以是函数指针、函数对象或Lambda表达式。
    ⭕需要注意的是,默认情况下,​​priority_queue​​​使用​​std::less​​​作为比较函数,即元素的优先级按照从大到小的顺序排列。如果需要按照从小到大的顺序排列,可以使用​​std::greater​​作为比较函数。
  2. 特点
  3. 优先级排序:​​priority_queue​​中的元素按照一定的优先级进行排序。默认情况下,元素的优先级按照从大到小的顺序排列,也可以通过自定义的比较函数来指定不同的排序方式。
  4. 自动排序:在插入元素时,​​priority_queue​​会根据元素的优先级自动进行排序。每次插入新元素时,都会将新元素放置在正确的位置上。
  5. 取出优先级最高元素:​​priority_queue​​提供了一种方便的方式来取出优先级最高的元素。使用​​top()​​函数可以访问优先级最高的元素,而使用​​pop()​​函数可以将该元素从队列中移除。
  6. 底层实现采用堆:​​priority_queue​​通常使用堆(heap)数据结构来实现。堆是一种具有特定性质的二叉树,可以高效地插入新元素和取出优先级最高的元素。
  7. 动态大小:​​priority_queue​​的大小可以根据需要进行动态调整。可以随时插入新元素和删除已有元素,并在必要时自动重新排序。
    ⭕需要注意的是,​​priority_queue​​并不支持直接访问和修改除了优先级最高元素外的其他元素。如果需要对特定元素进行操作,通常需要先将其取出,然后再进行操作,最后再将其放回优先队列中。
    二、priority_queue使用
  8. 基本操作
  9. 包含头文件:首先,在使用​​priority_queue​​​之前,你需要包含​​​​头文件。

    include 2. 定义容器和比较函数:然后,你需要定义一个​​priority_queue​​​对象,并指定元素类型和可选的比较函数(默认为​​std::less​​)。

    std::priority_queue, std::less> pq;上面的示例定义了一个存储整数的优先队列,使用​​std::less​​作为比较函数,以便元素按照从大到小的顺序排列。
  10. 插入元素:你可以使用​​push()​​​函数插入元素到​​priority_queue​​中。插入的元素会根据其优先级自动进行排序。
    pq.push(3);
    pq.push(1);
    pq.push(4);
    pq.push(1);在上面的示例中,我们依次插入了4个整数到优先队列中。
  11. 访问和移除元素:你可以使用​​top()​​​函数访问优先级最高的元素,使用​​pop()​​函数移除优先级最高的元素。
    std::cout << pq.top() << std::endl; // 访问优先级最高的元素
    pq.pop(); // 移除优先级最高的元素在上面的示例中,我们先输出了优先级最高的元素,然后将其从队列中移除。
  12. 检查队列是否为空:你可以使用​​empty()​​​函数来检查​​priority_queue​​是否为空。
    if (pq.empty()) {
    // 队列为空
    } else {
    // 队列不为空
    }下面的代码,演示了如何使用​​priority_queue​​:

    include

    include

int main() {
std::priority_queue, std::less> pq;

pq.push(3);
pq.push(1);
pq.push(4);
pq.push(1);

while (!pq.empty()) {
    std::cout << pq.top() << " ";
    pq.pop();
}

return 0;

}输出结果为:4 3 1 1
在上面的代码中,我们创建了一个存储整数的优先队列​​pq​​​,并依次插入了4个元素。然后,我们使用​​top()​​​函数和​​pop()​​​函数访问和移除元素,最后使用​​empty()​​函数检查队列是否为空。

  1. 底层结构
    ​​priority_queue​​​的底层通常使用堆(heap)数据结构来实现。堆是一种二叉树的数据结构​​(堆,数据结构详细介绍链接)​​,具有以下特点:
  2. 堆结构是一个完全二叉树(Complete Binary Tree),即除了最后一层外,其他层都必须是满的,且最后一层的结点都靠左排列。
  3. 二叉堆分为最大堆(Max Heap)和最小堆(Min Heap)两种类型。
    • 最大堆:每个父节点的值都大于或等于其子节点的值,即根节点的值最大。
    • 最小堆:每个父节点的值都小于或等于其子节点的值,即根节点的值最小。
    在​​priority_queue​​中,默认情况下采用最大堆实现,即优先级最高的元素存储在根节点,根节点的值最大。根据堆的性质,保证了在插入元素时,优先队列会根据元素的优先级进行自动排序,并在取出元素时能够取出优先级最高的元素。
    三、priority_queue模拟实现
    ⭕ C++代码

    pragma once

    include

    using namespace std;

    include

    namespace yws
    {
    template, class Compare = std::less>
    class priority_queue
    {
    public:

     priority_queue()
     {}
     template <class Inputinterpreator>
     priority_queue(Inputinterpreator first, Inputinterpreator last)
     {
         while (first != last)
         {
             _con.push_back(*first);
             ++first;
         }
         for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i)
         {
             adjust_down(i);
         }
     }
     void adjust_up(size_t child)
     {
         Compare com;
         size_t parent = (child - 1) / 2;
         while (child > 0)
         {
             if (com(_con[parent], _con[child]))
             {
                 std::swap(_con[child], _con[parent]);
             }
             else
             {
                 break;
             }
             child = parent;
             parent = (child - 1) / 2;
         }
     }
     void push(const T& x)
     {
         _con.push_back(x);
         adjust_up(_con.size() - 1);
    
     }
     void adjust_down(size_t parent)
     {
         Compare com;
         size_t child = (parent * 2) + 1;
         while (child < _con.size())
         {
             if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
             {
                 child = child + 1;
             }
             if (com(_con[parent], _con[child]))
             {
                 std::swap(_con[parent], _con[child]);
             }
             else
             {
                 break;
             }
             parent = child;
             child = (parent * 2) + 1;
         }
     }
     void pop()
     {
         std::swap(_con[0], _con[_con.size() - 1]);
         _con.pop_back();
    
         adjust_down(0);
     }
     const T& top()
     {
         return _con[0];
     }
     bool empty()  const
     {
         return _con.empty();
     }
     size_t size() const
     {
         return _con.size();
     }
    

    private:

     Container _con;
    

    };
    }⭕priority_queue中的仿函数
    在​​priority_queue​​中,仿函数用于比较元素的优先级,并根据其返回值确定它们在队列中的位置。默认情况下,​​priority_queue​​使用​​std::less​​作为仿函数,也就是将元素按照从大到小的顺序进行排序。
    你可以使用不同的仿函数来改变元素的排序方式。以下是一些常见的仿函数:
    • ​​std::less​​​:对于基本数据类型和自定义类型,默认使用​​<​​运算符进行比较,按照从大到小的顺序排序。
    • ​​std::greater​​​:对于基本数据类型和自定义类型,默认使用​​>​​运算符进行比较,按照从小到大的顺序排序。
    除了上述默认提供的仿函数外,你还可以自定义仿函数来实现自定义的元素比较规则。自定义仿函数需要满足严格弱排序(Strict Weak Ordering)的要求,即:
    • 比较关系必须是可传递的(transitive):对于任意元素a、b和c,如果a与b比较相等,b与c比较相等,则a与c比较也相等。
    • 比较关系不能是部分顺序(partial order):对于任意元素a和b,它们不能同时大于、小于或等于彼此。
    • 比较关系必须是可比较的(comparable):比较关系的结果必须对所有元素定义明确的大小关系。
    以下这段代码,演示了如何自定义一个仿函数来实现元素的自定义排序方式:

    include

    include

    include

struct Person {
std::string name;
int age;

Person(const std::string& n, int a) : name(n), age(a) {}

};

// 自定义仿函数
struct CompareByAge {
bool operator()(const Person& p1, const Person& p2) const {
return p1.age > p2.age; // 按照年龄从小到大排序
}
};

int main() {
std::priority_queue, CompareByAge> pq;

pq.push(Person("Alice", 25));
pq.push(Person("Bob", 30));
pq.push(Person("Charlie", 20));

while (!pq.empty()) {
    Person p = pq.top();
    pq.pop();
    std::cout << p.name << " - " << p.age << std::endl;
}

return 0;

}输出结果为:
Charlie - 20
Alice - 25
Bob - 30在上面的代码中,我们定义了一个名为​​CompareByAge​​​的结构体,重载了函数调用运算符​​operator()​​​,按照​​Person​​​对象的​​age​​​成员进行比较。然后,我们将​​CompareByAge​​​作为优先队列的仿函数类型,并插入3个​​Person​​对象到优先队列中。最后,我们按照自定义的排序方式依次取出元素并输出。
总结
优先队列是一种特殊的队列,其中存储的元素按照一定的优先级进行排列。在​​priority_queue​​中,优先级最高的元素能够快速被访问和删除。
首先,我们介绍了​​priority_queue​​的概念和特点。它是基于堆(heap)这种数据结构实现的,通常使用最大堆来进行内部排序。最大堆保证了根节点的值最大,并且任意节点的值大于或等于其子节点的值。这种特性使得优先队列能够高效地访问和删除具有最高优先级的元素。
接着,我们深入探讨了​​priority_queue​​的使用方法。基本操作包括插入元素、删除元素、访问元素和检查队列是否为空。
底层结构是​​priority_queue​​的关键部分,它通常使用堆来实现。在堆中,通过使用数组的索引来表示节点之间的关系,能够快速定位和操作元素。
最后,我们探讨了在​​priority_queue​​​中使用的仿函数。仿函数用于确定元素之间的优先级,决定元素在队列中的位置。默认情况下,​​priority_queue​​​使用​​std::less​​仿函数进行比较,对元素进行降序排列。你还可以选择其他仿函数或自定义仿函数来实现不同的排序方式。
温馨提示
感谢您对博主文章的关注与支持!在阅读本篇文章的同时,可以留下您宝贵的意见和反馈。如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!
再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!

目录
相关文章
|
5天前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
15 1
|
18天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
33 7
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
66 4
|
2月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
77 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
60 2
|
2月前
|
存储 算法 Linux
【c++】STL简介
本文介绍了C++标准模板库(STL)的基本概念、组成部分及学习方法,强调了STL在提高编程效率和代码复用性方面的重要性。文章详细解析了STL的六大组件:容器、算法、迭代器、仿函数、配接器和空间配置器,并提出了学习STL的三个层次,旨在帮助读者深入理解和掌握STL。
59 0
|
21天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
30 0
|
2月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
111 5
|
2月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
111 4
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
147 4