zoukankan      html  css  js  c++  java
  • C++ STL 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按照元素值从大到小进行排序,还可以使用std::greater按照元素值从小到大排序,但更多情况下是使用自定义的排序规则。

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

    创建priority_queue的几种方式

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

    # include <queue>
    using namespace std;
    

    创建 priority_queue 容器适配器的方法,大致有以下几种。

      1. 创建一个空的 priority_queue 容器适配器,第底层采用默认的 vector 容器,排序方式也采用默认的 std::less 方法:
    std::priority_queue<int> values;
    
    1. 可以使用普通数组或其它容器中指定范围内的数据,对 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 会自动对它们进行排序。

    1. 还可以手动指定 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 和 std::greater 适用的场景是有限的,更多场景中我们会使用自定义的排序规则。

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

    priority_queue提供的成员函数

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

    成员函数 功能
    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& 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 中其它成员函数的用法也非常简单,这里不再给出具体示例,后续章节用法会做具体介绍。

  • 相关阅读:
    博客园
    未释放的已删除文件
    ssh连接缓慢
    剑指 Offer 38. 字符串的排列
    剑指 Offer 37. 序列化二叉树
    剑指 Offer 50. 第一个只出现一次的字符
    剑指 Offer 36. 二叉搜索树与双向链表
    剑指 Offer 35. 复杂链表的复制
    剑指 Offer 34. 二叉树中和为某一值的路径
    剑指 Offer 33. 二叉搜索树的后序遍历序列
  • 原文地址:https://www.cnblogs.com/harrytsz520/p/13154835.html
Copyright © 2011-2022 走看看