当前位置: 首页 > news >正文

stack,queue,priority_queue的模拟实现及常用接口

目录

1.stack和queue应用

1.1stack的使用

1.2stack的模拟实现

1.3queue的使用

1.4queue的模拟实现

2.容器适配器

2.1什么是适配器

2.2 deque介绍

2.3 list和vector的区别

3.priority_queue

3.1priority_queue的使用

3.2 priority_queue的模拟实现

3.3 仿函数


1.stack和queue应用

stack(栈):先进后出

queue(队列):先进先出

1.1stack的使用

函数说明接口说明
stack()

构造空栈

empty判断是否为空
size返回栈中有效数据个数
top返回栈顶元素
push

将元素val压入栈中

pop将栈顶元素弹出
#include<stack>
#include<iostream>
using namespace std;
int main()
{stack<int> s1;s1.push(1);s1.push(2);s1.push(3);s1.push(4);while (!s1.empty()){cout << s1.top() << " ";s1.pop();}return 0;
}

1.2stack的模拟实现

对于stack的模拟实现,从栈的接口中可以看出,栈实际是一种特殊的vector,因此使用vector完全可以模拟实现stack

#include<iostream>
#include<vector>
using namespace std;
namespace chuxin
{template<class T>class stack{public:stack(){}void push(const T& x){_s.push_back(x);}void pop() { _s.pop_back(); }T& top() { return _s.back(); }const T& top()const { return _s.back(); }size_t size()const { return _s.size(); }bool empty()const { return _s.empty(); }private:std::vector<T> _s;};
}

1.3queue的使用

函数说明接口说明
queue()构造空的队列
empty
检测队列是否为空,是返回true,否则返回false
size
返回队列中有效元素的个数
front
返回队头元素的引用
back
返回队尾元素的引用
push
在队尾将元素val入队列
pop
将队头元素出队列
#include<queue>
#include<iostream>
using namespace std;
int main()
{queue<int> s1;s1.push(1);s1.push(2);s1.push(3);s1.push(4);cout << "s1.back()"<<" :";while (!s1.empty()){cout<< s1.back() << " ";s1.pop();}cout << endl;s1.push(1);s1.push(2);s1.push(3);s1.push(4);cout << "s1.front()" << " :";while (!s1.empty()){cout <<s1.front()<<" ";s1.pop();}return 0;
}

 

1.4queue的模拟实现

因为queue的接口中存在头删和尾插,因此使用vector来封装效率太低,故可以借助list来模拟实
queue
#include <list>
namespace chuxin
{template<class T>class queue{public:queue() {}void push(const T& x) { _c.push_back(x); }void pop() { _c.pop_front(); }T& back() { return _c.back(); }const T& back()const { return _c.back(); }T& front() { return _c.front(); }const T& front()const { return _c.front(); }size_t size()const { return _c.size(); }bool empty()const { return _c.empty(); }private:std::list<T> _c;};
}

2.容器适配器

2.1什么是适配器

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设
计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口,虽然stackqueue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STLstackqueue默认使用deque,对于stack,queue在上文中,容器的类型已经写死了,stack只能用vector类初始化,queue只能用list初始化,此时就可以利用模版进行改动
stack:
#include<iostream>
#include<deque>
using namespace std;
namespace chuxin
{template<class T, class Container = deque<T>>//适配多种容器class stack{public:stack(){}void push(const T& x){_s.push_back(x);}void pop() { _s.pop_back(); }T& top() { return _s.back(); }const T& top()const { return _s.back(); }size_t size()const { return _s.size(); }bool empty()const { return _s.empty(); }private:Container _s;};
}

queue:

#include <deque>
#include<iostream>
using namespace std;
namespace chuxin
{template<class T, class Container = deque<T>>class queue{public:queue() {}void push(const T& x) { _c.push_back(x); }void pop() { _c.pop_front(); }T& back() { return _c.back(); }const T& back()const { return _c.back(); }T& front() { return _c.front(); }const T& front()const { return _c.front(); }size_t size()const { return _c.size(); }bool empty()const { return _c.empty(); }private:Container _c;};
}

2.2 deque介绍

在上文,发现stack和queue容器的缺省值是deque,为什么用这个容器呢?

