目录

一、栈(stack)

1.stack的使用

2.容器适配器

3.stack的模拟实现

二、队列(queue)

1.queue的使用

2.queue的模拟实现

三、双端队列(deque)

1.vector,list的优缺点

2.认识deque

四、priority_queue-优先级队列

1.priority_queue的使用

2.priority_queue的模拟实现

3.仿函数


一、栈(stack)

1.stack的使用

stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。

相信stack的使用大家都已经熟悉了,下面的所有接口也都很好理解:

测试代码:

#include<iostream>
#include<stack>
using namespace std;void test()
{stack<int> s;s.push(1);s.push(2);s.push(3);s.push(4);while (!s.empty()){cout << s.top() << " ";s.pop();}
}int main()
{test()return 0;
}

2.容器适配器

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。简单而言,就是将一个类设计为另一个类的封装。

比如说,下面的stack和queue的实现就是一个很好的例子。

设计模式的使用将提高软件系统的开发效率和软件质量,节省开发成本。有助于初学者深入理解面向对象思想,设计模式使设计方案更加灵活,方便后期维护修改。

3.stack的模拟实现

我们之前提到过代码的复用,既然已经有现成的东西了,我为什么不用呢?

stack在我们看来是一个容器,数据只能后进先出,这个容器可以是vector,也可以是list甚至更多的数据结构。也就是说我们如果以vector作为stack的底层数据结构,stack的尾插就可以使用vector的push_back(),尾删就可以使用vector的pop_back()。以vector作为stack的底层数据结构也是一样的,只是使用了list的接口,这就是一种适配器的模式。

stack是一个模板类,有两个模板参数,T标识存储的数据类型,container表示实现stack的底层数据结构。

stack我们更常用vector作为被封装的类

#pragma once
#include<vector>
#include <list>namespace my_stack
{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)

1.queue的使用

队列也是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。

queue的接口也差不多:

测试代码:

#include<iostream>
#include<queue>
using namespace std;void test()
{queue<int> q;q.push(1);q.push(2);q.push(3);q.push(4);while (!s.empty()){cout << s.front() << " ";q.pop();}
}int main()
{test()return 0;
}

2.queue的模拟实现

依然是代码的复用,不过这次是先进先出。

queue我们更常用list作为被封装的类

#pragma once
#include<vector>
#include<list>namespace my_queue
{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& top(){return _con.back();}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:container _con;};
}

三、双端队列(deque)

在stack和deque中都有一个缺省的被封装的类以实现适配器,这个缺省值为什么既不是vector也不是list,而是deque呢?最主要的原因还是vector,list各有其优缺点。

1.vector,list的优缺点

vector

优缺点:支持随机访问,但是头部和中部的插入删除效率低,并且还需要扩容操作。

list

优缺点:list在任何地方插入删除效率都很高,但是链表不支持随机访问,且CPU高速缓存命中率低(CPU在访问内存时往往是取一块较大的内存,然后再其中找对应的数据位置,链表开辟的每一个节点的地址位置时不确定的,所以相比于连续的vector,缓存的命中率低)

而deque这个类是一个缝合怪,它既有vector的随机访问,也有list的高效率插入删除。所以对于这里选择deque最好。

2.认识deque

(1)底层原理

我们再cplusplus网站中可以查看deque:

deque - C++ Reference (cplusplus.com)

这是deque实现的思路:

deque是由中控和一段一段的buffer数组组成的,也就是说deque并不是完全连续的空间,它是由一段一段这样的buffer数组通过算法拼接而成,而所谓中控其实是一个指针数组,每一个元素都保存着各个buffer的数组指针。我们可以大致理解为C语言中的二维数组或者C++的vector>。

它的头尾插大概是这样的:

查找也是一样,先在map找buffer,再在buffer找数据。

我们可以很明显地发现它的下标访问相比vector一定是复杂而又缓慢的。当deque在中间插入数据时,它挪动数据的算法可就比vector复杂多了,与list相比的消耗就更大了。

(2)迭代器

由于deque的迭代器设计也很复杂,所以deque不适合遍历操作。在遍历时,deque的迭代器要频繁地检测其是否移动到数组buffer的的边界,这就导致它的访问效率很低下。而在现实中,遍历操作的使用是很频繁的,所以需要线性结构时,大多数情况下都会考虑vector和list,deque的应用很少,而我们目前能看到的一个就是:在STL中作为stack和queue的底层数据结构。

我们大致看一下它的迭代器的实现:

deque的维护需要两个迭代器,分别是start和finsh。因为deque更多时候是作为stack和queue的底层默认容器来使用的,所以一般deque是不需要在中间插入数据的,那么有了分别指向数据头尾的start和finsh就可以很好地处理头插与尾插。迭代器中的frist和last指向buffer的头尾,如果buffer满了,就可以通过node链接到map主控再新开辟buffer。其中的头部和尾部数据获取top()和back()就通过start的cur和finish的cur控制。

