【C++】优先级队列 priority_queue的使用&模拟实现 | 仿函数

简介: 【C++】优先级队列 priority_queue的使用&模拟实现 | 仿函数

一. 优先级队列的使用


头文件:<queue>


0a2653c851af460fa595bd959398a8f1.png


Container :默认情况下,它适配的是vector(因为要大量用到[]找下标)。理论上底层的容器可以是任何标准容器类模板,也可以是其他特定设计的容器类,但是必须支持随机访问迭代器访问,以及一系列基本接口。

Compare:默认情况下,大的优先级高(即默认是大堆),仿函数给的是less(这确实有点奇怪)。小堆需要传入仿函数类型,它的头文件在<functional>中

void test_priority_queue()
{
  //默认大的优先级高
  priority_queue<int> pq;
  pq.push(3);
  pq.push(1);
  pq.push(2);
  pq.push(5);
  pq.push(9);
  pq.push(0);
  while (!pq.empty())
  {
  cout << pq.top() << " ";
  pq.pop();
  }
  cout << endl;
  int a[] = { 3, 1, 5, 7, 4, 0, 3, 2 };
  priority_queue<int> heap(a, a + sizeof(a) / sizeof(int));//区间初始化
  while (!heap.empty())
  {
  cout << heap.top() << " ";
  heap.pop();
  }
  cout << endl;
}

0a2653c851af460fa595bd959398a8f1.png

多说无益,做一道题目上手:215. 数组中的第K个最大元素


方法一:建大堆(堆排) + pop (k-1)次取堆顶


时间复杂度:O(N+k*logN)


class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        //建堆  O(N)
        priority_queue<int> maxHeap(nums.begin(), nums.end());
        //O(logN* K)
        while(--k)
        {
            maxHeap.pop();
        }
        return maxHeap.top();
    }
};


一. priority_queue的模拟实现


priority_queue框架,他的底层是一个随机容器,模板第三个参数(仿函数)后面详谈——


🌈size & empty & top


const T& top()
  {
    return _con[0];
  }
  bool empty() const
  {
    return _con.empty();
  }
  size_t size() const
  {
    return _con.size();
  }


🌈仿函数


灵活控制的开关(仿函数):是排大堆还是小堆,总不可能改代码吧


我们写一个类,没有任何成员变量,重载了operator()运算符 ——


//仿函数/函数对象 ---- 类 重载了operator()
//类对象可以像函数一样去使用
namespace ljj
{
  template<class T>
  class less
  {
  public:
  bool operator()(const T& l, const T& r) const
  {
    return l < r;
  }
  };
  template<class T>
  class greater
  {
  public:
  bool operator()(const T& l, const T& r) const
  {
    return l > r;
  }
  };
}
  priority_queue<int>  heap(a, a + sizeof(a) / sizeof(int));
  priority_queue<int, vector<int>, greater<int>> heap(a, a + sizeof(a) / sizeof(int));


不同仿函数类型的对象,用()来调用对应的函数比较方式,就实现了像函数一样调用 ——


int main()
{
  ljj::less<int> lsFunc;
  cout << lsFunc(1, 2) << endl;
  //等价于下面
  //cout << lsFunc.operator()(1, 2) << endl;
  ljj::greater<int> gtFunc;
  cout << gtFunc(1, 2) << endl;
  return 0;
}


🧐优缺点:

🌍优点如下:


在同一时间里,由某个仿函数所代表的单一函数,可能有不同的状态(可以根据不同的类型代表不同的状态)


仿函数即使定义相同,也可能有不同的类型(可以有不同类型)


仿函数使程序代码变简单(仿函数在一定程度上使代码更通用,本质上简化了代码)


🌍缺点:


仿函数通常比一般函数速度慢


🎨push 和向上调整算法


优先级队列的插入及删除,就是在堆的基础上做插入删除,学到这我还回去复习了一下堆


堆插 = 尾插+ 向上调整


//插入 —— 向上调整
  void push(const T& x)
  {
  _con.push_back(x);
  adjust_up(_con.size()-1);
  }


🍅重头戏:向上调整算法 (看图5分钟内写出来,才可以)


0a2653c851af460fa595bd959398a8f1.png


//向上调整
void adjust_up(size_t child)
{
  Compare com;//仿函数
  int parent = (child - 1) / 2;
  while (child > 0)
  {
  //if ( _con[child] > _con[parent])
  if (com(_con[parent] , _con[child]))
  {
    std::swap(_con[child], _con[parent]);
    child = parent;
    parent = (child - 1) / 2;
  }
  else
  {
    break;
  }
  }
}


🎨pop 和向下调整算法


堆删 = 交换 + 删除 + 向下调整(不会破坏树的结构)


//交换 —— 删除 —— 向下调整
  void pop()
  {
  std::swap(_con[0], _con[_con.size() - 1]);
  _con.pop_back();
  adjust_down(0);
  }


💦向下调整算法


0a2653c851af460fa595bd959398a8f1.png


//高度次  logN  
void adjust_down(size_t parent)
{
  Compare com;//仿函数
  size_t child = parent * 2 + 1;//左孩子
  while (child < _con.size())
  {
  //左右孩子选大的交换
  //if (child + 1 < size() && _con[child + 1] > _con[child])
  if (child + 1 < size() && com(_con[child], _con[child + 1]))
  {
    ++child;
  }
  //if (_con[child] > _con[parent])
  if (com(_con[parent], _con[child]))
  {
    std::swap(_con[child], _con[parent]);
    parent = child;
    child = parent + 1;
  }
  else
  {
    break;
  }
  }
}


建大堆还是建小堆本质是由于ajust_up和ajust_down中的比较符号不同,那么就要通过仿函数来控制


