【C++】Stack Queue -- 详解

news2024/10/1 23:40:44

一、stack的介绍和使用

1、stack的介绍

https://cplusplus.com/reference/stack/stack/?kw=stack

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

2、stack的使用


3、stack的模拟实现

从栈的接口中可以看出,栈实际是一种特殊的 vector,因此使用 vector 完全可以模拟实现 stack
#include<vector>
namespace bite
{
    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的介绍和使用

1、queue的介绍

http://www.cplusplus.com/reference/queue/queue/

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

2、queue的使用


3、queue的模拟实现

因为 queue 的接口中存在头删和尾插,因此使用 vector 来封装效率太低,故可以借助 list 来模拟实现 queue, 具体如下:
#include <list>
namespace bite
{
    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的介绍和使用

1、priority_queue的介绍

http://www.cplusplus.com/reference/queue/priority_queue/

1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类, queue 提供一组特定的成员函数来访问其元素。元素从特定容器的 “尾部” 弹出,其称为优先队列的顶部。
4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
  • empty():检测容器是否为空。
  • size():返回容器中有效元素个数。
  • front():返回容器中第一个元素的引用。
  • push_back():在容器尾部插入元素。
  • pop_back():删除容器尾部元素
5. 标准容器类 vector 和 deque 满足这些需求。默认情况下,如果没有为特定的  priority_queue 类实例化指定容器类,则使用 vector。
6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap 和 pop_heap 来自动完成此操作。

2、priority_queue的使用

优先级队列默认使用 vector 作为其底层存储数据的容器,在 vector 上又使用了堆算法将vector 中元素构造成堆的结构,因此 priority_queue 就是堆,所有需要用到堆的位置,都可以考虑使用 priority_queue
注意 默认情况下 priority_queue 是 大堆

【注意】
1. 默认情况下,priority_queue 是大堆。
#include <vector>
#include <queue>
#include <functional> // greater算法的头文件

void TestPriorityQueue()
{
    // 默认情况下,创建的是大堆,其底层按照小于号比较
    vector<int> v{3, 2, 7, 6, 0, 4, 1, 9, 8, 5};
    priority_queue<int> q1;
    for (auto& e : v)
    {
        q1.push(e);
    }
    cout << q1.top() << endl;

    // 如果要创建小堆,将第三个模板参数换成greater比较方式
    priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
    cout << q2.top() << endl;
}
2. 如果在 priority_queue 中放自定义类型的数据,用户需要在自定义类型中提供 > 或者< 的重载。
class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year)
        , _month(month)
        , _day(day)
    {}
    bool operator<(const Date& d)const
    {
        return (_year < d._year)
        || (_year == d._year && _month < d._month)
        || (_year == d._year && _month == d._month && _day < d._day);
    }

    bool operator>(const Date& d)const
    {
        return (_year > d._year)
        || (_year == d._year && _month > d._month)
        || (_year == d._year && _month == d._month && _day > d._day);
    }

    friend ostream& operator<<(ostream& _cout, const Date& d)
    {
        _cout << d._year << "-" << d._month << "-" << d._day;
        return _cout;
    }
private:
    int _year;
    int _month;
    int _day;
};

void TestPriorityQueue()
{
    // 大堆,需要用户在自定义类型中提供<的重载
    priority_queue<Date> q1;
    q1.push(Date(2023, 9, 29));
    q1.push(Date(2023, 9, 28));
    q1.push(Date(2023, 9, 30));
    cout << q1.top() << endl;
 
    // 如果要创建小堆,需要用户提供>的重载
    priority_queue<Date, vector<Date>, greater<Date>> q2;
    q2.push(Date(2023, 9, 29));
    q2.push(Date(2023, 9, 28));
    q2.push(Date(2023, 9, 30));
    cout << q2.top() << endl;
}

3、priority_queue的模拟实现

通过对 priority_queue 的底层结构就是堆,因此此处只需对对进行通用的封装即可。

#pragma once

#include <iostream>
using namespace std;

#include <vector>
// priority_queue--->堆

namespace xxyy
{
	template<class T>
	struct less
	{
		bool operator()(const T& left, const T& right)
		{
			return left < right;
		}
	};

