stl_stack/queue

news2024/12/22 20:56:02

一.适配器

stackqueue实际上并不能算是一种容器,而是一种容器适配器。而适配器作为stl的6大组件之一,其实是一种设计模式。适配器模式其实就是将一个类的接口(该接口无法直接满足客户的需求)转换成客户希望的另一个接口,以满足客户的需求。

而对于stack和queue来说,其就是借助另一个容器实现了LIFO(last in firtst out)和FIFO(first in first out)的功能。

而我们看到stack和queue的第二个模板参数容器给了缺省值,如果不指定容器,就会默认用deque来实现queue和stack。当然也可以利用其他的容器来实现,但是容器的结构一定得符合queue和stack的压入和弹出规则。

 二.模拟实现stack

模拟实现stack时,必须要遵循stack的规则LIFO(last in first out)。而栈的接口只有压栈、出栈、取栈顶元素等操作,而我们用vector就可以完美实现stack的接口。

压栈直接调用vector的push_back即可,出栈调用vector的pop_back,取栈顶元素调用vector的back。

namespace xsc
{
	template<typename T,typename Container = vector<T>>
	class stack
	{
	public:
		void push(const T& val)
		{
			_con.push_back(val);
		}

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

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

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

		int size()
		{
			return _con.size();
		}

	private:
		Container _con;
	};
}

但是当我们了解了list的结构之后,发现其也可以很好的实现stack的各种接口。所以我们使用栈时,既可以用缺省参数vector,也可以显式给出容器list。

xsc::stack<int> is;
xsc::stack<double, list<double>> is2;

三.模拟实现queue

模拟实现queue时,我们要了解其规则FIFO(first in first out).当我们要借助其他容器来实现queue时就不能再用vector来实现了。因为queue涉及到了删除队头的数据,vector并没有头删的接口,当然list和vector都有erase的接口,我们可以借助这个来实现,但是用vector会涉及到数据的挪动影响效率。所以我们默认采取list来实现queue。

queue的push借助list的push_back,pop借助list的pop_front,back、front分别借助list的back和front 

namespace xsc
{
	template<typename T,typename Container = list<T>>
	class queue
	{
	public:
		void push(const T& val)
		{
			_con.push_back(val);
		}

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

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

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

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

		int size()
		{
			return _con.size();
		}

	private:
		Container _con;
	};
}

四.deque

虽然我们借助list或者vector实现了stack和deque,但是我们看到标准库中栈和队列的容器参数默认是deque。那什么是deque呢?

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

我们可以看一下deque都实现了那些接口:

 deque其实是vector和list的结合体。

因为vector和list都有其各自的优点和一些不足,所以设计容器的人就想是否可以设计出一个容器既包含vector的优点也包含list的优点——所有deque就问世了。

但是deque并没有达到预期,如果deque真的实现了vector和list优点的结合,那么vector和list就不会再用人用了。

1.vector和list的对比

 vector的优点:

        1、尾插尾删效率不错,支持高效下标随机访问

        2、物理空间连续,所以高速缓存利用率高

vector的缺点:

        1、空间需要扩容,扩容有一些代价(效率和空间浪费),且这个代价到后期数据多的时候更大

        2、头部和中间插入/删除效率低(挪动数据)

 list的优点:

        1、按需申请释放空间,不需要扩容

        2、支持任意位置O(1)的插入删除

list的缺点:

        1、不支持下标的随机访问

2.deque的结构 

deque的底层结构并不是一段连续的物理空间,而是类似于一个二维数组。由一个中控数组和若干个buff组成。

中控数组map其实是一个指针数组,其中存储着指向一个个buffer的指针。而buffer是一个定长数组(或者动态数组),所有的数据都存储在buffer上。

不同的是,中控数组map是从中间开始存储的,第一个buffer的指针存储在中控数组的中间部分,如果要头插就在该位置的前面存储一个buffer的地址。当map满了之后还需要对map进行扩容。

deque还重载了[] ,那么deque是如何支持下标访问的呢?

 我们可以采取/和%的方式,定位到指定下标的元素位置。

假设要获取下标为i的元素,每一个buffer的大小为N。我们先用 x = i / N就可以得到该元素位于第几个buffer中;然后用y = i % N就可以知道该元素是这个buffer的第几个位置,然后借助两次operator[][]的调用map[x][y]就可以得到该数据。