0a2653c851af460fa595bd959398a8f1.png


构造函数

🌈 迭代器区间构造:_con自定义类型会调用它的迭代器区间构造,不用再迭代器遍历+push了。在这基础上还要构建成堆,为了使左右都是大(小)堆且向下调整是O(N),要倒着建,从最后一个非叶子节点(即最后一个节点的父亲)开始向下调整。


priority_queue()
{}
//迭代器区间构造
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
  while (first != last)
  {
  _con.push_back(*first);
  ++first;
  }
  //向下调整 建堆
  for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i)
  {
  adjust_down(i);
  }
}


如果T是自定义类型

⚡我们考虑到如果T 是日期类Date等 —— 要看情况


如果是类里面有支持比较大小的,就直接用 比如:string类

如果是库里面的、人家写好的类,我们只能写仿函数,我们不可能改人家的代码,如果是自己写的类,二者都可以(看情况)

但有些情况是必须写仿函数的,因为原生比较大小的行为不一定是我们想要的,比如:Date*比较大小,但是我们不想用指针比较,那就写仿函数

void test_priority_queue()
  {
  //priority_queue<Date> pq;
  priority_queue<Date, vector<Date>, greater<Date>> pq;
  pq.push(Date(2022, 3, 26));
  pq.push(Date(2022, 4, 1));
  pq.push(Date(2022, 2, 19));
  pq.push(Date(2022, 4, 10));
  while (!pq.empty())
  {
    cout << pq.top() << endl;
    pq.pop();
  }
  }


我们当然可以重载这些运算符


但是如果数据类型 不支持比较 或 比较的方式不是你想要的,可以自己实现仿函数,按照你想要的方式(Compare给我们留的口子)去控制比较逻辑,比如 —— 我想比较地址大小:Date*


void test_priority_queue3()
  {
  //priority_queue<Date*> pq; //默认比较地址大小
  //priority_queue<Date*, vector<Date*>, lessPDate> pq;
  priority_queue<Date*, vector<Date*>, greaterPDate> pq;
  pq.push(new Date(2022, 3, 26));
  pq.push(new Date(2022, 4, 1));
  pq.push(new Date(2022, 2, 19));
  pq.push(new Date(2022, 4, 10));
  //默认比较地址大小,若想比较日期大小,自己写仿函数
  while (!pq.empty())
  {
    cout << *pq.top() << endl;
    pq.pop();
  }
  }


于是我们自己写了仿函数,又因为私有成员类外无法访问,我们把这两个仿函数类声明为priority_queue的友元类 ——


struct lessPDate
  {
  bool operator()(const Date* d1,const Date* d2)
  {
    //return *d1 < *d2;
    return (d1->_year < d2->_year) ||
    (d1->_year == d2->_year && d1->_month < d2->_month) ||
    (d1->_year == d2->_year && d1->_month == d2->_month && d1->_day < d2->_day);
  }
  };
  struct greaterPDate
  {
  bool operator()(const Date* d1, const Date* d2)
  {
    //return *d1 > *d2;
    return (d1->_year > d2->_year) ||
    (d1->_year == d2->_year && d1->_month > d2->_month) ||
    (d1->_year == d2->_year && d1->_month == d2->_month && d1->_day > d2->_day);
  }
  };
相关文章
|
6天前
|
C++ 容器
【C++】仿函数在模板中的应用——【默认模板实参】详解(n)
【C++】仿函数在模板中的应用——【默认模板实参】详解(n)
|
6天前
|
存储 算法 C++
c++的学习之路:17、stack、queue与priority_queue
c++的学习之路:17、stack、queue与priority_queue
31 0
|
6天前
|
存储 算法 C语言
【C++入门到精通】C++入门 —— priority_queue(STL)优先队列
本文介绍了C++的STL组件`std::priority_queue`,它是一个容器适配器,实现优先队列数据结构,通常基于堆实现。文章讨论了优先队列的基本概念、特点和底层堆结构,强调了其自动排序和优先级最高元素的访问。还展示了如何定义、插入、访问和移除元素,以及自定义比较函数。此外,提供了模拟实现`priority_queue`的代码示例,探讨了仿函数的作用,包括默认的`std::less`和自定义比较函数。文章鼓励读者进一步探索C++的优先队列及其应用。
27 3
|
6天前
|
存储 算法 C++
C++初阶(十六)优先级队列
C++初阶(十六)优先级队列
27 0
|
6天前
|
编译器 C语言 C++
【C++进阶(七)】仿函数深度剖析&模板进阶讲解
【C++进阶(七)】仿函数深度剖析&模板进阶讲解
|
6天前
|
设计模式 C语言 C++
【C++进阶(六)】STL大法--栈和队列深度剖析&优先级队列&适配器原理
【C++进阶(六)】STL大法--栈和队列深度剖析&优先级队列&适配器原理
|
6天前
|
存储 算法 C++
【C++初阶】STL详解(九) priority_queue的使用与模拟实现
【C++初阶】STL详解(九) priority_queue的使用与模拟实现
23 0
|
6天前
|
存储 算法 C++
C++:stack、queue、priority_queue增删查改模拟实现、deque底层原理
C++:stack、queue、priority_queue增删查改模拟实现、deque底层原理
36 0
|
6天前
|
算法 C++ 容器
【C++练级之路】【Lv.10】【STL】priority_queue类和反向迭代器的模拟实现
【C++练级之路】【Lv.10】【STL】priority_queue类和反向迭代器的模拟实现
|
6天前
|
存储 算法 调度
【C/C++ 数据结构 优先队列】了解学习`std::priority_queue`的使用
【C/C++ 数据结构 优先队列】了解学习`std::priority_queue`的使用
49 3