c++--stack,queue,priority_queue Love The Way You Lie 2024-03-30 16:10 80阅读 0赞 ### 前言 ### 对于[栈和队列][Link 1]我们是不陌生的,在数据结构阶段已经学习过,记得当时我们还是用c语言将它一步一步造出来,因为压栈与出栈正好满足数组的尾插与头删,数组的代价是及小的。对于队列是头出队列,尾插。所以就栈的实现就用的数组,队列实现就用链表。在c++中呢,vector和list就完美解决。priority\_queue叫优先级队列,实质就是大小堆,堆的实现就是数组。 在很多时候stack,queue,priority\_queue他们都叫做适配器,这里简单的提一下,它们就好比是农夫山泉,不生产水,是大自然的搬运工。也就意味着它“不生产代码,只是代码的搬运工”。下面我们通过底层代码的实现,就能看出这一特性。 -------------------- **目录** 前言 一、stack-栈 1.1 stack的使用 1.2stack的模拟实现 二、queue-队列 2.1queue的使用 2.2queue的模拟实现 2.3容器适配器 三、deque 3.2deque的原理介绍 3.3deque--插入 3.4deque的接口 四、priority\_queue-优先级队列 4.1priority\_queue的使用 4.2模拟实现 编辑 五、仿函数/函数对象 5.1仿函数的实现 5.2仿函数的使用 5.2仿函数的用法(进阶版) -------------------- -------------------- ### 一、stack-栈 ### stack是一种容器适配器,专门用在具有**后进先出**操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。 ![924a1fef9b07480b8c3709850af85def.png][] -------------------- #### 1.1 stack的使用 #### 下面这些接口的使用我相信大家已经是游刃有余了,这里就不用过多演示,若不熟悉查[文档][Link 2]即可。 <table style="width:650px;"> <tbody> <tr> <td>函数说明</td> <td>接口说明</td> </tr> <tr> <td>stack()</td> <td>构造空的栈</td> </tr> <tr> <td>empty()</td> <td>检测stack是否为空</td> </tr> <tr> <td>size()</td> <td>返回stack中元素的个数</td> </tr> <tr> <td>top()</td> <td>返回栈顶元素的引用</td> </tr> <tr> <td>push()</td> <td>将元素val压入stack中</td> </tr> <tr> <td>pop()</td> <td>将stack中尾部的元素弹出</td> </tr> </tbody> </table> void test_stack() { stack<int> s; s.push(1); s.push(2); s.push(3); s.push(4); while (!s.empty()) { cout << s.top() << " "; s.pop(); } } #### 1.2stack的模拟实现 #### 对stack的实现,现在只关注它的实现全是调用。关于deque,Container下面我们就会进行探究。 #pragma once #include<vector> #include <list> namespace qhx { template<class T, class Container = deque<T>> class stack { public: void push(const T& x) { _con.push_back(x); } void pop() { _con.pop_back(); } const T& top() { return _con.back(); } bool empty() { return _con.empty(); } size_t size() { return _con.size(); } private: Container _con; }; } ### 二、queue-队列 ### 队列也是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。 ![dd3c23c0ac0d4d9c97baad612d42e710.png][] -------------------- #### 2.1queue的使用 #### 队列的接口其实与栈的接口基本一样,而且使用方法也是一样。 <table style="width:650px;"> <tbody> <tr> <td style="width:195px;">函数声明</td> <td style="width:453px;">接口说明</td> </tr> <tr> <td style="width:195px;">queue()</td> <td style="width:453px;">构造空的队列</td> </tr> <tr> <td style="width:195px;">empty()</td> <td style="width:453px;">检测队列是否为空,是返回true,否则返回false</td> </tr> <tr> <td style="width:195px;">size()</td> <td style="width:453px;">返回队列中有效元素的个数</td> </tr> <tr> <td style="width:195px;">front()</td> <td style="width:453px;">返回队头元素的引用</td> </tr> <tr> <td style="width:195px;">back()</td> <td style="width:453px;">返回队尾元素的引用</td> </tr> <tr> <td style="width:195px;">push()</td> <td style="width:453px;">在队尾将元素val入队列</td> </tr> <tr> <td style="width:195px;">pop()</td> <td style="width:453px;">将队头元素出队列</td> </tr> </tbody> </table> void test_queue() { queue<int> q; q.push(1); q.push(2); q.push(3); q.push(4); while (!q.empty()) { cout << q.front() << " "; q.pop(); } cout << endl; } #### 2.2queue的模拟实现 #### #pragma once #include<vector> #include <list> namespace qhx { template<class T, class Container = deque<T>> class queue { public: void push(const T& x) { _con.push_back(x); } void pop() { _con.pop_front(); } const T& front() { return _con.front(); } const T& back() { return _con.back(); } bool empty() { return _con.empty(); } size_t size() { return _con.size(); } private: Container _con; }; } queue的模拟实现也是非常简单的,都是复用其他人代码。栈和队列的实现中,我们发现都有class Container = deque<T>。 在queue和stack中都有这样一段话。 > **Queue:** > > queues are a type of **container adaptor**, specifically designed to operate in a FIFO context (first-in first-out), where elements are inserted into one end of the container and extracted from the other. > **翻译:** > > 队列是一种**容器适配器**,专门设计用于在 FIFO 上下文(先进先出)中运行,其中元素插入容器的一端并从另一端提取。 > > **Stacks:** > > Stacks are a type of **container adaptor**, specifically designed to operate in a LIFO context (last-in first-out), where elements are inserted and extracted only from one end of the container. > > **翻译:** > > 堆栈是一种容器适配器,专门设计用于在后进先出(后进先出)环境中操作,其中元素仅从容器的一端插入和提取。 class Container = deque<T>在这里就是容器适配器。 #### 2.3容器适配器 #### 适配器是一种**设计模式**(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个**类的接口转换**成客户希望的**另外一个接口**。 设计模式的使用将提高软件系统的开发效率和软件质量,节省开发成本。有助于初学者深入理解面向对象思想,设计模式使设计方案更加灵活,方便后期维护修改。 在stack,queue中 deque<T>接口转换到Container中。deque是什么呢?我们不是说stack可以用vector,queue用list吗,怎么这里用的deque。 ### 三、deque ### 在容器适配器为什么会选择deque,那么就必须得从vector,list的优缺点说起 -------------------- **3.1vector,list的优缺点** **vector:** > stack可以随机访问,但是头部中部插入删除效率低,并且还需要扩容 **list:** > 虽然queue在任何地方插入删除效率高,但是不支持随机访问,CPU高速缓存命中率低 对于deque就完美兼容vector,list的优点。所以对于接口选择就是deque。 #### **3.2deque的原理介绍** #### **[deque文档][deque]** deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和 删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。 ![edd275adf67a481fa0d1b4d246d7abfe.png][] 这个是deque一段的buffer数组,所以deque并不是真正连续的空间,它是由一段一段这样的buffer数组链接而成,一段一段的buffer数组被放在**中控**,这个中控就是一个指针数组,实际上deque类似于一个动态的二维数组, 如图:![51e44bbbf622425eb4d3e608549c5ed5.png][] 这里的缓冲区就是buffer数组,用于存放数据。map就是中控器,就是存放指针。当map空间不够后,会再开辟一个中控-map。 #### 3.3deque--插入 #### **插入操作--头插**![ffbeb4a881a84ea69a2058dd925db857.png][] **插入操作--尾插** ![f89275f607584af3b07efb5cc6ccbfd5.png][] **查找:**即相当于二维数组一样,先找map中的地址(第一层),然后在找buffer(第二层) **缺点:** 那么我们发现它下标访问有一定的消耗,没有vector快。当我们中间插入时候,它的中间插入的时候需要挪动数据,与list相比也是有消耗的。 deque不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到 某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作 为stack和queue的底层数据结构。 我们通过发现deque其实是没有想象中那样完美的,它与**vector和list相比是不够极致的**。vector是吕布,list是诸葛亮,那么deque就是魏延。所以更多的时候我们更需要极致。 deque的底层实现是比较复杂的,不仅仅是上诉简单两句的问题。 ![9f621516e6324b478fa0274e866b08d0.png][] 根据上图,对于deque的维护是通过两个迭代器,start和finsh。因为daque是作为stack和queue的底层默认容器,一般来说deque是不需要进行中间插入的,那么start和finsh就很好的处理头插和尾插。它通过frist和last指向头尾,头插通过start的frist,如果满了node链接map新开辟buffer的指针位置。尾插通过finish的last控制。如果top()和back(),即通过start的cur和finish的cur控制。、 #### 3.4deque的接口 #### [ deque文档的接口][deque]![e3462bb923654189a575f4dc2eb84080.png][] ![8048ad75cd4e46ee9105678068974c54.png][] ![25cc01c583ee49148a0bd058111edf0a.png][]![ffbd20c8f6d94f54b7de717ab19d906b.png][] 通过stack,queue的接口与deque的接口对比,发现直接调用deque是非常适合充当stack,queue的默认容器。stack,queue就是直接调用deque的接口。 ### 四、priority\_queue-优先级队列 ### 优先队列是一种容器适配器,而它实质就是堆。是否还记得[堆][Link 3]是完全二叉树中用数组实现的,因为数组正好满足堆下标随机存取的需求,标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority\_queue类实例化指定容器类,则使用vector。相对deque,vector更加极致。priority\_queue是默认大根堆。 -------------------- #### 4.1priority\_queue的使用 #### priority\_queue的使用来说也是比较简单的,接口也比较少。 <table style="width:650px;"> <tbody> <tr> <td>函数声明</td> <td>接口说明</td> </tr> <tr> <td>priority_queue()/priority_queue(first, last)</td> <td>构造一个空的优先级队列</td> </tr> <tr> <td>empty( )</td> <td> <p>检测优先级队列是否为空,是返回true,否则返回</p> <p>false</p> </td> </tr> <tr> <td>top( )</td> <td>返回优先级队列中最大(最小元素),即堆顶元素</td> </tr> <tr> <td>push(x)</td> <td>在优先级队列中插入元素x</td> </tr> <tr> <td>pop()</td> <td>删除优先级队列中最大(最小)元素,即堆顶元素</td> </tr> </tbody> </table> 对于priority\_queue的头文件,我们通过手册发现,priority\_queue与queue都是一个[头文件][Link 4]。 ![713c487749ae44a4a07e7ecbcd2f878e.png][] 接口演示: //默认大根堆 void test() { priority_queue<int> p; p.push(7); p.push(1); p.push(9); p.push(2); p.push(3); p.push(4); while (!p.empty()) { cout << p.top() << " "; p.pop(); } } > 结果:9 7 4 3 2 1 小根堆 --greater void test() { priority_queue<int, vector<int>, greater<int> > p; p.push(7); p.push(1); p.push(9); p.push(2); p.push(3); p.push(4); while (!p.empty()) { cout << p.top() << " "; p.pop(); } } > 结果:1 2 3 4 7 9 #### 4.2模拟实现 #### #pragma once namespace qhx { template <class T,class Container = vector<int>> class priority_queue { public: template<class InputIterator> priority_queue(InputIterator first, InputIterator last) :_con(first,last) { //建堆-推荐向下调整建堆,时间复杂度更小 for (size_t i = (_con.size() - 1 - 1) / 2; i >= 0; --i)// { adjust_down(i); } } void adjust_up(size_t child) { size_t parent = (child - 1) / 2; while (child > 0) { if (_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); adjust_up(_con.size() - 1); } void adjust_down(size_t parent) { size_t michild = parent * 2 + 1; while (michild < _con.size()) { if (michild< _con.size() && _con[michild]>_con[michild + 1]) { michild++; } if ( _con[michild]>] < _con[parent]) { swap(_con[michild], _con[parent]); parent = michild; michild = parent * 2 + 1; } else { break; } } } void pop() { swap(_con[0], _con(_con.size(-1))); _con.pop_back(); adjust_down(0); } const T&top()const { return _con[0]; } const empty()const { return _con.empty(); } size_t size()const { return _con.size(); } private: Container _con; }; }; 如果对向上/向下调整忘记了的,就可以看下面图片回忆。 向上调整 #### ![c37a481e57ae46aaab38b443d90eb0f5.png][] #### 向下调整![d844881756da41e4a7fa342d9197acf2.png][] ### 五、仿函数/函数对象 ### 仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个[operator][](),这个类就有了类似函数的行为,就是一个仿函数类了。 -------------------- #### 5.1仿函数的实现 #### 这里是实现的比较大小的仿函数 #include <iostream> using namespace std; //仿函数/函数对象 namespace qhx { template<class T> class less { public: bool operator()(const T& x, const T& y) { return x < y; } }; template<class T> class greater { public: bool operator()(const T& x, const T& y) { return x>y; } }; }; int main() { qhx::less<int> lessFunc; if (lessFunc(3, 2)) cout << "yes" << endl; else cout << "no" << endl; return 0; } #### 5.2仿函数的使用 #### 冒泡排序 template <class T> void BubbleSort(T* a, int n) { for (int i = 0; i < n; i++) { int flag = 0; for (int j = 1; j < n - i; j++) { if (a[j - 1] > a[j]) swap(&a[j - 1], &a[j]); flag = 1; } if (flag == 0) break; } } 在C语言时期,冒泡函数进行比较的时候,是需要进入冒泡函数内部改变">","<"。或者是通过函数指针的方式,在多增加一个函数参数。 方法一: > if (a\[j - 1\] > a\[j\]) //改变其大与小 对于封装的好的函数来说,这样对使用者是非常不友好的,那么就可以通过接口的方式,增加函数指针。 方法二: > void BubbleSort(T\* a, int n,bool(\*pcom)(int,int)) 方法二的话,这个方法是比较搓的,使用的函数时需要传太多变量,阅读性也不够强。那么c++中函数模板就起到了重要的作用了。我们可以增加一个模板参数,再增加给函数的参数,通过类型的对象去比较,可以想函数一样去是使用。 template <class T,class compaer> // 冒泡排序 void BubbleSort(T* a, int n,compaer com) { for (int i = 0; i < n; i++) { int flag = 0; for (int j = 1; j < n - i; j++) { //if (a[j - 1] > a[j]) if (com(a[j - 1] , a[j])) swap(a[j - 1], a[j]); flag = 1; } if (flag == 0) break; } } void test_less() { qhx::less<int> lessFunc; if (lessFunc(3, 2)) cout << "yes" << endl; else cout << "no" << endl; } void test_BubbleSort() { qhx::less<int> lessFunc; int arr[] = { 1, 2, 4, 9, 8, 3, 6, 7 }; //BubbleSort(arr, sizeof(arr) / sizeof(arr[0]),lessFunc); BubbleSort(arr, sizeof(arr) / sizeof(arr[0]), lessFunc); for (auto e : arr) { cout << e << " "; } } int main() { test_BubbleSort(); return 0; } > 运行结果:9 8 7 6 4 3 2 1 这里的less是根据优先级队列来定义的,这里是降序,greater就是升序。 **注意:**这里模板参数是类,函数调用类模板增加的代码内存时不多的。例如上述只增加1个字节。 #### 5.2仿函数的用法(进阶版) #### 这里是比较Daet--自定义类型的大小。我们有Date类型比较大小的方式后,但是对于Date\*的比较是没有的,故此,我们创建一个struct(类-默认公共类),然后通过函数模板的调用,实现了比较非自定义变量指针的大小。 #include <iostream> #include <queue> #include <functional> using namespace std; class Date { public: Date(int year = 1900, int month = 1, int day = 1) : _year(year) , _month(month) , _day(day) {} bool operator<(const Date& d)const { return (_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day); } bool operator>(const Date& d)const { return (_year > d._year) || (_year == d._year && _month > d._month) || (_year == d._year && _month == d._month && _day > d._day); } friend ostream& operator<<(ostream& _cout, const Date& d) { _cout << d._year << "-" << d._month << "-" << d._day; return _cout; } private: int _year; int _month; int _day; }; struct PDateLess { bool operator()(const Date* d1, const Date* d2) { return *d1 < *d2; } }; struct PDateGreater { bool operator()(const Date* d1, const Date* d2) { return *d1 > *d2; } }; void TestPriorityQueue() { // 大堆,需要用户在自定义类型中提供<的重载 priority_queue<Date> q1; q1.push(Date(2018, 10, 29)); q1.push(Date(2018, 10, 28)); q1.push(Date(2018, 10, 30)); cout << q1.top() << endl; // 如果要创建小堆,需要用户提供>的重载 priority_queue<Date, vector<Date>, greater<Date>> q2; q2.push(Date(2018, 10, 29)); q2.push(Date(2018, 10, 28)); q2.push(Date(2018, 10, 30)); cout << q2.top() << endl; // 大堆 priority_queue<Date*, vector<Date*>, PDateLess> q3; q3.push(new Date(2018, 10, 29)); q3.push(new Date(2018, 10, 28)); q3.push(new Date(2018, 10, 30)); cout << *q3.top() << endl; // 小堆 priority_queue<Date*, vector<Date*>, PDateGreater> q4; q4.push(new Date(2018, 10, 29)); q4.push(new Date(2018, 10, 28)); q4.push(new Date(2018, 10, 30)); cout << *q4.top() << endl; } int main() { TestPriorityQueue(); return 0; } [Link 1]: http://t.csdn.cn/Qunmk [924a1fef9b07480b8c3709850af85def.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/2a9b2339f0e6443cb75949c39035ff2d.png [Link 2]: https://legacy.cplusplus.com/reference/stack/stack/ [dd3c23c0ac0d4d9c97baad612d42e710.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/3e18238798714b08baea54c3a6cba07e.png [deque]: https://legacy.cplusplus.com/reference/deque/deque/ [edd275adf67a481fa0d1b4d246d7abfe.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/c8e93020096f4910a2b3fe500bbf4aca.png [51e44bbbf622425eb4d3e608549c5ed5.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/a8e9333137124c19be3c3e221e47f192.png [ffbeb4a881a84ea69a2058dd925db857.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/e4215ca546854fd4b4b3d8f3b908f39b.png [f89275f607584af3b07efb5cc6ccbfd5.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/d09678cd5c6844138a42bda7dcf3a78e.png [9f621516e6324b478fa0274e866b08d0.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/31e7b6a529a94b3a93ce8b3221a8e828.png [e3462bb923654189a575f4dc2eb84080.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/5e96d281bab84c3d84930c8d7ddc97ab.png [8048ad75cd4e46ee9105678068974c54.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/f4aa13f76d1242deb79ecda778bbb79a.png [25cc01c583ee49148a0bd058111edf0a.png]: https://img-blog.csdnimg.cn/25cc01c583ee49148a0bd058111edf0a.png [ffbd20c8f6d94f54b7de717ab19d906b.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/ce6f681903024be1a2fa639449b8eb3a.png [Link 3]: http://t.csdn.cn/Diygt [Link 4]: https://cplusplus.com/reference/queue/ [713c487749ae44a4a07e7ecbcd2f878e.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/7e36b52f03464cb8b4f0a01f8be66647.png [c37a481e57ae46aaab38b443d90eb0f5.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/1fdee6d4444246d58383d70965720aa4.png [d844881756da41e4a7fa342d9197acf2.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/6d16a0e843314ef48fe5403672d6261e.png [operator]: https://baike.baidu.com/item/operator/2387244?fromModule=lemma_inlink
还没有评论,来说两句吧...