C++ 栈和队列(stack and queue)语法使用及底层实现原理

news2025/1/4 16:29:47

  

  本篇文章会对C++中的容器stack和queue用法进行详解,也包含对优先队列(priority_queue)的讲解。同时会模拟实现stack、queue和priority_queue底层。希望本篇文章会对你有所帮助!

 

目录

一、stack 栈

1、1 什么是适配器

1、2 stack 语法讲解

1、3 stack 底层实现

1、4 deque 双端队列简单介绍

1、5 为什么选择deque作为stack和queue的底层默认容器

二、queue or priority_queue 队列和优先队列

2、1 queue 队列

2、1、1 queue 语法讲解

2、1、2  queue 底层实现

2、2 priority_queue 优先队列

2、2、1 priority_queue 底层实现原理

2、2、2 仿函数

2、2、3  priority_queue 底层代码实现


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:C++  👀

💥 标题:stack、queue和priority_queue讲解💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️  

一、stack 栈

  stack(堆栈)是一种后进先出(Last-In-First-Out,LIFO)的数据结构。数据项只能从栈的顶部插入和删除。在C++中,stack、queue和priority_queue是一种容器适配器可以使用<stack>头文件来包含stack的定义。

1、1 什么是适配器

  我们在上述中说到stack是一种容器适配器。容器我们都知道,那什么是适配器呢?

  适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总 结),该种模式是将一个类的接口转换成客户希望的另外一个接口。通俗来说,在C++中适配器就是一种编程模式, 用于将一个类的接口适配成另一个类的接口,以满足不同的需求。
  我们所需要讲解stack、queue和priority_queue底层就是有用适配器来实现的。当然,只对语法有所需求的,不了解适配器也并无太大影响。
  只有概念太过抽象。我们不妨先来学一下语法使用,再了解底层实现原理后,就可很好的理解适配器是什么了。

1、2 stack 语法讲解

  我们早在学C语言时就学过栈。栈是一种先进后出的数据结构。在C++中,stack是一种容器。所以我们在使用时应引入相应头文件(#include<stack>),同时需要展开命名空间(namespace std)

  栈的操作很简单,一共就有如下几种:

  1. top:获取尾部元素操作;
  2. size:获取栈中的元素个数操作;
  3. pop:删除栈顶元素操作;
  4. push:栈顶插入元素操作;
  5. empty:判空操作;
  我们接下来看一下实例,结合理解一下,代码如下:
#include<iostream>
#include<stack>

using namespace std;

int main()
{
	//stack<data_type> stack_name;
	stack<int> st;  //声明一个stack对象,对象名为 st,存储数据类型为 int 

	//往栈中依次插入了1 2 3 三个元素
	st.push(1);
	st.push(2);
	st.push(3);
	cout << st.size() << endl; //打印栈中的元素个数

	cout << st.top() << endl;  //打印栈顶元素,栈顶元素为 3

	st.pop();
	cout << st.top() << endl;  //删除元素后,再次打印打印栈顶元素,栈顶元素为 2

	cout << st.empty() << endl; //判断栈是否为空,空返回 1 ,不为空返回 0。

	cout << st.size() << endl;
}

  我们再看输出结果如下:

  栈的使用十分简单,我们直接看底层实现原理。 

1、3 stack 底层实现

  为了能够便利的存储各种类型,C++在底层实现中采用了类模板。通过声明或者传参,进而实例化出不同的类。

  stack的底层并不是自己定义了数组进行维护,而是引用了其他容器。引用的那个容器呢?我们先看一下底层模板定义:

  模板的第一个参数即为我们所要存储的类型,第二个参数为我们所想使用的容器。我们也看到,其中有缺省参数。当你不传参时,默认为deque容器

  stack被称为容器适配器是因为它通过适配底层的容器实现了一种特定的接口和行为。而所谓的适配器,我们可以通俗理解就是可以根据实际需求选择适合的底层容器来实现Stack,并且可以在不影响代码的其他部分的情况下进行更改

  我们所选择的容器需要支持以下操作:

  • empty:判空操作;
  • pop_back:尾部删除元素操作;
  • push_back:尾部插入元素操作;
  • back:获取尾部元素操作;

  底层代码的实现原理就比较简单了,代码如下:

      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();                                                                                                                            
          }    
              
          const T& top() const
          {    
              return _con.back();    
          }
      
          bool empty() const
          {
              return _con.empty();
          }
      
          int size() const
          {
              return _con.size();
          }
      private:
          Container _con;
      };

