c++之stack_queue与反向迭代器的实现

目录

1. 简单介绍stack与queue的使用

1.1 stack的介绍与使用

stack的介绍

stack的使用 

相关题目

1.2 queue的介绍与使用 

queue的介绍

queue的使用

相关题目

2.stack与queue的模拟实现 

容器适配器 

2.1 stack的模拟实现

2.2 queue的模拟实现 

2.3 priority_queue的模拟实现  

仿函数是什么? 

3. deque的介绍 

3.1 deque的原理 

3.2 deque的缺陷 

3.3 为什么选择deque作为stack和queue的底层默认容器 

4. 反向迭代器 


1. 简单介绍stack与queue的使用

1.1 stack的介绍与使用

stack的介绍

1. stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
2. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。< 这里我们在模拟实现会重点讲解>
3. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
empty:判空操作
back:获取尾部元素操作
push_back:尾部插入元素操作
pop_back:尾部删除元素操作
4. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。

 对于栈的特性想必大家都不陌生,下图是它的特性图。

stack的使用 

stack的使用很简单,最常用的就是下表的几个函数,无非压栈出栈,判空取尾。

函数说明接口说明
stack()构造空的栈
empty()检测stack是否为空
size()返回stack中元素的个数
top()返回栈顶元素的引用
push()将元素val压入stack中
pop()将stack中尾部的元素弹出

这里就不做示范了,非常简单。

相关题目

155. 最小栈 - 力扣(LeetCode)

栈的压入、弹出序列_牛客题霸_牛客网 (nowcoder.com)

150. 逆波兰表达式求值 - 力扣(LeetCode)

1.2 queue的介绍与使用 

queue的介绍

1. 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列
3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
empty:检测队列是否为空
size:返回队列中有效元素的个数
front:返回队头元素的引用
back:返回队尾元素的引用
push_back:在队列尾部入队列
pop_front:在队列头部出队列
4. 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque

queue的使用

函数声明接口说明
queue()构造空的队列
empty()检测队列是否为空,是返回true,否则返回false
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素val入队列
pop()将队头元素出队列

相关题目

225. 用队列实现栈 - 力扣(LeetCode)

2.stack与queue的模拟实现 

容器适配器 

学习stack与queue的模拟实现就必然要与容器适配器打招呼,那么什么是容器适配器呢? 

在stack的介绍中我们看到有这样一句话。 

下图有两个不同的容器,把水分别倒进去,此时两边的水就有了圆柱与圆锥的形状,但本质还是水,把沙子倒进去也一样。我们要学习的容器适配器同理。

分别用vector与list实现stack,此时的vector与list就具有了栈后进先出的特性,我们要实现的各种功能也都是调用vector与list的接口。

分别用vector与list实现queue,此时的vector与list就具有了队列先进先出的特性,我们要实现的各种功能同样都是调用vector与list的接口。

在这里可以把stack与queue理解成不同特性的容器。将vector等容器填充进去,使他们具备容器的特性。

接下来我们来看看c++中的适配器是什么?

在C++中,适配器(adapter)通常指的是一种设计模式,用于将一个类的接口转换成另一个类的接口,以便两者能够协同工作而无需修改原始类的代码。适配器模式可以分为类适配器模式和对象适配器模式。

1. 类适配器模式:通过继承原始类和实现目标接口来实现适配器。
2. 对象适配器模式:通过包含原始类的实例并实现目标接口来实现适配器。

在STL(标准模板库)中,也有一些称为适配器的特定类,如:
std::stack:基于deque、list或vector实现的栈适配器。
std::queue:基于deque或list实现的队列适配器。
std::priority_queue:基于vector实现的优先队列适配器。

这些适配器类提供了一些特定的接口和功能,使得使用栈、队列或优先队列更加方便和高效。

2.1 stack的模拟实现

既然已经明白了stack是一个容器适配器,那么stack的模拟实现自然就是向其内填充 vector等容器。

这里要注意,因为实现stack可以使用vector,list,deque等容器,因此我们需要使用到模板。

在模板参数中,我们将容器参数缺省为deque。各种功能接口也都是调用其内容器的接口。

	template <class T, class container = deque<T>>
	class stack
	{
	public:
		stack(container con = container())
			:_con(con)
		{}

		void push(const T& x)
		{
			_con.push_back(x);
		}

		void pop()
		{
			_con.pop_back();
		}

		T& top()
		{
			return _con.back();
		}

		const T& top()const
		{
			return _con.back();
		}

		size_t size()const
		{
			return _con.size();
		}

		bool empty()const
		{
			return _con.empty();
		}
	private:
		container _con;
	};