优点:

deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端 进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高,相当于vector和list的缝合怪
stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()pop_back()操作的线性结构,都可以作为stack的底层容器,比如vectorlist都可以
queue是先进先出的特殊线性数据结构,只要具有push_backpop_front操作的线性结构,都可以作为queue的底层容器,比如list。
但是STL中对stackqueue默认选择deque作为其底层容器,主要是因为:
1. stackqueue不需要遍历(因此stackqueue没有迭代器),只需要在固定的一端或者两端进行操作。
2. stack中元素增长时,dequevector的效率高(扩容时不需要搬移大量数据)queue中的
元素增长时,deque不仅效率高,而且内存使用率高。
对于deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于动态的二维数组,deque的插入,删除则涉及它的迭代器,由于deque的迭代器较复杂本文不做叙述
缺陷:
vector比较deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。
list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。
但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实 际中,需要线性结构时,大多数情况下优先考虑vectorlistdeque的应用并不多,而目前能看到的一个应用就是,STL用其作为stackqueue的底层数据结构

2.3 list和vector的区别

vectorlist都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及
应用场景不同,其主要不同如下:
vector
list
动态顺序表,一段连续空间
带头结点的双向循环链表
访
支持随机访问,访问某个元素效率O(1)
不支持随机访问,访问某个元素效率O(N)
任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更
任意位置插入和删除效率高, 不需要搬移元素,时间复杂度 O(1)
任意位置插入和删除效率高,不需要搬移元素,时间复杂度O(1)
底层节点动态开辟,小节点容
易造成内存碎片,空间利用率
低,缓存利用率低
原生态指针
对原生态指针(节点指针)进行
封装
在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失
插入元素不会导致迭代器失
效,删除元素时,只会导致当
前迭代器失效,其他迭代器不
受影响
使
需要高效存储,支持随机访问,不关心插入删除效
大量插入和删除操作,不关心
随机访问

3.priority_queue

3.1priority_queue的使用

priority_queue的使用和queue的一样,不同的是他们的底层的一些结构,以及处理数据不同,

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认情况下priority_queue是大堆
函数声明接口说明
priority_queue()
构造一个空的优先级队列
priority_queue(first, last)利用迭代器区间建立
empty
检测优先级队列是否为空,是返回true,否
则返回false
top
返回优先级队列中最大(最小元素),即堆顶元
push
在优先级队列中插入元素x
pop
删除优先级队列中最大(最小)元素,即堆顶元
#include<queue>
#include<iostream>
#include<list>
using namespace std;
int main()
{priority_queue<int> s1;s1.push(5);s1.push(3);s1.push(7);s1.push(2);s1.push(6);while (!s1.empty()){cout <<s1.top()<<" ";s1.pop();}return 0;
}

3.2 priority_queue的模拟实现

priority_queue的底层就是一个堆,因此它的模拟实现,和堆的类似

#include<vector>
namespace chuxin
{// 默认是大堆template<class T, class Container = vector<T>>class priority_queue{public:void AdjustUp(int child){int 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);AdjustUp(_con.size() - 1);}void AdjustDown(int parent){// 先假设左孩子小size_t child = parent * 2 + 1;while (child < _con.size())  // child >= n说明孩子不存在,调整到叶子了{// 找出小的那个孩子if (child + 1 < _con.size() && _con[child] < _con[child + 1]){++child;}if (_con[parent] < _con[child]){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}const T& top(){return _con[0];}size_t size() const{return _con.size();}bool empty() const{return _con.empty();}private:Container _con;};
}

这种方法还是有缺陷,发现只能排降序,有没有一种方法既能排升序又能排降序?这时候就需要仿函数了

3.3 仿函数

来看看下面这段冒泡排序