1、4 deque 双端队列简单介绍

  deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。
    deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组。
  双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落 在了deque的迭代器身上,因此deque的迭代器设计就比较复杂其底层结构如下图所示:

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

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

1、5 为什么选择deque作为stackqueue的底层默认容器

  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的优点,而完美的避开了其缺陷。

二、queue or priority_queue 队列和优先队列

2、1 queue 队列

2、1、1 queue 语法讲解

  queue队列是一种先进先出的数据结构。在C++中,也是一个容器适配器。其底层定义如下:

  其主要的操作有如下几种:

  1. front:获取对列头部(第一个元素)操作;
  2. back:获取队列尾部(最后一个元素)操作;
  3. size:获取队列中的元素个数操作;
  4. pop:删除队列头部元素操作;
  5. push:队列尾部插入元素操作;
  6. empty:判空操作;
  我们来看实际例子,代码如下:

2、1、2  queue 底层实现

  queue底层实现与stack大同小异。当我们学完stack的底层实现后,我们就很容易构思出queue的底层实现了。我们直接看代码:

      template<class T, class Container=deque<T>>                                
      class queue                                                                                            
      {                                                                                   
      public:                                                                                                   
          void push(const T& x)                                                           
          {                                                                      
              _con.push_back(x);                                                                           
          }                                                                               
                                                                                                                 
          void pop()                                                                      
          {                                                                      
              _con.pop_back();                                                                           
          }                                                                               
                                                                                                                
          T& front()                                                                      
          {                                                                                  
              return _con.fornt();                                                                                                                           
          }                                                                            
                                                                                  
          T& back()                                                           
          {                                                                   
              return _con.back(); 
          }
  
          const T& front() const
          {                                                                                                                                                  
              return _con.fornt();
          }
          
          const T& back() const
          {
              return _con.back();
          }
  
          bool empty() const
          {
              return _con.empty();
          }
      
          size_t size() const
          {
              return _con.size();
          }
      private:
          Container _con;
      };
 

   我们接下俩重点看一下优先队列priority_queue的底层实现。

2、2 priority_queue 优先队列

   优先队列(priority_queue)也是队列的一种,priority_queue的接口是和queue的接口是相同的。所以两者的使用语法也是相同的。我们直接看优先队列(priority——queue)的底层实现原理。

2、2、1 priority_queue 底层实现原理

  优先队列中顾名思义:优先级高的元素在队列中的位置靠前,优先级低的元素在队列中的位置靠后。优先级就是指的元素大小。

  优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
  1. empty():检测容器是否为空;
  2. size():返回容器中有效元素个数;
  3. front():返回容器中第一个元素的引用;
  4. push_back():在容器尾部插入元素;
  5. pop_back():删除容器尾部元素
  标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
  优先队列的底层是由堆来维护的。也就是我们所操作的元素都是在堆的基础上进行操作的。提到堆,我们就知道堆分为大根堆和小根堆。优先队列的底层默认是由大根堆来维护的。为了更好的去控制优先队列底层维护的是大根堆还是小根堆,这里就引入了仿函数。我们先来了解一下什么是仿函数,再来看一下底层的代码实现。

2、2、2 仿函数

  仿函数就是模仿函数,但它根本上就不是一个函数。我们先看下面一个例子,代码如下:

template<class T>
struct Less
{
	T l, r;
	bool operator()(const T& left, const T& right)
	{
		return left < right;
	}
};
int main()
{
	test_priority_queue();

	//仿函数
	Less<int> com;

    // com.operator() (1,2)
	cout << com(1, 2) << endl;
	cout << com(2, 1) << endl;
	return 0;
}

  我们看到上面的代码定义了一个结构体 Less,我们同时定义了一个com(Compare)对象。com(1,2)直接就对 1 和 2 进行了比较大小。是不是很像函数调用。但实际上是定义了一个结构体(或者类),该结构体(或者类)中重载了操作符 ()。从而达到了函数的效果。这就是所谓了仿函数。

