数据结构——栈(C++实现)

数据结构——栈

  • 什么是栈
  • 栈的实现
    • 顺序栈的实现
    • 链栈的实现

今天我们来看一个新的数据结构——

什么是栈

栈是一种基础且重要的数据结构,它在计算机科学和编程中扮演着核心角色。栈的名称源于现实生活中的概念,如一叠书或一摞盘子,新添加的物品总是放在顶部,而取出物品时也总是从顶部开始。这种后进先出(Last In, First Out, LIFO)的特性决定了栈的行为。

以下是栈的核心特征和操作:

1. 结构与访问限制:
栈是一个线性数据结构,其中元素按照一定的顺序排列。然而,不同于数组或链表,栈只允许在一端(通常称为栈顶)进行数据的插入(也称为压入,push)和删除(也称为弹出,pop)操作。另一端(栈底)是固定的,不参与数据的直接增删。这意味着栈的元素访问受到严格的限制,用户只能与栈顶元素进行交互。
2. 后进先出(LIFO)原则:
栈遵循后进先出原则。这意味着最近添加到栈中的元素最先被移除。换句话说,最后压入栈的元素是离栈顶最近的,因此在弹出操作时会第一个被访问和移除。相反,最早压入栈的元素(即那些距离栈顶最远的元素)只有在所有后来压入的元素都被弹出后才能被访问。
3. 基本操作:

  • Push(压入): 将一个元素添加到栈顶。
  • Pop(弹出): 移除并返回栈顶元素。如果栈为空,尝试弹出操作通常会导致错误或异常。
  • Peek(查看)/Top: 返回栈顶元素的值,但不改变栈的状态(不移除元素)。
  • IsEmpty(是否为空)/Size(大小): 检查栈是否为空或获取栈中元素的数量。
    4. 应用场景:
    栈因其简单且高效的特性在许多编程任务中得到广泛应用,包括但不限于:
  • 函数调用栈: 在编程语言实现中,每当一个函数被调用时,其局部变量、返回地址等信息会被压入一个系统维护的栈中。函数执行完毕后,通过弹出操作清除这些信息,返回到调用函数的位置继续执行。
  • 表达式求值和符号解析: 在计算逆波兰表示法(RPN)表达式或处理编程语言的括号匹配时,栈用于临时存储操作数和运算符,确保正确的计算顺序。
  • 深度优先搜索(DFS)和回溯算法: 在遍历树形结构或解决涉及多种可能路径的问题时,栈用于存储待访问节点或中间状态,以便回溯到前一个状态。
  • 浏览器历史记录: 用户浏览网页时,后访问的页面压入历史记录栈,前进和后退操作对应于栈的弹出和压入。

总的来说,栈是一种高效、受限的线性数据结构,通过其特有的后进先出性质,为处理需要保持数据顺序、尤其是需要频繁撤销最近操作的场景提供了简洁而强大的工具。

通俗理解,栈的确可以看作是一种操作受限的线性表。线性表是一类数据结构,其中的元素按一定顺序排列,每个元素都有一个唯一的前驱和后继(除了首尾元素外)。栈继承了线性表的这一基本特征,即元素间的线性关系。但是,与常规线性表相比,栈对元素的插入和删除操作施加了严格限制:

操作限制:
常规线性表通常允许在任意位置插入和删除元素,而栈只允许在表的一端(栈顶)进行这两种操作。这意味着你不能随意地在栈的中间或底部插入或删除元素,只能对栈顶进行操作。
行为特点:
由于这种操作限制,栈体现出后进先出(LIFO)的特性。想象一下一个真实的堆栈,比如一叠书或者一叠盘子。当你把新的物品(书或盘子)放在堆栈顶部时,它们就成了最新的“后进”元素。当你需要取走一个物品时,你只能从最上面拿走,所以取出的是最晚放入的那个“先进”元素。这就是所谓的“后进先出”。这种特性使得栈非常适合处理那些需要按照“最后来,最先走”顺序处理数据的场景。
通俗比喻:
可以把栈比作一个只能从上面放东西和取东西的箱子。往箱子里放物品(压入)时,新物品总是在最上面;取出物品(弹出)时,也只能拿走最上面那个。这样,箱子里的物品就像排队一样,后放入的总是在前面,先放入的在后面,想要取走一个物品,必须先把所有后来放入的物品都拿出来。

总结来说,虽然栈具备线性表的基本结构特点,但它通过严格限制操作位置,使其成为一种具有特定行为(后进先出)的特殊线性表。这种操作上的约束赋予了栈独特的应用场景和价值。

栈的实现

