一文带你了解栈的基本概念以及栈的实现

✏️✏️✏️今天给大家分享一下栈的基本概念、线性栈的自定义实现,以及栈的应用题目。

清风的CSDN博客

😛😛😛希望我的文章能对你有所帮助,有不足的地方还请各位看官多多指教,大家一起学习交流!

动动你们发财的小手,点点关注点点赞!在此谢过啦!哈哈哈!😛😛😛

目录

一、关于栈(Stack)

1.1 栈的概念

1.2 栈的使用

 1.3 栈的模拟实现

1.3.1 栈的类定义

1.3.2 判断栈空或栈满

1.3.3 出栈

1.3.4 入栈

1.3.5 获取栈顶元素

1.3.6 获取栈中当前元素个数

二、栈的应用

2.1 后缀表达式求值

2.2 括号匹配

 2.3 最小栈

 2.4 栈的压入、弹出序列


一、关于栈(Stack)

1.1 栈的概念

一种特殊的线性表,其 只允许在固定的一端进行插入和删除元素操作 。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO( Last In First Out)原则。

 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶

 出栈:栈的删除操作叫做出栈。出数据在栈顶

 栈在现实生活中的例子:

 上面的两个例子都遵循后进先出的原则。

1.2 栈的使用

栈可以在库函数中直接调用,比如下面的代码:

public static void main(String[] args) {
Stack<Integer> s = new Stack();
     s.push(1);
     s.push(2);
     s.push(3);
     s.push(4);
     System.out.println(s.size()); // 获取栈中有效元素个数---> 4
     System.out.println(s.peek()); // 获取栈顶元素---> 4
     s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3
     System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为3
     if(s.empty()){
     System.out.println("栈空");
     }else{
         System.out.println(s.size());
     }
}

 1.3 栈的模拟实现

从上图中可以看到, Stack 继承了 Vector Vector ArrayList 类似,都是动态的顺序表,不同的是 Vector 是线程安全的。

 所以,我们就可以利用顺序表来实现栈。

1.3.1 栈的类定义

默认栈的大小为10,也可以通过构造函数自己定义栈的大小。

public class MyStack {
    private final int DEFALUT_CAPACITY=10;
    private int[] elem;
    private int usedSize;
    public MyStack(){
        elem=new int[DEFALUT_CAPACITY];
    }

    public MyStack(int size){
        int[] elem=new int[size];
    }
}

1.3.2 判断栈空或栈满

栈空:在栈的类定义中,我们定义了一个usedSize来表示当前栈中的元素个数,因此,判断栈是否为空,只需要判断usedSize是否为0即可。

 public boolean isEmpty(){
        return usedSize==0;
    }

栈满:如果当前栈中的元素个数和数组的长度相等,那么就判栈满。

  public boolean isFull(){
        return elem.length==usedSize;
    }

1.3.3 出栈

数组的最后一个元素便是栈顶的元素,返回这个元素即可。

    public int pop(){
        if(isEmpty()){
            System.out.println("栈空!");
            return -1;
            //或者抛自定义的异常
        }
        int old=elem[usedSize-1];
        usedSize--;
        //若是引用类型:>elem[usedSize]=null;
        return old;
    }

1.3.4 入栈

    public void push(int data){
        if(isFull()){
            elem= Arrays.copyOf(elem,elem.length*2);
        }
        elem[usedSize]=data;
        usedSize++;
    }

1.3.5 获取栈顶元素

    public int peak(){
        if(isEmpty()){
            System.out.println("栈空!");
            return -1;
        }
        return elem[usedSize-1];//获取栈顶元素
    }

1.3.6 获取栈中当前元素个数

  public int size(){
        return usedSize;
    }

二、栈的应用

2.1 后缀表达式求值

后缀表达式求值的基本思路是:>当遇到的字符串是数字时,把它压入栈中,而当遇到的字符串是操作符时,从栈中弹出两个元素做对应的运算,再把运算结果压入栈中。字符串遍历完成后,栈顶元素就是计算的结果。这里需要注意,当遇到操作符要执行出栈操作是,第一次出栈的元素是计算时的右操作数,第二次出栈的元素是计算时的左操作数。

比如下面的题目:

