[C++基础]-stack和queue

news2024/12/23 9:08:35

前言

作者小蜗牛向前冲

名言我可以接受失败,但我不能接受放弃

  如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。

目录

一、stack的基本知识

1、什么是栈

2、栈的基本使用

3、栈的模拟实现

二、queue的基本知识

1、什么是队列

2、队列的基本用法

 3、队列的模拟实现

三、优先队列

1、优先队列的基本知识

2、仿函数 

3、priority_queue的模拟实现 

4、反向迭代器的模拟实现 ​​​​​​​


本期学习目标:了解栈和队列的基本知识,简单的模拟实现栈和队列,认识双端队列和priority_queue的使用,知道什么是仿函数,模拟实现反向迭代器。

一、stack的基本知识

1、什么是栈

栈(Stack)是一种常见的数据结构,它遵循后进先出(LIFO)的原则。栈是一种STL中的容器,类似于现实生活中的堆叠物体,只能在顶部进行插入和删除操作。

栈具有两个主要操作:

  1. 入栈(Push):将元素添加到栈的顶部,成为新的栈顶。
  2. 出栈(Pop):从栈的顶部移除元素,并返回被移除的元素。

由于栈遵循后进先出的原则,最后一个入栈的元素将是第一个出栈的元素,即最新的元素先被访问或移除。

栈还具有一个特殊的属性——栈顶指针(Top),它指向栈的顶部元素。当执行入栈操作时,栈顶指针上移;而在执行出栈操作时,栈顶指针下移。

栈常常用于解决与层次结构相关的问题,例如函数调用、表达式求值、括号匹配、回溯算法等。在计算机内存管理中,栈也被用于存储函数的局部变量、参数以及程序执行期间的临时数据。

需要注意的是,栈具有一定的容量限制,称为栈的大小。当栈已满时,执行入栈操作将导致栈溢出(Stack Overflow);而当栈为空时,执行出栈操作将导致栈下溢(Stack Underflow)

2、栈的基本使用

对栈(stack)来说在C++中进行来封装,他的底层我们看做是用一个类来实现的,所以他有自己的成员函数和成员变量。通过前面我们对vector和list的学习相信大家对STL容器的都比较熟悉了,下面我们主要和大家介绍一下stack的重点接口.

 栈的重要接口:

函数说明接口说明
stack()构造空的栈
empty()判断栈是否为空
size()返回栈中有多少的元素
top()返回栈顶中的元素
push()将元素val压入栈中
pop()将stack中尾部弹出

栈代码使用演示:

 在演示之前,看一个代码:

	for (auto e : st)
	{

	}
//代码不可用

运行之后程序会报错,这是为什么呢? 这是因为使用aoto for循环的本质其中也运用到了迭代器,也就是要有begin(),end()的成员函数,但是stack是没有的。

那我们将怎么样去打印出stack中的数据呢?

这里我就用用到top()获取栈顶元素,和pop()将栈顶元素出栈就可以了。

#define  _CRT_SECURE_NO_WARNINGS

#include<iostream>
#include<stack>

using namespace std;
int main()
{
	stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);
	st.push(5);
	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}
	cout << endl;
	return 0;
}

3、栈的模拟实现

 为了更好的理解栈,下面我将模拟实现栈。模拟实现栈时,我们不妨思考一下我们是用数组实现还是用链表实现栈呢?

我们知道数组有数组的好处,链表也有链表的的好处,那我们可以二个都用可以吗?

其实是可以的,这就不得不提一下适配器模式,那什么是适配器模式呢?

其实就是将以有的东西封装转化成你想要的东西。

	template<class T, class Container>
	class stack
	{
	public:
	private:
		Container _con;
	};

 这里我们只要多传一个模板参数,Container就可以了。

我们在用自己定义栈,创建对象时多传一个参数就可以了,这样我们就在底层上既实现用list实现的栈,又实现了vector实现的栈

   stack<int,linst<int>> st;//链表实现
   stack<int,vector<int>> st;//数组实现

下面我们只要完善好成员函数就可以实现自己的栈了