总的来说,deque虽然集成了vector和list的优点,但它也不是完美的,而在写代码中我们大部分时间都会追求极致,它也就很少用到了。

四、priority_queue-优先级队列

优先级队列(priority_queue)虽然叫队列,但它不满足先进先出的条件,优先级队列会保证每次出队列的元素是队列所有元素中优先级最高的那个元素,这个优先级可以通过元素的大小等进行定义。比如定义元素越大(或越小)优先级越高,那么每次出队,都是将当前队列中最大(最小)的那个元素出队,它的底层其实就是一个堆,也就是二叉树的结构。

1.priority_queue的使用

priority_queue的使用来说也是比较简单的,接口也比较少。

测试代码:

#include<iostream>
using namespace std;
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();}
}
int main()
{test();return 0;
}

2.priority_queue的模拟实现

我这里建的是小堆,大堆也是同理,如果不记得向上向下调整,可以看这里:

(6条消息) 二叉树详解_聪明的骑士的博客-CSDN博客

template<typename T, class container = vector<T>>
class priority_queue
{
public:template<class iterator>priority_queue(iterator first, iterator last)//建堆:_con(first, last){for (size_t i = (_con.size() - 1 - 1) / 2; i >= 0; --i)//所有节点的父节点全部向下调整,size()减1得到尾下标,再减1除2得到父节点{adjust_down(i);}}void adjust_down(size_t parent){size_t min_child = parent * 2 + 1;while (min_child < _con.size()){if (min_child + 1 < _con.size() && _con[min_child] > _con[min_child + 1]){minchild++;}if (_con[min_child] < _con[parent]){std::swap(_con[min_child], _con[parent]);parent = min_child;min_child = parent * 2 + 1;}elsebreak;}}void adjust_up(size_t child){size_t parent = (child - 1) / 2;while (parent >= 0){if (_con[parent] > _con[child]){std::swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}}void push(const T& x){_con.push_back(x);_con.adjust_up(_cons.size() - 1);}void pop(){assert(!_con.empty());std::swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}const T& top(){return _con[0];}const empty() const{return _con.empty();}size_t size() const{return _con.size();}private:container _con;
};

3.仿函数

(1)什么是仿函数

仿函数(functor),就是使一个类的使用看上去像一个函数。本质就是在类中实现一个()的重载函数,这个类就有了类似函数的行为,就成为一个仿函数类了。

namespace function
{template<class T>class less{public:bool operator()(const T& x, const T& y){return x < y;}};template<class T>class more{public:bool operator()(const T& x, const T& y){return x > y;}};
}
int main()
{function::less<int> lessFunc;//创建一个变量if (lessFunc(3, 2))//用变量也可以达到函数的效果cout << "yes" << endl;elsecout << "no" << endl;return 0;
}

(2)仿函数的使用

我们写一个插入排序

namespace sort
{template<class T>void insert_sort(T* arr, size_t size){int i = 1;for (i = 1; i < size; ++i){int j = i;while (j > 0){if (arr[j - 1] < arr[j]){std::swap(arr[j - 1], arr[j]);--j;}elsebreak;}}}
}

排序一个数组

int main()
{int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };sort::insert_sort<int>(arr, sizeof(arr) / sizeof(arr[0]));for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}
}
//结果:0 1 2 3 4 5 6 7 8 9

我们发现,如果我们想把数组排为降序,那么就要改变底层代码中的arr[j - 1] < arr[j]变为arr[j - 1] > arr[j],再大部分情况下,使用者都是不能看到代码的实现的,使用者也就不能更改为排降序。那么我们是否可以通过加一个函数指针来实现呢?

template

void insert_sort(T* arr, size_t size, bool (*ptr)(int, int))

函数指针最直观的特点就是它太不直观了,阅读性很低,而且这个函数还要传递更多的参数。

所以我们为什么部添加一个仿函数的模板参数来实现升降序的控制呢?

我们修改一下,就可以正常运行了:

template<class T, typename compare>
void insert_sort(T* arr, size_t size, typename compare)
{int i = 1;for (i = 1; i < size; ++i){int j = i;while (j > 0){if (compare(arr[j - 1], arr[j])){std::swap(arr[j - 1], arr[j]);--j;}else{break;}}}
}void test_insertSort()
{func::less<int> lessFunc;int arr[] = { 9,8,7,6,5,4,3,2,1,0 };insert_sort(arr, sizeof(arr) / sizeof(arr[0]), lessFunc);for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}
}int main()
{test_insertSort();return 0;
}

在比较内置类型时,可以直接使用大于小于号,而比较自定义类型的大小,我们可以通过大于小于的重载确定类型比较大小的方式。但是如果传递的参数是指针类型,我们传指针大部分时候都是为了比较指针指向的数据,如果只是比较指针的数值本身,就失去比较的意义了。

那么对于指针的比较就不应该沿用之前的比较方式,所以我们创建一个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;
}

