C++初阶 priority_queue(优先级队列)的使用和模拟实现

简介: C++初阶 priority_queue(优先级队列)的使用和模拟实现

priority_queue的使用


priority_queue的介绍


优先级队列(priority queue)

是0个或多个元素的集合,每个元素都有一个优先权,对优先级队列执行的操作有(1)查找(2)插入一个新元素(3)删除一般情况下,查找操作用来搜索优先权最大的元素,删除操作用来删除该元素。

对于优先权相同的元素,可按先进先出次序处理或按任意优先权进行。


这里还是用简单的语言来描述下


我们可以将优先级队列想象成一个堆(默认是大堆)


priority_queue的定义


它的定义包括下面的三个参数


priority_queue<int, vector<int>,less<int> > q1;


我们通过观察可以发现 这里的三个参数代表的分别是


存储的数据类型 存储的容器类型 比较的方式


其中存储容器的类型和比较方式都是可以省略的

(默认是 vector 和 less)


所以说可以衍生出下面的两种定义方式


priority_queue<int, vector<int>> q2;
priority_queue<int> q3;


实际上来说它们定义的优先级队列是相同的


tips : 这里的优先级队列设计的有点奇怪


less对应的是大堆 而greater则对应的是小堆


priority_queue的使用


接口函数如下

95bac41e9a354fc8baf45826601f1e44.png


使用代码如下


void test_priority_queue()
{
  priority_queue<int, vector<int>, less<int> > q1;
  q1.push(1);
  q1.push(4);
  q1.push(9);
  q1.push(3);
  q1.push(7);
  while (!q1.empty())
  {
  cout << q1.top() << endl;
  q1.pop();
  }
}

这段代码的意思呢 就是我们随机插入几个数字


然后一个个打印出来 看看打印出来的顺序是什么?


显示结果如下


122f6494cb7b42c583697740d5c855e4.png

我们可以发现 它们在优先级队列中实际上就是按照从大到小的顺序


那么如果我们想到将它变成从小到大呢?


是不是改变下比较的仿函数就可以了

1ed5b375f74044728e655a674644613d.png


我们可以发现 变成greater之后就会是从小到大排序了


priority_queue的题目

9cdf73b50d30413f9fd837c7487b2c25.png

就像这个问题 我们就可以使用我们的优先级队列来解决(堆)


因为大堆的顶部一定是最大的数字 所以说我们只需要删除掉前面top(k -1)个元素 之后我们就能得到我们第k大的数了


代码表示如下


class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        // 我们使用优先级队列来做
        // 因为要求第k大的数字  所以说我们只要建立一个大堆
        // 然后pop掉前面k-1个数字 最后的一个数字就是我们要求的了
        priority_queue<int> q1;
        for (auto x : nums)
        {
            q1.push(x);
        }
        // 之后删掉前面k-1个数 就是第k大个数在最前面了
        while(k-1)
        {
            q1.pop();
            k--;
        }
        return q1.top();
    }
};

运行结果如下


7ff0247f70724a15a6259e3160e5969c.png


priority_queue的模拟实现


我们前面已经说过了 priority_queue的底层其实就是堆


所以说要我们实现一个优先级队列和实现一个堆 其实是差不多的


注意! 博主下面的实现全部以大堆为例


堆的向上调整

30be4da6a5654cc58fc6230044e35996.png


主要用于我们建堆的时候


我们在堆的末尾插入一个元素


由于我们的堆是大堆的缘故 所以说父节点 一定是大于子节点的


这个时候我们就需要判断 我们的父节点是否确实大于我们的子节点


如果大于 我们就交换它们的位置 之后重复这个过程


void adjust_up(vector<int>& v ,int child)
{
  // 首先找出孩子的父节点
  int father = (child - 1) / 2;
  // 当子节点不为根节点时就一直判断
  while ( child > 0 )
  {
  if (v[child] > v[father])
  {
    swap(v[child], v[father]);
    child = father;
    father = (child - 1) / 2;
  }
  else
  {
    // 符合规则 直接跳出循环
    break;
  }
  }
}


堆的向下调整


向下调整通常用于删除数据的时候


同样的 我们如果直接删除大堆顶部的数据有点不太好操作


这个时候我们就可以将大堆顶部和底部的数据交换 之后删除底部的数据


这个时候我们只需要让顶部的元素 向下进行调整就好了


代码表示如下


