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

news2024/11/17 7:24:38

前言

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

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的优点,而完美的避开了其缺陷。

结语

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

因为这对我很重要。

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

感谢阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2076087.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++从入门到起飞之——vector模拟实现 全方位剖析!

​ &#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1、vector的成员变量 2、迭代器 3、size与capacity 4、[]运算符重载 5、reserve 6、push_bac…

LSTM结合时序异常检测直接写!小论文闭着眼睛发!

还在愁小论文&#xff1f;不如考虑考虑这个方向&#xff1a;LSTM时间序列异常检测。 这是个比较活跃且热门的研究方向&#xff0c;因为LSTM具有非常优秀的时序数据深度处理能力&#xff0c;能够灵活适应不同复杂度的数据&#xff0c;给我们提供高精度的预测结果&#xff0c;在…

时间继电器和定时器

一、概述 1.时间继电器是可以在设定的定时周期内或周期后闭合或断开触点的元器件。 2.时间继电器上可设定的定时周期数量有限&#xff0c;多为一个或两个。定时时长从0.02s至300h(根据产品型号范围不同)。 3.定时器可以理解为一台钟表&#xff0c;它在某个时间点上闭合(断开…

PostgreSQL如何设置主键自增(序列、SERIAL)

文章目录 PostgreSQL如何设置主键自增背景什么是序列Postgresql的自增机制基本使用使用SERIAL或BIGSERIAL数据类型手动创建序列和设置默认值实战demo&#xff1a;PostgreSQL 手动序列管理设置序列的当前值 工作常用总结创建表时候自定义序列&#xff1a;id SERIAL PRIMARY KEY …

调用具体接口的所有实现类

Java获取接口的所有实现类方法-CSDN博客https://blog.csdn.net/feeltouch/article/details/135399078

最实用接地气的 .NET 微服务框架

目录 前言 项目介绍 快速入门 1、服务注册 2、启动UI 3、服务发现与调用 4、启动服务网关 项目地址 最后 前言 微服务架构已经成为搭建高效、可扩展系统的关键技术之一&#xff0c;然而&#xff0c;现有许多微服务框架往往过于复杂&#xff0c;使得我们普通开发者难以…

基于生成对抗模型GAN蒸馏的方法FAKD及其在EdgesSRGAN中的应用

文章目录 FAKD系列论文paper1: FAKD&#xff1a;用于高效图像超分辨率的特征亲和知识蒸馏&#xff08;2020&#xff09;ABSTRACT1. INTRODUCTION2. PROPOSED METHOD2.1. Feature Affinity-based Distillation (FAKD) 2.2. Overall Loss Function3. EXPERIMENTAL RESULTS3.1. Ex…

TypeSript9 命名空间namesapce

我们在工作中无法避免全局变量造成的污染&#xff0c;TypeScript提供了namespace 避免这个问题出现 内部模块&#xff0c;主要用于组织代码&#xff0c;避免命名冲突。命名空间内的类默认私有通过 export 暴露通过 namespace 关键字定义 TypeScript与ECMAScript 2015一样&…

React学习day02-React事件绑定、组件、useState、React组件样式处理方式

3、React事件绑定&#xff08;以点击事件为例&#xff09; &#xff08;1&#xff09;语法&#xff08;整体遵循驼峰命名法&#xff09;&#xff1a;on事件名称{事件处理程序} 比如&#xff1a;点击事件onClick&#xff08;类似于vue中的click&#xff09; &#xff08;2&…

成为Python高手,我能给出的最好建议

今天笔者将向大家分享5个良好的Python编程习惯&#xff0c;大牛认证&#xff0c;通过不断实践&#xff0c;助你写出更Pythonic的代码&#xff0c;让你向Python大师之路更进一步。 今天笔者将向大家分享5个良好的Python编程习惯&#xff0c;大牛认证&#xff0c;通过不断实践&a…

Java面试题精选:消息队列(二)

一、Kafka的特性 1.消息持久化&#xff1a;消息存储在磁盘&#xff0c;所以消息不会丢失 2.高吞吐量&#xff1a;可以轻松实现单机百万级别的并发 3.扩展性&#xff1a;扩展性强&#xff0c;还是动态扩展 4.多客户端支持&#xff1a;支持多种语言&#xff08;Java、C、C、GO、…

WPF中如何根据数据类型使用不同的数据模板

我们在将一个数据集合绑定到列表控件时&#xff0c;有时候想根据不同的数据类型&#xff0c;显示为不同的效果。 例如将一个文件夹集合绑定到ListBox时&#xff0c;系统文件夹和普通文件夹分别显示为不同的效果&#xff0c;就可以使用模板选择器功能。 WPF提供了一个模板选择…

查找4(散列表)

1&#xff09;基本概念 、 2)散列函数的构造 3&#xff09;解决冲突 I&#xff09;开放地址发 II&#xff09;链地址法 4&#xff09;散列表的查找

vs2022 C++ 使用MySQL Connector/C++访问mysql数据库

1、下载MySQL Connector/C&#xff0c;我这里下载的是debug版本&#xff0c;下载链接MySQL :: Download MySQL Connector/C (Archived Versions) 2、解压并且放到MySQL文件夹中&#xff0c;便于使用 3、打开vs2022&#xff0c;右键项目&#xff0c;点击属性 4、在 “C/C” ->…

el-input中show-password密码提示功能去掉

el-input中show-password密码提示功能去掉 一、效果图二、封装个组件三、如何使用 一、效果图 二、封装个组件 <template><divclass"el-password el-input":class"[size ? el-input-- size : , { is-disabled: disabled }]"><inputclass…

Java线上监控诊断产品Arthas(续集)

Java线上监控诊断产品Arthas&#xff08;续集&#xff09; 前言1.auth指令2.monitor指令解读 3.classloader指令场景 4.dump指令场景 5.getstatic指令场景 6.heapdump指令场景 7.profiler指令场景 8.sc指令场景 9.trace指令场景 前言 在去年&#xff0c;我发表了一片文章&…

心血管内科常用评估量表汇总,附操作步骤与评定标准

心血管内科常用量表来评估患者病情、预测风险&#xff0c;量表在制定治疗方案和预测疾病进展等方面发挥着重要作用。常笑医学整理了6个心血管内科常用的评估量表&#xff0c;支持下载和在线使用&#xff0c;供临床医护人员参考。 01 GRACE缺血风险评估 &#xff08;完整量表请点…

Qt 学习第7天:Qt核心特性

元对象系统Meta-object system 来自AI生成&#xff1a; Qt中的元对象系统&#xff08;Meta-Object System&#xff09;是Qt框架的一个核心特性&#xff0c;它为Qt提供了一种在运行时处理对象和类型信息的能力。元对象系统主要基于以下几个关键概念&#xff1a; 1. QObject&a…

【91-136】行为型模式

目录 一.模板方法模式 1.1 概述 1.2 结构 1.3 案例 1.4 优缺点 1.5 使用场景 二.策略模式 2.1 概述 2.2 结构 2.3 案例 2.4 优缺点 2.5 使用场景 2.6 JDK 源码解析 三.命令模式 3.1 概述 3.2 结构 3.3 案例 3.4 优缺点 3.5 使用场景 四.责任链模式 4.1 概…

NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis 翻译

NeRF&#xff1a;将场景表示为用于视图合成的神经辐射场 引言。我们提出了一种方法&#xff0c;该方法通过使用稀疏的输入视图集优化底层连续体场景函数来实现用于合成复杂场景的新视图的最新结果。我们的算法使用全连通&#xff08;非卷积&#xff09;深度网络来表示场景&…