• 【C++/STL】stack和queue(容器适配器、优先队列、双端队列)


      🌈个人主页:秦jh_-CSDN博客
    🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12575764.html?spm=1001.2014.3001.5482

    9efbcbc3d25747719da38c01b3fa9b4f.gif

    目录

    stack的介绍

    stack常用接口

     queue的介绍

    queue的使用 

     容器适配器

    什么是适配器 

    STL标准库中stack和queue的底层结构 

    deque的简单介绍 

    deque的缺陷

    STL的六大组件

    模拟实现

    stack

    queue

     优先队列

    常用接口

    简单使用

    sort函数排序

    模拟实现(简单版)

    自定义类型

    测试完整代码

     queue.h

    stack.h

    test.cpp


    前言

        💬 hello! 各位铁子们大家好哇。

                 今日更新了stack、queue的相关内容
        🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

    stack的介绍

    1. stack是一种容器适配器。
    2. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定 的成员函数来访问其元素。
    3. stack的底层容器应该支持以下操作:empty 、back、push_back、pop_back
    4. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器, 默认情况下使用deque。 

    stack常用接口

     queue的介绍

    1. 队列是一种容器适配器。
    2. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作: empty,size,front,back,push_back,pop_front。
    3. 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。(vector不满足,因为queue的接口中存在头删和尾插,因此使用vector来封装效率太低)

    queue的使用 

     容器适配器

    什么是适配器 

    适配器是一种设计模式,该种模式是将一个类的接口转换成客户希望的另外一个接口。 

    STL标准库中stack和queue的底层结构 

    虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque。

    deque的简单介绍 

    deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。

    deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组。

    deque的缺陷

    • 与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素。
    • 与list比较,其底层是连续空间,空间利用率比较高。
    • 在实际中,需要线性结构时,大多数情况下优先考虑vector和list,目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。

    STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

    1.  stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
    2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长 时,deque不仅效率高,而且内存使用率高。

    STL的六大组件

    模拟实现

    stack

    queue

    stack和queue的模拟实现基本一样。

     优先队列

    • 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。

    优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认情况下priority_queue是大堆。

    常用接口

    简单使用

    我们插入时,是乱序插入的,取出来却是降序的。因为优先队列默认是大堆,不过它并没有在里面进行排序,它在里面依旧是乱序的,只是取出来的是堆顶,是最大的。

    如果我们想让他是小堆,就得改一下他的仿函数。 

    sort函数排序

    sort排序默认是升序,想要降序就得改仿函数。注意这里是函数模板,要传对象,所以有括号。而优先队列那里没有括号,是因为那里是类模板。

    在C语言中,我们排序如果要控制升序降序,传的是函数指针。而这里我们传的是仿函数。

    上方是仿函数的简单模拟。 

    模拟实现(简单版)

    1. template<class T,class Container=vector>
    2. class priority_queue
    3. {
    4. public:
    5. void adjust_up(size_t child)
    6. {
    7. int parent = (child - 1) / 2;
    8. while (child > 0)
    9. {
    10. if (_con[child] > _con[parent])
    11. {
    12. swap(_con[child], _con[parent]);
    13. child = parent;
    14. parent = (child - 1) / 2;
    15. }
    16. else
    17. {
    18. break;
    19. }
    20. }
    21. }
    22. void push(const T& x)
    23. {
    24. _con.push_back(x);
    25. adjust_up(_con.size() - 1);
    26. }
    27. void adjust_down(size_t parent)
    28. {
    29. size_t child = parent * 2 + 1;
    30. while (child<_con.size())
    31. {
    32. if (child + 1 < _con.size() && _con[child + 1] > _con[child])
    33. {
    34. ++child;
    35. }
    36. if (_con[child] > _con[parent])
    37. {
    38. swap(_con[child], _con[parent]);
    39. parent = child;
    40. child = parent * 2 + 1;
    41. }
    42. else
    43. {
    44. break;
    45. }
    46. }
    47. }
    48. void pop()
    49. {
    50. swap(_con[0], _con[_con.size() - 1]);
    51. _con.pop_back();
    52. adjust_down(0);
    53. }
    54. bool empty()
    55. {
    56. return _con.empty();
    57. }
    58. size_t size()
    59. {
    60. return _con.size();
    61. }
    62. const T& top()
    63. {
    64. return _con[0];
    65. }
    66. private:
    67. Container _con;
    68. };

    上面是默认的大堆。如果想要它是小堆,要怎么办呢?

    如下图修改:

     这样做的优势是,我们只需要传不同的仿函数即可修改为升序或者降序。不传默认是大堆。

    自定义类型

    如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。

    上面是日期类,Date类型,比较时,只需要重载运算符即可。如果我们传的是Date*又该怎么办呢?

    可以看到,第二行每次的结果都是不一样的。空间并不一定越晚开,地址就越高。 这里不能通过重载运算符解决,因为重载必须包含自定义类型,而指针是内置类型。解决方法是:专门写一个仿函数。

    从这个样例可以得出,仿函数不仅可以控制比较逻辑,还可以控制如何比较。

    测试完整代码

     queue.h

    1. #pragma once
    2. #include
    3. namespace qjh
    4. {
    5. template<class T, class Container = deque> //有了Container就可以根据需要实现链式栈或数组栈了
    6. class queue
    7. {
    8. public:
    9. void push(const T& x)
    10. {
    11. _con.push_back(x);
    12. }
    13. void pop()
    14. {
    15. _con.pop_front();
    16. }
    17. size_t size()
    18. {
    19. return _con.size();
    20. }
    21. bool empty()
    22. {
    23. return _con.empty();
    24. }
    25. const T& front()
    26. {
    27. return _con.front();
    28. }
    29. const T& back()
    30. {
    31. return _con.back();
    32. }
    33. private:
    34. Container _con;
    35. };
    36. template<class T>
    37. struct less
    38. {
    39. bool operator()(const T& x, const T& y)
    40. {
    41. return x < y;
    42. }
    43. };
    44. template<class T>
    45. struct greater
    46. {
    47. bool operator()(const T& x, const T& y)
    48. {
    49. return x > y;
    50. }
    51. };
    52. //大堆
    53. template<class T,class Container=vector,class Compare=less>
    54. class priority_queue
    55. {
    56. public:
    57. void adjust_up(size_t child)
    58. {
    59. Compare com;
    60. int parent = (child - 1) / 2;
    61. while (child > 0)
    62. {
    63. //if (_con[child] > _con[parent])
    64. //if (_con[parent]<_con[child] )
    65. if (com(_con[parent] , _con[child]))
    66. {
    67. swap(_con[child], _con[parent]);
    68. child = parent;
    69. parent = (child - 1) / 2;
    70. }
    71. else
    72. {
    73. break;
    74. }
    75. }
    76. }
    77. void push(const T& x)
    78. {
    79. _con.push_back(x);
    80. adjust_up(_con.size() - 1);
    81. }
    82. void adjust_down(size_t parent)
    83. {
    84. Compare com;
    85. size_t child = parent * 2 + 1;
    86. while (child<_con.size())
    87. {
    88. //if (child + 1 < _con.size() && _con[child + 1] > _con[child])
    89. //if (child + 1 < _con.size() && _con[child]<_con[child + 1] )
    90. if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))
    91. {
    92. ++child;
    93. }
    94. //if (_con[child] > _con[parent])
    95. //if ( _con[parent]< _con[child])
    96. if (com(_con[parent] , _con[child]))
    97. {
    98. swap(_con[child], _con[parent]);
    99. parent = child;
    100. child = parent * 2 + 1;
    101. }
    102. else
    103. {
    104. break;
    105. }
    106. }
    107. }
    108. void pop()
    109. {
    110. swap(_con[0], _con[_con.size() - 1]);
    111. _con.pop_back();
    112. adjust_down(0);
    113. }
    114. bool empty()
    115. {
    116. return _con.empty();
    117. }
    118. size_t size()
    119. {
    120. return _con.size();
    121. }
    122. const T& top()
    123. {
    124. return _con[0];
    125. }
    126. private:
    127. Container _con;
    128. };
    129. }

    stack.h

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. namespace qjh
    6. {
    7. //设计模式
    8. //适配器模式--转换
    9. //stack> st1;
    10. //stack> st2;
    11. template<class T,class Container=deque> //有了Container就可以根据需要实现链式栈或数组栈了
    12. class stack
    13. {
    14. public:
    15. void push(const T& x)
    16. {
    17. _con.push_back(x);
    18. }
    19. void pop()
    20. {
    21. _con.pop_back();
    22. }
    23. size_t size()
    24. {
    25. return _con.size();
    26. }
    27. bool empty()
    28. {
    29. return _con.empty();
    30. }
    31. const T& top()
    32. {
    33. return _con.back();
    34. }
    35. private:
    36. Container _con;
    37. };
    38. }

    test.cpp

    1. #include
    2. using namespace std;
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include"stack.h"
    8. #include"queue.h"
    9. void test_queue1()
    10. {
    11. qjh::queue<int> q;
    12. q.push(1);
    13. q.push(2);
    14. q.push(3);
    15. q.push(4);
    16. while (!q.empty())
    17. {
    18. cout << q.front() << " ";
    19. q.pop();
    20. }
    21. cout << endl;
    22. }
    23. void test_stack1()
    24. {
    25. qjh::stack<int> st;
    26. st.push(1);
    27. st.push(2);
    28. st.push(3);
    29. st.push(4);
    30. while (!st.empty())
    31. {
    32. cout << st.top() << " ";
    33. st.pop();
    34. }
    35. cout << endl;
    36. }
    37. void test_priority_queue1()
    38. {
    39. //默认大的优先级高,底层是大堆
    40. //priority_queue pq;
    41. //大堆
    42. //qjh::priority_queue pq; //类模板,传类型
    43. //小堆
    44. qjh::priority_queue<int,vector<int>,greater<int>> pq; //类模板,传类型
    45. pq.push(2);
    46. pq.push(1);
    47. pq.push(4);
    48. pq.push(3);
    49. pq.push(7);
    50. pq.push(8);
    51. while (!pq.empty())
    52. {
    53. cout << pq.top() << " ";
    54. pq.pop();
    55. }
    56. cout << endl;
    57. //vector v = { 3,1,7,4,6,3 };
    58. 默认升序
    59. //sort(v.begin(), v.end());
    60. //for (auto e : v)
    61. //{
    62. // cout << e << " ";
    63. //}
    64. //cout << endl;
    65. 降序
    66. //sort(v.begin(), v.end(),greater()); //匿名对象,函数模板
    67. //for (auto e : v)
    68. //{
    69. // cout << e << " ";
    70. //}
    71. //cout << endl;
    72. }
    73. class Date
    74. {
    75. friend ostream& operator<<(ostream& _cout, const Date& d);
    76. public:
    77. Date(int year = 1900, int month = 1, int day = 1)
    78. : _year(year)
    79. , _month(month)
    80. , _day(day)
    81. {}
    82. bool operator<(const Date& d)const
    83. {
    84. return (_year < d._year) ||
    85. (_year == d._year && _month < d._month) ||
    86. (_year == d._year && _month == d._month && _day < d._day);
    87. }
    88. bool operator>(const Date& d)const
    89. {
    90. return (_year > d._year) ||
    91. (_year == d._year && _month > d._month) ||
    92. (_year == d._year && _month == d._month && _day > d._day);
    93. }
    94. private:
    95. int _year;
    96. int _month;
    97. int _day;
    98. };
    99. ostream& operator<<(ostream& _cout, const Date& d)
    100. {
    101. _cout << d._year << "-" << d._month << "-" << d._day;
    102. return _cout;
    103. }
    104. struct GreaterPDate
    105. {
    106. bool operator()(const Date* p1, const Date* p2)
    107. {
    108. return *p1>*p2;
    109. }
    110. };
    111. void test_priority_queue2()
    112. {
    113. qjh::priority_queue, greater> pq;
    114. Date d1(2024, 4, 8);
    115. pq.push(d1);
    116. pq.push(Date(2024,4,10));
    117. pq.push({2024,2,15});
    118. while (!pq.empty())
    119. {
    120. cout << pq.top() << " ";
    121. pq.pop();
    122. }
    123. cout << endl;
    124. qjh::priority_queue, GreaterPDate> pqptr;
    125. pqptr.push(new Date(2024, 4, 14));
    126. pqptr.push(new Date(2024, 4, 11));
    127. pqptr.push(new Date(2024, 4, 15));
    128. while (!pqptr.empty())
    129. {
    130. cout << *(pqptr.top()) << " ";
    131. pqptr.pop();
    132. }
    133. cout << endl;
    134. }
    135. //仿函数/函数对象
    136. //它的对象可以像函数一样的去使用
    137. template<class T>
    138. struct Less
    139. {
    140. bool operator()(const T& x, const T& y)
    141. {
    142. return x < y;
    143. }
    144. };
    145. int main()
    146. {
    147. //test_stack1();
    148. //test_queue1();
    149. test_priority_queue2();
    150. //Less lessfunc;
    151. //cout << lessfunc(1, 2) << endl;
    152. //cout << lessfunc.operator()(1, 2) << endl;
    153. return 0;
    154. }

  • 相关阅读:
    【归并排序】| 详解归并排序核心代码之合并两个有序数组 力扣88
    FastDFS搭建及整合Nginx实现文件上传
    kafka面试连环问(2),你能撑到哪一问?
    【操作系统】为什么需要内核
    大厂搅局网约车,聚合平台别有用心?
    服务器神秘挂起:一场惊心动魄的内核探案
    Autocad绘制的基于工控机的电加热炉的强电电路图
    FS03MR12A6MA1LBBPSA1 1200V 400A 紧凑型 六单元模块
    Hive数据定义语言-DDL-入门基础(含四个实践案例)
    产品思维训练 | 你的项目总是不能按期上线,你会如何解决?
  • 原文地址:https://blog.csdn.net/qinjh_/article/details/137747999