栈、队列、优先级队列的模拟实现

优先级队列的模拟实现

    • stack的模拟实现
      • push()
      • pop()
      • top()
      • size()
      • empty()
      • swap()
    • stack总代码
  • 队列
    • queue的模拟实现
      • push()
      • pop()
      • front()
      • back()
      • empty()
      • size()
      • swap()
    • queue总代码
  • 优先级队列(堆)
      • push()
      • pop()
      • top()
      • empty()
      • size()
      • swap()
    • priority_queue总代码
  • deque的了解

在C++STL中栈并不属于容器一类,而属于适配器!

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

stack的模拟实现

对于栈的话,我们可以在其底层封装一个容器来实现:

template<class T, class Container=std::deque<T>>
	class stack
	{
		private:
		Container _con;//使用该容器来实现栈,具体使用那个类型的容器,完全由程序员自己决定!默认情况是使用缺省值(deque)双端队列来实现!
	}

对于stack我们可以不用写构造函数、拷贝构造函数、析构函数、重载赋值运算符!因为stack类的成员变量是个自定义类型,如果我们使用stack的默认构造函数的话,编译器会调用自定义类型的默认构造函数来初始化_con成员变量;同理如果使用默认拷贝构造的话,对于自定义类型也会调用自定义类型的拷贝构造函数;如果使用默认析构函数的话,对于自定义类型也会调用自定义类型的析构函数来释放空间,不会造成内存泄漏;赋值运算符同理!

push()

void push(const T& val)//对于栈的插入,也就是对于容器_con的尾插
{
_con.push_back(val);
}

pop()

void pop()//对于stack的删除就是对于容器_con的尾删除!
{
	assert(empty() == false);
	_con.pop_back();
}

top()

T Top()const//获取stack的栈顶元素就是获取容器_con的尾部元素
{
	assert(empty()==false);
	return _con.back();
}

size()

size_t size()const//获取stack的元素就是获取容器_con的元素个数
{
	return _con.size();
}

empty()

bool empty()const//判断stack是不是空就是判断容器_con是不是空
{	return _con.empty();
}

swap()

void swap(stack<T,Container>&st)//交换stack就是交换两个stack内部的容器_con
{
	_con.swap(st._con);
}

stack总代码

#pragma once
#include<iostream>
#include<assert.h>
#include<vector>
#include<list>
#include<deque>
namespace MySpace
{
	template<class T, class Container=std::deque<T>>
	class stack
	{
	public:
		explicit stack(const Container&con= Container())
			:_con(con)
		{
		}
		bool empty()const
		{	return _con.empty();
		}
		size_t size()const
		{
			return _con.size();
		}
		T Top()const
		{
			assert(empty()==false);
			return _con.back();
		}
		void push(const T& val)
		{
			_con.push_back(val);
		}
		void pop()
		{
			assert(empty() == false);
			_con.pop_back();
		}
		void swap(stack<T,Container>&st)
		{
			_con.swap(st._con);
		}
	private:
		Container _con;//组成stack的容器
	};
}

队列

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

queue的模拟实现

队列与栈一样不属于容器,而属于适配器;因此队列的底层也是封装的一个容器;
因此队列也不需要自己写默认构造函数、拷贝构造函数、赋值运算符重载、析构函数,我们用编译器默认生成的就够了!

template <class T,class Container=std::deque<T>>//不建议使用vector作为容器,这样的话头插效率低
class queue
	{
	private:
	Container _con;
	}

push()

void push(const T& val)//对于queue的插入就是对于容器的尾插
{
	_con.push_back(val);
}

pop()

void pop()//对于queue的删除就是对于容器_con的头删
{
	assert(empty() == false);
	_con.erase(_con.begin());
}

front()

T front()const//获取queue队头元素,也就是获取容器_con首元素
{
	assert(empty() == false);
	return _con.front();
}

back()

T back()const//获取队尾元素,也就是获取容器_con的尾元素
{
	assert(empty() == false);
	return _con.back();
}

empty()

bool empty()const//对于queue的判空,也就是对于容器_con的判空
{
	return _con.empty();
}

