C++ STL priority_queue容器适配器究竟是如何实现高效优先级队列管理的?

2026-04-17 01:062阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2037个文字,预计阅读时间需要9分钟。

C++ STL priority_queue容器适配器究竟是如何实现高效优先级队列管理的?

priority_queue 容器适配器模拟的也是一种队列存储结构,它使用此容器存储元素。元素只能从一端(称为队尾)进入,从另一端(称为队头)退出,且每次只能访问队头元素。

priority_queue 容器适配器模拟的也是队列这种存储结构,即使用此容器适配器存储元素只能“从一端进(称为队尾),从另一端出(称为队头)”,且每次只能访问 priority_queue 中位于队头的元素。

但是,priority_queue 容器适配器中元素的存和取,遵循的并不是 “First in,First out”(先入先出)原则,而是“First in,Largest out”原则。直白的翻译,指的就是先进队列的元素并不一定先出队列,而是优先级最大的元素最先出队列。

注意,“First in,Largest out”原则是笔者为了总结 priority_queue 存取元素的特性自创的一种称谓,仅为了方便读者理解。

那么,priority_queue 容器适配器中存储的元素,优先级是如何评定的呢?很简单,每个 priority_queue 容器适配器在创建时,都制定了一种排序规则。根据此规则,该容器适配器中存储的元素就有了优先级高低之分。

举个例子,假设当前有一个 priority_queue 容器适配器,其制定的排序规则是按照元素值从大到小进行排序。根据此规则,自然是 priority_queue 中值最大的元素的优先级最高。

priority_queue 容器适配器为了保证每次从队头移除的都是当前优先级最高的元素,每当有新元素进入,它都会根据既定的排序规则找到优先级最高的元素,并将其移动到队列的队头;同样,当 priority_queue 从队头移除出一个元素之后,它也会再找到当前优先级最高的元素,并将其移动到队头。

基于priority_queue 的这种特性,因此该容器适配器有被称为优先级队列。

priority_queue 容器适配器“First in,Largest out”的特性,和它底层采用堆结构存储数据是分不开的。有关该容器适配器的底层实现,后续章节会进行深度剖析。

STL 中,priority_queue 容器适配器的定义如下:

template <typename T, typename Container=std::vector<T>, typename Compare=std::less<T> > class priority_queue{ //...... } 可以看到,priority_queue 容器适配器模板类最多可以传入 3 个参数,它们各自的含义如下:

  • typename T:指定存储元素的具体类型;
  • typename Container:指定 priority_queue 底层使用的基础容器,默认使用 vector 容器。

    作为 priority_queue 容器适配器的底层容器,其必须包含 empty()、size()、front()、push_back()、pop_back() 这几个成员函数,STL序列式容器中只有 vector 和 deque 容器符合条件。

  • typename Compare:指定容器中评定元素优先级所遵循的排序规则,默认使用std::less<T>按照元素值从大到小进行排序,还可以使用std::greater<T>按照元素值从小到大排序,但更多情况下是使用自定义的排序规则。

    其中,std::less<T> 和 std::greater<T> 都是以函数对象的方式定义在 <function> 头文件中。关于如何自定义排序规则,后续章节会做详细介绍。

创建priority_queue的几种方式

由于 priority_queue 容器适配器模板位于<queue>头文件中,并定义在 std 命名空间里,因此在试图创建该类型容器之前,程序中需包含以下 2 行代码:

#include <queue> using namespace std;
创建 priority_queue 容器适配器的方法,大致有以下几种。
1) 创建一个空的 priority_queue 容器适配器,第底层采用默认的 vector 容器,排序方式也采用默认的 std::less<T> 方法:

std::priority_queue<int> values;
2) 可以使用普通数组或其它容器中指定范围内的数据,对 priority_queue 容器适配器进行初始化:

//使用普通数组 int values[]{4,1,3,2}; std::priority_queue<int>copy_values(values,values+4);//{4,2,3,1} //使用序列式容器 std::array<int,4>values{ 4,1,3,2 }; std::priority_queue<int>copy_values(values.begin(),values.end());//{4,2,3,1} 注意,以上 2 种方式必须保证数组或容器中存储的元素类型和 priority_queue 指定的存储类型相同。另外,用来初始化的数组或容器中的数据不需要有序,priority_queue 会自动对它们进行排序。

3) 还可以手动指定 priority_queue 使用的底层容器以及排序规则,比如:

int values[]{ 4,1,2,3 }; std::priority_queue<int, std::deque<int>, std::greater<int> >copy_values(values, values+4);//{1,3,2,4} 事实上,std::less<T> 和 std::greater<T> 适用的场景是有限的,更多场景中我们会使用自定义的排序规则。

