C++ -stack、queue

在这里插入图片描述

博客主页:【夜泉_ly】
本文专栏:【C++】
欢迎点赞👍收藏⭐关注❤️

在这里插入图片描述

文章目录

  • 💡 简介
    • 1.
    • 2. 队列
    • 3. 容器适配器的特点
    • 4. 适配器的本质
    • 5. 小结
  • 💡 简单实现
    • 1. 栈的实现
    • 2. 队列的实现
  • 💡 简单使用
    • 1. 用队列实现栈
      • 1 题目描述
      • 2 代码实现1
        • 逻辑详解
        • 代码
        • 总结
      • 3 代码实现2
        • 逻辑详解
        • 代码
        • 总结
    • 2. 用栈实现队列
      • 1 题目描述
      • 2 逻辑详解
      • 3 代码实现
      • 4 总结

💡 简介

栈(Stack)和队列(Queue)是两种常用的数据结构,它们在使用上相对简单,但却在计算机科学中扮演着重要的角色。它们的核心特性和行为使得它们在特定场景下比其他数据结构(如向量或链表)更具优势。

在数据结构-栈、队列-详解中,我用C语言模拟实现了栈和队列,感兴趣的可以去看看。

1.

栈是一种容器适配器,其工作原理类似于电源适配器对电压的转换,但其内部使用的仍然是其他容器。
在这里插入图片描述
栈的默认底层容器是deque(双端队列),但也可以用其他容器实现栈的功能。

栈的核心特点是遵循后进先出(LIFO)原则,即最后入栈的数据最先出栈。栈的基本操作包括:

  • top:获取栈顶元素,但不移除它。
    在这里插入图片描述

  • push:将数据压入栈中。
    在这里插入图片描述

  • pop:移除栈顶元素并返回其值。
    在这里插入图片描述

通过这三种操作,栈能够有效地管理数据,适用于递归算法、表达式求值等场景。

📖例如《剑指offer》面试题6:从尾到头打印链表
就可以使用栈 后进先出 的特性来完成。

2. 队列

队列同样是一种容器适配器,主要用于按照先进先出(FIFO)原则处理数据。
在这里插入图片描述

与栈不同,队列允许数据在一端插入(入队)并从另一端移除(出队)。队列的核心操作包括:

  • push:将数据入队。
    在这里插入图片描述

  • pop:移除队首元素并返回其值。
    在这里插入图片描述

  • front:获取队首元素,但不移除它。
    在这里插入图片描述

  • back:获取队尾元素,但不移除它。
    在这里插入图片描述

队列可以用于广度优先搜索,例如二叉树的层序遍历。

3. 容器适配器的特点

作为容器适配器,栈和队列并没有提供迭代器。这是因为它们不支持随意遍历元素,必须严格遵循LIFO或FIFO的规则。这种设计确保了数据的顺序性和结构的完整性。
此外,容器适配器通过封装底层容器,提供了更加简单易用的接口,使得开发者能够专注于数据操作,而无需关心内部实现的复杂性。

4. 适配器的本质

适配器的本质是复用

例如容器适配器封装了其他的容器,数据存储在这个容器里面,而容器适配器会根据要求适配出对应的接口。

通过适配器,开发者可以使用不同的数据结构实现特定的行为,而无需重新实现算法或接口。这种灵活性大大提高了代码的复用性和可维护性。

在栈和队列的实现中,适配器模式使得其行为符合实际需求,同时又保持了与其他容器的兼容性。

5. 小结

综上所述,栈和队列作为基本的数据结构,在编程中发挥着重要的作用。理解它们的特点和使用场景,可以帮助开发者在实际应用中更有效地选择合适的数据结构,提升代码的效率与可读性。在今后的编程实践中,掌握栈和队列的使用无疑将为解决复杂问题提供有效的支持。

💡 简单实现

💡C语言版:数据结构-栈、队列-详解

1. 栈的实现

适配器是对已有的东西进行适配、转换。在 C 语言中模拟实现一个栈时,需要考虑是用数组实现还是用链表实现。
这就引出了一个问题:如何做到数组栈链式栈的快速切换?

在 C++ 中,可以使用模板类来实现这一点:

namespace ly
{
	template<class T, class Container>
	class stack
	{
	public:
		// ...
	private:
		Container _con;
	}
}

这里用Container定义了一个容器,至于这个容器具体是什么,不知道。
但这就是想要达到的目的,因为使用更加灵活了:

void test_stack()
{
	ly::stack<int, std::vector<int>> st1;
	ly::stack<int, std::list<int>> st2;
}