给你一个字符串数组 tokens ,表示一个根据后缀表达式表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

 根据上面的思路,我们写这个代码其实就非常简单了:>

      public int evalRPN(String[] tokens) {
        Stack<Integer> stack=new Stack<>();
        for (String x:tokens) {
            if(!isOperation(x)){
                //如果不是操作符,就把x转为数字并压栈
                stack.push(Integer.parseInt(x));
            }else{
                //弹出两个操作数,并做相应的运算
                int num2=stack.pop();
                int num1=stack.pop();
                switch (x){
                    case "+":
                        stack.push(num1+num2);
                        break;
                    case "-":
                        stack.push(num1-num2);
                        break;
                    case "*":
                        stack.push(num1*num2);
                        break;
                    case "/":
                        stack.push(num1/num2);
                        break;
                }
                
            }
        }
        int ret = stack.pop();
        return ret;
    }
    private boolean isOperation(String s){
        if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")){
            return true;
        }
        return false;
    }

2.2 括号匹配

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 每个右括号都有一个对应的相同类型的左括号。

思路:>

  • 遍历字符串,当遇到这三种左括号时,全部压入栈中。
  •  当遇到右括号时,如果当前栈空,直接返回false,因为这种情况是不可能匹配成功的。
  • 如果当前栈不空,先获取(不能直接出栈)栈顶元素,与当前的右括号进行匹配。
  • 若匹配成功,当前与之匹配的栈顶元素出栈,继续向后遍历。
  • 否则匹配不成功,返回false。
  • 当遍历完成后,只需判断当前栈是否为空,若为空,那肯定是匹配成功。
  • 若遍历完成后,当前栈非空,说明匹配失败,返回false。(说明左括号多)

下面是代码实现:>

       public boolean isValid(String s) {
        Stack<Character> stack=new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            char ch=s.charAt(i);
            if((ch=='(') || (ch=='[') || (ch=='{')){
                stack.push(ch);
            }else{
                if(!stack.empty()){
                    //如果栈不空
                    char ch2=stack.peek();//ch是右括号,ch2是左括号
                    if((ch2=='(' && ch==')') || (ch2=='{' && ch=='}') || (ch2=='[' && ch==']')){
                        //左括号出栈
                        stack.pop();
                    }else {
                        return false;
                    }
                }else {
                    return false;
                }
            }
        }
        if(!stack.empty()){
            //i已经遍历完成,栈还不为空,返回false
            return false;
        }
        return true;
    }

 2.3 最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。(这里指普通栈)
  • int top() 获取堆栈顶部的元素。(这里指普通栈)
  • int getMin() 获取堆栈中的最小元素。

思路:>

利用两个栈来同时进行相关的操作,需要定义一个普通栈(Stack),还需要定义一个存放最小元素的栈(MinStack)。

关于入栈:>

  • 普通栈无论如何是要进行入栈操作的,那么只需要考虑最小栈是否要进行入栈操作。
  • 最小栈存放的是最小元素,所以每次普通栈进行入栈的时候,需要把当前要进入普通栈的元素(val)和在最小栈里的栈顶元素进行比较,如果val小于等于最小栈中的栈顶元素,此时最小栈也是需要执行入栈操作的。
  • 需要注意的是,在普通栈进行第一次入栈操作的时候,最小栈也是需要入栈的,也就是说,当最小栈当前为空,直接入栈即可。若最小栈非空,才需要比较大小,让小的压入最小栈。

关于出栈:> 

  •  执行出栈操作时,为了确保在获取最小元素的时候不出错,同样需要把当前从普通栈弹出的元素和最小栈中的栈顶元素比较(因为要确保最小栈存放的是当前栈的最小值)。
  • 如果普通栈中弹出的元素比最小栈中的栈顶元素大,那么普通栈弹出元素并不会影响获取当前栈中的最小元素,直接出栈即可。
  • 当普通栈中弹出元素等于(不可能小于)最小栈的栈顶元素时,这两个栈要同时执行出栈操作。(因为如果此时最小栈不弹出,并不能更新普通栈弹出元素后,此时普通栈的最小值)

下面的具体的代码实现:>

class MinStack {
    private Stack<Integer> stack;
    private Stack<Integer> MinStack;
    
    
    public MinStack() {
         stack=new Stack<>();
         MinStack=new Stack<>();
    }
    
    public void push(int val) {
        stack.push(val);
        if(MinStack.empty()){
            MinStack.push(val);
        }else{
            int peekVal=MinStack.peek();
            if(val<=peekVal){
                MinStack.push(val);
            }
        }
    }
    