由于自定义排序规则的方式不只一种,因此这部分知识将在后续章节做详细介绍。

C++ STL priority_queue容器适配器究竟是如何实现高效优先级队列管理的?

priority_queue提供的成员函数

priority_queue 容器适配器提供了表 2 所示的这些成员函数。

表 2 priority_queue 提供的成员函数 成员函数 功能 empty() 如果 priority_queue 为空的话,返回 true;反之,返回 false。 size() 返回 priority_queue中存储元素的个数。 top() 返回 priority_queue中第一个元素的引用形式。 push(const T& obj) 根据既定的排序规则,将元素 obj 的副本存储到 priority_queue中适当的位置。 push(T&& obj) 根据既定的排序规则,将元素 obj 移动存储到 priority_queue中适当的位置。 emplace(Args&&... args) Args&&... args 表示构造一个存储类型的元素所需要的数据(对于类对象来说,可能需要多个数据构造出一个对象)。此函数的功能是根据既定的排序规则,在容器适配器适当的位置直接生成该新元素。 pop() 移除 priority_queue容器适配器中第一个元素。 swap(priority_queue<T>& other) 将两个 priority_queue 容器适配器中的元素进行互换,需要注意的是,进行互换的 2 个 priority_queue 容器适配器中存储的元素类型以及底层采用的基础容器类型,都必须相同。

和 queue 一样,priority_queue 也没有迭代器,因此访问元素的唯一方式是遍历容器,通过不断移除访问过的元素,去访问下一个元素。

下面的程序演示了表 2 中部分成员函数的具体用法:

#include <iostream> #include <queue> #include <array> #include <functional> using namespace std; int main() { //创建一个空的priority_queue容器适配器 std::priority_queue<int>values; //使用 push() 成员函数向适配器中添加元素 values.push(3);//{3} values.push(1);//{3,1} values.push(4);//{4,1,3} values.push(2);//{4,2,3,1} //遍历整个容器适配器 while (!values.empty()) { //输出第一个元素并移除。 std::cout << values.top()<<" "; values.pop();//移除队头元素的同时,将剩余元素中优先级最大的移至队头 } return 0; } 运行结果为:

4 3 2 1

表 2 中其它成员函数的用法也非常简单,这里不再给出具体示例,后续章节用法会做具体介绍。

本文共计2037个文字,预计阅读时间需要9分钟。

C++ STL priority_queue容器适配器究竟是如何实现高效优先级队列管理的?

priority_queue 容器适配器模拟的也是一种队列存储结构,它使用此容器存储元素。元素只能从一端(称为队尾)进入,从另一端(称为队头)退出,且每次只能访问队头元素。

priority_queue 容器适配器模拟的也是队列这种存储结构,即使用此容器适配器存储元素只能“从一端进(称为队尾),从另一端出(称为队头)”,且每次只能访问 priority_queue 中位于队头的元素。

但是,priority_queue 容器适配器中元素的存和取,遵循的并不是 “First in,First out”(先入先出)原则,而是“First in,Largest out”原则。直白的翻译,指的就是先进队列的元素并不一定先出队列,而是优先级最大的元素最先出队列。

注意,“First in,Largest out”原则是笔者为了总结 priority_queue 存取元素的特性自创的一种称谓,仅为了方便读者理解。

那么,priority_queue 容器适配器中存储的元素,优先级是如何评定的呢?很简单,每个 priority_queue 容器适配器在创建时,都制定了一种排序规则。根据此规则,该容器适配器中存储的元素就有了优先级高低之分。

举个例子,假设当前有一个 priority_queue 容器适配器,其制定的排序规则是按照元素值从大到小进行排序。根据此规则,自然是 priority_queue 中值最大的元素的优先级最高。

priority_queue 容器适配器为了保证每次从队头移除的都是当前优先级最高的元素,每当有新元素进入,它都会根据既定的排序规则找到优先级最高的元素,并将其移动到队列的队头;同样,当 priority_queue 从队头移除出一个元素之后,它也会再找到当前优先级最高的元素,并将其移动到队头。

基于priority_queue 的这种特性,因此该容器适配器有被称为优先级队列。

priority_queue 容器适配器“First in,Largest out”的特性,和它底层采用堆结构存储数据是分不开的。有关该容器适配器的底层实现,后续章节会进行深度剖析。

STL 中,priority_queue 容器适配器的定义如下:

