stack和queue的模拟实现

news2024/9/29 9:17:21

stack和queue的模拟实现

  • 容器适配器
    • 什么是适配器
    • STL标准库中stack和queue的底层结构
    • deque的简单介绍
    • deque的缺陷
  • stack模拟实现
  • queue模拟实现
  • priority_queue
    • priority_queue的使用
    • priority_queue的模拟实现

容器适配器

什么是适配器

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

STL标准库中stack和queue的底层结构

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

deque的简单介绍

deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。
在这里插入图片描述
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组。

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

deque的缺陷

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。

与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为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不仅效率高,而且内存使用率高。

stack模拟实现

stack是一种后入先出的数据结构,有了容器适配器以后,就可以很容易去实现它了:
在这里插入图片描述

函数说明接口说明
empty()检测stack是否为空
size()返回stack中元素的个数
top()返回栈顶元素的引用
push()将元素val压入stack中
pop()将stack中尾部的元素弹出
swap()交换两个栈中的数据
#pragma once
#include<deque>

namespace gtt
{
	template<class T, class Container = deque<T>>
	class stack
	{
	public:
		//插入
		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();
		}
		//判空
		bool empty() const
		{
			return _con.empty();
		}
		//求有效元素个数
		size_t size() const
		{
			return _con.size();
		}
		//交换两个栈的数据
		void swap(stack<T, Container>& st)
		{
			_con.swap(st._con);
		}
	private:
		Container _con;
	};
}

queue模拟实现

队列是队尾入,队头出的数据结构:
在这里插入图片描述

函数说明接口说明
empty()检测队列是否为空,是返回true,否则返回false
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()将元素val压入stack中
pop()将队头元素出队列
swap()交换两个队列中的数据
#pragma once
#include<deque>

namespace gtt
{
	template<class T, class Container = deque<T>>
	class queue
	{
	public:
		//插入
		void push(const T& x)
		{
			_con.push_back(x);
		}
		//删除
		void pop()
		{
			_con.pop_front();
		}
		//取队头元素
		T& front()
		{
			return _con.front();
		}
		const T& front() const
		{
			return _con.front();
		}
		//取队尾元素
		T& back()
		{
			return _con.back();
		}
		const T& back()const
		{
			return _con.back();
		}
		//判空
		bool empty()
		{
			return _con.empty();
		}
		//有效元素个数
		size_t size() const
		{
			return _con.size();
		}
		//交换两个栈中的数据
		void swap(queue<T, Container>& q)
		{
			_con.swap(q._con);
		}
	private:
		Container _con;
	};
}

priority_queue

priority_queue被称作为优先级队列,类似于我们数据结构阶段所学习的堆:

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

priority_queue的使用

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

函数声明接口说明
priority_queue() priority_queue(first,last)构造一个优先级队列
empty( )检测优先级队列是否为空,是返回true,否则返回false
top( )返回优先级队列中最大(最小元素),即堆顶元素
push(x)在优先级队列中插入元素x
pop()删除优先级队列中最大(最小)元素,即堆顶元素

在这里插入图片描述

#include<iostream>
#include<vector>
#include<functional>
#include<queue>
using namespace std;

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 priority_queue_test()
{
	//默认情况下创建的是大堆
	vector<int> v{ 3,2,7,6,0,4,1,9,8,5 };
	priority_queue<int> q1;
	for (auto& e : v)
	{
		q1.push(e);
	}
	while (!q1.empty())
	{
		cout << q1.top() << " ";//9 8 7 6 5 4 3 2 1 0
		q1.pop();
	}
	cout << endl;

	//创建小堆,需要第三个模板参数换出greater比较方式
	priority_queue<int, vector<int>, greater<int>> heap(v.begin(), v.end());
	while (!heap.empty())
	{
		cout << heap.top() << " ";//0 1 2 3 4 5 6 7 8 9
		heap.pop();
	}
	cout << endl;

	//如果需要自定义数据,则需要对运算进行重载
	 // 大堆,需要用户在自定义类型中提供<的重载
	priority_queue<Date> q2;
	q2.push(Date(2018, 10, 29));
	q2.push(Date(2018, 10, 28));
	q2.push(Date(2018, 10, 30));
	cout << q2.top() << endl;
	// 如果要创建小堆,需要用户提供>的重载
	priority_queue<Date, vector<Date>, greater<Date>> q3;
	q3.push(Date(2018, 10, 29));
	q3.push(Date(2018, 10, 28));
	q3.push(Date(2018, 10, 30));
	cout << q2.top() << endl;
}

int main()
{
	priority_queue_test();
	return 0;
}

priority_queue的模拟实现

在这儿我们需要了解仿函数的概念:

仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。

通过下面这段代码我们就可以很好的认识我们的仿函数:

namespace gtt
{
	template<class T>
	class less
	{
	public:
		bool operator()(const T& l, const T& r) const
		{
			return l < r;
		}
	};

	template<class T>
	class greater
	{
	public:
		bool operator()(const T& l, const T& r) const
		{
			return l > r;
		}
	};
}

int main()
{
	gtt::less<int> lsFunc1;
	cout << lsFunc1(1, 2) << endl;

	gtt::greater<int> lsFunc2;
	cout << lsFunc2(1, 2) << endl;
	return 0;
}

我们可以将优先级队列就理解为一个堆结构,他的实现也就是一个建堆的过程,就像我们实现堆一样,需要用到向上,向下调整算法,下面就是priority_queue的模拟实现:

namespace gtt
{
	//Compare时进行比较的仿函数,less->大堆, greater->小堆
	template<class T, class Container = vector<T>, class Compare = std::less<T>>
	class priority_queue
	{
	public:
		//构造函数
		priority_queue()
		{}

		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				_con.push_back(*first);
				first++;
			}
			//建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
			{
				adjust_down(i);
			}
		}
		//向上调整算法
		void adjust_up(size_t child)
		{
			Compare com;
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				//建大堆
				if (com(_con[parent], _con[child]))
				{
					std::swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
		//堆的插入
		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}
		//向下调整算法
		void adjust_down(size_t parent)
		{
			Compare com;
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					child++;
				}
				if (com(_con[parent], _con[child]))
				{
					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();
			adjust_down(0);
		}
		//取堆顶元素
		const T& top()
		{
			return _con[0];
		}
		//判断堆是否为空
		bool empty()
		{
			return _con.empty();
		}
		//堆有效元素个数
		size_t size()
		{
			return _con.size();
		}
	private:
		Container _con;
	};
}

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

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

相关文章

深入解析:如何打造高效的直播视频美颜SDK

在当今数字化时代&#xff0c;视频直播已经成为人们交流、娱乐和信息传递的重要方式。然而&#xff0c;许多人在直播时都希望能够呈现出最佳的外观&#xff0c;这就需要高效的直播视频美颜技术。本文将深入解析如何打造高效的直播视频美颜SDK&#xff0c;以实现令人满意的视觉效…

磁盘格式化工具的详细指南!一文看懂五分钟搞定

什么是磁盘格式化工具&#xff1f; 磁盘格式化工具是一种软件&#xff0c;可让你擦除硬盘上的所有数据&#xff08;包括操作系统&#xff09;&#xff0c;并为新数据做好准备。格式化硬盘是提高电脑性能并消除你可能遇到的问题的好方法。 使用磁盘格式化工具有什么好处&am…

NineData x SelectDB 完成产品兼容互认证

近日&#xff0c;新一代实时数据仓库厂商 SelectDB 与云原生智能数据管理平台 NineData 完成产品兼容互认证。经过严格的联合测试&#xff0c;双方软件完全相互兼容、功能完善、整体运行稳定且性能表现优异。基于本次的合作&#xff0c;双方将进一步为数据管理与大数据分析业务…

Blazor前后端框架Known-V1.2.13

V1.2.13 Known是基于C#和Blazor开发的前后端分离快速开发框架&#xff0c;开箱即用&#xff0c;跨平台&#xff0c;一处代码&#xff0c;多处运行。 Gitee&#xff1a; https://gitee.com/known/KnownGithub&#xff1a;https://github.com/known/Known 概述 基于C#和Blazo…

微信ipad协议

前言 微信协议就是基于微信IPad协议的智能控制系统&#xff0c;利用人工智能AI技术、云计算技术、虚拟技术、边缘计算技术、大数据技术&#xff0c; 打造出智能桌面系统RDS、 智能聊天系统ACS 、智能插 件系统PLUGIN 、云计算服务CCS 、 任务管理系统TM、设备管理服务DM、 应…

Webpack 配置多入口

1、配置多入口 entry index 和 other 为入口名称&#xff0c;即页面名称 2、配置出口 output filename 中的 [name] 对应入口的文件名 contentHash 会命中缓存&#xff0c;提高性能 3、配置插件 htmlWebpackPlugin&#xff0c;生成多页面 htmlWebpackPlugin 插件会生成页…

【大虾送书第六期】搞懂大模型的智能基因,RLHF系统设计关键问答

目录 ✨1、RLHF是什么&#xff1f; ✨2、RLHF适用于哪些任务&#xff1f; ✨3、RLHF和其他构建奖励模型的方法相比有何优劣&#xff1f; ✨4、什么样的人类反馈才是好的反馈 ✨5、RLHF算法有哪些类别&#xff0c;各有什么优缺点&#xff1f; ✨6、RLHF采用人类反馈会带来哪些局…

Swagger2 使用