3.deque的迭代器 

deque的迭代器是由4个指针来实现的

cur指向当前正在访问的元素,first指向这段buffer的开始位置,last指向这段buffer的结束位置,node指向中控数组中存储这个buffer的位置。

借助这个迭代器,deque模拟出来物理空间连续的假象。 

当我们遍历时,当cur等于last时说明这个buffer已经遍历完了,此时需要走到下一个buffer,只需要让node++即可。

4.deque的维护

deque这个容器的维护主要是借助两个迭代器来实现的:

这两个迭代器就相当于begin()和end()。分别指向开始和结尾。 

 遍历容器时,其实是创建一个迭代器,然后将start的值给他,然后通过cur来遍历。

5.deque的push_back()/push_front()

deque的尾插和头插效率都很高,尾插直接向buffer里面插入即可,如果buffer满了在申请一个即可。

deque的头插数据的方式有些许不同。头插时是往中控数组的的前面插入一个buffer,然后将头插的元素插入到buffer的尾部。

要注意的是头插和尾插之后start和finish都需要改变。 

 6.deque的insert/erase

当在中间位置插入删除时,有两种方式:

1、挪动数据,保证buffer的大小不变,但是这样就会导致效率极低

2、扩大或缩小buffer,但是这样会导致下标访问会更加困难,不能用/和%的方式得出下标位置。

总结:

1、deque的头尾插入删除效率很高,更甚于vector和list

2、下标随机访问也还不错,但略逊于vector

3、中间位置的插入删除效率很低O(N),需要挪动数据

 7.deque的缺陷

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

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

但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器需要频繁的去检查其是否移动到buffer的边界,导致效率低下,而在序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构

8.为什么选择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的优点,而完美的避开了其缺陷。

9.对比deque和vector的排序效率 

我们从结果可以分析出,在存储相同数据的情况下,不论是直接比较deque和vector,还是用两个存储相同数据的deque,一个直接排序,一个先将数据拷贝到vector中,排序,拷贝会去,效率都是采用vector的高。

这是因为在sort算法下采用的是快排,快排需要对数据进行访问,而vector对数据的随机访问效率要比deque高得多。

void test_op1()
{
	srand(time(0));
	const int N = 1000000;

	deque<int> dq;
	vector<int> v;

	for (int i = 0; i < N; ++i)
	{
		auto e = rand() + i;
		v.push_back(e);
		dq.push_back(e);
	}

	int begin1 = clock();
	sort(v.begin(), v.end());
	int end1 = clock();

	int begin2 = clock();
	sort(dq.begin(), dq.end());
	int end2 = clock();

	printf("vector:%d\n", end1 - begin1);
	printf("deque:%d\n", end2 - begin2);
}

void test_op2()
{
	srand(time(0));
	const int N = 1000000;

	deque<int> dq1;
	deque<int> dq2;

	for (int i = 0; i < N; ++i)
	{
		auto e = rand() + i;
		dq1.push_back(e);
		dq2.push_back(e);
	}

	int begin1 = clock();
	sort(dq1.begin(), dq1.end());
	int end1 = clock();

	int begin2 = clock();
	// 拷贝到vector
	vector<int> v(dq2.begin(), dq2.end());
	sort(v.begin(), v.end());
	dq2.assign(v.begin(), v.end());
	int end2 = clock();

	printf("deque sort:%d\n", end1 - begin1);
	printf("deque copy vector sort, copy back deque:%d\n", end2 - begin2);
}

五.priority_queue 

1.priority_queue基本概念

priority_queue就是优先级队列,它也是一种容器适配器。但是它不同于queue,不再以deque作为底层容器,而是vector。

 而priority_queue在底层是vector的前提下,又采用了堆的算法,将其包装成一个堆。所以priority_queue其实就是一个堆(默认是大堆)。其设计的一系列接口也都是模拟了堆的行为。

top取出堆顶的元素(最大/最小的元素)

push插入数据然后采用向上调整算法使其依旧是一个堆 

pop删除堆顶的数据然后采用向下调整算法使其依旧是一个堆

在使用向上(向下)调整算法时,要保证其原本就是堆。


小伙饿坏了,赶紧点了一份爱学的二叉树——堆,狼吞虎咽学完很过瘾-CSDN博客