template <typename T, typename Container=std::vector<T>, typename Compare=std::less<T> > class priority_queue{ //...... } 可以看到,priority_queue 容器适配器模板类最多可以传入 3 个参数,它们各自的含义如下:

  • typename T:指定存储元素的具体类型;
  • typename Container:指定 priority_queue 底层使用的基础容器,默认使用 vector 容器。

    作为 priority_queue 容器适配器的底层容器,其必须包含 empty()、size()、front()、push_back()、pop_back() 这几个成员函数,STL序列式容器中只有 vector 和 deque 容器符合条件。

  • typename Compare:指定容器中评定元素优先级所遵循的排序规则,默认使用std::less<T>按照元素值从大到小进行排序,还可以使用std::greater<T>按照元素值从小到大排序,但更多情况下是使用自定义的排序规则。

    其中,std::less<T> 和 std::greater<T> 都是以函数对象的方式定义在 <function> 头文件中。关于如何自定义排序规则,后续章节会做详细介绍。

创建priority_queue的几种方式

由于 priority_queue 容器适配器模板位于<queue>头文件中,并定义在 std 命名空间里,因此在试图创建该类型容器之前,程序中需包含以下 2 行代码:

#include <queue> using namespace std;
创建 priority_queue 容器适配器的方法,大致有以下几种。
1) 创建一个空的 priority_queue 容器适配器,第底层采用默认的 vector 容器,排序方式也采用默认的 std::less<T> 方法:

std::priority_queue<int> values;
2) 可以使用普通数组或其它容器中指定范围内的数据,对 priority_queue 容器适配器进行初始化:

//使用普通数组 int values[]{4,1,3,2}; std::priority_queue<int>copy_values(values,values+4);//{4,2,3,1} //使用序列式容器 std::array<int,4>values{ 4,1,3,2 }; std::priority_queue<int>copy_values(values.begin(),values.end());//{4,2,3,1} 注意,以上 2 种方式必须保证数组或容器中存储的元素类型和 priority_queue 指定的存储类型相同。另外,用来初始化的数组或容器中的数据不需要有序,priority_queue 会自动对它们进行排序。

3) 还可以手动指定 priority_queue 使用的底层容器以及排序规则,比如:

int values[]{ 4,1,2,3 }; std::priority_queue<int, std::deque<int>, std::greater<int> >copy_values(values, values+4);//{1,3,2,4} 事实上,std::less<T> 和 std::greater<T> 适用的场景是有限的,更多场景中我们会使用自定义的排序规则。

由于自定义排序规则的方式不只一种,因此这部分知识将在后续章节做详细介绍。

C++ STL priority_queue容器适配器究竟是如何实现高效优先级队列管理的?

priority_queue提供的成员函数

priority_queue 容器适配器提供了表 2 所示的这些成员函数。

表 2 priority_queue 提供的成员函数 成员函数 功能 empty() 如果 priority_queue 为空的话,返回 true;反之,返回 false。 size() 返回 priority_queue中存储元素的个数。 top() 返回 priority_queue中第一个元素的引用形式。 push(const T& obj) 根据既定的排序规则,将元素 obj 的副本存储到 priority_queue中适当的位置。 push(T&& obj) 根据既定的排序规则,将元素 obj 移动存储到 priority_queue中适当的位置。 emplace(Args&&... args) Args&&... args 表示构造一个存储类型的元素所需要的数据(对于类对象来说,可能需要多个数据构造出一个对象)。此函数的功能是根据既定的排序规则,在容器适配器适当的位置直接生成该新元素。 pop() 移除 priority_queue容器适配器中第一个元素。 swap(priority_queue<T>& other) 将两个 priority_queue 容器适配器中的元素进行互换,需要注意的是,进行互换的 2 个 priority_queue 容器适配器中存储的元素类型以及底层采用的基础容器类型,都必须相同。

和 queue 一样,priority_queue 也没有迭代器,因此访问元素的唯一方式是遍历容器,通过不断移除访问过的元素,去访问下一个元素。

下面的程序演示了表 2 中部分成员函数的具体用法:

#include <iostream> #include <queue> #include <array> #include <functional> using namespace std; int main() { //创建一个空的priority_queue容器适配器 std::priority_queue<int>values; //使用 push() 成员函数向适配器中添加元素 values.push(3);//{3} values.push(1);//{3,1} values.push(4);//{4,1,3} values.push(2);//{4,2,3,1} //遍历整个容器适配器 while (!values.empty()) { //输出第一个元素并移除。 std::cout << values.top()<<" "; values.pop();//移除队头元素的同时,将剩余元素中优先级最大的移至队头 } return 0; } 运行结果为:

4 3 2 1

表 2 中其它成员函数的用法也非常简单,这里不再给出具体示例,后续章节用法会做具体介绍。