c++栈和队列(stack和queue)

2024-08-26 19:12
文章标签 c++ stack 队列 queue

本文主要是介绍c++栈和队列(stack和queue),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

栈和队列是两个极其相似的数据结构,栈具有先进后出的特性,队列具有先进先出的特性。今天我们就来简单的介绍一下栈和队列这两数据结构,其中队列我们介绍普通队列、双端队列(了解)和优先级队列(其实这就是堆这种数据结构),我们顺便再来认识一下标准库在实现这两数据结构时使用的容器适配器,它对我们实现代码的复用起到怎样的效果

stack

栈是一个先进后出的数据结构,c++标准库的实现可参考stack - C++ Reference (cplusplus.com)

 功能上都不复杂,下面我们先不用容器适配器的方式模拟实现一个,我们可以以组合的方式就,在栈里内置一个顺序表,复用顺序表的实现

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

queue

普通队列

普通队列和栈的功能类似,只不过它是先进先出的结构queue - C++ Reference (cplusplus.com)

 功能上都不复杂,下面我们先不用容器适配器的方式模拟实现一个,我们可以以组合的方式就,在栈里内置一个链表,复用链表的实现

#include <list>
namespace zzzyh
{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;};
}

优先级队列

前面我们介绍了优先级队列其实就是堆,在此基础上我们就来简单的认识一下优先级队列priority_queue - C++ Reference (cplusplus.com)

功能并不复杂,我们知道堆的底层是数组(顺序表),我们可以不使用容器适配器的方式实现,以组合的形式复用顺序表的实现。但这样的实现并不灵活,因为我们在底层可以复用其它的数据结构,不适应容器适配器更换底层的结构是极其复杂的。下面我们就先来学习一下什么是容器适配器,再来用容器适配器的方式实现我们的优先级队列

容器适配器

容器适配器是一种设计模式,它可以屏蔽底层实现的细节,只需要调用接口即可,这也是封装思想的一种实现。容器适配器和我们前面将类模板/函数模板时提到的类型参数类似,也是通过传递类型参数来实现底层复用不同的容器

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为
容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装

再谈stack和queue

下面我们用容器适配器的方式重构我们的代码

stack