namespace pjb
{
	template<class T, class Container >
	class stack
	{
	public:
		void  push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_back();
		}
		const T& top()
		{
			return _con.back();
		}
		bool empty()
		{
			return _con.empty();
		}
		size_t size()
		{
			return _con.size();
		}
	private:
		Container _con;
	};
}

测试 :

二、queue的基本知识

1、什么是队列

队列(Queue)是一种数据结构,它遵循先进先出(First-In-First-Out,FIFO)的原则。队列中的数据项按照插入的顺序排列,最先插入的数据项首先被移除。

队列可以比喻成现实生活中的排队等候的场景。新来的人必须排在队尾,而排在队头的人最先离开队列。

队列有两个基本操作:

  1. 入队(Enqueue):将数据项添加到队列的末尾。
  2. 出队(Dequeue):将队列的第一个数据项移除,并返回该数据项。

队列常常用于模拟系统中的排队行为,如任务调度、消息传递等。在编程中,队列可以使用数组或链表等数据结构来实现。

但是要注意并非所有队列都是先进先出的,有一个叫优先级的队列就不是这样的,后面在为大家介绍,我们先了解基本队列的用法。

2、队列的基本用法

队列我们在使用这个容器的时候,重点关注几个接口就好了

 队列的重要接口

函数声明接口说明
queue()构造空的队列
empty()检测队列是否为空,是返回true,否则返回false
size()回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素val入队列
pop()将队头元素出队列

队列代码演示: 

int main()
{
	queue<int> qe;
	qe.push(1);
	qe.push(2);
	qe.push(3);
	qe.push(5);
	qe.push(6);
	while (!qe.empty())
	{
		cout << qe.front() << " ";
		qe.pop();
	}
}

 3、队列的模拟实现

这里我们常常说,vector和list都有各自的优缺点:

vector:  

缺点:头部中部插入删除效率低,扩容有消耗

list:

缺点:不支持随机访问,CPU高速缓命中低。

其实vector和list的缺点和优点是互补的,那有没有一个容器兼容二者的优点呢?

C++中有一个容器 deque(双端队列)(Double Ended Queue,简称为Deque)是一种允许从两端进行插入和删除操作的队列。它可以在队列的前端和后端同时进行插入和删除操作。

双端队列支持以下几种基本操作:

  1. 在队头插入元素(Insert at Front):将一个元素插入到双端队列的前端。
  2. 在队尾插入元素(Insert at Rear):将一个元素插入到双端队列的后端。
  3. 从队头删除元素(Delete from Front):从双端队列的前端删除并返回一个元素。
  4. 从队尾删除元素(Delete from Rear):从双端队列的后端删除并返回一个元素。
  5. 获取队头元素(Get Front):返回双端队列的前端元素。
  6. 获取队尾元素(Get Rear):返回双端队列的后端元素。

 双端队列优缺点:

优点:

  • 与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不 需要搬移大量的元素,因此其效率是必vector高的。
  • 与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段

 缺点:

  • 不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到 某段小空间的边界,导致效率低下

 所以当我们传模板的第二个参数时可以直接传deque。

namespace pjb
{
	template<class T, class Container = deque<T>>
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

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

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

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

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

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

	private:
		Container  _con;
	};
}

三、优先队列

1、优先队列的基本知识

优先队列(Priority Queue)是一种特殊的队列(是一种容器适配器),它根据元素的优先级来确定出队的顺序。与普通队列不同,优先队列中的元素被赋予了优先级,优先级高的元素会先被出队。

优先队列的基本操作包括:

  1. 入队(Enqueue):将元素插入到优先队列中,并根据其优先级进行适当的排序。
  2. 出队(Dequeue):移除具有最高优先级的元素,并返回该元素。
  3. 获取队头元素(Get Front):获取具有最高优先级的元素,但不从队列中移除。

优先队列可以使用不同的数据结构来实现,其中一个常见的实现方式是使用堆(Heap)。堆是一种完全二叉树,具有一些特殊的性质使得它适用于实现优先队列。在堆中,树的每个节点都比它的子节点具有更高的优先级。

优先队列的特点:

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