stack、queue和priority_queue相关推荐

  1. C++知识点25——使用C++标准库(容器适配器stack、queue、priority_queue)

    除了vector,list,deque等常用的容器,还有根据这些常用的容器进行改造来满足特殊要求的容器,这些特殊容器的行为和常用容器很相近,也称为容器适配器. 常用的容器适配器有三个,分别是stack ...

  2. C++中的deque、stack、queue及priority_queue

    C++中的deque.stack.queue及priority_queue 文章目录 C++中的deque.stack.queue及priority_queue 一.deque 二.stack 三.q ...

  3. C++---容器适配器(stack、queue、priority_queue)

    容器适配器 首先我们要知道容器适配器是干啥的. 我们可以简单的将容器适配器当做一个接口装置.有的电脑上没有数据转接口,但是有usb接口,这是我们没必要重新买一个电脑,我们可以做一个usb数据转接线.而 ...

  4. c++--stack,queue,priority_queue

    前言 对于栈和队列我们是不陌生的,在数据结构阶段已经学习过,记得当时我们还是用c语言将它一步一步造出来,因为压栈与出栈正好满足数组的尾插与头删,数组的代价是及小的.对于队列是头出队列,尾插.所以就栈的 ...

  5. [C++](13)stack queue priority_queue 模拟实现:容器适配器,deque介绍,仿函数详解

    文章目录 使用 stack 栈 queue 队列 priority_queue 优先级队列 什么是容器适配器? deque 容器简单介绍 模拟实现 stack queue priority_queue ...

  6. C++ queue和priority_queue

    queue 和 priority_queue 都是容器适配器,要使用它们,必须包含头文件 . queue queue 就是"队列".队列是先进先出的,和排队类似.队头的访问和删除操 ...

  7. Stack/Queue与Vector/List的联系

    Vector:(顺序表[数组存储]) 1.当申请的空间不足的时候,需要再次开辟一块更大的空间,并把值拷过去. 2.对于尾删和尾插是比较方便的,只需要改动最后一个元素即可.不会改动原有的空间.适用于多次 ...

  8. programming review (c++): (1)vector, linked list, stack, queue, map, string, bit manipulation

    编程题常用知识点的review. most important: 想好(1)详尽步骤(2)边界特例,再开始写代码. I.vector #include <iostream> //0.头文件 ...

  9. C++ Stack Queue priority_queue

    栈stack:stack 后入先出(LIFO) q.top() 获取栈顶元素(并不删除) q.pop() 删除栈顶元素 q.push(x) 向栈中加入元素 q.empty() 判断栈是否为空 队列qu ...

  10. stl的set,multiset, map, multimap, deque, list, stack, queue, priority_queue

    set实际上是平衡二叉树,需要声明头文件#include<set> Insert:将元素插入集合中 使用前向迭代器对集合中序遍历 使用反向迭代器reverse_iterator可以反向遍历 ...

最新文章

  1. web表单设计:点石成金_设计复杂的用户表单:12个UX最佳实践
  2. [css] body{height:100%}和html,body{height:100%}有什么区别?为什么html要设置height:100%呢,html不就是整个窗口吗?
  3. 如何建立队列c语言_什么是优先队列
  4. jqueryAjax的使用
  5. 【ArcGIS|3D分析】要素的立体显示
  6. 如何设置微信小程序地图控件满屏
  7. 微信小程序播放器实战开发教程
  8. linux 端口转发 udp,Linux Socat TCP/UDP端口转发及使用
  9. 代码笔记源码php,读 PHP - Pimple 源码笔记(上)
  10. 机器学习-胯下运球之Naive Bayes<朴素贝叶斯法>
  11. python发送qq邮件_python基于SMTP发送邮件(qq邮箱)
  12. 数字图像处理中的车牌识别
  13. 12、Zabbix 结合Grafana
  14. JAVA 中的代码生成包 CGLIB (Code Generation Library)
  15. 旅游业如何使用数据分析?
  16. 安装flashplay的debug版本调试flex
  17. 【翻译1】Multivariate Time Series Forecasting with LSTMs in Keras——PM2.5
  18. IOC和AOP的面试题
  19. IT项目管理 第七章 习题
  20. 电脑qq如何关闭广告

热门文章

  1. 基于暗通道优先的单幅图像去雾算法(Matlab)
  2. 如何让你的内网服务器可以被外网访问到(端口映射、NAT、域名解析、IP地址)
  3. 物联网平台技术架构和应用场景
  4. 安装CV2,安装opencv
  5. 基于深度学习的多任务人脸属性分析(基于飞桨PaddlePaddle)
  6. TCP和UDP的区别有哪些?
  7. 进程资源和进程状态 TASK_RUNNING TASK_INTERRUPTIBLE TASK_UNINTERRUPTIBLE
  8. WebSestalt,好用的富集分析工具,介绍及使用教程
  9. 【神经网络】梯度消失与梯度爆炸问题
  10. RF-SIM卡的多应用COS研究与设计