#pragma once
//#include <deque>
#include <list>
#include <vector>namespace zzzyh
{template<class T,class Container = std::vector<T>>class stack{public:stack() {}void push(const T& x){_con.push_back(x);}void pop(){_con.pop_back();}const T& top() {return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};
}

queue

queue包含普通队列和优先级队列

普通队列

#pragma once
#include <list>
#include <vector>
//#include <deque>namespace zzzyh
{template<class T, class Container = std::list<T>>class queue{public:queue() {}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();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};}

优先级队列

#pragma once
#include <vector>
//#include <algorithm>namespace zzzyh
{template<class T>class Less{public:bool operator()(const T& t1, const T& t2){return t1 < t2;}};template<class T>class Greate{public:bool operator()(const T& t1, const T& t2){return t1 > t2;}};template<class T, class Container = std::vector<T>,class Com = Less<T>>class priority_queuq{public:void push(const T& x){_con.push_back(x);xiangshangtiaozheng(size() - 1);}const T& top(){return _con.front();}void pop(){std::swap(_con.front(), _con.back());_con.pop_back();xiangxiatiaozheng(0,size());}size_t size(){return _con.size();}bool empty() {return _con.empty();}private:Container _con;void xiangshangtiaozheng(int i) {Com com;while (i > 0 && com(_con[i], _con[(i - 1) / 2])) {std::swap((_con[i]), (_con[(i - 1) / 2]));i = (i - 1) / 2;}}void xiangxiatiaozheng( int i, int sz) {Com com;while (i * 2 + 1 < sz) {int chile = i * 2 + 1;if (chile + 1 < sz && com(_con[chile + 1], _con[chile])) {chile++;}if (com(_con[chile],_con[i] )) {std::swap(_con[i], _con[chile]);i = chile;}else return;}}};}

再谈优先级队列

堆的相关算法

其实,在algorithm这个算法和容器相关的头文件里包含了一些关于堆的算法

 仿函数

前面我们对于升序降序都是写死的,如果我们需要自定义排序方式需要使用到仿函数

仿函数(Functor)是一种能行使函数功能的类,它重载了`operator()`,使得类对象可以像函数一样调用。在C++中,仿函数常用于提高代码复用性和资源管理,尤其是在STL中。仿函数可以避免全局变量,提供更好的封装和扩展性,且可结合模板技术和其他编程思想。

这时我们就可以用仿函数实现比较大小

template<class T>class Less{public:bool operator()(const T& t1, const T& t2){return t1 < t2;}};template<class T>class Greate{public:bool operator()(const T& t1, const T& t2){return t1 > t2;}};

再将仿函数传递给容器,容器就可以按照我们的想法排序

双端队列(deque)

我们在倒回去观察一下标准库实现时的模板

 我们发现标准库在实现时默认底层使用的是deque,我们就来简单的认识一下这个双端队列

我们知道顺序表和链表互为补充,它们的优缺点互为补充,那么是否有一种数据结构可以既有顺序表的优点也有链表的优点呢,双端队列就在这种美好的憧憬下诞生了,但双端队列只有在特定场景下使用,并不能完全代替顺序表和链表的功能

deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端
进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与
list比较,空间利用率比较高。

双端队列(deque)是队列的一种变形,一般队列只能在队尾添加元素(push),在队首删除元素(pop),双端队列则同时在队首或者队尾执行添加和删除工作。C++中,使用双端队列需要包含头文件<deque>。C++中队列的基本操作如下:

  • push_back():在队列尾部添加元素,无返回值。这个操作跟普通队列(queue)的push()方法类似,在队列的尾部添加一个元素;
  • push_front():在队列头部添加元素,无返回值;
  • pop_back():删除队列尾部的元素,无返回值;
  • pop_front():删除队列头部的元素,无返回值;
  • front() :获得队列头部元素。此函数返回值为队列的头部元素,常与pop_front()函数一起,先通过front()获得队列头部元素,然后用pop_front()将其从队列中删除;
  • back(): 获得队列尾部元素。此函数返回值为队列的尾部元素,常与pop_back()函数一起,先通过back()获得队列头部元素,然后用pop_back()将其从队列中删除;
  • size():获得队列大小。此函数返回队列的大小,返回值是“size_t”类型的数据,“size_t”是“unsigned int”的别名;
  • empty() :判断队列是否为空。此函数返回队列是否为空,返回值是bool类型。队列空:返回true;不空:返回false。

更多介绍,可以参考deque - C++ Reference (cplusplus.com)使用详解。

 至于其底层实现,是靠迭代器维护的,相对比较复杂,今天就不在这里展开,我们只介绍其特性和使用

优缺点

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩
容时,也不需要搬移大量的元素,因此其效率是必vector高的。
与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段

deque有一个致命缺陷:不适合遍历,前面我们也介绍了底层是靠迭代器维护的,遍历时迭代器需要频繁校验合法性

在实际中使用线性结构时,大多数情况下优先考虑vector和list,queue的一大实际使用就是vector和list的默认底层实现,这是因为stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。结合了deque的优点,而完美的避开了其缺陷。

结语

以上便是今天的全部内容。如果有帮助到你,请给我一个免费的赞。

因为这对我很重要。

编程世界的小比特,希望与大家一起无限进步。

感谢阅读!

这篇关于c++栈和队列(stack和queue)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1109433

相关文章

hdu1180(广搜+优先队列)

此题要求最少到达目标点T的最短时间,所以我选择了广度优先搜索,并且要用到优先队列。 另外此题注意点较多,比如说可以在某个点停留,我wa了好多两次,就是因为忽略了这一点,然后参考了大神的思想,然后经过反复修改才AC的 这是我的代码 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

poj 3190 优先队列+贪心

题意: 有n头牛,分别给他们挤奶的时间。 然后每头牛挤奶的时候都要在一个stall里面,并且每个stall每次只能占用一头牛。 问最少需要多少个stall,并输出每头牛所在的stall。 e.g 样例: INPUT: 51 102 43 65 84 7 OUTPUT: 412324 HINT: Explanation of the s

poj 2431 poj 3253 优先队列的运用

poj 2431: 题意: 一条路起点为0, 终点为l。 卡车初始时在0点,并且有p升油,假设油箱无限大。 给n个加油站,每个加油站距离终点 l 距离为 x[i],可以加的油量为fuel[i]。 问最少加几次油可以到达终点,若不能到达,输出-1。 解析: 《挑战程序设计竞赛》: “在卡车开往终点的途中,只有在加油站才可以加油。但是,如果认为“在到达加油站i时,就获得了一

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