优先队列的操作函数:

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

注意: 

  • 默认情况下,priority_queue是大堆。
  • 如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载

2、仿函数 

仿函数(Functor)是一种具有函数行为的对象,实际上是将函数行为封装在一个类或结构体中。它可以像函数一样被调用,可以作为函数参数传递,也可以作为返回值返回。

在C++中,仿函数可以通过重载operator()运算符来实现函数调用操作。通过这种方式,仿函数可以像普通函数一样被调用,而且可以带有自己的状态和行为。

class AddFunctor {
public:
    int operator()(int a, int b) const {
        return a + b;
    }
};

int main() {
    AddFunctor add;
    int result = add(3, 4);  // 调用仿函数
    // 结果为7
    return 0;
}

那我们不由的想到函数和仿函数有什么区别吗?

仿函数(Functor)实际上是一个类对象,它的对象可以像函数一样被调用。

函数是一段可执行的代码块,可以通过函数名直接调用。

 区别:

  1. 类对象:仿函数是一个类对象,而函数是一个独立的代码块。仿函数的行为可以通过重载函数调用运算符 operator() 来定义,使得它可以被像函数一样调用。

  2. 状态保持:仿函数可以在自己的内部保留状态信息,这些信息可以在不同的函数调用之间保持,并影响后续的函数调用。而函数通常不具备状态保持的能力,每次调用都是独立的,无法直接记住之前的状态。

  3. 可定制性:由于仿函数是一个类对象,我们可以根据需要定制它的行为,通过它的成员变量和函数实现更复杂的逻辑。函数则相对简单,无法直接修改其内部的逻辑。

  4. 应用场景:仿函数常用于STL的算法中,比如排序、查找、遍历等操作,因其灵活的行为定义和可定制性。函数则广泛应用于各种编程场景,无论是简单的数学运算还是复杂的业务逻辑

3、priority_queue的模拟实现 

 在模拟实现优先队列,我们重点关注向上调整和向下调整的方法,来进行建堆。

这里我们也用到我们上面讲的仿函数

	template<class T>
	class less
	{
	public:
		bool operator()(const T& x, const T& y)const
		{
			return x < y;
		}
	};
	
	template<class T>
	class greator
	{
		bool operator()(const T& x, const T& y)const
		{
			return x > y;
		}
	};

 大家可能不由的会想,这个就不是一个简单的大小比较吗?有必要写的这么复杂吗?,这种仿函数有必要被定义出来吗?

其实不然,虽然我们在写简单大小比较时候,是会增加了代码的复杂性,但是在实际的开发中,我们经常遇到需要对数据进行复杂排序或计算等操作,此时使用仿函数就可以极大地提高程序的灵活性和可扩展性。

以排序为例,STL提供了多种排序算法,如sortstable_sort等,这些算法均可通过传入一个比较函数来定义排序规则。使用仿函数定义排序规则时,我们可以根据自己的需要灵活地定制排序规则,从而实现更加复杂的排序操作

#pragma once
#include<algorithm>

namespace pjb
{
	template<class T>
	class less
	{
	public:
		bool operator()(const T& x, const T& y)const
		{
			return x < y;
		}
	};
	
	template<class T>
	class greator
	{
		bool operator()(const T& x, const T& y)const
		{
			return x > y;
		}
	};
	template<class T, class Container = vector<T>, class Compare = less<T>>
	class priority_queue
	{
	public:
		priority_queue()
		{}
		//建堆
		template <class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)
		{
			for (int i = (_con.size() - 1 - 1) / 2;i >= 0;--i)
			{
				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])
				if (com(_con[parent], _con[child]))
				{
					std::swap(_con[child], _con[parent]);
					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[child], _con[parent]);
					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()const
		{
			return _con[0];
		}

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

		size_t size()const
		{
			return _con.size();
		}
	private:
		Container _con;
	};
}

4、反向迭代器的模拟实现 