2.2 queue的模拟实现 

queue的原理同stack一样,直接上代码。

    template<class T, class Con = deque<T>>

    class queue

    {

    public:

        queue(Con con=Con())
            :_con(con)
        {}

        void push(const T& x)
        {
            _con.push_back(x);
        }

        void pop()
        {
            _con.pop_front();
        }
     
        T& back()
        {
            
        }

        const T& back()const
        {
            return _con.back();
        }

        T& front()
        {
            return _con.front();
        }

        const T& front()const
        {
            return _con.front();
        }

        size_t size()const
        {
            return _con.size();
        }

        bool empty()const
        {
            return _con.empty();
        }

    private:

        Con _con;

    };

2.3 priority_queue的模拟实现  

 priority_queue是什么呢?

优先级队列,顾名思义,优先级队列是会给队列中的数据排序的,那么是怎么做到的呢?

这里请想想堆,堆在插入数据时尾插,插入时按照向上调整算法维护堆;删除数据时删头,交换头尾,删除尾,再进行向下调整算法维护堆。和队列的特性非常符合,是完美的优先级队列底层结构。

template <class T>
class less
{
public:
    bool operator()(T x, T y)
    {
        return x < y;
    }
};
template <class T>
class greater
{
public:
    bool operator()(T x, T y)
    {
        return x > y;
    }
};
template <class T, class Container = vector<T>, class Compare = greater<T> >
class priority_queue

{

public:
    Compare com;
    priority_queue(Container con=Container())
        :_con(con)
    {}

    bool empty() const
    {
        return _con.empty();
    }

    size_t size() const
    {
        return _con.size();
    }

    const T& top() const
    {
        return _con[0];
    }

    void Addjust_Up(int child)
    {
        T parent = (child - 1) / 2;
        while (parent >= 0)
        {
            if (com(_con[child], _con[parent]))
            {
                std::swap(_con[parent], _con[child]);
                child = parent;
                parent = (child - 1) / 2;
            }
            else
            {
                break;
            }
        }

    }
    void push(const T& x)
    {
        _con.push_back(x);

        Addjust_Up(_con.size()-1);
    }

    void AddJust_Down(int parent)
    {
        size_t child = parent * 2 + 1;
        
        while (child < _con.size())
        {
            if (child + 1 < _con.size() && com(_con[child + 1], _con[child]))
            {
                ++child;
            }
            if (com(_con[child], _con[parent]))
            {
                std::swap(_con[parent], _con[child]);
                parent = child;
                child = parent * 2 + 1;
            }
            else
            {
                break;
            }
        }
    }
    void pop()//堆顶的删除,先交换堆顶与堆尾,然后删除堆尾,再向下调整
    {
        
            std::swap(_con[0], _con[_con.size() - 1]);
            _con.pop_back();

            AddJust_Down(0);
        
    }
private:

    Container _con;
};

priority_queue的模拟实现相较普通queue要复杂一些,因为我们要实现建堆与堆删除的向上调整与向下调整算法。同时堆有大堆小堆之分,但他们的代码只在某个位置有所不同,为了提高代码的复用率,我们使用了仿函数。

仿函数是什么? 

在C++中,仿函数(functor)是一个类或结构体,它重载了函数调用运算符(),从而可以像函数一样被调用。仿函数可以用作函数对象,用于实现自定义的函数行为。 

如下图就是仿函数的使用方法。 

在优先级队列的向上(下)调整算法中我们使用到了它。

3. deque的介绍 

在上文中,我们提到stack与deque的默认实现结构都是deque,那么他到底有怎样神奇的力量呢?

3.1 deque的原理 

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

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:

这里的map是一个指针数组,map内存放的指针各自指向一个存放数据的数组。我们将map内存放的数组叫做buffer。


 

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示

对于一个数组buffer,first指向buffer的第一个元素,last指向最后一个元素的下一个,cur指当前元素,node则是指向buffer的,用来表明当前处于哪一个buffer。 


 

那deque是如何借助其迭代器维护其假想连续的结构呢?

不同的编译器,对于buffer的处理不同,每一个map内存放的缓冲区buffer有可能是登场的,也可能是非等长的,这里我们只需要了解。


3.2 deque的缺陷 

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。
与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。
但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。

3.3 为什么选择deque作为stack和queue的底层默认容器 

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:
1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。
结合了deque的优点,而完美的避开了其缺陷。

4. 反向迭代器 

