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

优先级队列的模拟实现

    • 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.kler.cn/a/2078.html

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

相关文章

【计算机组成原理】:计算机系统概述

目录 一、计算机系统层次结构 1️⃣计算机系统的组成 2️⃣计算机硬件 1. 冯诺依曼机的基本思想 &#x1f4a4;思考&#xff1a;冯诺依曼机的来源❓ &#x1f338;知识点&#xff1a;冯诺依曼机的特点 &#x1f4a4;思考&#xff1a;以运算器为中心的计算机有什么缺点❓ 2…

鸟哥的Linux私房菜 Shell脚本

第十二章、学习 Shell Scripts https://linux.vbird.org/linux_basic/centos7/0340bashshell-scripts.php 12.2 简单的 shell script 练习 #!/bin/bash# Program: # User inputs his first name and last name. Program shows his full name.read -p "Please in…

【Leetcode】队列实现栈和栈实现队列

目录 一.【Leetcode225】队列实现栈 1.链接 2.题目再现 3.解法 二.【Leetcode232】栈实现队列 1.链接 2.题目再现 3.解法 一.【Leetcode225】队列实现栈 1.链接 队列实现栈 2.题目再现 3.解法 这道题给了我们两个队列&#xff0c;要求去实现栈&#xff1b; 首先&…

【三维几何学习】从零开始网格上的深度学习-3:Transformer篇(Pytorch)

本文参加新星计划人工智能(Pytorch)赛道&#xff1a;https://bbs.csdn.net/topics/613989052 从零开始网格上的深度学习-3:Transformer篇引言一、概述二、核心代码2.1 位置编码2.2 网络框架三、基于Transformer的网格分类3.1 分类结果3.2 全部代码引言 本文主要内容如下&#…

【C++初阶】四、类和对象(下)

文章目录一、再谈构造函数构造函数体赋值初始化列表explicit关键字二、Static成员引入- 计算类中创建了多少个类对象概念特性静态成员函数的访问三、友元友元函数友元类四、内部类五、匿名对象六、拷贝对象时的一些编译器优化一、再谈构造函数 构造函数体赋值 在创建对象时&a…

沁恒CH32V307使用记录:使用TIM输出PWM信号

文章目录目的基础说明使用例程总结目的 使用TIM输出PWM信号是单片机中比较常用的一个功能。这篇文章将对CH32V307中相关内容进行说明。 本文使用沁恒官方的开发板 &#xff08;CH32V307-EVT-R1沁恒RISC-V模块MCU赤兔评估板&#xff09; 进行演示。 基础说明 CH32V307拥有多…

C++笔记——第六篇 list的简介和使用

目录 一、list的介绍和使用 1. list的介绍 2. list的使用 2.1 list的构造 2.2 list iterator的使用 2.3 list capacity 2.4 list element access 2.5 list modifiers 2.6 list的迭代器失效 二、list与vector的对比 一、list的介绍和使用 1. list的介绍 1. list是可以…

多线程 (六) 单例模式

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

函数(上)——“Python”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰的内容是Python的函数呀&#xff0c;下面&#xff0c;就让我们进入函数的世界吧 首先可以选择性地看一下小雅兰很久之前写的C语言函数章节的知识&#xff1a; 函数——“C”_认真学习的小雅兰.的博客-CSDN博客 函数递归&#xf…

金三银四Java面试题及答案整理(2023最新版) 持续更新

作为一名优秀的程序员&#xff0c;技术面试是不可避免的一个环节&#xff0c;一般技术面试官都会通过自己的方式去考察程序员的技术功底与基础理论知识。 如果你参加过一些大厂面试&#xff0c;肯定会遇到一些这样的问题&#xff1a; 1、看你项目都用的框架&#xff0c;熟悉 …

使用旧电脑玩Linux