我们可以用数组来模拟栈的行为:

template<class T>
class MyStack
{
public:
    MyStack() //无参构造
        :_capacity(10)
        ,_size(0)
    {
        //开辟空间
        _data = new T(_capacity); //开辟这么大的空间
    }
		
    MyStack(const size_t& capacity) //带参构造
        :_capacity(capacity)
        ,_size(0)
    {
        //开辟空间
        _data = new T(_capacity);
    }	


private:
    //动态数组
    T* _data;

    //最大容量
    size_t _capacity;

    //当前数量
    size_t _size;
};

我们这里开了一个动态数组:
在这里插入图片描述
我们一般想象的栈是竖着的,我们可以把这个数组倒一头:
在这里插入图片描述
然后我们可以用一个_size的下标,指向我们0号位置(这里的下标有妙用):

在这里插入图片描述
如果有元素入栈,我们先入栈:
在这里插入图片描述

_size加一:
在这里插入图片描述
模拟这样的行为,我们发现_size总是指向栈顶位置的下一个的位置,但是又因为数组的下标又是从0开始,_size也可以表示栈中有多少个元素。

我们可以用这样的特性,来实现push和top和pop:

    //push
    void push(const T& data)
    {
        assert(_size < _capacity);

        _data[_size++] = data; //在_data[_size]放入之后,_size+1,指向下一个位置
    }

    //pop
    const T& pop()
    {
        assert(_size != 0);

        return _data[--_size]; //因为_size指向栈顶元素的下一个位置,首先先减一取到栈顶
    }

    //top
    const T& top()
    {
        assert(_size != 0);

        return _data[_size - 1];
    }

既然这里的_size是指向栈顶元素的下一个位置,我们也可以让_size指向栈顶元素,这样_size的初始位置就得从-1开始
在这里插入图片描述这样元素入栈,首先要加一(防止下标越界)
在这里插入图片描述然后再放入元素:
在这里插入图片描述

大家可以根据这个写一下这个版本的栈,这里不再赘述。

顺序栈的实现

下面是完整顺序栈的实现:

#pragma once
#include <cassert>
#include <iostream>

template <class T>
class MyStack
{
public:
    // 无参构造函数,使用默认容量创建栈
    MyStack()
        : _capacity(10)
        , _size(0)
    {
        // 开辟空间,创建动态数组,初始容量为 _capacity
        _data = new T[_capacity];
    }

    // 带参构造函数,根据指定容量创建栈
    MyStack(const size_t& capacity)
        : _capacity(capacity)
        , _size(0)
    {
        // 开辟空间,创建动态数组,容量为传入的 _capacity
        _data = new T[_capacity];
    }

    // 压栈操作,将新元素添加到栈顶
    void push(const T& data)
    {
        // 断言检查当前栈是否已满,若已满则抛出断言失败
        assert(_size < _capacity);

        // 将新元素存入动态数组的当前位置,并递增栈大小
        _data[_size++] = data;
    }

    // 出栈操作,删除栈顶元素并返回其值
    const T& pop()
    {
        // 断言检查当前栈是否为空,若为空则抛出断言失败
        assert(_size != 0);

        // 返回栈顶元素的值,并递减栈大小
        return _data[--_size];
    }

    // 查看栈顶元素的值,不改变栈状态
    const T& top()
    {
        // 断言检查当前栈是否为空,若为空则抛出断言失败
        assert(_size != 0);

        // 返回栈顶元素的值(动态数组的最后一个元素)
        return _data[_size - 1];
    }

    // 判断栈是否为空
    bool empty()
    {
        // 返回当前栈大小是否为0,即栈是否为空
        return _size == 0;
    }

    // 返回栈中元素数量
    size_t size()
    {
        // 返回当前栈大小(元素数量)
        return _size;
    }

private:
    // 动态数组,用于存储栈中的元素
    T* _data;

    // 最大容量,即动态数组的容量
    size_t _capacity;

    // 当前数量,即栈中元素的数量
    size_t _size;
};

我们可以测试一下:

#include"my_stack.h"

int main()
{
    MyStack<int> mystack;

    mystack.push(23);
    mystack.push(2);
    mystack.push(20);
    mystack.push(11);
    mystack.push(20);

    std::cout<<"size of mystack: "<<mystack.size()<<std::endl;
   
   
    while(!mystack.empty())
    {
        std::cout<<mystack.pop()<<std::endl;
    }

    if(mystack.empty()==true)
    {
        std::cout<<"stack is empty"<<std::endl;
    }


    return 0;
}


在这里插入图片描述

链栈的实现