void BubbleSort(int* a, int n)
{for (int j = 0; j < n; j++){// 单趟int flag = 0;for (int i = 1; i < n - j; i++){if (a[i] < a[i - 1]){swap(a[i - 1], a[i]);flag = 1;}}if (flag == 0){break;}}
}

发现它的逻辑是排一个升序,如果要排一个降序怎么办,为了一个比较重载一个大部分相似的函数,显然不怎么好,而且比较的数据可能不同,有时候比较整形,有时候是浮点型,这时候就可以利用模版自己写一个比较函数,而这个函数称为仿函数

// 仿函数:本质是一个类,这个类重载operator(),他的对象可以像函数一样使用
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;}
};
template<class Compare>
void BubbleSort(int* a, int n, Compare com)
{for (int j = 0; j < n; j++){// 单趟int flag = 0;for (int i = 1; i < n - j; i++){//if (a[i] < a[i - 1])if (com(a[i], a[i - 1])){swap(a[i - 1], a[i]);flag = 1;}}if (flag == 0){break;}}
}
int main()
{Less<int> LessFunc;Greater<int> GreaterFunc;// 函数对象cout << LessFunc(1, 2) << endl;cout << LessFunc.operator()(1, 2) << endl;int a[] = { 9,1,2,5,7,4,6,3 };BubbleSort(a, 8, LessFunc);BubbleSort(a, 8, GreaterFunc);BubbleSort(a, 8, Less<int>());BubbleSort(a, 8, Greater<int>());return 0;
}

此时就发现,改变是否是升降序,就可以利用两个类对象就可以做到,此时priority_queue的比较就是这样

#include<vector>
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;}
};
namespace chuxin
{// 默认是大堆template<class T, class Container = vector<T>, class Compare = Less<T>>class priority_queue{public:void AdjustUp(int child){Compare com;int parent = (child - 1) / 2;while (child > 0){//if (_con[parent] < _con[child])if (com(_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);AdjustUp(_con.size() - 1);}void AdjustDown(int parent){// 先假设左孩子小size_t child = parent * 2 + 1;Compare com;while (child < _con.size())  // child >= n说明孩子不存在,调整到叶子了{// 找出小的那个孩子//if (child + 1 < _con.size() && _con[child] < _con[child + 1])if (child + 1 < _con.size() && com(_con[child], _con[child + 1])){++child;}//if (_con[parent] < _con[child])if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}const T& top(){return _con[0];}size_t size() const{return _con.size();}bool empty() const{return _con.empty();}private:Container _con;};
}

http://www.dtcms.com/a/284877.html

相关文章:

  • Qt6-学习Cmakelist(翻译官方文档)
  • Pytorch深度学习框架实战教程02:开发环境部署
  • python学智能算法(二十二)|SVM-点与超平面的距离
  • faster-lio仿真环境问题及解决
  • 腾讯云服务上下载docker以及使用Rabbitmq的流程
  • Python网络爬虫——介绍
  • 【unitrix】 6.5 基础整数类型特征(base_int.rs)
  • Redis:哨兵(Sentinel)
  • MySQL的索引操作及底层结构浅析
  • 产品经理如何描述用户故事
  • modelscope ProxyError: HTTPSConnectionPool(host=‘www.modelscope.cn‘, port=443)
  • 作物长势产量预测yyds!遥感数据同化DSSAT模型,区域精准农业就靠它!
  • 27、鸿蒙Harmony Next开发:ArkTS并发(Promise和async/await和多线程并发TaskPool和Worker的使用)
  • Hyperledger Fabric:构建企业区块链网络的实践指南
  • 【实时Linux实战系列】硬件中断与实时性
  • 什么是GEO 和 SEO ?GEO 与 SEO 有什么区别?如何快速入门GEO?
  • 解决宝塔面板SSL报错 - AttributeError: module ‘acme_v2‘ has no attribute ‘acme_v2‘
  • 搜广推校招面经九十四
  • 神经网络构建
  • STM32之土壤湿度传感器模块
  • Toward the unification of kselftests and KUnit
  • MYSQL-进阶-锁
  • 离散与组合数学 杂记
  • 电子制造企业排产优化实战:用JVS智能排产的排产策略实现交期100%可控
  • OCR 与 Agent:智能协作的 “黄金搭档”
  • Spring Boot整合阿里云OSS:企业级文件存储最佳实践
  • ZYNQ UltraScale+ MPSoC芯片 pcie switch级联ssd高速存储方案
  • 基于springboot+vue+mysql平台的医疗病历交互系统(源码+论文)
  • 解析:视频创作中常见版权问题
  • JAVA原生实现SOAP POST调用