今天给大家讲讲使用旧电脑玩Linux&#xff0c;大家应该都知道旧电脑的硬件一般比较落后&#xff0c;特别是一些非常老的电脑&#xff0c;目前还在使用的是机械硬盘&#xff0c;如是要跑windows可想而知&#xff0c;但是Linux系统对硬件性能的要求可比windows低的多了&#xff0…

spring2

1.Spring配置数据源1.1 数据源&#xff08;连接池&#xff09;的作用 数据源(连接池)是提高程序性能如出现的事先实例化数据源&#xff0c;初始化部分连接资源使用连接资源时从数据源中获取使用完毕后将连接资源归还给数据源常见的数据源(连接池)&#xff1a;DBCP、C3P0、BoneC…

CSDN 周赛38期题解

CSDN 周赛38期题解1、题目名称&#xff1a;代写匿名信2、题目名称&#xff1a;寻因找祖3、题目名称&#xff1a;小Q新式棋盘4、题目名称&#xff1a;拯救公主结束语1、题目名称&#xff1a;代写匿名信 小Q想要匿名举报XX领导不务正业&#xff01; 小Q害怕别人认出他的字迹。 他…

【性能分析】分析JVM出现的内存泄漏的性能故障

分析JVM出现的内存持续增加的性能故障手册 前言 本文通过常见的性能文件为例&#xff0c;提供简单清晰的思路去快速定位问题根源&#xff0c;从而可以快速解决性能故障。 性能问题介绍 在性能测试工作中针对Java程序最重要的是要关注JVM的内存消耗情况&#xff0c;JVM的内存…

基于数据安全的沙盘推演体系

背景 2022年由IBM和Ponemon研究所联合发布的一份全球性的研究报告&#xff0c;分析了550家遭受数据泄露事件的组织的各种成本和影响因素。根据报告&#xff0c;2022年全球数据泄露规模和平均成本均创下历史新高&#xff0c;数据泄露事件的平均成本高达435万美元&#xff0c;比2…

自动标注工具 Autolabelimg

原理简介~~ 对于数据量较大的数据集&#xff0c;先对其中一部分图片打标签&#xff0c;Autolabelimg利用已标注好的图片进行训练&#xff0c;并利用训练得到的权重对其余数据进行自动标注&#xff0c;然后保存为xml文件。 一、下载yolov5v6.1 https://github.com/ultralytic…

java HashSet 源码分析(深度讲解)

目录 一、前言 二、HashSet简介 三、HashSet的底层实现 四、HashSet的源码解读&#xff08;断点调试&#xff09; 0.准备工作 : 1.向集合中添加第一个元素&#xff08;141&#xff09; : ①跳入无参构造。 ②跳入add方法。 ③跳入put方法。 ④跳入putVal方法。 ⑤跳入res…

inode和软硬链接

文章目录&#xff1a;一、理解文件系统1.1 什么是inode1.2 磁盘了解1.2.1磁盘的硬件结构1.2.2 磁盘的分区1.2.3 EXT2文件系统二、软硬链接2.1 软链接2.2 硬链接一、理解文件系统 1.1 什么是inode inodes 是文件系统中存储文件元数据的数据结构。每个文件或目录都有一个唯一的 …

用Pytorch构建一个喵咪识别模型

本文参加新星计划人工智能(Pytorch)赛道&#xff1a;https://bbs.csdn.net/topics/613989052 目录 一、前言 二、问题阐述及理论流程 2.1问题阐述 2.2猫咪图片识别原理 三、用PyTorch 实现 3.1PyTorch介绍 3.2PyTorch 构建模型的五要素 3.3PyTorch 实现的步骤 3.3.…

Linux--线程安全、多线程fork问题

目录 一、概念&#xff1a; 二、利用空格分割字符串&#xff1a; 1.代码&#xff1a; 2.结果&#xff1a; 3.解决方法&#xff1a; 三、多线程fork&#xff08;&#xff09; 1.代码&#xff1a; 2.线程id 3.增加fork&#xff08;&#xff09;代码&#xff1a; 4.改变fo…
最新文章