c++--stack,queue,priority_queue
前言
对于栈和队列我们是不陌生的,在数据结构阶段已经学习过,记得当时我们还是用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是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
1.1 stack的使用
下面这些接口的使用我相信大家已经是游刃有余了,这里就不用过多演示,若不熟悉查文档即可。
函数说明 | 接口说明 |
stack() | 构造空的栈 |
empty() | 检测stack是否为空 |
size() | 返回stack中元素的个数 |
top() | 返回栈顶元素的引用 |
push() | 将元素val压入stack中 |
pop() | 将stack中尾部的元素弹出 |
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上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
2.1queue的使用
队列的接口其实与栈的接口基本一样,而且使用方法也是一样。
函数声明 | 接口说明 |
queue() | 构造空的队列 |
empty() | 检测队列是否为空,是返回true,否则返回false |
size() | 返回队列中有效元素的个数 |
front() | 返回队头元素的引用 |
back() | 返回队尾元素的引用 |
push() | 在队尾将元素val入队列 |
pop() | 将队头元素出队列 |
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(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和 删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。
这个是deque一段的buffer数组,所以deque并不是真正连续的空间,它是由一段一段这样的buffer数组链接而成,一段一段的buffer数组被放在中控,这个中控就是一个指针数组,实际上deque类似于一个动态的二维数组, 如图:
这里的缓冲区就是buffer数组,用于存放数据。map就是中控器,就是存放指针。当map空间不够后,会再开辟一个中控-map。
3.3deque--插入
插入操作--头插
插入操作--尾插
查找:即相当于二维数组一样,先找map中的地址(第一层),然后在找buffer(第二层)
缺点:
那么我们发现它下标访问有一定的消耗,没有vector快。当我们中间插入时候,它的中间插入的时候需要挪动数据,与list相比也是有消耗的。
deque不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到 某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作 为stack和queue的底层数据结构。
我们通过发现deque其实是没有想象中那样完美的,它与vector和list相比是不够极致的。vector是吕布,list是诸葛亮,那么deque就是魏延。所以更多的时候我们更需要极致。
deque的底层实现是比较复杂的,不仅仅是上诉简单两句的问题。
根据上图,对于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文档的接口
通过stack,queue的接口与deque的接口对比,发现直接调用deque是非常适合充当stack,queue的默认容器。stack,queue就是直接调用deque的接口。
四、priority_queue-优先级队列
优先队列是一种容器适配器,而它实质就是堆。是否还记得堆是完全二叉树中用数组实现的,因为数组正好满足堆下标随机存取的需求,标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。相对deque,vector更加极致。priority_queue是默认大根堆。
4.1priority_queue的使用
priority_queue的使用来说也是比较简单的,接口也比较少。
函数声明 | 接口说明 |
priority_queue()/priority_queue(first, last) | 构造一个空的优先级队列 |
empty( ) |
检测优先级队列是否为空,是返回true,否则返回 false |
top( ) | 返回优先级队列中最大(最小元素),即堆顶元素 |
push(x) | 在优先级队列中插入元素x |
pop() | 删除优先级队列中最大(最小)元素,即堆顶元素 |
对于priority_queue的头文件,我们通过手册发现,priority_queue与queue都是一个头文件。
接口演示:
//默认大根堆
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 oncenamespace 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;};};
如果对向上/向下调整忘记了的,就可以看下面图片回忆。
向上调整
向下调整
五、仿函数/函数对象
仿函数(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;elsecout << "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;elsecout << "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;
}
c++--stack,queue,priority_queue相关推荐
- (P85)stl(十三):容器适配器,stack,queue,优先级队列priority_queue,make_heap
文章目录 1.容器适配器 2.stack 3.queue 4.优先级队列priority_queue 5.make_heap 6.set 1.容器适配器 利用基本容器构造的容器,称之为容器适配器 基本 ...
- Map,List,Queue,Set和Stack的区别
Map,List,Queue,Set和Stack的区别:
- C++中的deque、stack、queue及priority_queue
C++中的deque.stack.queue及priority_queue 文章目录 C++中的deque.stack.queue及priority_queue 一.deque 二.stack 三.q ...
- C++---容器适配器(stack、queue、priority_queue)
容器适配器 首先我们要知道容器适配器是干啥的. 我们可以简单的将容器适配器当做一个接口装置.有的电脑上没有数据转接口,但是有usb接口,这是我们没必要重新买一个电脑,我们可以做一个usb数据转接线.而 ...
- Java集合List,Set,Map,Queue,Deque
集合是Java基础中非常重要的一部分,Java提供了非常丰富的集合API,了解各个集合的特点,怎么样在各种各样的场景中使用正确的集合,非常重要,也是一个Java程序员最基本的素养. 整体了解 Java ...
- 集合,stack,queue,dictionary,ArrayList,listT
哎呀我去,昨天搞那个全排列和寻路算法搞得我脑袋都大了,忘写博了,唉 ,早起补上....... 今天的东西挺多,但没什么难度. 集合:创建和管理相关对象组的第⼆种⽅式就是:创建对象集合. 集合提供⼀种灵 ...
- 成长随心记12(C++容器,stack,queue,list)
4,stack容器 功能:与数据结构的栈类似 限制:不能遍历数据,只能访问栈顶元素 1)stack构造函数 函数原型: stack<T>stk//默认构造形式 stack(const st ...
- C++知识点25——使用C++标准库(容器适配器stack、queue、priority_queue)
除了vector,list,deque等常用的容器,还有根据这些常用的容器进行改造来满足特殊要求的容器,这些特殊容器的行为和常用容器很相近,也称为容器适配器. 常用的容器适配器有三个,分别是stack ...
- C++ 容器适配器(stack、queue、priority_queue)
容器适配器 首先,我们要明白适配器是干什么的?其实就是一个接口转换装置,是得我们能用特定的方法去操作一些我们本来无法操作的东西.举一个例子,比如你的一个设备支持串口线,而你的电脑支持的是usb口,这时 ...
最新文章
- 【BZOJ】2734: [HNOI2012]集合选数
- gcc 常用命令-Wall
- iPhone4S出现应用无法打开时的解决方案
- Spring Cloud Netflix项目进入维护模式之我见
- MVC 3.0 Html.ActionLink
- 【MFC】创建第一个MFC界面项目
- linux 高级命令
- 【渝粤题库】国家开放大学2021春3938管理英语2题目
- dj鲜生-25-用户登陆验证成功-但是没有激活的处理
- 快手副总裁林粼:快手将从事5G相关业务线研究布局
- 卸载驱动出现:rmmod: can't change directory to '/lib/modules': No such file or directory
- HP Loadrunner 11下载地址
- word中批量调整图片大小的方法
- android开启wifi热点命令,Android便携式热点的开启状态检测和SSID的获取方法
- 桥接模式与路由模式有什么不同
- JAVA钓鱼游戏_java实现小猫钓鱼游戏
- pycharm远程调试的“bug”:/miniconda3/envs/learn/bin/python: can‘t open file ‘//<a2a1d7b1c-6145-4e45-...
- mac mini php开发,mac mini主要用来干嘛
- 用powershell代码安装Windows软件
- JAVA设计模式总结之六大设计原则(一)
热门文章
- 在pe下怎样重装服务器系统,PE怎么重装系统|PE系统重装图文教程
- Unloaded branch node detected. “loadOptions“ prop is required to load its children
- 【前端部署】vue项目打包并部署到Linux服务器
- 广义表的长度和深度计算
- poi 读取excel合并单元格两种方式
- LeetCode算法题解 38-报数
- 设置字段默认值Java_小书MybatisPlus第9篇-常用字段默认值自动填充
- BUUCTF 2021-10-4 Pwn
- 【Git】Git pull 拉代码卡在Unpacking objects
- [转]800个有趣句子帮你记忆7000个单词