2、2、3  priority_queue 底层代码实现

  我们通过把仿函数当作模板参数,就可以很好的控制底层是大根堆还是小根堆了。代码如下:

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)
		{
			while (first != last)
			{
				_con.push_back(*first);
				first++;
			}

			//建堆  向下调整算法O(n)
			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]))
				{
					std::swap(_con[parent], _con[child]);
					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[parent], _con[child]);
					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;
	};

   我们看到上述代码,删除元素就是删除的堆顶元素。每次删除都需要先与最后一个元素交换,再通过所引用的容器进行尾删,再向下调整算法进行维护队列。

  当然,上述代码只是实现了一部分主要接口。priority_queue(优先队列)的声明方式如下:

// 底层是大根堆
priority_queue<int> heap1;
// 底层是小根堆
priority_queue<int, vector<int>, greater<int>> heap2;

  到这里,希望你对优先队列有一个更好的认识。同时,对stack、queue和priority_queue的用法有一个很好的掌握。感谢阅读ovo~

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

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

相关文章

C++ 线程池实现

思路 创建多个工作线程同时维护一个公共的任务队列, 任务队列非空时通过信号量唤醒阻塞等待的工作线程, 工作线程通过互斥锁互斥的从任务队列中取出任务, 然后执行任务 实现 信号量类 class sem {//封装信号量类 public:sem(int num 0) {if (sem_init(&m_sem, 0, num)…

Kernel-Pwn-FGKASLR保护绕过

FGKASLR FGASLR&#xff08;Function Granular KASLR&#xff09;是KASLR的加强版&#xff0c;增加了更细粒度的地址随机化。因此在开启了FGASLR的内核中&#xff0c;即使泄露了内核的程序基地址也不能调用任意的内核函数。 layout_randomized_image 在fgkaslr.c文件中存在着…

支持中文手写和多画布的Handraw

什么是 Handraw ? Handraw 是支持中文手写和多画布的 Excalidraw 白板工具。 官网上项目名称还是 Excalidraw-CN&#xff0c;所以 Handraw 应该是基于 Excalidraw 二开的&#xff0c;特点是支持中文手写字体和多画布 官方也提供了免费使用的站点&#xff1a;https://handraw.t…

ModaHub魔搭社区:向量数据库Zilliz Cloud插入 Entity教程

目录 开始前 插入单个 Entity 批量插入 Entity 准备数据 插入数据 写入操作 本文介绍如何将 Entity 插入到 Zilliz Cloud 集群中的 Collection。 Entity 是 Collection 中的基本数据单元。同一个 Collection 中的 Entity 具有相同的属性,这些属性共同定义在 Schema 中…

低代码开发平台助力解决企业开发效率问题

编者按&#xff1a;随着企业应用需求的不断增加&#xff0c;提高企业开发效率已经成为许多企业的目标。传统的开发方法显然不适用&#xff0c;开发平台通过可视化拖拉拽搭建等易用性和高扩展性可以帮助企业解决这个问题。 关键词&#xff1a;可视化开发、私有化部署、前后端分离…

设计模式--------行为型模式

行为型模式 行为型模式用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务&#xff0c;它涉及算法与对象间职责的分配。 行为型模式分为类行为模式和对象行为模式&#xff0c;前者采用继承机制来在类间…

飞控学习笔记-IMU姿态算法

扩展卡尔曼滤波算法 传感器融合算法 卡尔曼滤波算法 最小二乘法 毕卡逼近法 对上式进行泰勒展开 得到四元数各阶近似算法&#xff1a; 梯度下降算法 梯度下降 互补滤波算法 chatgpt解释&#xff1a; 互补滤波&#xff08;Complementary Filter&#xff09;算法是一种常用…

zk-IMG:对抗虚假信息

1. 引言 前序博客&#xff1a; ZKP图片授权——PhotoProof&#xff1a;proofs of permissible photo edits Daniel Kang等人2022年论文《ZK-IMG: Attested Images via Zero-Knowledge Proofs to Fight Disinformation》&#xff0c;在该论文中提供了一个简单的deep fake ima…

高级编程技巧之Python装饰器详解

概要 装饰器是Python中一种强大而灵活的编程技巧&#xff0c;它可以用于修改或扩展函数的行为&#xff0c;同时又不需要修改函数的源代码。本文将介绍Python中的装饰器的基本概念、使用方法以及高级技巧&#xff0c;帮助你从入门到精通装饰器的使用。 一、基本概念 在深入学习…

【面试必考点】这一次带你彻底学会this的指向问题

文章目录 前言一、this的指向问题1.1 全局中的this1.2 普通函数中的this1.3 定时器中的this1.4 事件处理函数中的this1.5 构造函数中的this1.6 构造函数静态方法中的this1.7 箭头函数中的this 二、修改函数中的this指向2.1 call2.2 apply2.3 bind 三、 this指向练习3.1 某小游戏…

【软件分析/静态分析】chapter6 课程08 指针分析(Pointer Analysis)

&#x1f517; 课程链接&#xff1a;李樾老师和谭天老师的&#xff1a; 南京大学《软件分析》课程08&#xff08;Pointer Analysis&#xff09;_哔哩哔哩_bilibili 目录 第六章 指针分析&#xff08;Pointer Analysis&#xff09; 6.1 为什么需要指针分析 6.2 指针分析的基本…

AMAT 工业输入输出模块0100-77037

W;① ⑧ 0 ③ 0 ① 7 7 7 ⑤ 9 AMAT 工业输入输出模块0100-77037 0100-76124 0100-71313 0100-71311 0100-71309 0100-71278 0100-71267 0100-71229 0100-71224 0100-20100 IGBT 和 IGCT 是四层器件&#xff0c;乍一看并没有什么不同。但是&#xff0c;当您“ 深入了解…

Spring Boot原理分析(三):IoC容器的继承层次

文章目录 一、Spring Ioc容器的继承层次1.BeanFactory2.ListableBeanFactory3.HierarchicalBeanFactory4.ApplicationContext 二、常用的ApplicationContext的实现类1.ClassPathXmlApplicationContext&#xff08;基于XML配置&#xff09;2.AnnotationConfigApplicationContext…

[Android]使用jni实现高斯模糊

1.高斯模糊的原理&#xff1a; 根据周边的像素值来确定自己的像素值&#xff0c;平均值&#xff0c;最大值&#xff0c;最小值&#xff0c;正太分布值 2.均值模糊blur 函数声明&#xff1a; CV_EXPORTS_W void blur( InputArray src, OutputArray dst,Size ksize, Point anc…

python绘制分组条形图

文章目录 数据导入多组条形图堆叠条形图 数据导入 我们经常会遇到对比多个统计量随时间变化的图像&#xff0c;比如想知道中国、美国以及欧盟最近几年GDP变化&#xff0c;如下表所示&#xff0c;单位是万亿美元。 中国美国欧盟201813.8920.5315.98201914.2821.3815.69202014.…

转换或是克隆的虚拟机无法联网,网络服务无法启动

新转换的虚拟机&#xff0c;无法联网&#xff0c;启动网络服务&#xff0c;报错&#xff1a; systemctl start network.service job for network.service failed because the control process exited with error code. 查看网络服务状态&#xff0c;systemctl status network…

SpringMVC 中的控制器如何处理文件上传

SpringMVC 中的控制器如何处理文件上传 Spring MVC 是一个基于 Java 的 Web 框架&#xff0c;它是 Spring 框架的一部分&#xff0c;提供了一系列的组件和工具&#xff0c;帮助开发人员构建 Web 应用程序。其中&#xff0c;控制器是 Spring MVC 中的核心组件之一&#xff0c;它…

SpringMVC 中的控制器如何返回 JSON 数据

SpringMVC 中的控制器如何返回 JSON 数据 SpringMVC 是一个基于 Spring 框架的 Web 框架&#xff0c;它提供了一种方便的方式来处理 HTTP 请求和响应。在 SpringMVC 中&#xff0c;控制器是用来处理请求的组件&#xff0c;它们负责接收请求、处理请求并返回响应。在本文中&…

三大城市分会场精彩呈现—2023架构·可持续未来峰会圆满收官!

2023年6月30日&#xff0c;由The Open Group主办的2023架构可持续未来峰会三大城市分会场成功举办&#xff0c;也代表着本次The Open Group半年度架构峰会圆满收官&#xff01; 本次大会以“可持续未来”为主题&#xff0c;采用“13”&#xff0c;即北京主会场上海/成都/深圳三…

svg修改图标颜色

对于svg图标&#xff0c;想通过hover或者active 添加颜色&#xff0c;没有办法修改&#xff0c;解决办法&#xff1a; 1. 修改svg图片源 最开始的svg图标&#xff1a; 修改这个fill"currentColor" 要是要修改线条颜色就修改stroke属性&#xff1a; fill属性设置对象…