当前位置: 首页 > 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.lryc.cn/news/590928.html

相关文章:

  • 从AWS MySQL数据库下载备份到S3的完整解决方案
  • istio如何自定义重试状态码
  • NLP——迁移学习
  • pytorch学习笔记(五)-- 计算机视觉的迁移学习
  • 浅探C语言的回调函数(Callback Function)
  • 要实现在调用  driver.get()  后立即阻止页面自动跳转到 Azure 登录页,可通过以下几种方法实现:
  • AWS Lambda 最佳实践:构建高效无服务器应用的完整指南
  • Kubernetes ConfigMap 深度指南
  • 大模型Agent应用开发实战:从框架选型到行业落地
  • ros2 标定相机
  • 三轴云台之测距算法篇
  • 《C++初阶之STL》【auto关键字 + 范围for循环 + 迭代器】
  • 【Dv3Admin】菜单管理集成阿里巴巴自定义矢量图标库
  • 大型语言模型(LLM)在网络安全中最具商业价值的应用场景(Grok3 回答 DeepSearch模式)
  • Python包测试全攻略:从单元测试到持续集成
  • sqli-labs靶场通关笔记:第24关 二次注入
  • LiteSQL:让C++与数据库无缝对接的ORM利器
  • 河南萌新联赛2025第一场-河南工业大学
  • Redis面试相关问题总结
  • string + 栈 bitset 可达性统计(拓扑排序)
  • Redis深度解析:从缓存原理到高并发实战
  • Go语言高并发聊天室(三):性能优化与压力测试
  • 防火墙准入与拦截技术文档
  • Qt初阶开发:QMediaPlayer的介绍和使用
  • 杭州卓健信息科技有限公司 Java 面经
  • iOS App 电池消耗管理与优化 提升用户体验的完整指南
  • 暑期算法训练.3
  • 基于 Electron + Vue 3 的桌面小说写作软件架构设计
  • Python应用指南:使用PyKrige包实现ArcGIS的克里金插值法
  • Kubernetes (k8s)环境重启Pod方式总结