void adjust_down(vector<int>& v, int father)
{
  int child = 2 * father + 1;//左孩子
  // 开始循环 直到父亲没有孩子 或者符合小堆结构位置
  while (child < v.size())
  {
  // 首先判断右孩子是否存在并且右孩子是否大于左孩子
  // 因为要是比较大的那个做父亲
  if (child + 1 < v.size() && v[child+1] > v[child])
  {
    child++;
  }
  // 之后就判断 孩子是否大于父亲 如果大于就交换并且继续比较 如果不大于就结束
  if (v[child] > v[father])
  {
    swap(v[child], v[father]);
    father = child;
    child = 2 * father + 1;
  }
  else
  {
    break;
  }
  }
}

仿函数的实现


两个很简单的仿函数 operator()重载


这两个函数比较特殊 我们先看代码

struct Less
{
  template<class T>
  bool operator() (T& x, T& y)
  {
  return x < y;
  }
};
struct Greater
{
  template<class T>
  bool operator() (T& x, T& y)
  {
  return x > y;
  }
};


我们可以看到 它们实际上就是重载了一个括号运算符来判断大小而已


priority_queue模拟实现
namespace shy //防止命名冲突
{
  //比较方式(使内部结构为大堆)
  template<class T>
  struct less
  {
  bool operator()(const T& x, const T& y)
  {
    return x < y;
  }
  };
  //比较方式(使内部结构为小堆)
  template<class T>
  struct greater
  {
  bool operator()(const T& x, const T& y)
  {
    return x > y;
  }
  };
  //优先级队列的模拟实现
  template<class T, class Container = vector<T>, class Compare = less<T>>
  class priority_queue
  {
  public:
  //堆的向上调整
  void AdjustUp(int child)
  {
    int parent = (child - 1) / 2; //通过child计算parent的下标
    while (child > 0)//调整到根结点的位置截止
    {
    if (_comp(_con[parent], _con[child]))//通过所给比较方式确定是否需要交换结点位置
    {
      //将父结点与孩子结点交换
      swap(_con[child], _con[parent]);
      //继续向上进行调整
      child = parent;
      parent = (child - 1) / 2;
    }
    else//已成堆
    {
      break;
    }
    }
  }
  //插入元素到队尾(并排序)
  void push(const T& x)
  {
    _con.push_back(x);
    AdjustUp(_con.size() - 1); //将最后一个元素进行一次向上调整
  }
  //堆的向下调整
  void AdjustDown(int n, int parent)
  {
    int child = 2 * parent + 1;
    while (child < n)
    {
    if (child + 1 < n&&_comp(_con[child], _con[child + 1]))
    {
      child++;
    }
    if (_comp(_con[parent], _con[child]))//通过所给比较方式确定是否需要交换结点位置
    {
      //将父结点与孩子结点交换
      swap(_con[child], _con[parent]);
      //继续向下进行调整
      parent = child;
      child = 2 * parent + 1;
    }
    else//已成堆
    {
      break;
    }
    }
  }
  //弹出队头元素(堆顶元素)
  void pop()
  {
    swap(_con[0], _con[_con.size() - 1]);
    _con.pop_back();
    AdjustDown(_con.size(), 0); //将第0个元素进行一次向下调整
  }
  //访问队头元素(堆顶元素)
  T& top()
  {
    return _con[0];
  }
  const T& top() const
  {
    return _con[0];
  }
  //获取队列中有效元素个数
  size_t size() const
  {
    return _con.size();
  }
  //判断队列是否为空
  bool empty() const
  {
    return _con.empty();
  }
  private:
  Container _con; //底层容器
  Compare _comp; //比较方式
  };
}


总结


优先级队列的模拟实现主要是运用到了堆的知识 对于目前的同学们来说还是比较简单的

相关文章
|
11月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
549 77
|
11月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
458 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
10月前
|
存储 算法 C++
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
592 1
|
11月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
245 9
|
11月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
258 7
|
缓存 安全 C++
C++无锁队列:解锁多线程编程新境界
【10月更文挑战第27天】
867 7
|
消息中间件 存储 安全
|
存储 设计模式 C++
【C++】优先级队列(容器适配器)
本文介绍了C++ STL中的线性容器及其适配器,包括栈、队列和优先队列的设计与实现。详细解析了`deque`的特点和存储结构,以及如何利用`deque`实现栈、队列和优先队列。通过自定义命名空间和类模板,展示了如何模拟实现这些容器适配器,重点讲解了优先队列的内部机制,如堆的构建与维护方法。
182 0
|
10月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
6月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
183 0