可以看见,即可以用 vector 实现, 也可以用 list 实现,这取决于传的是什么容器。

下面我将继续完善这个栈:

  • 使用容器的插入、删除方法
    void push(const T& value) { _con.push_back(value); }
    void pop() { _con.pop_back(); }
  • 返回栈顶元素
    T top() const { return _con.back(); }

    ⭕值得一提的是这里不能用 [ ],因为 list 不支持[]

  • 判空
    bool empty() const { return _con.empty(); }
  • 返回栈的大小
    size_t size() const { return _con.size(); }

这就完了🤣,很简单吧。
并且数组栈、链式栈在用的角度没有区别,还能做到秒切换。

以下是一个简单的测试函数,演示如何使用这个栈:

void test_stack()
{
	// 使用 vector 实现的栈
	ly::stack<int, std::vector<int>> vecStack;  
	vecStack.push(1);
	vecStack.push(2);
	vecStack.push(3);
	std::cout << " vecStack:";
	while (!vecStack.empty())
	{
		std::cout << vecStack.top() << " ";  // 输出栈顶元素
		vecStack.pop();  // 弹出栈顶元素
	}
	std::cout << std::endl;

	// 使用 list 实现的栈
	ly::stack<int, std::list<int>> listStack;  
	listStack.push(1);
	listStack.push(2);
	listStack.push(3);
	std::cout << "listStack:";
	while (!listStack.empty())
	{
		std::cout << listStack.top() << " ";  // 输出栈顶元素
		listStack.pop();  // 弹出栈顶元素
	}
}

Output:

 vecStack:3 2 1
listStack:3 2 1

每次传模板参数比较麻烦,因此库里面也没要求每次都传:
在这里插入图片描述
deque 是什么,在我之后的文章中会讲。
这里可以直接给 vector

template<class T, class Container = vector<T>>

⭕栈需不需要提供构造、析构?

不用,因为这是自定义类型,它会调用自己成员的构造、析构。而容器是已经实现好的,构造、析构就是现成的。

2. 队列的实现

大部分类似于栈的实现,这里直接引出问题:队列能不能用 vector 适配?可以,但不推荐。
强制适配:

void pop()
{ 
	_con.erase(_con.begin());
}

eraselistvector 都提供的接口,因此能用。

但这样强制适配好吗?这样不好。
如果在VS写出下面这几句代码:

std::queue<int, vector<int>> q; 
q.push(1);
q.pop();

再运行:
在这里插入图片描述
在这里插入图片描述

⭕库里面支持list,不支持vector
⭕库里面pop调的pop_front,而vector没有pop_front

💡 简单使用

下面的题目我在数据结构-栈、队列-相关练习中曾经讲过,不过是用C语言写的,有兴趣的可以去看看。

1. 用队列实现栈