在这里可以回顾一下堆的基本操作:

1、堆是一个完全二叉树,完全二叉树就是前面每层都是满的,最后一层从左到右是连续的。 

2、知道一个父亲节点的下标parent,怎么求它的左右孩子:left = 2*parent+1,right  = 2*parent+2

3、知道孩子节点的下标child,怎么求父亲节点:parent = (child-1)/2.

4、大堆:父亲节点的值大于等于左右孩子的值,但左右孩子之间没有大小关系

5、小堆:父亲节点的值小于等于左右孩子的值,但左右孩子之前没有大小关系。

6.插入节点:插入节点是插入到最后一层从左到右的最后一个位置,但是插入必须要保证插入之后也是一个堆,所以插入之后要进行向上调整,与它的父亲节点比较,如果是大堆的话,插入的大于父亲节点就交换,直到小于某个父亲节点或者到达堆顶就停止交换。

7.删除堆顶元素:首先将堆顶元素与最后一个节点交换,然后再删除最后一个节点,然后进行向下调整,使之仍然是堆


优先级队列之所以默认是大堆,原因在于其第三个模板参数 ,当我们改变第三个模板参数时,就可以将其变为小堆

//priority_queue<int> q; // 大堆
priority_queue<int,vector<int>,greater<int>> q; // 小堆

2.模拟实现priority_queue

默认是大堆

插入之后向上调整时,将孩子与父亲进行比较,如果孩子大于父亲就交换,直到孩子小于父亲/已经交换到了堆顶。

删除之后向下调整时,父亲与左右孩子大的那个交换(采取假设法:先假设左孩子大,再判断右孩子是否大于左孩子,如果大于那么child+1),直到父亲大于左右孩子,或者已经交换到最后一层。

小伙饿坏了,赶紧点了一份爱学的二叉树——堆,狼吞虎咽学完很过瘾-CSDN博客

namespace xsc
{
    //默认是大堆
	template <typename T, typename Container = vector<T>>
	class priority_queue
	{
	public:
		void AdjustUp(int child)
		{

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

		void push(const T& val)
		{
			_con.push_back(val);
			AdjustUp(_con.size() - 1);
		}

		void AdjustDown(int parent)
		{
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && _con[child] < _con[child + 1])//只有这个条件可能会越界:a[child] > a[child + 1]
				{
					child += 1;
				}

				if (_con[parent] < _con[child])
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);
		}

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

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

		int size()
		{
			return _con.size();
		}

	private:
		Container _con;
	};
}

那如果要实现一个小堆呢?难道要写出两个类模板么?

在标准库中的priority_queue是借助第三个模板参数来实现的:

//priority_queue<int> q; // 大堆
//priority_queue<int, vector<int>, less<int>>;// 第三个默认参数是less<int>

priority_queue<int,vector<int>,greater<int>> q; // 小堆

 而这第三个模板参数其实是借助仿函数来实现的。

3.仿函数

仿函数人如其名,它并不是一个函数,而是一个重载了()的一个类或者结构体,它可以使其看上去像一个函数一样,像函数一样使用。仿函数可以接受参数并返回值,可以用于STL算法中的函数对象参数,也可以用于函数指针的替代。

template <typename T>
struct Less
{
	bool operator()(const T& x, const T& y)
	{
		return x < y;
	}
};

int main()
{
	Less<int> l;
	cout << l(1, 2) << endl;

	return 0;
}

我们实际上是借助这个类创建了一个对象,但是我们使用这个对象的方式却是调用函数的行为,这就是仿函数。

实际上是调用了该类的operator()。

cout << l.operator(1, 2) << endl;

我们可以实现两个仿函数Less和Greater来封装比较的逻辑,对priority_queue增加第三个模板参数,并将其内部的比较都换成仿函数的比较方式,以此来改变向上向下调整的比较逻辑,实现大堆/小堆。

在库中,less默认是<,gerater默认是>,所以我们采取和库里一样的比较方式。

template <typename T>
struct Less
{
	bool operator()(const T& x, const T& y)
	{
		return x < y;
	}
};

template <typename T>
struct Greater
{
	bool operator()(const T& x, const T& y)
	{
		return x > y;
	}
};

 用第三个模板参数创建一个对象,然后用该对象调用operator(),进行比较。注意在比较时要注意符合符号以及是大堆还是小堆。