上面我们使用的是数组来模拟实现的栈,我们也可以用链表来模拟栈(我这里用带尾指针双向链表来模拟):

我们还是定义一个结点类:
在这里插入图片描述

//结点类
template<class T>
struct Node
{
   Node()
    :_data(T())
    ,_next(nullptr)
    ,_prve(nullptr)
   {

   }

   Node(const T& data)
    :_data(data)
    ,_next(nullptr)
    ,_prve(nullptr)
   {

   }
  //数据域
  T _data;

  //指针域
  Node<T>* _next;
  Node<T>* _prve;
};

这是单链表中的结构:
在这里插入图片描述
我们可以稍微改一下名字:
在这里插入图片描述
在这里我没有用带头结点的双向链表,所以一开始_top和_first会指向nullptr:
在这里插入图片描述
等要插入时,才插入第一个结点:
在这里插入图片描述在这里插入图片描述

void push(const T& data)
{
    // 当栈为空时,直接创建新的节点作为栈的第一个元素和栈顶元素
    if (_first == nullptr)
    {
        _first = new _Node(data);
        _top = _first;
    }
    // 当栈非空时,创建新节点并插入到链表末尾,更新栈顶指针
    else
    {
        _Node* newnode = createNode(data); // 创建新节点
        // 更新链表结构:将新节点的 _prve 指针指向当前栈顶节点
        newnode->_prve = _top;

        // 将当前栈顶节点的 _next 指针指向新节点
        _top->_next = newnode;

        // 更新栈顶指针,使新节点成为新的栈顶元素
        _top = newnode;
    }
    // 增加栈大小
    _size++;
}

下面是完整代码:

#pragma once
#include<iostream>

// 结点类
template<class T>
struct Node
{
   // 默认构造函数,初始化数据和指针为默认值
   Node()
    : _data(T())
    , _next(nullptr)
    , _prve(nullptr)
   {}

   // 带参构造函数,根据给定数据初始化结点
   Node(const T& data)
    : _data(data)
    , _next(nullptr)
    , _prve(nullptr)
   {}

   // 数据域,存储链栈中实际的元素值
   T _data;

   // 指针域,分别指向下一个结点和前一个结点
   Node<T>* _next;
   Node<T>* _prve;
};

// 链栈类
template<class T>
class MyStack 
{
    // 内部类型定义,简化代码中的类型书写
    typedef Node<T> _Node;

public:
    // 构造函数,初始化栈为空
    MyStack()
        : _first(nullptr)
    {
        _top = _first;
    }

    // 创建结点
    _Node* createNode(const T& data)
    {
        _Node* newnode = new _Node(data);

        if (newnode == nullptr)
        {
            exit(EXIT_FAILURE); // 如果内存分配失败,直接终止程序
        }

        return newnode;
    }

    // 压栈操作,将新元素添加到栈顶
    void push(const T& data)
    {
        if (_first == nullptr) // 当栈为空时
        {
            _first = new _Node(data); // 创建新节点作为栈的第一个元素和栈顶元素
            _top = _first;
        }
        else // 当栈非空时
        {
            _Node* newnode = createNode(data); // 创建新节点

            // 更新链表结构:将新节点的 _prve 指针指向当前栈顶节点
            newnode->_prve = _top;
            // 将当前栈顶节点的 _next 指针指向新节点
            _top->_next = newnode;

            // 更新栈顶指针,使新节点成为新的栈顶元素
            _top = newnode;
        }

        // 增加栈大小
        _size++;
    }

    // 出栈操作,删除栈顶元素并返回其值
    T pop()
    {
        T top = _top->_data; // 保存栈顶元素的值

        if (_top == _first) // 当栈只剩一个元素时
        {
            delete _top; // 删除栈顶元素
            _top = nullptr;
            _first = nullptr; // 清空栈
        }
        else // 当栈中有多个元素时
        {
            _Node* prve = _top->_prve; // 获取栈顶元素的前一个结点

            prve->_next = nullptr; // 断开与已删除节点的连接
            delete _top; // 删除栈顶元素

            _top = prve; // 更新栈顶指针
        }

        // 减小栈大小
        --_size;
        return top; // 返回栈顶元素的值
    }

    // 查看栈顶元素的值,不改变栈状态
    const T& top() const
    {
        return _top->_data;
    }

    // 判断栈是否为空
    bool empty() const
    {
        return _size == 0;
    }

    // 返回栈中元素数量
    size_t size() const
    {
        return _size;
    }

private:
    // 第一个结点,用于初始化栈
    _Node* _first = nullptr;

    // 栈顶指针,始终指向栈顶元素
    _Node* _top;