题目链接:
225. 用队列实现栈
(https://leetcode.cn/problems/implement-stack-using-queues/description/)

1 题目描述

在此问题中,需要实现一个栈(Stack),但仅允许使用队列(Queue)来完成操作。
在这里插入图片描述

并且题目指出:

你只能使用队列的标准操作 —— 也就是 push to backpeek/pop from frontsizeis empty 这些操作。

栈是一种后进先出(LIFO)的数据结构,而队列是一种先进先出(FIFO)的数据结构。
因此,必须通过一些巧妙的方式,利用队列的特性来模拟出栈的行为。

2 代码实现1

逻辑详解

数据成员

  • 根据题目要求,该类有两个队列 queue1queue2,用于存储栈的元素。可以根据队列的大小和使用情况选择其中一个进行操作。

构造函数

  • MyStack 的构造函数是空的,因为这个类的实例并不需要初始化其他内容。

push

  • 当调用 push 时,如果 queue1 为空,则将元素 x 放入 queue2 中,否则放入 queue1 中。

这一步其实也可以就用一个队列来入元素,但我当时没这么想。

pop

  • pop 方法是最关键的函数。它负责从当前的主队列中弹出元素。
  • 首先检查 queue2 是否为空:
    • 如果为空,则表示当前的 queue1 是操作的主队列。将 queue1 中除了最后一个元素外的所有元素依次转移到 queue2 中,直到 queue1 只剩一个元素。
    • 取出 queue1 中的最后一个元素(即要弹出的元素),并将其返回。
  • 如果 queue2 不为空,操作类似,从 queue2 转移到 queue1

这一设计确保了最后被放入的元素总是能在 pop() 方法中被弹出,符合栈的特性。
top

  • top 返回当前栈顶的元素而不移除它。它会检查 queue1queue2,返回非空队列的最后一个元素(即栈顶元素)。
    empty

  • empty 方法用来检查栈是否为空。检查两个队列是否都为空就行。

代码

以下是实现的代码:

class MyQueue 
{
    stack<int> stackPush;
    stack<int> stackPop;
public:
    MyQueue() {}
    
    void checkSTPopEmpty()
    {
        if(stackPop.empty())
        {
            while(!stackPush.empty())
            {
                stackPop.push(stackPush.top());
                stackPush.pop();
            }
        }
    }
    
    void push(int x) 
    { 
    	stackPush.push(x);
    }
    
    int pop() 
    {
        checkSTPopEmpty();
        int tmp = stackPop.top();
        stackPop.pop();
        return tmp;
    }
    
    int top() 
    {
        checkSTPopEmpty();
        return stackPop.top();
    }
    
    bool empty() 
    { 
    	return stackPop.empty()&&stackPush.empty();
    }
};
总结

这个实现利用了两个队列通过转移元素的方式来模拟栈的行为,相对高效。push 操作的平均时间复杂度为 O(1),而 poptop 操作的平均时间复杂度为 O(1)。尽管个别操作可能在转移时有 O(n) 的复杂度,但整体表现是非常好的。
但是在空间上,题目提出了进一步的要求:

进阶:你能否仅用一个队列来实现栈。

3 代码实现2

相信只要会用两个队列实现,很快就能想到用一个队列实现的方法。

逻辑详解

要仅使用一个队列来实现队列的功能,可以采用一种不同的策略:在 push 操作时,将新元素加入队列的末尾,然后通过队列中的元素调整队列,以保持后进先出的顺序。

数据成员

  • 根据题目要求,仅使用一个队列 queue1 来存储所有的元素。

构造函数

  • MyQueue 的构造函数为空,无需额外初始化。

push

  • 首先,将元素 x 放入 queue1 的末尾。
  • 然后,通过循环,将队列中除新加入的元素外的其他元素依次移到队列的末尾。这样可以确保队列的顺序满足先进先出的特性,即最新加入的元素总是位于队列的前端。

pop

  • 直接从队列的前端弹出元素,并返回该元素。

top

  • 返回队列前端的元素,但不从队列中移除它。

empty

  • 检查 queue1 是否为空,以确定队列是否为空。
代码

以下是实现的代码:

class MyStack 
{
    queue<int> queue1;
public:
    MyStack() {}
    void push(int x) 
    {   
        queue1.push(x);
        for (int i = 0; i < queue1.size() - 1; i++) 
        {
            queue1.push(queue1.front());
            queue1.pop();
        }
    }
    int pop() 
    {
        int tmp = queue1.front();
        queue1.pop();
        return tmp;
    }
    int top() {  return queue1.front();}
    bool empty() { return queue1.empty();}
};
总结

这种方法虽然可以使用单个队列来实现队列的功能,但在 push 操作中,由于需要调整队列中所有元素的位置,因此时间复杂度为 O(n)。
相较于使用两个栈或两个队列的方法,该实现可能在性能上有所下降。
但它展现了如何使用基础数据结构的灵活性来实现特定功能。

2. 用栈实现队列

题目链接:
232. 用栈实现队列
(https://leetcode.cn/problems/implement-queue-using-stacks/description/)

1 题目描述

在此问题中,需要实现一个队列,但仅允许使用栈来完成操作。

在这里插入图片描述
队列是先进先出(FIFO)的数据结构,而栈是后进先出(LIFO)的数据结构。
因此,和前面的题目一样,需要利用栈的特性来模拟队列的行为。

2 逻辑详解

数据成员

这个类有两个栈 stackPushstackPop,分别用于存储加入队列的元素和用于弹出的元素。

构造函数

MyQueue 的构造函数是空的,实例的初始化不需要额外的设置。

checkSTPopEmpty

  • 这是一个辅助函数,负责检查 stackPop 是否为空。
  • 如果为空,则将 stackPush 中的所有元素逐个弹出并推入到 stackPop 中。这样确保了最新入栈的元素在 stackPop 中的最上面,符合队列的先入先出特性。

push

  • 该方法简单地将元素 x 推入 stackPush 中。所有的新元素都会在 stackPush 中存储。

pop

  • 当调用 pop() 时,首先调用 checkSTPopEmpty 来确保 stackPop 中有元素可以弹出。
  • 然后从 stackPop 中弹出并返回栈顶元素。

peek

  • peek 方法同样使用 checkSTPopEmpty() 确保 stackPop 中有元素,然后返回栈顶元素而不移除它。

empty

  • 检查两个栈是否都为空,以判断队列是否为空。

3 代码实现

以下是实现的代码:

class MyQueue 
{
    stack<int> stackPush;
    stack<int> stackPop;
    
public:
    MyQueue() {}
    
    void checkSTPopEmpty()
    {
        if(stackPop.empty())
        {
            while(!stackPush.empty())
            {
                stackPop.push(stackPush.top());
                stackPush.pop();
            }
        }
    }
    
    void push(int x) 
    { 
    	stackPush.push(x);
    }
    
    int pop()
    {
        checkSTPopEmpty();
        int tmp = stackPop.top();
        stackPop.pop();
        return tmp;
    }
    
    int peek() 
    {
        checkSTPopEmpty();
        return stackPop.top();
    }
    
    bool empty() 
    { 
    	return stackPop.empty()&&stackPush.empty();
    }
};

4 总结

使用两个栈的实现方法来模拟队列有效地解决了先进先出的问题。push 操作的时间复杂度为 O(1),而 poppeek 操作的平均时间复杂度为 O(1),在最坏的情况下可能需要 O(n) 的时间进行转移,但整体性能仍然可以接受。
在这里插入图片描述


希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

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

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

相关文章

什么是机器人流量?如何识别和预防有害机器人流量?

机器人流量是指由自动软件程序&#xff08;或机器人&#xff09;而非人类用户生成的互联网流量。机器人可以执行各种任务&#xff0c;包括有益的和恶意的&#xff0c;而且速度比人类快得多。 据估计&#xff0c;大约 30% 的互联网流量来自旨在窃取内容、破坏服务和开展其他恶意…

手机柔性屏全贴合视觉应用

在高科技日新月异的今天&#xff0c;手机柔性显示屏作为智能手机市场的新宠&#xff0c;以其独特的可弯曲、轻薄及高耐用性特性引领着行业潮流。然而&#xff0c;在利用贴合机加工这些先进显示屏的过程中&#xff0c;仍面临着诸多技术挑战。其中&#xff0c;高精度对位、应力控…

stm32的f103---esp8266模块(一)

1 esp8266作为设备  &#xff45;&#xff53;&#xff50;&#xff0d;&#xff10;&#xff11;&#xff53; 1.1入网设置 设置工作模式 AT_CWMODE1 //1. 是station&#xff08;设备&#xff09;模式2.是AP&#xff08;路由器&#xff09;模式3.是双模 OK 以设备模式接入…

[手机Linux PostmarketOS]七, Linux使用selenium爬虫

一&#xff0c;selenium安装 # 用pip 安装 selenium pip3 install selenium --break-system-packages 二&#xff0c;安装浏览器Chrome Alpine Linux 环境中没有google Chrome&#xff0c; 使用 Chromium 浏览器作为 Chrome 的替代品&#xff0c;Chromium 是 Chrome 的开源版本…

Java:抽象类和接口

一.抽象类 1.抽象类概念和语法 ⨀概念&#xff1a; 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类。 ⨀语…

stm32F103 实现呼吸灯效果

目录 硬件连接 软件实现步骤 初始化系统时钟。 配置 GPIO 引脚。 配置定时器以生成 PWM 信号。 在主循环中调整 PWM 占空比以实现呼吸效果。 示例代码 1. 初始化系统时钟 2. 配置 GPIO 引脚 3. 配置定时器以生成 PWM 信号 4. 在主循环中调整 PWM 占空比以实现呼吸效…

python基础综合案例(数据可视化—折线图可视化)

可视化案例的学习目标&#xff1a; 通过案例&#xff0c;回忆巩固python基础的语法 锻炼编程能力&#xff0c;熟练语法的使用 1.json数据格式 两种不同的语言由于数据格式不同&#xff0c;所以没有办法直接沟通&#xff0c;就比如我们可以将python 的数据格式转成json&…

股票的一些术语

杠杆倍数 当你有1万港元&#xff0c;并且融资比例是1:9时&#xff0c;这意味着你可以用1万港元的保证金借入额外的9万港元。这样一来&#xff0c;你总共可以支配10万港元&#xff08;1万港元本金 9万港元借款&#xff09;来投资。 在这种情况下&#xff0c;杠杆倍数是这样计…

无人机之自主飞行关键技术篇

无人机自主飞行指的是无人机利用先进的算法和传感器&#xff0c;实现自我导航、路径规划、环境感知和自动避障等能力。这种飞行模式大大提升了无人机的智能化水平和操作的自动化程度。 一、传感器技术 传感器是无人机实现自主飞行和数据采集的关键组件&#xff0c;主要包括&a…

AndroidStudio部署多渠道打包环境(一)

对于游戏来说&#xff0c;需要上架国内很多家应用商店&#xff0c;还有一些小的渠道SDK&#xff0c;大大小小加起来也有几十家了&#xff0c;那么我们部署了多渠道打包环境之后就很方便了。 一 、配置游戏基本参数&#xff1a;在app下面的build.gradle文件里编辑&#xff0c; …

java实现类似C++的union

目录 1.引言 2.实际案例 3.java实现 1.引言 在C中有一种用户自定义数据类型叫联合体union&#xff0c;它允许你在相同的内存位置存储不同的数据类型。与结构体&#xff08;struct&#xff09;不同&#xff0c;结构体中的所有成员都占用各自的内存空间&#xff0c;而 union 的…

递归算法之快速幂(Exponentiation by Squaring)详细解读

快速幂&#xff08;Exponentiation by Squaring&#xff09;是一种用于快速计算大整数的幂次的算法。传统的幂运算需要进行 nnn 次乘法&#xff0c;而快速幂通过将幂次指数二分&#xff0c;减少了所需的乘法次数&#xff0c;使得计算复杂度从 O(n) 降低到 O(log⁡n)。它广泛应用…

【图论】(五)最短路径算法(D / BF / SPFA / F / A*)

最短路径算法&#xff08;D / BF / SPFA / F / A*&#xff09; 1. 最短路径之dijkstra&#xff08;D算法&#xff09;思路模拟过程程序实现拓展 2. dijkstra算法堆优化思路程序实现 3. Bellman_ford 算法&#xff08;BF算法&#xff09;松弛模拟过程拓展 4. Bellman_ford 队列优…

数据结构-B树和B+树

一、B树 一个节点包含多个key-value值 假设一棵B树由M个参数构建&#xff0c;我们将其称为M阶B树 每个节点最多有M-1个key-value值&#xff0c;并且key值升序排列&#xff0c;每个节点最多能有M个叉 1.1 分类 二节点 三节点 四节点 五节点 key: 给每一个文件进行标号&…

关于iPhone 16 Pro评测视频评论区特征的多维度分析

1.项目背景 随着智能手机的迅速发展&#xff0c;消费者在选择新设备时越来越依赖于网络评价和用户反馈&#xff0c;B站作为中国领先的视频分享平台&#xff0c;聚集了大量科技评测内容&#xff0c;其中UP主的评论区成为用户讨论和交流的重要场所&#xff0c;特别是在iPhone 16…

论文阅读:Guided Linear Upsampling

今天介绍一篇有趣的文章&#xff0c;Guided Linear Upsampling&#xff0c;基于引导的线性上采样&#xff0c;这是发表在 ACM transaction on Graphic 的一篇工作。 Abstract 引导上采样是加速高分辨率图像处理的一种有效方法。在本文中&#xff0c;文章作者提出了一种简单而…

Jetpack架构组件_LiveData组件

1.LiveData初识 LiveData:ViewModel管理要展示的数据&#xff08;VM层类似于原MVP中的P层&#xff09;&#xff0c;处理业务逻辑&#xff0c;比如调用服务器的登陆接口业务。通过LiveData观察者模式&#xff0c;只要数据的值发生了改变&#xff0c;就会自动通知VIEW层&#xf…

excel判断某一列(A列)中的数据是否在另一列(B列)中

如B列如果有7个元素&#xff0c;在A列右边的空白列中&#xff0c;输入如下公式&#xff1a; COUNTIF($B$1:$B$7,A1), 其中&#xff0c;$B$1:$B$7代表A列中的所有数据即绝对范围&#xff0c;A1代表B列中的一个单元格.

Opensearch集群部署【docker、服务器、Helm多种部署方式】

操作系统兼容性 我们建议在 Red Hat Enterprise Linux (RHEL) 或使用systemd的基于 Debian 的 Linux 发行版上安装 OpenSearch &#xff0c;例如 CentOS、Amazon Linux 2 和 Ubuntu Long-Term Support (LTS)。OpenSearch 应该适用于大多数 Linux 发行版&#xff0c;但我们只测…

ctfshow-文件上传-151-161

CTFshow文件上传 PHP文件上传&#xff1a;1、代码思路 黑名单是设置不能通过的用户&#xff0c;黑名单以外的用户都能通过。 phtml、pht、php3、php4、php5后缀都会按做php文件执行&#xff0c;且不在黑名单内。 2、绕过 找漏网之鱼:cer、php3、php4、phtml等。 大小写绕…