size()

size_t size()const {//对于queue的大小就是容器_con的大小
return _con.size();
}

swap()

void swap(queue<T, Container> &q)//对于queue的交换就是交换底层的两个容器;
{
_con.swap(q._con);
}

queue总代码

#pragma once
#include<iostream>
#include<assert.h>
#include<vector>
#include<list>
#include<deque>
namespace MySpace
{
	template <class T,class Container=std::deque<T>>//不建议使用vector作为容器,这样的话头插效率低
	class queue
	{
	public:
		explicit queue(const Container& con = Container())
			:_con(con)
		{}
		bool empty()const
		{
			return _con.empty();
		}
		size_t size()const {
			return _con.size();
		}
		T front()const
		{
			assert(empty() == false);
			return _con.front();
		}
		T back()const
		{
			assert(empty() == false);
			return _con.back();
		}
		void push(const T& val)
		{
			_con.push_back(val);
		}
		void pop()
		{
			assert(empty() == false);
			_con.erase(_con.begin());
		}
		void swap(queue<T, Container> &q)
		{
			_con.swap(q._con);
		}
	private:
		Container _con;
	};
}

优先级队列(堆)

学过数据结构的读者应该直到数据结构中有一种叫做“堆”的数据结构,在C++中这种数据结构叫做“优先级队列”!关于堆的详细了解可以参考我的另一篇博客:二叉树和堆
优先级队列在STL中也是一个适配器!
因此对于优先级队列的定义我们可以参照stack和queue:

template <class T,class Container=std::vector<int>,class Compare=std::less<T>>
class priority_queue
	{
	private:
	Container _con;
	}

其中模板参数Container是容器类型;T是元素类型:Compare是仿函数的类型!用于控制建立大堆还是建立小堆;
其中根据STL的实现方法是:优先级队列默认是建大堆,如果需要建小堆的话,Compare模板参数需要传递greater <T>;

push()

优先级队列的push与stack、queue的插入不同,优先级队列push过后还需要调整当前的堆结构,使其再插入一个元素过后仍然保持堆结构,我们通常使用向上调整的算法建队!