完!

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

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

相关文章

计算机网络:网络层 —— 虚拟专用网 VPN

文章目录 虚拟专用网 VPN 概述内联网 VPN外联网 VPN 虚拟专用网 VPN 概述 虚拟专用网&#xff08;Virtual Private Network&#xff0c;VPN&#xff09;&#xff1a;利用公用的因特网作为本机构各专用网之间的通信载体&#xff0c;这样形成的网络又称为虚拟专用网。 出于安全…

SSM酒店预订住宿管理系统-计算机毕业设计源码01085

摘要 随着互联网和移动技术的快速发展&#xff0c;酒店行业也面临着巨大的变革和机遇。传统的酒店管理方式存在着信息不透明、预订流程繁琐等问题&#xff0c;无法满足现代消费者对便捷、高效、个性化服务的需求。因此&#xff0c;开发酒店预订住宿管理系统具有重要的意义。本文…

STM32CubeMX学习(三) SPI+DMA通信

STM32CubeMX学习&#xff08;三&#xff09; SPIDMA通信 一、简介二、新建STM32CubeMX项目并使用外部时钟三、SPI3配置四、相关代码五、测试 一、简介 本文将基于STM32F103RCT芯片介绍如何在STM32CubeMXKEIL5开发环境下进行SPIDMA通信。 操作系统&#xff1a;WIN10 x64硬件电…

LLaMA系列一直在假装开源...

伙伴们&#xff0c;很奇怪~ 关于LLM的开源与闭源模型的竞争又开始愈发激烈。 众所周知&#xff0c;开源模型以其开放性和社区驱动的特点受到一部分用户的青睐&#xff0c;而闭源模型则因其专业性和性能优化被广泛应用于商业领域。由于大模型最近2年的突然兴起&#xff0c;开源…

C语言 核心语法2

时间&#xff1a;2024.11.1 一、学习内容 1、计算机的存储规则 1.1存储规则 视频是图片和声音的结合体。 在计算机中&#xff0c;任意数据都是以二进制的形式进行存储的。 在计算机中&#xff0c;二进制可以表示万事万物。 1.2十进制 1.3二进制的运算过程 1.4文本存储 …

客户端与微服务之间的桥梁---网关

当我们创建好了N多个微服务或者微服务的实例之后&#xff0c;每个服务暴露出不同的端口地址&#xff0c;一般对于客户端请求&#xff0c;只需要请求一个端口&#xff0c;要隔离客户端和微服务的直接关系&#xff0c;保证微服务的安全性和灵活性&#xff0c;避免敏感信息的泄露。…

萤石设备视频接入平台EasyCVR私有化部署视频平台高速公路视频上云的高效解决方案

经济的迅猛发展带来了高速公路使用频率的激增&#xff0c;其封闭、立交和高速的特性变得更加显著。然而&#xff0c;传统的人工巡查方式已不足以应对当前高速公路的监控挑战&#xff0c;监控盲点和响应速度慢成为突出问题。比如&#xff0c;非法占用紧急车道的情况屡见不鲜&…

【论文速读】| APILOT:通过避开过时API陷阱,引导大语言模型生成安全代码

基本信息 原文标题&#xff1a;APILOT: Navigating Large Language Models to Generate Secure Code by Sidestepping Outdated API Pitfalls 原文作者&#xff1a;Weiheng Bai, Keyang Xuan, Pengxiang Huang, Qiushi Wu, Jianing Wen, Jingjing Wu, Kangjie Lu 作者单位&a…

泡泡玛特行至巅峰,又顷刻“瓦解”?

今年&#xff0c;在海外“一呼百应”的LABUBU与“老妈”泡泡玛特一同步入了潮玩时代的全新阶段。 先是LABUBU获授“神奇泰国体验官”&#xff0c;首个LABUBU主题店也落地曼谷。随后泡泡玛特也在发布三季度财报后迎来股价新高&#xff08;10月24日收盘价75.85元/股&#xff0c;…

如何看待长周期项目?

有一个客户&#xff0c;想找你做一个软件项目。你大体评估了一下&#xff0c;项目成本300万&#xff0c;项目收入400万&#xff0c;有大概100万左右的毛利。但项目的周期&#xff0c;会比较长&#xff0c;大概是3年。 你会做吗&#xff1f; 我从自己的经验和直觉来看&#x…