大家好 , 我是苏麟 , 今天带来Swagger的使用 . 官方文档 : 招摇文档 (swagger.io) 访问地址 : 在路径后加上doc.html 例如: http://localhost:8000/doc.html Swagger 使用 依赖 <!--Swagger依赖 核心--><dependency><groupId>io.springfox</groupId&g…

关于ubuntu下面安装cuda不对应版本的pyTorch

最近换了台新的linux的ubuntu的服务器&#xff0c;发现其实际安装的cuda版本为11.4&#xff0c;但是pytorch官方给出的针对cuda 11.4并没有具体的pytorch的安装指令&#xff0c;于是采用不指定pytorch版本直接安装让其自动搜索得到即可 直接通过&#xff1a; pip3 install tor…

series的数据对齐功能

Series 是一种类似于 Numpy 中一维数组的对象&#xff0c;它由一组任意类型的数据以及一组与之相关的数据标签&#xff08;即索引&#xff09;组成。举个最简单的例子&#xff1a; 上面的代码将打印出如下内容&#xff1a; 左边的是数据的标签&#xff0c;默认从 0 开始依次递增…

快解析Linux搭建FTP服务器:轻松实现文件传输

在Linux操作系统中&#xff0c;搭建FTP服务器是一种常见且重要的操作。快解析提供了便捷的解决方案&#xff0c;帮助用户快速搭建FTP服务器&#xff0c;实现高效的文件传输和共享。本文将介绍Linux搭建FTP服务器的定义、作用以及其独特的优势&#xff0c;助您了解并利用这一强大…

用手势操控现实:OpenCV 音量控制与 AI 换脸技术解析

基于opencv的手势控制音量和ai换脸 HandTrackingModule.py import cv2 import mediapipe as mp import timeclass handDetector():def __init__(self, mode False, maxHands 2, model_complexity 1, detectionCon 0.5, trackCon 0.5):self.mode modeself.maxHands max…

理想的微生物组研究结果要注意高质量实验方案与取样细节

谷禾健康 最近有客户在拿到科研报告分析结果的时候问&#xff1a; “在同样的数据分析流程情况下&#xff0c;为什么我拿到的分析结果提取不出什么有价值的结果&#xff0c;而别人有着类似的项目课题&#xff0c;样本类型也相似&#xff0c;却可以拿到全面的差异分析结果&#…

GNU GRUB version 2.06 Minimal Bash-lke line editing is supported 问题修复

一、问题背景 博主喜欢折腾系统&#xff0c;电脑原来有一个windows系统&#xff0c;想整一个Linux双系统&#xff0c;结果开机时出现以下画面&#xff1a; GNU GRUB version 2.06 Minimal Bash-lke line editing is supported. TAB lists possible comand completions, Anywh…

SRM系统询价竞价管理:优化采购流程的全面解析

SRM系统的询价竞价管理模块是现代企业采购管理中的重要工具。通过该模块&#xff0c;企业可以实现供应商的询价、竞价和合同管理等关键环节的自动化和优化。 一、概述 SRM系统是一种用于管理和优化供应商关系的软件系统。它通过集成各个环节&#xff0c;包括供应商信息管理、询…

Spring Schedular 定时任务

大家好 , 我是苏麟 , 今天带来定时任务的实现 . Spring网站 : 入门 |计划任务 (spring.io) 什么是定时任务 通过时间表达式来进行调度和执行的一类任务被称为定时任务 定时任务实现 1.Spring Schedule (Spring boot 默认整合了) 2.Quartz(独立于Spring 存在的定时任务框架…

Windows 10编译SeetaFace6

Seeface 6 是中科视拓开源的人脸识别方案。 该项目使用C开发&#xff0c;在不同的系统中需要编译成对应的系统的动态链接库。 该项目在Windows 10 下使用VS2022编译成功 所需要的前提条件&#xff1a; cmake 下载地址 Download | CMake 将cmake的bin目录放到环境变量中 D:\…

循环神经网络RNN完全解析:从基础理论到PyTorch实战

目录 一、循环神经网络全解1.1 什么是循环神经网络网络结构工作原理数学模型RNN的优缺点总结 1.2 循环神经网络的工作原理RNN的时间展开数学表述信息流动实现示例梯度问题&#xff1a;梯度消失和爆炸总结 1.3 循环神经网络的应用场景文本分析与生成1.3.1 自然语言处理1.3.2 机器…

大数据:NumPy进阶应用详解

专栏介绍 结合自身经验和内部资料总结的Python教程&#xff0c;每天3-5章&#xff0c;最短1个月就能全方位的完成Python的学习并进行实战开发&#xff0c;学完了定能成为大佬&#xff01;加油吧&#xff01;卷起来&#xff01; 全部文章请访问专栏&#xff1a;《Python全栈教…