在之前的list与vector模拟实现中,我们实现了他们的正向迭代器,并未对反向迭代器做处理。

为什么在这里谈反向迭代器呢?是因为当我们实现了stack与queue后再学习反向迭代器会简单很多,反向迭代器的实现与stack有异曲同工之妙,让我们来看看吧!

下面的代码就是反向迭代器的模拟实现。复用传入的迭代器来实现我们需要的功能。

这里的rbegin与rend指向位置同begin,end位置是对称的。因此我们实现operator*时需要返回前一个的数值。

	template<class Iterator, class Ref, class Ptr>
	struct Reverse_iterator
	{
		typedef Reverse_iterator<Iterator, Ref, Ptr> self;
		Reverse_iterator(Iterator it)
		{
			_it(it);
		}

		Ref operator*()
		{
			Iterator tmp(_it);
			tmp--;
			return *tmp;
		}

		Ptr operator->()
		{
			return &(operator*());
		}

		self& operator++()
		{
			return --_it;
		}

		self& operator++(int)
		{
			Iterator tmp(_it);
			--_it;
			return tmp;
		}

		self& operator--()
		{
			return ++_it;
		}

		self& operator--(int)
		{
			Iterator tmp(_it);
			++_it;
			return tmp;
		}

		bool operator==(const self& s)
		{
			return s._it == _it;
		}

		bool operator!=(const self& s)
		{
			return s._it != _it;
		}


		Iterator _it;


	};


 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

博客系统实现

一.准备工作 1.创建项目&#xff0c;把前端写好的博客静态页面拷贝到webapp目录中 2.引入依赖&#xff0c;这里主要用到servlet&#xff0c;mysql5.1.47&#xff0c;jacson2.15.0 3.找到右上角的edit configurations->smartTomcat->进行配置 4.数据库设计&#xff1a…

【经典算法】LeetCode 5: 最长回文子串(Java/C/Python3实现含注释说明,Medium)

目录 题目描述思路及实现方式一&#xff1a;动态规划法思路代码实现Java版本C语言版本Python3版本 复杂度分析 方式二&#xff1a;中心扩展法思路代码实现Java版本C语言版本Python3版本 复杂度分析 总结相似题目 标签(题目类型)&#xff1a;回文串、动态规划 题目描述 给定一…

【C++】unordered 系列关联式容器

文章目录 1. unordered 系列关联式容器2. unordered_map2.1 unordered_map 的文档介绍2.2 unordered_map 的接口说明 3. unordered_set4. 在线 OJ 1. unordered 系列关联式容器 在 C 98 中&#xff0c;STL 提供了底层为红黑树结构的一系列关联式容器&#xff0c;在查询时效率可…

吴恩达AndrewNg 关于Agent工作流的分享

主要观点 &#x1f393; 基于HumanEval的测试,使用智能体工作流确实能够显著提升大语言模型的表现,有时甚至超过下一代更强大的模型。&#x1f504; AI智能体设计包括四种模式&#xff1a;反思、工具使用、规划、多智能体协作。&#x1f3d7;️ 快速token生成对于提高AI智能体…

智慧公厕是智慧城市建设中不可或缺的一部分

智慧城市的数字化转型正在取得显著成效&#xff0c;各项基础设施的建设也在迅速发展&#xff0c;其中智慧公厕成为了智慧城市体系中不可或缺的一部分。作为社会生活中必要的设施&#xff0c;公共厕所的信息化、数字化、智慧化升级转型能够实现全区域公共厕所管理的横向打通和纵…

第12章 集合框架

一 集合框架概述 1.1 生活中的容器 1.2 数组的特点与弊端 一方面&#xff0c;面向对象语言对事物的体现都是以对象的形式&#xff0c;为了方便对多个对象的操作&#xff0c;就要对对象进行存储。另一方面&#xff0c;使用数组存储对象方面具有一些弊端&#xff0c;而Java 集合…

设计模式 -- 发布订阅模式

发布订阅模式&#xff1a; 订阅者把自己想订阅的事件注册到调度中心&#xff0c;当发布者发布该事件到调度中心&#xff0c;也就是该事件触发时&#xff0c;由调度者统一调度订阅者注册到调度中心的处理代码。 在javaScript 中我们一般使用事件模型来代替传统的发布订阅模式。 …

一文搞懂路由器2.4G和5G的区别,以及双频合一模式

我们知道&#xff0c;无线路由器是平时生活和工作中最常见不过的一个无线设备&#xff0c;通过它我们的手机、笔记本、智能电视、摄像头等&#xff0c;都可以接入互联网。 其实WiFi在1998年就开始使用了&#xff0c;当时仅仅是在欧美地区小范围使用&#xff0c;我们国家在2008年…