template<class Iterator,class Ref,class Ptr>
class ReverseIterator
{
	typedef ReverseIterator<Iterator, Ref, Ptr>Self;

public:
	ReverseIterator(Iterator it)
		:_it(it)
	{}
	Ref operator*()
	{
		Iterator tmp = _it;
		return *(--tmp);
	}
	Ptr operator->()
	{
		return &(operator*());
	}
	Self& operator--()
	{
		++_it;
		return *this;
	}
	bool operator!=(const Self& s)const
	{
		return _it != s._it;
	}

private:
	Iterator _it;
};

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

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

相关文章

setInterval倒计时切换页面后不准

背景 最近在做一个倒计时时&#xff0c;发现当切换浏览器tab后&#xff0c;再切回倒计时页面&#xff0c;倒计时的数据不准&#xff0c;比真正的剩余时间多&#xff0c;短时间还好&#xff0c;时间长了&#xff0c;计时器的误差会很大。 原因 倒计时是用setInterval每1000毫…

盘点:人工智能发展趋势下的4大常见AI算法以及应用场景

近年来&#xff0c;人工智能的发展速度十分惊人&#xff0c;在安防监控、工业制造、农业、教育、金融、医疗等领域中的应用越来越广泛&#xff0c;并且未来几年也将继续保持高速的发展趋势。通过人工智能技术提高自动化程度、减少人工干预、提高监管效率&#xff0c;已经成为当…

在QML中如何使用首字母小写(蛇形)命名法

前言 最近在学习研究QML&#xff0c;想要自定义组件、信号、属性等&#xff0c;但如何对.qml文件、变量命名却是个问题。习惯了蛇形命名规范后&#xff0c;再让我回到驼峰命名规范&#xff0c;多少是不愿意的。 为了坚守信仰&#xff0c;我翻遍了各大技术网站&#xff0c;然而…

里氏替换原则~

里氏替换原则&#xff08;Liskov Substitution Principle&#xff09;是面向对象设计中的一个基本原则&#xff0c;它是由Barbara Liskov提出的。 如果对于每一个类型为Apple的对象1&#xff0c;都有类型为fruit的对象2&#xff0c;使得以fruit定义的所有程序 P 在所有的对象1都…

Web服务器解析:从基础到高级的全面指南

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 引言 Web服务器是现代互…

竞赛选题 基于机器视觉的车道线检测

文章目录 1 前言2 先上成果3 车道线4 问题抽象(建立模型)5 帧掩码(Frame Mask)6 车道检测的图像预处理7 图像阈值化8 霍夫线变换9 实现车道检测9.1 帧掩码创建9.2 图像预处理9.2.1 图像阈值化9.2.2 霍夫线变换 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分…

el-calendar日历 简易排班

公司物流部要个简易的排班功能&#xff0c;由主管去设置线路&#xff0c;线路绑定上负责人。然后直接往日历里添加。 1、隐藏了自带的切换月份&#xff0c;改用了日期选择器。 2、禁用了非本月的点击事件&#xff0c;防止点击自动跳转到其他月份。 3、添加了点击多选&…

C# 流Stream详解(3)——FileStream源码

【FileStream】 构造函数 如果创建一个FileStream&#xff0c;常见的参数例如路径Path、操作方式FileMode、权限FileAccess。 这里说下FileShare和SafeFileHandle。 我们知道在读取文件时&#xff0c;通常会有两个诉求&#xff1a;一是如何更快的读取文件内容&#xff1b;二…

ATECLOUD二极管测试系统可以解决反向电流测试哪些痛点?

二极管具有单向导电性&#xff0c;但是有时候也会出现反向电流的情况。当反向电流变得很大时&#xff0c;二极管性能和寿命会受到很大影响。通过二极管测试系统对反向电流进行测试可以评估其性能和稳定性。 反向电流测试的重要性 理想情况下反向电流非常小&#xff0c;甚至可以…

2021年电工杯数学建模A题高铁牵引供电系统运行数据分析及等值建模求解全过程论文及程序

2021年电工杯数学建模 A题 高铁牵引供电系统运行数据分析及等值建模 原题再现&#xff1a; 我国是世界上电气化铁路运营里程最长、服役电力机车型号最多、运营最繁忙的国家。截至 2020 年底&#xff0c;我国铁路年消耗电量约 800 亿千瓦时&#xff0c;约占三峡年总发电量的 8…