    public void pop() {
        /**
         * pop的时候和stack的栈顶元素比较,如果相等,全部出栈
         * 不一样,只出普通栈
         */
        int val=stack.pop();
        if(!MinStack.empty()){
            if(val==MinStack.peek()){
                MinStack.pop();
            }
        }
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        if(!MinStack.empty()){
            return MinStack.peek();
        }
        return -1;
    }
}

 2.4 栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。

  •  0<=pushV.length == popV.length <=1000
  • -1000<=pushV[i]<=1000 
  • pushV 的所有数字均不相同

 思路:>

  • 遍历入栈数组,同时遍历给定的弹出序列。
  • 每次将入栈数组中的元素入栈后,就和给定的弹出序列比较。
  • 若相等,那么直接将入栈的元素弹出。
  • 遍历结束后,若栈空,说明给定的序列可以成为该栈的弹出序列。否则,返回false。 

 下面是具体的实现代码:>

    public boolean IsPopOrder (int[] pushV, int[] popV) {
        // write code here
        Stack<Integer> stack = new Stack<>();
        int j = 0;
        for (int i = 0; i < pushV.length; i++) {
            stack.push(pushV[i]);
            while (!stack.empty() && j < popV.length && stack.peek() == popV[j]) {
                stack.pop();
                j++;
            }
        }
        if (stack.empty()) {
            return true;
        }
        return false;
    }

希望各位看官读完文章后,能够有所提升。

🎉好啦,今天的分享就到这里!!

✨创作不易,还希望各位大佬支持一下!

👍点赞,你的认可是我创作的动力!

⭐收藏,你的青睐是我努力的方向!

✏️评论:你的意见是我进步的财富!

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

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

相关文章

阿里云配置ECS实例的IPv6地址,开通公网IPv6

1.阿里云ECS服务器开通IPv6地址&#xff0c;开通公网IPv6 1.1.官网教程 配置ECS实例的IPv6地址 1.2.相关截图 1 2 3 4 5 6

ElasticSearch中常见的分词器介绍

文章目录 ElasticSearch中常见的分词器介绍前言分词器的作用如何指定分词器分词器的组成分词器的类型标准分词器空格分词器简单分词器关键词分词器停用词分词器IK分词器NGram分词器正则匹配分词器语言分词器自定义分词器 ElasticSearch中常见的分词器介绍 前言 ElasticSearch是…

抖音小程序开发:探索技术创新的代码之旅

随着抖音小程序的兴起&#xff0c;企业纷纷将目光投向这个充满活力的平台。抖音小程序开发不仅为品牌提供了更广泛的曝光机会&#xff0c;更是技术创新的舞台。本文将带领读者深入探索抖音小程序开发的技术要点&#xff0c;探讨如何通过代码实现个性化、高效的小程序。 1. 小…

【2】Gradle-快速入门使用【Gradle项目结构概念】

目录 【2】Gradle-快速入门使用【Gradle项目结构概念】安装本地安装先决条件 官网安装教程 Gradle 快速指南初始化项目查看Gradle的项目结构了解Gradle Wrapper调用Gradle包装器了解Gradle的项目结构了解settings文件了解构建脚本 IDEA中使用Gradle创建一个新项目创建一个Sprin…

【STM32】

STM32 1 CMSIS1.1 概述1.2 CMSIS 应用程序文件描述 2 库2.1 简介2.2 标准外设库&#xff08;standrd Peripheral Libraries&#xff09;2.3 HAL 库2.3.1 目录结构2.3.2 HAL库API函数和变量的命名规则2.3.3 HAL库对寄存器位操作的相关宏定义2.3.4 HAL库回调函数2.3.5 HAL使用注意…

6.存储器概述,主存储器

目录 一. 存储系统基本概念 &#xff08;1&#xff09;存储系统的层次结构 &#xff08;2&#xff09;分类 &#xff08;3&#xff09;存储器的性能指标 二. 主存储器的基本组成 三. SRAM和DRAM 四. 只读存储器ROM 五. 提升主存速度的方法 &#xff08;1&#xff09;双…

【tg】 5 :线程切换

manager 可以切到 其他类的其他线程去执行。线程切换 先通过 networkmgr 线程 执行 ,但是传递了Manager 自己的线程 进去。在networkmgr 的network线程中,获取到stats数据,然后扔给 manager的线程thread ,去posttask 还行这个task里调用了mediamanager 的perform ,在media…

U盘不可以访问的维护