void push(const T& val)
{
	//先插入
	_con.push_back(val);
	//向上调整
	AdjustUp(_con.size()-1);
}
	void AdjustUp(int child)//向上调整算法
		{
			Compare com;//实例化一个比较对象
			//如果Compare是个小于类的话,那么com实例化出来的就是小于对象,里面的operator(A,B)函数是比较A是否小于B的
			int parent = (child - 1) / 2;
			while (child>0)
			{
				//if (_con[child] > _con[parent])//判断[parent]是否小于[child]
				if (com(_con[parent], _con[child]))//如果_con[parent]<_con[child]就建立小堆;如果_con[parent]>_con[child]就建立大堆;至于_con[parent]与_con[child]使用<比较还是>比较由我们传递的类型参数Compare来决定!
				{
					std::swap(_con[child],_con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
					break;
			}
		}

pop()

优先级队列删除元素,是删除堆顶的元素,再删除堆顶元素过后,我们仍需要保持该结构是个堆;为了降低调整成本,优先级队列的删除操作的步骤一般都是,交换堆顶元素与堆底元素,然后堆的大小减1,然后再从对顶开始执行向下调整算法调整堆;

void pop()
{
	assert(empty()==false);
	//swap
	std::swap(_con[0],_con[_con.size()-1]);
	//size--
	_con.pop_back();
	//向下调整
	AdjustDown(0);
}
void AdjustDown(int parent)
		{
			Compare com;//实例化一个比较对象
			int child = 2 * parent + 1;
			while (child < _con.size())
			{
				//if (child + 1 < _con.size() && _con[child + 1] > _con[child])
					if (child + 1 < _con.size() && com(_con[child],_con[child+1]))
					child++;
					//if (_con[child] > _con[parent])
				if (com(_con[parent],_con[child]))
				{
					std::swap(_con[child], _con[parent]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
					break;
			}
		}

top()

const T& top()const//返回堆顶元素,也就死返回容器_con的首元素
{
	return _con.front();
}

empty()

bool empty()const//判断一个优先级队列是不是空,就是判断一个容器是不是空
{
	return _con.empty();
}

size()

size_t size()const//求优先级队列的元素个数,就是返回容器的元素个数
{
	return _con.size();
}

swap()

void swap(priority_queue<T, Container, Compare>&pq)//两个优先级队列之间的交换就是交换底层的容器;
{
	_con.swap(pq._con);
}

priority_queue总代码

#pragma once
#include<iostream>
#include<vector>
#include<queue>
#include<deque>
#include<assert.h>
namespace MySpace
{
	template<class T>
	struct less//小于模板(用于建大堆)
	{
		bool operator()(const T& v1, const T& v2)//这个函数的目的是为了比较v1是否小于v2
		{
			return v1 < v2;
		}
	};
	template<class T>
	struct less<T*>//小于模板偏特化,如果less模板的模板参数是指针,则用这个版本的less模板实例化对象
	{
		bool operator()( T* x,  T * y)const
		{
			return *x < *y;
		}
	};
	template<class T>
	struct greater//大于模板(用于建小堆)
	{
		bool operator()(const T& v1, const T& v2)const//这个函数的目的是为了比较v1是否大于v2
		{
			return v1 > v2;
		}
	};
	template<class T>
	struct greater<T*>//大于模板偏特化,如果greater模板的模板参数为指针类型,那么编译器就用这个版本的greater模板实例化对象!
	{
		bool operator()( T* x,  T* y)
		{
			return *x > *y;
		}
	};
	//默认建的是大堆,建大堆用less(小于)
	//建小堆用greter;
	template <class T,class Container=std::vector<int>,class Compare=MySpace::less<T>>
	class priority_queue
	{
	public:
		void push(const T& val)
		{
			//先插入
			_con.push_back(val);
			//向上调整
			AdjustUp(_con.size()-1);
		}
			void pop()
			{
				assert(empty()==false);
				//swap
				std::swap(_con[0],_con[_con.size()-1]);
				//size--
				_con.pop_back();
				//向下调整
				AdjustDown(0);
			}
		const T& top()const
		{
			return _con.front();
		}
		bool empty()const
		{
			return _con.empty();
		}
		void swap(priority_queue<T, Container, Compare>&pq)
		{
			_con.swap(pq._con);
		}

		size_t size()const
		{
			return _con.size();
		}
	private:
		void AdjustDown(int parent)
		{
			Compare com;//实例化一个比较对象
			int child = 2 * parent + 1;
			while (child < _con.size())
			{
				//if (child + 1 < _con.size() && _con[child + 1] > _con[child])
					if (child + 1 < _con.size() && com(_con[child],_con[child+1]))
					child++;
					//if (_con[child] > _con[parent])
				if (com(_con[parent],_con[child]))
				{
					std::swap(_con[child], _con[parent]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
					break;
			}
		}

		void AdjustUp(int child)
		{
			Compare com;//实例化一个比较对象
			int parent = (child - 1) / 2;
			while (child>0)
			{
				//if (_con[child] > _con[parent])//判断[parent]是否小于[child]
				if (com(_con[parent], _con[child]))//判断[parent]是否小于[child]
				{
					std::swap(_con[child],_con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
					break;
			}
		}
		Container _con;//容器
	};
}

deque的了解

通过前面的学习,我们知道了vector与list的优缺点:

vector:
优点
1、支持随机访问;
2、尾插尾删效率高;
缺点:
1、中间和头部的插入删除,效率极低!
list
优点
1、插入删除效率高;
缺点:
2、不支持随机访问;

那么有没有结合vector与list两个容器的优点的数据结构呢?
答案是有的!
双端队列(deque)就出现了:

什么是deque?
deque是一个两端开口,逻辑上空间是连续的数据结构,该数据结构的头部插入删除、尾部插入删除效率极高,时间复杂度为O(1);与list比较,空间利用率比较高!它的每个元素中不需要像list那样存储下一个节点的指针!
在这里插入图片描述
同时deque也支持像vector一样用[ ]来访问元素!
但是deque的底层真正实现并不是一块连续的空间,刚才我们也说了deque是在逻辑上连续的空间!真正的deque是由一段一段的连续空间组成起来的,有点类似于C语言中的二维数组!
其底层结构如下:
在这里插入图片描述
在这里插入图片描述

既然deque这么强,那么为什么还没替代掉vector和list呢?
deque的缺点:
与vector相比,头插头删效率的确高,不需要挪动元素,同时扩容代价下,deque的扩容只需要对中控数组进行扩容,不需要对存储元素的小段数组进行扩容,拷贝过来的也是这些小段数组的指针!
与list相比,空间利用率的确变高了,同时也支持了随机访问;
但是deque有一个致命的缺点:不适合遍历,因为deque迭代器在遍历的时候,会频繁的检测这次遍历是否移动到下一个小段数组中去,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,同时deque特别不适合中间来插入和删除数据,在中间删除和插入数据都需要大量的挪动数据,代价非常大!
而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构;

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

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

相关文章

吃透Java面试题,建议收藏

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

LeetCode:27. 移除元素

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340;算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 一、&#x1f331;27. 移除元素 题目描述&#xff1a;给你一个数组 nums 和一个值 val&am…

Chapter6.2:其他根轨迹及综合实例分析

该系列博客主要讲述Matlab软件在自动控制方面的应用&#xff0c;如无自动控制理论基础&#xff0c;请先学习自动控制系列博文&#xff0c;该系列博客不再详细讲解自动控制理论知识。 自动控制理论基础相关链接&#xff1a;https://blog.csdn.net/qq_39032096/category_10287468…

使用vite创建vue3工程

定义 什么是vite&#xff1f;-----新一代前端构建工具 优势 开发环境中&#xff0c;无需打包操作&#xff0c;可快速的冷启动---最牛的地方轻量快速的热重载&#xff08;HMR&#xff09;---一修改代码就局部刷新&#xff0c;webpack也具备&#xff0c;但vite更快真正的按需编…

【数据结构与算法】用队列实现栈

文章目录&#x1f60e;前言如何用队列实现栈&#xff1f;用队列实现栈整体的实现代码&#x1f60e;写在最后&#x1f60e;前言 &#x1f63c;前面我们相继实现了 栈 和 队列 &#xff0c;是不是愁没有练手的地方呢&#xff1f;别担心&#xff0c;本章带大家用队列来实现一个栈&…

synchronized 加锁 this 和 class 的区别

synchronized 是 Java 语言中处理并发问题的一种常用手段&#xff0c;它也被我们亲切的称之为“Java 内置锁”&#xff0c;由此可见其地位之高。然而 synchronized 却有着多种用法&#xff0c;当它修饰不同对象时&#xff0c;其意义也是不同的&#xff0c;下面我们一起来看。 ​…

云原生时代顶流消息中间件Apache Pulsar部署实操之Pulsar IO与Pulsar SQL

文章目录Pulsar IO (Connector连接器)基础定义安装Pulsar和内置连接器连接Pulsar到Cassandra安装cassandra集群配置Cassandra接收器创建Cassandra Sink验证Cassandra Sink结果删除Cassandra Sink连接Pulsar到PostgreSQL安装PostgreSQL集群配置JDBC接收器创建JDBC Sink验证JDBC …

【网络】网络层协议——IP

目录网络层IP协议IP基础知识IP地址IP报头格式网段划分CIDR特殊的IP地址IP地址的数量限制私有IP地址和公有IP地址路由IP总结网络层 在复杂的网络环境中确定一个合法的路径。 IP协议 IP协议作为整个TCP/IP中至关重要的协议&#xff0c;主要负责将数据包发送给最终的目标计算机…

多线程 (六) 单例模式

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

蓝桥杯刷题冲刺 | 倒计时19天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;马上就要蓝桥杯了&#xff0c;最后的这几天尤为重要&#xff0c;不可懈怠哦&#x1f43e; 文章目录1.抓住那头牛2.排列序数1.抓住那头牛 题目 链接&#xff1a; 抓住那头牛 - C语言网 (dotcpp.com…

网络安全之防火墙

目录 网络安全之防火墙 路由交换终归结底是联通新设备 防御对象&#xff1a; 定义&#xff1a; 防火墙的区域划分&#xff1a; 包过滤防火墙 --- 访问控制列表技术 --- 三层技术 代理防火墙 --- 中间人技术 --- 应用层 状态防火墙 --- 会话追踪技术 --- 三层、四层 UTM …

CrossOver零知识学习1 —— 初识

本文部分内容参考CrossOver22全新版功能简介 免费mac虚拟机工具_CoCo玛奇朵的博客-CSDN博客 特此致谢&#xff01; 一、CrossOver简介 CrossOver是由CODE WEAVERS公司开发的类虚拟机软件&#xff0c;目的是使Linux和Mac OS X操作系统和Window系统兼容。CrossOver英文原意为“…

强烈推荐:0基础入门网安必备《网络安全知识图谱》

蚁景网安学院一直专注于网安实战技能培养&#xff0c;提供全方位的网安安全学习解决方案。我们集聚专业网安技术大佬资源&#xff0c;倾力打造了这本更全面更系统的“网络安全知识图谱”&#xff0c;让大家在网络安全学习路上不迷茫。 在这份网安技能地图册里&#xff0c;我们对…

01 | Msyql系统架构

目录MySQL系统架构连接器查询缓存分析器优化器执行器MySQL系统架构 大体来说&#xff0c;MySQL分为Server层和引擎层两部分。 Server层包含链接器、查询缓存、分析器、优化器和执行器&#xff0c;而引擎层负责的是数据的存储和读取&#xff0c;支持InnoDB、Myisam、Memory等多…

CSS实现文字凹凸效果

使用两个div分别用来实现凹凸效果&#xff1b;text-shadow语法 text-shadow: h-shadow v-shadow blur color; h-shadow&#xff1a;必需。水平阴影的位置。允许负值。 v-shadow &#xff1a;必需。垂直阴影的位置。允许负值。 blur&#xff1a;可选&#xff0c;模糊的距离。 co…

【C语言】你真的了解结构体吗

引言✨我们知道C语言中存在着整形(int、short...)&#xff0c;字符型(char)&#xff0c;浮点型(float、double)等等内置类型&#xff0c;但是有时候&#xff0c;这些内置类型并不能解决我们的需求&#xff0c;因为我们无法用这些单一的内置类型来描述一些复杂的对象&#xff0c…

k8s部署prometheus

k8s部署prometheus 版本说明&#xff1a; k8s&#xff1a;1.24.4 prometheus&#xff1a;release-0.12&#xff08;https://github.com/prometheus-operator/kube-prometheus.git&#xff09; 本次部署采用operator的方式将prometheus部署到k8s中&#xff0c;需对k8s和prom…

springboot+vue驾校管理系统 idea科目一四预约考试,练车

加大了对从事道路运输经营活动驾驶员的培训管理力度&#xff0c;但在实际的管理过程中&#xff0c;仍然存在以下问题&#xff1a;(1)管理部门内部人员在实际管理过程中存在人情管理&#xff0c;不进行培训、考试直接进行发证。(2)从业驾驶员培训机构不能严格执行管理部门的大纲…

SpringBoot解析指定Yaml配置文件

再来个文章目录 文章目录前言1、自定义配置文件2、配置对象类3、YamlPropertiesSourceFactory下面还有投票&#xff0c;帮忙投个票&#x1f44d; 前言 最近在看某个开源项目代码并准备参与其中&#xff0c;代码过了一遍后发现多个自定义的配置文件用来装载业务配置代替数据库…

使用 Python 从点云生成 3D 网格

从点云生成 3D 网格的最快方法 已经用 Python 编写了几个实现来从点云中获取网格。它们中的大多数的问题在于它们意味着设置许多难以调整的参数&#xff0c;尤其是在不是 3D 数据处理专家的情况下。在这个简短的指南中&#xff0c;我想展示从点云生成网格的最快和最简单的过程。…