    // 当前元素数量,表示栈大小
    size_t _size = 0;
};

测试一下:

//#include"my_stack_sequence.h"
#include"my_stack_link.h"

int main()
{
    MyStack<int> mystack;

    mystack.push(23);
    mystack.push(2);
    mystack.push(20);
    mystack.push(11);
    mystack.push(20);

    std::cout<<"size of mystack: "<<mystack.size()<<std::endl;
   
    while(!mystack.empty())
    {
        std::cout<<mystack.pop()<<std::endl;
    }
    
    if(mystack.empty()==true)
    {
        std::cout<<"stack is empty"<<std::endl;
    }


    return 0;
}


在这里插入图片描述

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

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

相关文章

AI概念普及-LangChain

文章目录 概念产品架构核心特性核心组件使用场景其他资源开发支持结论Langchain详细介绍LangChain的具体实现原理LangChain如何与其他大型语言模型&#xff08;LLM&#xff09;集成&#xff0c;有哪些具体的接口或协议&#xff1f;LangChain的性能表现和优化策略有哪些&#xf…

由于找不到msvcr120.dll,无法继续执行代码的详细处理方法,教你快修复msvcr120.dll

DLL文件&#xff0c;全称动态链接库文件&#xff0c;在计算机系统中具有重要作用。其中&#xff0c;msvcr120.dll是一个常见的DLL文件&#xff0c;它关联了许多程序和应用的正常运行。本指南将深入解释 msvcr120.dll文件的功能&#xff0c;并阐述如果缺少该文件会引起什么样的问…

Banana Pi开源社区推出BPI-5202开发板,国产龙芯Loongson 2K1000LA

BPI-5202开发板&#xff0c;国产龙芯Loongson 2K1000LA BPI-5202作为单纯的嵌入式通用控制器软硬件开发平台&#xff0c;采用龙芯2K1000LA芯片设计&#xff0c;基本配置中有2个独立MAC以太网端口、2个RS485端口1个RS232端口2个CAN2.0端口&#xff0c;配置灵活&#xff0c;广泛适…

# ABAP SQL 字符串处理-CONCATCAST

经常我都要在ABAP的sql语句中对字符串进行处理&#xff0c;现在就总结一下可以用到的方法 文章目录 字符串处理拼接字段运行结果 填充字符串运行结果 截取字符串 SUBSTRING运行结果 CAST转换类型程序运行结果 CAST 转换成 DATS类型&#xff08;日期&#xff09; 字符串处理 在…

客户案例:金蝶云星空对接纷享销客

正文&#xff1a;某国内食品贸易类客户&#xff0c;目前内部使用了多套系统。金蝶云星空ERP&#xff0c;纷享销客&#xff0c;钉钉&#xff0c;旺店通等系统。金蝶云星空作企业的业务财务一体化管理&#xff0c;与专业CRM平台纷享销客的战略合作&#xff0c;在产品管理、客户关…

Java智慧工地可视化管理云平台源码 施工进度、施工质量

目录 1、基础数据管理 2、考勤管理 3、安全隐患管理 4、视频监控 5、塔吊监控 6、升降机监控 7、管理分析报表 8、移动端数据推送 9、数据接收管理 慧工地全套源码&#xff08;PC端&#xff0c;移动端&#xff0c;大屏端&#xff09; 智慧工地系统利用APP监管施工现场…

SQL注入利用学习 - 延时盲注

延时盲注原理 无法利用页面显示结果判断SQL注入是否执行成功&#xff0c;此时可以利用 SQL语句执行的延时 判断SQL是 否执行成功。 只要可以执行延时&#xff0c;那么就可以利用该注入技术。 sql时间类型的盲注本质是利用插入的SQL语句执行造成时间延迟&#xff0c;插入的SQ…

软件测试中完整的Web请求流程

在软件开发的过程中&#xff0c;测试是一个至关重要的环节。而在现代互联网应用中&#xff0c;Web请求是很常见的一个测试需求。本文将介绍Web请求的完整测试流程&#xff0c;帮助读者更好地理解软件测试的关键步骤。 一、测试准备阶段 在进行Web请求测试之前&#xff0c;测试…

IK分词器安装、配置、分词自定义、Rest使用、SpringBoot使用

文章目录 1. 概述2. 安装配置3. 自定义拆分文本4. 调用4.1 拆分规则4.2 Rest 调用4.3 SpringBoot 调用 1. 概述 IK分词器是ElasticSearch(es)的一个最最最有名插件&#xff0c;能够把一段中文或者别的语句划分成一个个的关键字&#xff0c;进而在搜索的时候对数据库中或者索引库…