关于Ansible模块 ④

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 继《关于Ansible的模块 ①》、《关于Ansible的模块 ②》与《关于Ansible的模块 ③》之后&#xff0c;继续学习ansible常用模块之…

C++流程控制语句:嵌套循环案例分析【九九乘法表】

在C编程中&#xff0c;循环语句的嵌套是一种常见且强大的技术手段&#xff0c;它允许我们将多个循环结构相互嵌套&#xff0c;形成多维循环。不论是for循环、while循环还是do…while循环&#xff0c;均可以进行嵌套。 而在实践中&#xff0c;由于for循环具有明确的循环变量初…

[法规规划|数据概念]数据要素市场三月速递

“ 代表关注&#xff0c;市场活跃&#xff0c;发展迅速” 01—听听两会代表怎么说 在2024年的全国两会期间&#xff0c;数据要素作为新型的生产要素受到广泛关注&#xff0c;众多代表围绕数据要素市场化、立法、安全监管、人才培养及基础设施建设等方面&#xff0c;积极建言献策…

基于centos7安装docker+k8s+KubeSphere

实验环境&#xff1a;&#xff08;每个服务器推荐内存为8G&#xff09; 服务器 ip地址 主机名 centos7 192.168.80.1…

模型量化——NVIDIA——方案选择(PTQ、 partialPTQ、 QAT)

PTQ、 partialPTQ、 QAT 选择流程 PTQ、 partialPTQ、 QAT 咨询NVIDIA 官方后&#xff0c;他们的校正过程一致&#xff0c;支持的量化算子本质是一样的&#xff0c;那么如果你的算子不是如下几类&#xff0c;那么需要自己编写算子。参考TensorRT/tools/pytorch-quantization/py…

数据库入门-----SQL基础知识

目录 &#x1f4d6;前言&#xff1a; &#x1f4d1;SQL概述&&通用语法&#xff1a; &#x1f433;DDL&#xff1a; &#x1f43b;操作数据库&#xff1a; &#x1f41e;数据类型&#xff1a; &#x1f989;操作表&#xff1a; &#x1f9a6;DML: 语法规则&#x…

helm与k8基础

文章目录 一、helm二、K8S/K3S1.K8S基本组件1.1 资源对象1.2 核心组件1.3典型的创建 Pod 的流程1.4 Kubernetes 多组件之间的通信原理 三、容器运行时 Containerd1.查看当前k3s使用的容器运行时CRI2.K3S修改docker为运行环境3. Containerd 参考 一、helm Helm是Kubernetes的包…

吴恩达机器学习理论基础解读—线性模型(单一特征拟合)

吴恩达机器学习理论基础——线性模型 机器学习最常见的形式监督学习&#xff0c;无监督学习 线性回归模型概述 应用场景一&#xff1a;根据房屋大小预测房价 应用场景二&#xff1a;分类算法&#xff08;猫狗分类&#xff09; 核心概念&#xff1a;将训练模型的数据称为数…

使用C语言函数对数组进行操作

前言 在我们了解数组和函数之后&#xff0c;我们对数组和函数进行结合&#xff0c;之后完成一些操作吧 题目描述 杰克想将函数与数组结合进行一些操作&#xff0c;以下是他想要达到的效果&#xff0c;请你帮帮他吧&#xff01; 创建一个整型数组&#xff0c;完成对数组的操作 1…

Taro框架中的H5 模板基本搭建

1.H5 模板框架的搭建 一个h5 的基本框架的搭建 基础template 阿乐/H5 Taro 的基础模板

人民网至顶科技:《开启智能新时代:2024中国AI大模型产业发展报告发布》

​3月26日&#xff0c;人民网财经研究院与至顶科技联合发布《开启智能新时代&#xff1a;2024年中国AI大模型产业发展报告》。该报告针对AI大模型产业发展背景、产业发展现状、典型案例、挑战及未来趋势等方面进行了系统全面的梳理&#xff0c;为政府部门、行业从业者以及社会公…

推荐一款自动化测试神器---Katalon Studio

Katalon Studio介绍 Katalon Studio 是一款在网页应用、移动和网页服务方面功能强大的自动化测试解决方案。基于 Selenium 和 Appium框架&#xff0c;Katalon Studio集成了这些框架在软件自动化方面的优点。这个工具支持不同层次的测试技能集。非程序员也可以快速上手一个自动…