『C++ - STL』之优先级队列( priority_queue )

简介: 『C++ - STL』之优先级队列( priority_queue )

前言

什么是优先级队列,从该名中可以知道他一定有队列的一定属性,即先入先出(LILO),而这里的优先级则可以判断出它的另一个特点就是可以按照一定的条件将符合该条件的先进行出队,这就是优先级队列;

而在数据结构中有一个支持该操作的结构 - 堆( heap );

而在STL中,这个优先级队列( priority_queue )也正是堆;


优先级队列的结构

既然优先级队列的结构是堆,那想必结构上也不难;

堆的结构是以顺序表为基础,从而实现完全二叉树的结构;

从该容器的接函数接口中也可以知道实际上它就是个堆;


优先级队列的模拟实现

优先级队列priority_queue为一个类模板容器;

且同STL中的栈stack与队列queue一样都为适配器模式的容器,即以某个容器为基础;

template <class T, class Container = vector<T>,class Compare = less<typename Container::value_type> > class priority_queue;

其模板参数有三个分别为:

  • class T
    容器所存储的数据类型 T ;
  • class Container = vector<T>
    容器适配器且定缺省参数默认为 vector< T >;
  • class Compare = less<typename Container::value_type>
    一个用来比较大小的仿函数,给定缺省参数默认为 less ,该仿函数在标准库std中;

根据文档中的信息来看,优先级队列主要的几个接口也正是数据结构中堆应有的结构;

#pragma once 
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
namespace my_priority{//命名空间
  template<class T,class Container = std::vector<T>>//暂未设置仿函数
    class priority_queue{//总体框架
 public:
        void push(const T& val){//增
          _con.push_back(val);
          adjust_up(_con.size()-1);
        }
        void pop(){
          std::swap( _con[_con.size()-1],_con[0]);//删
          _con.pop_back();
          adjust_down(0);
        }
        bool empty(){//判空
          return _con.empty();
        }
        size_t size(){//返回大小
          return _con.size();
        }
        const T& top()const{//返回堆顶
          assert(!_con.empty());
          return _con[0];
 }
        void swap(priority_queue& con){//交换
          if(_con!=con._con)
          _con.swap(con._con);
        }
      private:
  //必要函数 - 向上调整&&向下调整
        void adjust_up(size_t child){
          size_t parent = child;
          while(parent>0){
            parent = (child-1)/2;
            if(_con[parent]<_con[child]){
              std::swap(_con[parent],_con[child]);
            }
            child = parent;
          }
        }
        void adjust_down(size_t parent){
          Compare comfunc;
          size_t child = parent*2+1;
          while(child<_con.size()){
            if(child+1<_con.size()&&_con[child]<_con[child+1]){
              ++child;
            }if(_con[parent]<_con[child]){
              std::swap(_con[parent],_con[child]);
            }
            parent = child;
            child = parent*2+1;
          }
        }
        Container _con; //容器适配器所实例化的对象,当前代码为vector<int>
    };

但是在库中,模板参数共有三个,具体的第三个仿函数到底是什么?


仿函数

仿函数,也被称为函数对象;

即一个可以使用函数功能的类,本质上就是在类中重载了operator();

举个简单的例子,当我们想要写一个能将两个数进行相加的仿函数即可以这么写;

struct Add{
  int operator()(int a,int b){
    return a+b;
  } 
}
int main()
{
  Add addfunc;
  int ret = addfunc(1,2);//仿函数的调用;
  ret = Add() (1,2);//利用匿名对象;
  return 0;
}

同理,也可以根据该方法写一个比较两个对象大小的仿函数;

为了能接受多种类型的数据进行比较也可以将其设置为类模板;

template<class T>
struct less{
  bool operator()(const T& a,const T& b){
    return a<b;
  }

这也就是在实现当中缺失的模板参数,仿函数;

由于优先级队列priority_queue的大小根堆属性是由其中的向上调整算法adjust_up与向下调整算法adjust_down来决定的(建堆以及堆的调整);所以只要将对应的比较大小><换成仿函数即可;


最终代码

#pragma once 
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
namespace my_priority{
  template<class T>
    struct less{
      bool operator()(const T& v1,const T& v2){
        return v1<v2;
      }
    };
  template<class T>
    struct greater{
      bool operator()(const T& v1,const T& v2){
        return v1>v2;
      }
    };
  template<class T,class Container = std::vector<T> ,class Compare = less<T>>
    class priority_queue{
 public:
        void push(const T& val){
          _con.push_back(val);
          adjust_up(_con.size()-1);
        }
        void pop(){
          std::swap( _con[_con.size()-1],_con[0]);
          _con.pop_back();
          adjust_down(0);
        }
        bool empty(){
          return _con.empty();
        }
        size_t size(){
          return _con.size();
        }
        const T& top()const{
          assert(!_con.empty());
          return _con[0];
 }
        void swap(priority_queue& con){
          if(_con!=con._con)
          _con.swap(con._con);
        }
      private:
        void adjust_up(size_t child){
          Compare comfunc;
          size_t parent = child;
          while(parent>0){
            parent = (child-1)/2;
            if(comfunc(_con[parent],_con[child])){
              std::swap(_con[parent],_con[child]);
            }
            child = parent;
          }
        }
        void adjust_down(size_t parent){
          Compare comfunc;
          size_t child = parent*2+1;
          while(child<_con.size()){
            if(child+1<_con.size()&&comfunc(_con[child],_con[child+1])){
              ++child;
            }if(comfunc(_con[parent],_con[child])){
              std::swap(_con[parent],_con[child]);
            }
            parent = child;
            child = parent*2+1;
          }
        }
        Container _con;
    };
相关文章
|
7天前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
123 75
|
7天前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
31 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
7天前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
32 9
|
7天前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
26 7
|
18天前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
21 1
|
30天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
48 7
|
1月前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
39 0
|
7天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
44 18
|
7天前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
32 13
|
7天前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
26 5

热门文章

最新文章