u盘打不开&#xff0c;可按下图&#xff0c;设置&#xff1a;winR→gpedit.msc&#xff1b;配置“管理模板”→“系统”→“可移动存储访问”→“所有可移动存储类”。 然后&#xff0c;选择“未配置”&#xff0c;如下图

环形处理习题,举例:约瑟夫环,魔方阵

目录 约瑟夫环 魔方阵 约瑟夫环 题目描述&#xff1a;有n 个人围成一圈,顺序排号。从第1个人开始报数从1到3报数凡是报到3 的人退出圈子,问最后留下的是原来的第几号? 环形处理:依次遍历数据集的每个元素&#xff08;每个人依次报号&#xff09;&#xff0c;直到遍历到最后…

xlua游戏热更新(lua访问C#)

CS.UnityEngine静态方法访问unity虚拟机 创建游戏物体 CS.UnityEngine.GameObject(new by lua);静态属性 CS.UnityEngine.GameObject(new by lua); -- 创建 local camera CS.UnityEngine.GameObject.Find(Main Camera); --查找 camera.name Renamed by Lua;访问组件 loca…

思维模型 斯金纳箱原理

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。通过合理奖惩&#xff0c;塑造行为&#xff0c;此名为“学习”。 1 斯金纳箱原理的应用 1.1 斯金纳箱在游戏设计中的应用-《糖果传奇》 《糖果传奇》是一款由 King 开发的三消游戏&#x…

C语言--定义一个包含年月日的结构体Day,实现一个函数,根据传入的结构体指针计算,该日期是当年的第几天?

一.题目要求 输入2000年6月5日&#xff0c;输出&#xff1a;这是2000年的第157天。 二.思路分析 首先定义一个包含年月日的结构体 年份&#xff1a;要判断是否是闰年&#xff0c;闰年的二月有29天&#xff0c;平年的二月有28天。 月份&#xff1a;一个月份分大月和小月&#…

leetCode 493 翻转对

给定一个数组 nums &#xff0c;如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。你需要返回给定数组中的重要翻转对的数量。 未完待续~

IDEA的优化配置教程

前言 IDEA 全称 IntelliJ IDEA&#xff0c;是java编程语言开发的集成环境。IntelliJ在业界被公认为最好的java开发工具&#xff0c;尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以…

Win11专业版安装Docker Desktop,并支持映射主机的gpu

一、Windows环境下安装 Docker 必须满足: 1. 64位Windows 11 Pro(专业版和企业版都可以) 2. Microsoft Hyper-V,Hyper-V是微软的虚拟机,在win11上是自带的,我们只需要启动就可以了 二、下载Docker Desktop安装包 方式一:进入官网下载 https://docs.docker.com/desktop…

基于VSCode + PlatformIO创建运行第一个esp32程序

文章目录 使用VSCode创建项目安装驱动下载驱动安装驱动连接开发板电脑识别开发板 编写程序烧录程序第一步、编译程序第二步、烧录程序第三步、开发板观察效果 原理讲解项目源码 在之前的课程&#xff0c;我们已经介绍了ESP32单片机&#xff0c;并且也已经安装好了开发环境&…

matplotlib 创建图和子图

Matplotlib 可能是 Python 2D-绘图领域使用最广泛的套件。它能让使用者很轻松地将数据图形化&#xff0c;并且提供多样化的输出格式。这里将会探索 matplotlib 的常见用法。 plt方式是先生成了一个画布&#xff0c;然后在这个画布上隐式的生成一个画图区域来进行画图&#xff1…

yum工具的使用

yum工具的使用 rpm的弊端 前面我们讲了下rpm&#xff0c;那么rpm有什么弊端呢&#xff1f;其弊端是显而易见的&#xff0c;当用rpm安装软件时&#xff0c;若遇到有依赖关系的软件&#xff0c;必须先安装依赖的软件才能继续安装我们要安装的软件&#xff0c;当依赖关系很复杂的…

drawio连接线的样式设置

drawio是一款强大的图表绘制软件&#xff0c;支持在线云端版本以及windows, macOS, linux安装版。 如果想在线直接使用&#xff0c;则直接输入网址draw.io或者使用drawon(桌案), drawon.cn内部完整的集成了drawio的所有功能&#xff0c;并实现了云端存储&#xff0c;以及在线共…

ModStartBlog v8.5.0 评论开关布局调整,系统后台全面优化

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议。 功能特性 丰富的模块市场&#xff0c;后台一键快速安装 …
最新文章