姓名升序,若相同则按照年龄升序——集合的几种排序方式(有问必答版)

见者有缘&#xff0c;缘来好运。诚邀各位围观我的博客【CS_GUIDER】&#xff1a; 我的云服务器到期了&#xff0c;所以这里放两个部署在码云和 GitHub 的链接&#xff1a; https://wlei224.gitee.io &#xff08;Gitee托管&#xff0c;速度极快&#xff09; https://wl2o2o.git…

go work模块与go mod包管理是的注意事项

如下图所示目录结构 cmd中是服务的包&#xff0c;显然auth,dbtables,pkg都是为cmd服务的。 首先需要需要将auth,dbtables,pkg定义到go.work中&#xff0c;如下&#xff1a; 在这样在各个单独的go mod管理的模块就可以互相调用了。一般情况下这些都是IDE自动进行的&#xff0c;…

Go微服务: 服务限流原理, 负载均衡与API网关

微服务里面的限流 (uber/limit)概述 go 微服务保稳三剑客: 熔断&#xff0c;限流&#xff0c;负载均衡限流的作用 限制流量&#xff0c;在服务端生效 注意&#xff1a;熔断是客户端生效 保护后端服务 餐厅吃饭排队的问题&#xff0c;提供凳子&#xff0c;让等候&#xff0c;这就…

Leetcode 221. 最大正方形

心路历程&#xff1a; 这道题是一个动态规划题&#xff0c;但是其实递推关系很难想到&#xff0c;如下图所示&#xff1a; MDP建模&#xff1a; 状态&#xff1a;以i,j为右下角的正方形 动作候选集&#xff1a;这道题的动作候选集其实是是否选择其左上角邻接的三个位置&#x…

安达发|体育产业体育装备生产车间APS排产软件

在体育产业中&#xff0c;体育装备的生产是保障运动员成绩和安全的关键一环。随着市场需求的多样化和个性化&#xff0c;传统的生产排程方法已经难以满足现代体育装备生产的复杂性和灵活性。因此&#xff0c;应用高级排产软件&#xff08;APS&#xff09;进行生产计划和控制成为…

Docker仅需3步搭建免费私有化的AI搜索引擎-FreeAskInternet

简介 FreeAskInternet 是一个完全免费、私有且本地运行的搜索引擎&#xff0c;并使用 LLM 生成答案&#xff0c;无需 GPU。用户可以提出问题&#xff0c;系统会进行多引擎搜索&#xff0c;并将搜索结果合并到ChatGPT3.5 LLM中&#xff0c;并根据搜索结果生成答案。 什么是 Fr…

2024年A特种设备相关管理(电梯)证考试题库及A特种设备相关管理(电梯)试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年A特种设备相关管理&#xff08;电梯&#xff09;证考试题库及A特种设备相关管理&#xff08;电梯&#xff09;试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲…

深入理解神经网络学习率(定义、影响因素、常见调参方法、关键代码实现)

目录 什么是学习率&#xff1f; 有哪些影响因素&#xff1f; 常用调整方法&#xff1f; 博主介绍&#xff1a;✌专注于前后端、机器学习、人工智能应用领域开发的优质创作者、秉着互联网精神开源贡献精神&#xff0c;答疑解惑、坚持优质作品共享。本人是掘金/腾讯云/阿里云等平…

中科驭数:DPU是构建高效智算中心基础设施的必选项

4 月 15 日&#xff0c;在江苏省未来网络创新研究院、网络通信与安全紫金山实验室举办的“2024智算网络技术与应用创新峰会”上&#xff0c;中科驭数作为DPU算力基础设施领军企业&#xff0c;受邀出席本次峰会。中科驭数产品运营部副总经理曹辉先生在《基于DPU的高效智算中心算…

libcurl 简单使用

LibCurl是一个开源的免费的多协议数据传输开源库&#xff0c;该框架具备跨平台性&#xff0c;开源免费&#xff0c;并提供了包括HTTP、FTP、SMTP、POP3等协议的功能&#xff0c;使用libcurl可以方便地进行网络数据传输操作&#xff0c;如发送HTTP请求、下载文件、发送电子邮件等…

BackTrader 中文文档(十八)

原文&#xff1a;www.backtrader.com/ OCO 订单 原文&#xff1a;www.backtrader.com/blog/posts/2017-03-19-oco/oco/ 版本 1.9.34.116 添加了OCO&#xff08;又称一次取消其他&#xff09;到回测工具中。 注意 这只在回测中实现&#xff0c;尚未实现对实时经纪商的实现 注…