当前位置: 首页 > news >正文

C++——STL之stack和queue详解

C++——STL之stack和queue详解

  • 🏐什么是stack和queue
  • 🏐stack和queue的实现
    • 🏀什么是deque
    • 🏀stack的模拟实现
    • 🏀queue的模拟实现
  • 🏐优先级队列(priority_queue)
    • 🏀优先级队列的实现
      • ⚽push
      • ⚽pop
      • ⚽top,empty,size
      • ⚽构造
      • ⚽仿函数
  • 💬总结

👀先看这里👈
😀作者:江不平
📖博客:江不平的博客
📕学如逆水行舟,不进则退
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
❀本人水平有限,如果发现有错误的地方希望可以告诉我,共同进步👍

🏐什么是stack和queue

之前我们了解 stl六大组件中的容器,迭代器,这里再来了解下适配器stack和queue
在这里插入图片描述
堆栈是一种容器适配器,专门设计用于在后进先出的内容(后进先出)中运行,其中元素仅从容器的一端插入和提取。
在这里插入图片描述
队列是一种容器适配器,专门设计用于在的内容(先进先出)中运行,其中元素插入容器的一端并从另一端提取。

因为这个限制特性,所以没有迭代器接口。

第二个模板参数传的是一个容器,我们知道stack和queue的实现是可以通过不同的方式的,可以是顺序表的实现方式也可以是链表的实现方式,这里就是我们选择不同的容器,来达到不同的实现,这里默认容器为deque

在这里插入图片描述

🏐stack和queue的实现

🏀什么是deque

在上面stack和queue的类模板声明中我们就可以看到,它们的模板参数有两个,第一个是存储在stack和queue中的元素类型,而另一个是使用的容器类型。默认使用deque作为指定容器。那么deque是什么呢?
在这里插入图片描述

deque跟vector,list一样,也是一个容器,是一个既能支持随机访问,也能轻松做到头尾插入删除,看起来具备了vector和list二者的优势的一个双端开口队列。正是因为这种特性符合栈和队列的特性,所以用deque作为默认容器。
deque的底层是通过很多个小的数组buffer来实现的,这些buffer的地址存储在一个指针数组——中控数组中,如图
在这里插入图片描述

🏀stack的模拟实现

这里的模拟实现还是比较简单的,与数据结构差不多,就是要通过容器来做到相关接口。

#pragma once
#include <deque>

namespace ptj
{
	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();
		}

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

		size_t size() const
		{
			return _con.size();
		}
	private:
		//vector<T> _con;
		Container _con;
	};
}

🏀queue的模拟实现

#pragma once
#include <deque>

namespace ptj
{
	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& back()
		{
			return _con.back();
		}

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

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

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


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

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

在这里插入图片描述

🏐优先级队列(priority_queue)

  • 优先级队列是一种容器适配器,根据一些严格的弱排序标准,专门设计使其第一个元素始终是它包含的最大元素。

  • 类似于堆,其中可以随时插入元素,并且只能检索最大堆元素(优先级队列顶部的那个)。

在这里插入图片描述

我们发现这里用的底层容器默认是vector,没有用deque。因为它这里要大量运用[],用deque效率低,没有vector好。当然你也可以换容器,没有规定死。

🏀优先级队列的实现

我们也来模拟实现下优先级队列,其实跟跟数据结构时建大堆的操作差不多(感兴趣的看堆排序相关内容——你知道有一种树叫二叉树吗?)

⚽push

这里写push的重点在于把插入的数向上调整到顶端

void adjust_up(size_t child)
		{
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[child] > _con[parent])
				if (_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);
		}

⚽pop

这里写pop的重点在于向下调整

void adjust_down(size_t parent)
		{
			Compare com;
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				// 选出左右孩子中大的那一个
				//if (child+1 < _con.size() && _con[child+1] > _con[child])
				if (child + 1 < _con.size() && _con[child] < _con[child + 1])
				{
					++child;
				}

				//if (_con[child] > _con[parent])
				if (_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);
		}

⚽top,empty,size

再来看一些常用的接口

取堆顶的数据
const T& top()//加const,不允许被修改数据
{
	return _con[0];
}
判空
bool empty()  const
{
	return _con.empty();
}
算数据大小
size_t size() const
{
	return _con.size();
}

⚽构造

这里我们建堆采用向下调整建堆,因为这样是时间复杂度是要低于向上调整建堆的(详情看堆排序相关内容——你知道有一种树叫二叉树吗?)
在这里插入图片描述
我们模拟迭代器构造,这里的第三个参数是仿函数(后面会有介绍),我们先不管它

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);
    }
}