	template<class T>
	struct greater
	{
		bool operator()(const T& left, const T& right)
		{
			return left > right;
		}
	};

	template<class T, class Container = std::vector<T>, class Compare = less<T>>
	class priority_queue
	{
	public:
		// 创造空的优先级队列
		priority_queue() : c()
		{}

		template<class Iterator>
		priority_queue(Iterator first, Iterator last)
			: c(first, last)
		{
			// 将c中的元素调整成堆的结构
			int count = c.size();
			int root = ((count - 2) >> 1);
			for (; root >= 0; root--)
			{
				AdjustDown(root);
			}	
		}

		void push(const T& data)
		{
			c.push_back(data);
			AdjustUP(c.size() - 1);
		}

		void pop()
		{
			if (empty())
			{
				return;
			}	
			swap(c.front(), c.back());
			c.pop_back();
			AdjustDown(0);
		}

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

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

		// 堆顶元素不允许修改,因为堆顶元素修改会破坏堆的特性
		const T& top()const
		{
			return c.front();
		}
	private:
		// 向上调整
		void AdjustUP(int child)
		{
			int parent = ((child - 1) >> 1);
			while (child)
			{
				if (Compare()(c[parent], c[child]))
				{
					swap(c[child], c[parent]);
					child = parent;
					parent = ((child - 1) >> 1);
				}
				else
				{
					return;
				}
			}
		}

		// 向下调整
		void AdjustDown(int parent)
		{
			size_t child = parent * 2 + 1;
			while (child < c.size())
			{
				// 找以parent为根的较大的孩子
				if (child + 1 < c.size() && Compare()(c[child], c[child + 1]))
					child += 1;

				// 检测双亲是否满足情况
				if (Compare()(c[parent], c[child]))
				{
					swap(c[child], c[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					return;
				}	
			}
		}
	private:
		Container c;
	};
}


void TestQueuePriority()
{
	xxyy::priority_queue<int> q1;
	q1.push(5);
	q1.push(1);
	q1.push(4);
	q1.push(2);
	q1.push(3);
	q1.push(6);
	cout << q1.top() << endl;

	q1.pop();
	q1.pop();
	cout << q1.top() << endl;

	vector<int> v{5, 1, 4, 2, 3, 6};
	xxyy::priority_queue<int, vector<int>, xxyy::greater<int>> q2(v.begin(), v.end());
	cout << q2.top() << endl;

	q2.pop();
	q2.pop();
	cout << q2.top() << endl;
}

⚪【补充】 

之前我们要让大堆 -> 小堆,都是直接去修改符号,那能不能在不修改符号的情况下就能达到效果呢?

C 语言其实可以利用函数指针来解决。但是 C++ 放弃用函数指针的方式,且非常不建议用函数指针,因为这样做比较复杂。可以说 C++ 里的仿函数 / 函数对象就是为了替代 C 语言里的函数指针。

" () " 的功能可以提高优先级、强制类型转换、函数名 (形参表),仿函数用的是函数名 (形参表)。我们可以实现两个类,分别重载 " () " 完成比较大小功能,然后用 priority_queue 这个类来实例化控制。 


四、容器适配器

1、什么是适配器

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口


2、STL标准库中stackqueue的底层结构

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

 


3、deque的简单介绍(了解)

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

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

 

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

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


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

4、为什么选择deque作为stackqueue的底层默认容器 

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

5、STL标准库中对于stack和queue的模拟实现

(1)stack的模拟实现
#include<deque>
namespace bite
{
    template<class T, class Con = deque<T>>
    //template<class T, class Con = vector<T>>
    //template<class T, class Con = list<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:
        Con _c;
    };
}

(2)queue的模拟实现
#include<deque>
#include <list>
namespace bite
{
    template<class T, class Con = deque<T>>
    //template<class T, class Con = list<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:
        Con _c;
    };
}

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

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

相关文章

改变世界-生成式人工智能

