• <C++> 优先级队列


    目录

    前言

    一、priority_queue的使用

    1. 成员函数

    2. 例题

    二、仿函数

    三、模拟实现

    1.  迭代器区间构造函数 && AdjustDown

    2. pop

    3.  push && AdjustUp

    4. top

    5. size

    6. empty

     四、完整实现

    总结


    前言

            优先级队列以及前面的双端队列基本上已经脱离了队列定义,只是占了队列名字

    优先级队列——priority_queue

    1. 优先级队列是一种容器适配器,根据一些严格的弱排序标准,经过专门设计,其第一个元素始终是它所包含的最大元素。

    2. 此上下文类似于堆,其中元素可以随时插入,并且只能检索最大堆元素(优先级队列中顶部的元素)。

    3. 优先级队列作为容器适配器实现,容器适配器是使用特定容器类的封装对象作为其基础容器的类,提供一组特定的成员函数来访问其元素。元素从特定容器的“背面”弹出,这称为优先级队列的顶部

    4. 底层容器可以是任何标准容器类模板,也可以是一些其他专门设计的容器类。容器应可通过随机访问迭代器访问,并支持以下操作:

    • empty()
    • size()
    • front()
    • push_back()
    • pop_back()


    5. 标准容器类vector和deque满足这些要求。默认情况下,如果未为特定priority_queue类实例化指定容器类,则使用标准容器vector。

    6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。这是由容器适配器通过自动调用算法函数 make_heap、push_heap、pop_heap 自动完成的,并在需要时完成。

    注意:

    • priority_queue还是适配器,但是适配的是vector
    • 底层是二叉树的堆
    • priority_queue仍然包括在queue头文件中 
    • 默认大堆
    • Compare的缺省是less,表示的是大根堆

            可以使用仿函数修改为小根堆

    priority_queue<int, deque<int>, greater<int>> pq;

    一、priority_queue的使用

    1. 成员函数

    2. 例题

    215. 数组中的第K个最大元素

    方法一:直接使用sort排序

    1. class Solution {
    2. public:
    3. int findKthLargest(vector<int>& nums, int k)
    4. {
    5. sort(nums.begin(), nums.end(), greater<int>());
    6. return nums[k - 1];
    7. }
    8. };

     方法二:优先级队列,建大堆

    1. class Solution {
    2. public:
    3. int findKthLargest(vector<int>& nums, int k)
    4. {
    5. priority_queue<int> pq(nums.begin(), nums.end());
    6. while (--k)
    7. {
    8. pq.pop();
    9. }
    10. return pq.top();
    11. }
    12. };

    方法三:维护一个有K个数据的小堆,遍历nums数组,若比top()值大,就入堆,最后返回top()数据

    1. class Solution {
    2. public:
    3. int findKthLargest(vector<int>& nums, int k)
    4. {
    5. priority_queue<int, vector<int>, greater<int>> pq(nums.begin(), nums.begin() + k);
    6. for (int i = k; i < nums.size(); ++i)
    7. {
    8. if (nums[i] > pq.top())
    9. {
    10. pq.pop();
    11. pq.push(nums[i]);
    12. }
    13. }
    14. return pq.top();
    15. }
    16. };

    注意:

            sort函数最后一个参数是greater(),是一个匿名对象,而在priority_queue<>内部,第三个模板是greater,是类型,不能加括号

    二、仿函数

            我们在之前就遇到过sort函数如果想实现降序,需要加上仿函数greater<类型>(),那么什么是仿函数呢?它又有什么作用?

    我们来看一个简单的例子:

    1. class Fun
    2. {
    3. public:
    4. bool operator()(int a, int b)
    5. {
    6. return a < b;
    7. }
    8. };
    9. int main()
    10. {
    11. Fun func;
    12. cout << func(1, 2) << endl;
    13. //等价于
    14. cout << func.operator()(1, 2) << endl;
    15. return 0;
    16. }
    • 这里的Fun就是仿函数,由Fun类定义的对象称为函数对象
    • 仿函数,顾名思义,一个类的对象可以像函数一样使用,它替代了c语言里的函数指针,我们只需要重载 () 符号就可以像使用函数一样调用它的 () 运算符重载函数

    那么这样的仿函数有什么作用呢?

    •  替代c语言的函数指针,因为c语言的函数指针很复杂、容易出错
    • 搭配模板可以实现多类型的函数运算,不会将函数“写死”,例如:我们写Less、Greater类,重载()符号,在需要的地方实例化函数对象,如果有比较大小的情况就使用函数对象(参数1,参数2),原理就是调用operator()函数,像函数一样调用

    下面是priority_queue带上第三个模板参数Less后使用仿函数的代码:

    1. template<class T>
    2. class Less
    3. {
    4. public:
    5. bool operator()(const T& a, const T& b)
    6. {
    7. return a < b;
    8. }
    9. };
    10. template<class T>
    11. class Greater
    12. {
    13. public:
    14. bool operator()(const T& a, const T& b)
    15. {
    16. return a > b;
    17. }
    18. };
    19. namespace my_priority_queue
    20. {
    21. // Less 才是Less类的类型
    22. template<class T, class Container = vector, class Compare = Less>
    23. class priority_queue
    24. {
    25. private:
    26. //大堆,向下调整
    27. void AdjustDown(int parent)
    28. {
    29. //创建函数对象,Less模板类型就是<比较,Greater模板类型就是>比较
    30. Compare com;
    31. //找左右孩子中最大的哪一个
    32. int child = parent * 2 + 1;
    33. while (child < _con.size())
    34. {
    35. //因为com是比较小于关系,所以需要将原先大于的表达式逆一下
    36. //判断右孩子是否存在
    37. /*if (child + 1 < _con.size() && _con[child + 1] > _con[child])*/
    38. if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
    39. {
    40. ++child;
    41. }
    42. //if (_con[child] > _con[parent])
    43. if (com(_con[parent], _con[child]))
    44. {
    45. swap(_con[parent], _con[child]);
    46. parent = child;
    47. child = parent * 2 + 1;
    48. }
    49. else
    50. {
    51. break;
    52. }
    53. }
    54. }
    55. void AdjustUp(int child)
    56. {
    57. Compare com;
    58. int parent = (child - 1) / 2;
    59. while (child > 0) //最坏情况:孩子等于0时结束
    60. {
    61. if (com(_con[parent], _con[child]))
    62. {
    63. swap(_con[parent], _con[child]);
    64. child = parent;
    65. parent = child - 1 >> 1;
    66. }
    67. else
    68. {
    69. break;
    70. }
    71. }
    72. }
    73. public:
    74. template<class InputIterator> //input只写迭代器
    75. //迭代器区间构造函数
    76. priority_queue(InputIterator first, InputIterator last)
    77. {
    78. while (first != last)
    79. {
    80. _con.push_back(*first);
    81. ++first;
    82. }
    83. //建堆
    84. for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i )
    85. {
    86. AdjustDown(i);
    87. }
    88. }
    89. void pop()
    90. {
    91. //交换后,尾删,并向下调整
    92. swap(_con[0], _con[_con.size() - 1]);
    93. _con.pop_back();
    94. AdjustDown(0);
    95. }
    96. void push(const T& x)
    97. {
    98. _con.push_back(x);
    99. AdjustUp(_con.size() - 1);
    100. }
    101. private:
    102. //容器
    103. Container _con;
    104. };
    105. }

            如果比较的类型是其他自定义类型,并且该类没有重载operator<函数,那么我们就需要手写一个仿函数进行比较,否则编译错误,无法进行比较

           因为库里的仿函数使用了模板,是什么类型就按什么类型相比,那么如果是new返回的指针类型,由于每次new返回的地址相当于是随机的,又比较的是指针类型的大小,所以比较结果也是随机结果。所以我们还是需要写仿函数,修改比较方式,去比较指针指向的内容即可。

    三、模拟实现

    编译错误,找不到出错位置怎么办?

            如果代码编译错误,找不到在哪,那么逐步屏蔽掉一些代码,逐步排查,是十分有效的找到错误处方法

            priority_queue也同queue、stack一样,是适配器,适配vector容器

    1.  迭代器区间构造函数 && AdjustDown

    • 采用封装容器vector的push_back尾插数据,因为默认是大堆,向下建堆,所以尾插数据后,将从最后一个元素的父亲结点—— (_con.size() - 1 - 1) / 2 开始向下调整。
    • 向下调整函数不用多说,在二叉树部分我们详细讲解过
    1. namespace my_priority_queue
    2. {
    3. // Less 才是Less类的类型
    4. template<class T, class Container = vector, class Compare = Less>
    5. class priority_queue
    6. {
    7. private:
    8. //大堆,向下调整
    9. void AdjustDown(int parent)
    10. {
    11. //创建函数对象,Less模板类型就是<比较,Greater模板类型就是>比较
    12. Compare com;
    13. //找左右孩子中最大的哪一个
    14. int child = parent * 2 + 1;
    15. while (child < _con.size())
    16. {
    17. //因为com是比较小于关系,所以需要将原先大于的表达式逆一下
    18. //判断右孩子是否存在
    19. /*if (child + 1 < _con.size() && _con[child + 1] > _con[child])*/
    20. if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
    21. {
    22. ++child;
    23. }
    24. //if (_con[child] > _con[parent])
    25. if (com(_con[parent], _con[child]))
    26. {
    27. swap(_con[parent], _con[child]);
    28. parent = child;
    29. child = parent * 2 + 1;
    30. }
    31. else
    32. {
    33. break;
    34. }
    35. }
    36. }
    37. public:
    38. template<class InputIterator> //input只写迭代器
    39. //迭代器区间构造函数
    40. priority_queue(InputIterator first, InputIterator last)
    41. {
    42. while (first != last)
    43. {
    44. _con.push_back(*first);
    45. ++first;
    46. }
    47. //建堆
    48. for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i )
    49. {
    50. AdjustDown(i);
    51. }
    52. }
    53. private:
    54. //容器
    55. Container _con;
    56. };
    57. }

     2. pop

    • 堆的pop,将堆顶数据与最后一个数据进行交换,再进行pop_back,再将堆顶数据向下调整
    1. void pop()
    2. {
    3. //交换后,尾删,并向下调整
    4. swap(_con[0], _con[_con.size() - 1]);
    5. _con.pop_back();
    6. AdjustDown(0);
    7. }

     3.  push && AdjustUp

    • 尾插数据后,将该数据向上调整
    1. void AdjustUp(int child)
    2. {
    3. Compare com;
    4. int parent = (child - 1) / 2;
    5. while (child > 0) //最坏情况:孩子等于0时结束
    6. {
    7. if (com(_con[parent], _con[child]))
    8. {
    9. swap(_con[parent], _con[child]);
    10. child = parent;
    11. parent = child - 1 >> 1;
    12. }
    13. else
    14. {
    15. break;
    16. }
    17. }
    18. }
    19. void push(const T& x)
    20. {
    21. _con.push_back(x);
    22. AdjustUp(_con.size() - 1);
    23. }

     4. top

    • 返回堆顶数据,返回值是 const T&
    1. const T& top()
    2. {
    3. return _con[0];
    4. }

     5. size

    • 返回vector的size()即可
    1. size_t size()
    2. {
    3. return _con.size();
    4. }

     6. empty

    • 直接调用vector的empty函数即可
    1. size_t size()
    2. {
    3. return _con.size();
    4. }

     四、完整实现

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. template<class T>
    7. class Less
    8. {
    9. public:
    10. bool operator()(const T& a, const T& b)
    11. {
    12. return a < b;
    13. }
    14. };
    15. template<class T>
    16. class Greater
    17. {
    18. public:
    19. bool operator()(const T& a, const T& b)
    20. {
    21. return a > b;
    22. }
    23. };
    24. namespace my_priority_queue
    25. {
    26. // Less 才是Less类的类型
    27. template<class T, class Container = vector, class Compare = Less>
    28. class priority_queue
    29. {
    30. private:
    31. //大堆,向下调整
    32. void AdjustDown(int parent)
    33. {
    34. //创建函数对象,Less模板类型就是<比较,Greater模板类型就是>比较
    35. Compare com;
    36. //找左右孩子中最大的哪一个
    37. int child = parent * 2 + 1;
    38. while (child < _con.size())
    39. {
    40. //因为com是比较小于关系,所以需要将原先大于的表达式逆一下
    41. //判断右孩子是否存在
    42. /*if (child + 1 < _con.size() && _con[child + 1] > _con[child])*/
    43. if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
    44. {
    45. ++child;
    46. }
    47. //if (_con[child] > _con[parent])
    48. if (com(_con[parent], _con[child]))
    49. {
    50. swap(_con[parent], _con[child]);
    51. parent = child;
    52. child = parent * 2 + 1;
    53. }
    54. else
    55. {
    56. break;
    57. }
    58. }
    59. }
    60. void AdjustUp(int child)
    61. {
    62. Compare com;
    63. int parent = (child - 1) / 2;
    64. while (child > 0) //最坏情况:孩子等于0时结束
    65. {
    66. if (com(_con[parent], _con[child]))
    67. {
    68. swap(_con[parent], _con[child]);
    69. child = parent;
    70. parent = child - 1 >> 1;
    71. }
    72. else
    73. {
    74. break;
    75. }
    76. }
    77. }
    78. public:
    79. priority_queue()
    80. {}
    81. //迭代器区间构造函数
    82. template<class InputIterator> //input只写迭代器
    83. priority_queue(InputIterator first, InputIterator last)
    84. {
    85. while (first != last)
    86. {
    87. _con.push_back(*first);
    88. ++first;
    89. }
    90. //建堆
    91. for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i )
    92. {
    93. AdjustDown(i);
    94. }
    95. }
    96. void pop()
    97. {
    98. //交换后,尾删,并向下调整
    99. swap(_con[0], _con[_con.size() - 1]);
    100. _con.pop_back();
    101. AdjustDown(0);
    102. }
    103. void push(const T& x)
    104. {
    105. _con.push_back(x);
    106. AdjustUp(_con.size() - 1);
    107. }
    108. const T& top()
    109. {
    110. return _con[0];
    111. }
    112. size_t size()
    113. {
    114. return _con.size();
    115. }
    116. bool empty()
    117. {
    118. return _con.empty();
    119. }
    120. private:
    121. //容器
    122. Container _con;
    123. };
    124. }
    125. void test_priority_queue1()
    126. {
    127. // 默认是大堆 -- less
    128. //priority_queue pq;
    129. // 仿函数控制实现小堆
    130. my_priority_queue::priority_queue<int, vector<int>, Greater<int>> pq;
    131. pq.push(3);
    132. pq.push(5);
    133. pq.push(1);
    134. pq.push(4);
    135. while (!pq.empty())
    136. {
    137. cout << pq.top() << " ";
    138. pq.pop();
    139. }
    140. cout << endl;
    141. }

    总结

            priority_queue优先级队列就是存储在vector内的堆,掌握向上向下调整函数,可以回顾之前的文章堆的实现一节。

            最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

  • 相关阅读:
    第一章 概述
    Vue3+TypeScript学习
    STM32-14-FSMC_LCD
    [附源码]java毕业设计校园拓展活动管理系统
    Grandle安装配置使用
    深入理解通知服务NotificationListenerService原理
    linux操作命令
    常用软件静默安装参数
    使用Oracle自带SqlPlus导入导出数据库脚本
    基于SSM框架的民宿预订系统的设计与实现毕业设计源码281118
  • 原文地址:https://blog.csdn.net/prodigy623/article/details/134358565