Flutter仿微信,高度还原,开源

Flutter仿微信开源项目&#xff0c;持续更新中 Flutter仿微信项目&#xff0c;已开源&#x1f680;&#x1f680;&#x1f680;说明效果预览开发进度说明未来计划项目结构说明组件封装示例最后持续更新中... Flutter仿微信项目&#xff0c;已开源&#x1f680;&#x1f680;&am…

HBA:基于分层激光雷达集束调整的一致性建图

文章目录 前言一、介绍二、相关工作三、方法A. 概述B. 自底向上的分层BA&#xff08;Bundle Adjustment&#xff09;C. 自顶向下位姿图优化 四. 实验A. 精度分析 前言 代码&#xff1a;github 原文&#xff1a;原文 摘要——重建准确且一致的大规模LiDAR点云地图对机器人应用至…

Docker — 跨平台和环境部署

Docker 是一个开源的容器化平台&#xff0c;通过将应用程序和其依赖打包在一个轻量级、独立的容器中&#xff0c;能够跨平台和环境部署。 1. Docker 基本概念 镜像 (Image)&#xff1a;Docker 镜像是一个只读模板&#xff0c;包含运行应用程序所需的代码、库、依赖和环境配置。…

消息队列-Rabbitmq(消息发送,消息接收)

将来我们开发业务功能的时候&#xff0c;肯定不会在控制台收发消息&#xff0c;而是应该基于编程的方式。由于RabbitMQ采用了AMQP协议&#xff0c;因此它具备跨语言的特性。任何语言只要遵循AMQP协议收发消息&#xff0c;都可以与RabbitMQ交互。并且RabbitMQ官方也提供了各种不…

一机多控无人机集群飞行控制技术详解

一机多控无人机集群飞行控制技术是指通过单一控制端或多个协同工作的控制端&#xff0c;对多架无人机进行集群管理和控制的技术。这种技术结合了通信技术、路径规划、碰撞避免、分布式与集中式控制等多个方面&#xff0c;以实现无人机集群的协同作战或完成其他特定任务。以下是…

DEVOPS: 认证与调度

概述 不知道大家有没有意识到一个现实&#xff0c;就是大部分时候&#xff0c;我们已经不像以前一样通过命令行&#xff0c;或者可视窗口来使用一个系统了现在我们上微博、或者网购&#xff0c;操作的其实不是眼前这台设备&#xff0c;而是一个又一个集群 通常&#xff0c;这样…

【鸢尾花书籍】编程不难

&#x1f4dd;本文介绍 本文为作者拜读鸢尾花书籍《编程不难》后所做的笔记&#xff0c;整理成文章&#xff0c;以供回顾 &#x1f44b;作者简介&#xff1a;一个正在积极探索的本科生 &#x1f4f1;联系方式&#xff1a;943641266(QQ) &#x1f6aa;Github地址&#xff1a;htt…

HTML 文档规范与解析模式:DOCTYPE、<html> 标签以及结构化页面

文章目录 `<!DOCTYPE html>` 文档类型声明标准模式与怪异模式HTML5 的简化声明`<html>` 标签`<head>` 标签`<body>` 标签小结<!DOCTYPE html> 文档类型声明 在 HTML 文档中,<!DOCTYPE html> 是一个重要的文档类型声明,主要用于告知浏览…

CFA全球投资分析大赛专访:与投资人胡建平共话投资智慧

在复杂多变的金融市场中&#xff0c;每一个精准的投资决策背后&#xff0c;都隐藏着无数次的策略考量与深入分析。CFA全球投资分析大赛&#xff0c;作为业界公认的检验投资能力的舞台&#xff0c;每年都会吸引来自世界各地的顶尖投资者一展身手。今年&#xff0c;我们有幸在比赛…

Halcon 一维卡尺测量找点之模糊集测量法

模糊测量是对标准测量的一种扩展&#xff0c;并不意味着测量是“模糊的”&#xff0c;而是用模糊隶属函数来控制边缘的选择。所谓的模糊隶属函数&#xff0c;就是将边缘的特征值转换为隶属度值&#xff0c;基于这些隶属值做出是否选择边缘的决定&#xff0c;即当隶属值大于你设…