⚽仿函数

仿函数顾名思义,它做到了函数的功能,却不是个函数,这是怎么做到的呢?我们来看一下吧

namespace ptj
{
	template<class T>
	class less
	{
	public:
		bool operator()(const T& data1, const T& data2) const
		{
			return data1 < data2;
		}
	};

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

int main()
{
	ptj::less<int> lsData;
	cout << lsFunc(1, 2) << endl;
	// 等价于下面
	//cout << lsData.operator()(1, 2) << endl;

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

通过阅读上面的代码,我们可以发现我们封装了一个类,用类对象的方式去调用了一个函数operator(),通过将运算符()重载来实现我们想要的功能。这就是仿函数,通过传参的类似方式做到了我们想要的功能。
我们可以将一个对象当成函数来用,比如在if语句那里(以向上调整里判断父亲和孩子大小为例),我们就可以写成

//if (_con[child] > _con[parent])
//if (_con[parent] < _con[child])
if (com(_con[parent], _con[child]))
{
	std::swap(_con[child], _con[parent]);
	child = parent;
	parent = (child - 1) / 2;
}

💬总结

  • 优先级队列(priority_queue)传的是类,sort传的是对象,一个是容器适配器,一个在算法模块Algorithm中
  • 这些容器适配器不是简单的选择容器封装,比如priority_queue中,我们还做了堆的相关算法操作。

在这里插入图片描述

觉得还不错的铁汁点赞关注一下吧😀

相关文章:

  • 结构体嵌套函数指针
  • 基于Xlinx的时序分析与约束(4)----主时钟约束
  • Arcgis使用教程(十三)ARCGIS地图制图之地图输出参数设置详解
  • QT中Qthread线程彻底销毁的实例与注意事项(防止线程资源内存泄露)
  • PCL点云处理之曲面法线估计(八十二)
  • MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《1》
  • 数据库系统概论第七章(数据库设计)知识点总结(1)—— 概述
  • 【QT】信号与槽
  • 河道非法采砂识别系统 yolov5
  • JavaWeb语法四:多线程案例
  • Unity使用飞书在线表格做配置表
  • 计算机网络最新复习【太原理工大学】
  • 视频异常检测技术研究进展
  • 【Java 数据结构】-二叉树OJ题
  • 【C++天梯计划】1.14 区间最值算法(RMQ)
  • ES6指北【2】—— 箭头函数
  • 【347天】每日项目总结系列085(2018.01.18)
  • hadoop集群管理系统搭建规划说明
  • HomeBrew常规使用教程
  • Javascript编码规范
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • Material Design
  • Median of Two Sorted Arrays
  • nodejs实现webservice问题总结
  • ReactNativeweexDeviceOne对比
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • vue总结
  • 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1
  • 将 Measurements 和 Units 应用到物理学
  • 坑!为什么View.startAnimation不起作用?
  • 如何选择开源的机器学习框架?
  • 一个JAVA程序员成长之路分享
  • MPAndroidChart 教程:Y轴 YAxis
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • $(function(){})与(function($){....})(jQuery)的区别
  • (c语言版)滑动窗口 给定一个字符串,只包含字母和数字,按要求找出字符串中的最长(连续)子串的长度
  • (Java岗)秋招打卡!一本学历拿下美团、阿里、快手、米哈游offer
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (转)VC++中ondraw在什么时候调用的
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • *** 2003
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别
  • .NET Remoting学习笔记(三)信道
  • .NET导入Excel数据
  • [ Linux 长征路第五篇 ] make/Makefile Linux项目自动化创建工具
  • [.net] 如何在mail的加入正文显示图片
  • [].slice.call()将类数组转化为真正的数组
  • [2019/05/17]解决springboot测试List接口时JSON传参异常
  • [AIGC] Java 和 Kotlin 的区别
  • [AIGC] SQL中的数据添加和操作:数据类型介绍
  • [APIO2012] 派遣 dispatching
  • [AR]Vumark(下一代条形码)
  • [C#]OpenCvSharp结合yolov8-face实现L2CS-Net眼睛注视方向估计或者人脸朝向估计
  • [ERROR ImagePull]: failed to pull image k8s.gcr.io/kube-controller-manager失败
  • [Flutter]设置应用包名、名称、版本号、最低支持版本、Icon、启动页以及环境判断、平台判断和打包