麦肯锡在其《生成人工智能的经济潜力&#xff1a;下一个生产力前沿》中声称&#xff0c;“ChatGPT、GitHub Copilot、Stable Diffusion 等生成式人工智能应用程序以 AlphaGo 没有的方式吸引了世界各地人们的想象力&#xff0c;这要归功于它们广泛的实用性——几乎任何人都可以使…

[mysql工具]Windows批处理方式实现MySQL定期自动备份

Windows批处理方式实现MySQL定期自动备份 对MySQL数据库而言&#xff0c;大部分数据库工具都具有备份功能&#xff0c;但并不能做到定期自动备份&#xff0c;在Windows环境下&#xff0c;手工备份MySQL是很繁琐的&#xff0c;所以我们通过MySQL提供的备份命令mysqldump&#xf…

如何防止重复提交订单

产生的原因 一种是由于用户在短时间内多次点击下单按钮&#xff0c;或浏览器刷新按钮导致。另一种则是由于Nginx或类似于SpringCloud Gateway的网关层&#xff0c;进行超时重试造成的。由于网速等原因造成页面卡顿&#xff0c;用户重复刷新提交页面黑客或恶意用户使用 postman…

maven配置代理

1.找到文件 find / -name "settings.xml" 当 maven 无法正常访问网络时候&#xff0c;需要通过代理进行访问 找到Maven的setting.conf文件 2.找到proxies 在maven的 setting.conf文件中找到 默认找到的时候文件 这里是被注释的。 3.配置如下 3.1配置截图 <…

QML 带框最大化显示方法

1.QML窗口最大化很多会给出如下方法: visibility: "FullScreen" 此方法不好的方面是没有最大化&#xff0c;最小化&#xff0c;关闭按钮 2.通过showMaximized() 方法可以满足我们需求:在onCompleted 方法中执行 实现的效果如下:

前后端分离计算机毕设项目之基于SpringBoot的无人智慧超市管理系统的设计与实现《内含源码+文档+部署教程》

博主介绍&#xff1a;✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久&#xff0c;选择我们就是选择放心、选择安心毕业✌ &#x1f345;由于篇幅限制&#xff0c;想要获取完整文章或者源码&#xff0c;或者代做&am…

浏览器自动化神器:Automa 轻松实现任务编排 | 开源日报 No.52

usememos/memos Stars: 13.8k License: MIT memos&#xff0c;一个轻量级的、自托管的备忘录中心。开源且永久免费。 开源且永久免费使用 Docker 可以在几秒钟内完成自我托管支持 Markdown 格式可定制和共享提供 RESTful API 用于自助服务 mamoe/mirai Stars: 12.6k Licen…

2023.10.7 Java 创建线程的七种方法

目录 继承 Tread 类&#xff0c;重写 run 方法 实现 Runnable 接口 使用匿名内部类&#xff0c;继承 Thread 类 使用匿名内部类&#xff0c;实现 Runable 接口 使用 Lambda 表达式 使用线程池创建线程 实现 Callable 接口 继承 Tread 类&#xff0c;重写 run 方法 自定…

uni-app项目成功编译到微信开发者工具出现警告:当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!

问题描述 为什么uni-app项目编译成功后&#xff0c;运行到微信开发者工具&#xff0c;却出现警告&#xff1a;当前组件仅支持 uni_modules 目录结构 &#xff0c;请升级 HBuilderX 到 3.1.0 版本以上&#xff01; 初识uni-app的童鞋&#xff0c;经常会问&#xff1a;我使用HBui…

2019年[海淀区赛 第2题] 阶乘

题目描述 n的阶乘定义为n!n*(n -1)* (n - 2)* ...* 1。n的双阶乘定义为n!!n*(n -2)* (n -4)* ...* 2或n!!n(n - 2)*(n - 4)* ...* 1取决于n的奇偶性&#xff0c;但是阶乘的增长速度太快了&#xff0c;所以我们现在只想知道n!和n!!末尾的的个数 输入格式 一个正整数n &#xff…

酷开会员 | 亚运会来啦!酷开系统陪你一起看赛事!