Java实现添加文字水印、图片水印功能实战

java实现给图片添加水印实现步骤&#xff1a; 获取原图片对象信息&#xff08;本地图片或网络图片&#xff09; 添加水印&#xff08;设置水印颜色、字体、坐标等&#xff09; 处理输出目标图片 java实现给图片添加文字水印 获取原图片对象信息 第一步&#xff1a;获取需要…

人工智能安全-6-SQL注入检测

0 提纲 概述SQL注入方法SQL注入的检测方法SQL语句的特征提取天池AI上的实践 1 概述 SQLIA&#xff1a;SQL injection attack SQL 注入攻击是一个简单且被广泛理解的技术&#xff0c;它把 SQL 查询片段插入到 GET 或 POST 参数里提交到网络应用。 由于SQL数据库在Web应用中的…

【微服务实战之Docker容器】第四章-【微服务实战之Docker容器】第三章-镜像仓库

系列文章目录 【微服务实战之Docker容器】第一章-下载及安装 文章目录 系列文章目录坑&#xff1a;容器卷记得加入以下命令配置是个啥&#xff1f;能干啥&#xff1f;基本的命令读写规则映射添加说明卷的继承和共享 坑&#xff1a;容器卷记得加入以下命令配置 --privilegedtr…

van-calendar 实现移动端日历效果

<!--移动端端展示日历--><divv-if"isMobile &&tabActiveName true &&(formName 值班日历 || formName 值班编排)"><template v-if"Array.isArray(listData) && listData.length"><van-calendar:poppable&…

一生一芯14——chisel环境搭建

本人使用的ubuntu版本为22.04 anaconda 版本为23.1.0 本博客参考自https://blog.csdn.net/qq_38798111/article/details/129190615?ops_request_misc%257B%2522request%255Fid%2522%253A%2522169465704516800222836484%2522%252C%2522scm%2522%253A%252220140713.130102334…%…

芯科蓝牙BG27开发笔记8-片上Flash读写

目标 熟悉片上Flash的特点&#xff0c;知道如何使用&#xff0c;最好找到示例代码&#xff0c;有完整例程那是最好的 查找参考手册 除了768K的主空间&#xff0c;还包含&#xff1a; 1. USERDATA区域&#xff0c;用户定义数据&#xff0c;可以读写。大小只有1K。 2. 设备特…

长胜证券:十大流通股东占比例高好还是低好?

近年来&#xff0c;跟着我国本钱商场的不断发展&#xff0c;越来越多的投资者开始了解和关注股东占比这个目标。而在股东占比中&#xff0c;十大流转股东的持股份额是一个重要的目标。可是&#xff0c;关于投资者来说&#xff0c;十大流转股东占比是高好还是低好&#xff1f;本…

【SPI读取外部Flash】使用逻辑分析仪来读取FLASH Device ID

实验设备&#xff1a;25块钱的 逻辑分析仪 和 野火F429开发板 注意点&#xff0c;这个逻辑分析仪最大只能检测24M的波形&#xff0c;而SPI是在外部通道2&#xff0c;所以我们对系统时钟的分频&#xff0c;也就是给到通道2的时钟速度要在24M内&#xff0c;不然检测到的数据是有…

Peppertype.ai:人工智能内容营销平台

【产品介绍】 名称 Peppertype.ai 具体描述 Peppertype.ai是一个AI驱动的文章生成工具&#xff0c;可以帮助你在几秒钟内为各种渠道创建吸引人 的内容。无论你是想要写广告文案、社交媒体标题、博客大纲还是网站内容&#xff0c;Peppertype…

C++ PrimerPlus 复习 第五章 循环和关系表达式

第一章 命令编译链接文件 make文件 第二章 进入c 第三章 处理数据 第四章 复合类型 &#xff08;上&#xff09; 第四章 复合类型 &#xff08;下&#xff09; 第五章 循环和关系表达式 文章目录 for循环&#xff1b;基本语法重要问题和解答 基于范围的for循环&#xff08…