第十九届亚洲运动会已经开始啦&#xff01;坐标杭州&#xff0c;本次亚运会有来自亚洲45个国家和地区的1.2万余名运动员参赛&#xff0c;是史上规模最大、覆盖面最广的一届亚运会。它是亚洲具有世界性影响的体育盛会&#xff0c;来自亚洲各国和地区的运动员在赛场上奋力拼搏&am…

南美阿根廷市场最全分析开发攻略,收藏一篇就够了

聊到阿根廷&#xff0c;大家可能对阿根廷的足球印象比较深&#xff0c;比如球星梅西&#xff0c;不管是不是球迷应该大部分都有听说过&#xff0c;阿根廷作为南美洲面积第二大的国家&#xff0c;市场潜力也是非常不错的&#xff0c;今天就主要来聊一下关于阿根廷市场的一些相关…

工作流程引擎有几个特点?可以提高办公效率吗?

如果想要实现高效率的自动化办公&#xff0c;还依靠传统的办公软件是没有办法实现的。在自动化发展程度越来越高的今天&#xff0c;职场办公也拥有了优质的办公软件&#xff0c;助力实现高效率办公。低代码技术平台是专业的企业级应用低代码平台&#xff0c;其中的工作流程引擎…

深入理解树状数组 | 京东物流技术团队

树状数组 树状数组&#xff08;BIT, Binary Indexed Tree&#xff09;是简洁优美的数据结构&#xff0c;它能在很少的代码量下支持单点修改和区间查询&#xff0c;我们先以a[] {1, 2, 3, 4, 5, 6}数组为例建立树状数组看一下树状数组的样子&#xff1a; 可以发现&#xff1a;不…

websocket协议 | http协议

文章目录 一、前言二、websocket协议2.1 怎么建立websocket连接 三、HTTP协议3.1 特点3.2 报文格式3.3 连接方式三次握手四次挥手 3.4 版本HTTP 1.0HTTP 1.1 3.1 http长轮询场景&#xff1a;扫码登陆 四、二者比较4.1 相同4.2 区别1.通讯方式不同2.通信效率3.数据格式 一、前言…

XD 文件怎么打开,一分钟快速搞定

Adobe XD 是一款强大的用户界面和用户体验设计工具&#xff0c;广泛用于创建交互式原型、网站和移动应用程序&#xff0c;其中包含设计的所有元素和交互信息。 如果你拿到.xd 文件&#xff0c;却没有安装 Adobe XD 软件&#xff0c;下载安装步骤也很繁琐&#xff0c;纠结如何打…

LeetCode刷题笔记【34】:动态规划专题-6(完全背包、零钱兑换II、组合总合IV)

文章目录 前置知识经典完全背包问题(纯完全背包问题)题目描述解题思路如何实现"物品可以被多次添加"?遍历物品和遍历背包容量(内外层遍历)能否调换? 代码 518. 零钱兑换 II题目描述解题思路初始化&递推公式内层for循环顺序内外层for循环的顺序(先遍历物品还是先…

聊聊电商系统架构演进

具体以电子商务网站为例&#xff0c; 展示web应用的架构演变过程。 1.0时代 这个时候是一个web项目里包含了所有的模块&#xff0c;一个数据库里包含了所需要的所有表&#xff0c;这时候网站访问量增加时&#xff0c;首先遇到瓶颈的是应用服务器连接数&#xff0c;比如tomcat连…

含叠氮的代谢糖蛋白标记试剂361154-30-5,N -叠氮乙酰基甘露糖胺-四酰基化

产品简介&#xff1a;N-叠氮乙酰基甘露糖胺-三酰化&#xff08;AC4MANAZ&#xff09;可用作标记试剂&#xff0c;点击糖化学试剂&#xff0c;叠氮化物基团允许它与炔烃反应&#xff0c;是一种含叠氮的代谢糖蛋白标记试剂&#xff0c;叠氮化物修饰的蛋白质可以通过与炔烃反应检测…

(c语言进阶)指针的进阶

一.字符指针 1.一般应用 &#xff08;1&#xff09;%c的应用 &#xff08;2&#xff09;%s的应用 字符指针没有权限通过解引用去改变指针指向的值 2.笔试题 题目&#xff1a;判断输出结果 int main() { const char* p1 "abcdef"; const char* p2 "…