【数据结构初阶】第五节.栈的详讲

news2025/5/19 23:54:47

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

前言

一、栈的基本认识

二、栈模拟实现: 

三、栈的实战演练

3.1 有效的括号

3.2 逆波兰表达式

3.3 栈的压入、弹出序列

总结


前言

上一节内容我们学习了链表的有关内容,今天我们将进行栈的学习;栈也是一种十分重要的数据结构;就让我们一起进入到今天的学习当中吧!!!!!!


提示:以下是本篇文章正文内容,下面案例可供参考

一、栈的基本认识

同链表和顺序表一样,栈也是用来存储逻辑关系为 "一对一" 数据线性数据结构,如图所示。

从图 中我们看到,栈存储结构与之前所学的链表和顺序表有所差异,这缘于栈对数据 "存" 和 "取" 的过程有特殊的要求: 

1.栈只能从表的一端存取数据,另一端是封闭的;
2.在栈中,无论是存数据还是取数据,都必须遵循"先进后出"的原则,即最先进栈的元素最后出栈。拿图中的栈来说,从图中数据的存储状态可判断出,元素 1 是最先进的栈。因此,当需要从栈中取出元素 1 时,根据"先进后出"的原则,需提前将元素 3 和元素 2 从栈中取出,然后才能成功取出元素 1。

因此,我们可以给栈下一个定义,即栈是一种只能从表的一端存取数据且遵循 "先进后出" 原则的线性存储结构。

通常,栈的开口端被称为栈顶;相应地,封口端被称为栈底。因此,栈顶元素指的就是距离栈顶最近的元素,同理,栈底元素指的是位于栈最底部的元素;

那么了解了栈的基本结构,在栈中我们如何实现元素的增加和删除呢? 

从上面的图我们不难发现,在栈中我们无论是放元素还是取元素,顺序都是从上到下,即都是从栈顶开始、到栈底结束,所以增加和删除的也是通过栈顶来实现的,即我们删除一个元素就是把当前栈顶的元素给挪开,让该元素下面的元素变成新的栈顶;我们增加一个元素就是让新增加的元素变成我们新的栈顶;

  1. 我们存放数据的过程就叫做入栈
  2. 我们取出数据的过程就叫做出栈

二、栈模拟实现: 

别看说了这么多其实栈的实现很简单,不信你看

public class MyStack {
    public int[] elem;
    public int usedSize;
    MyStack() { // 栈的初始化
        elem = new int[]{1, 2, 3, 4};
        usedSize = elem.length;
    }
    public void push(int val) { // 默认相当于是尾插, 来入栈
        elem[usedSize] = val;
        ++usedSize;
    }
    public void pop() { // 出栈
        if (empty()) {
            System.out.println("当前栈为空,你的操作不合法!");
            return;
        }
        --usedSize;
    }
    // 判断栈是否为空, 空则返回true,非空则返回false
    public boolean empty() {
        if (usedSize == 0) return true;
        return false;
    }
    // 获取栈中元素的个数
    public int size() {
        return usedSize;
    }
    // 顺序打印当前栈中的所有元素
    public void printMyStack(){
        for (int i = 0; i < usedSize; i++) {
            System.out.print(elem[i] + " ");
        }
        System.out.println();
    }
}

运行结果如图所示 :

三、栈的实战演练

3.1 有效的括号

题目链接 

有效的括号——力扣

本题的思路:

只要所给字符串的左右两边是对称的——就合法;

即字符串中左右括号的个数是相当的,注意这里所说的左右括号各有三种类型——"{}、[]、()"

我们不妨用栈来储存左括号,借助栈来看看左右括号的个数是否相等;

再对该字符串进行遍历的过程,如果我们的右边的元素和储存在栈中的左括号相对于,则把当前栈中的所匹配的元素出栈,继续对字符串遍历;

🍑当字符串遍历完后,如果栈中的元素刚好为空,则说明该字符串有效;

🍑如果字符串遍历完后,栈中依然有元素,说明左括号多;

🍑如果字符串还没遍历完,栈就为空了,说明右括号多;

 代码分析:
 

class Solution {
    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()) return false; // 当然如果右括号所对应的右边元素为空,自然是不匹配
                // 注意此时我们并没有取出栈中的元素(因为还不确定这对括号是否匹配),只是显示当前的堆栈元素
                char top = stack.peek();
                if ( (top == '{' && ch == '}') || (top == '[' && ch == ']') || (top == '(' && ch == ')') ) {
                    stack.pop();
                }
                else{
                    return false; // 说明此时右边的元素和栈中所存放的左边的元素不匹配
                }
            }
        }
        if (!stack.empty()) {
            return false;
        }
        return true;
    }
}


3.2 逆波兰表达式

 原题链接

对于这个题目,首先我们需要先补充一点知识;

中缀表达式转后缀表达式

比如(1 +(2 * 3))+ ((4 * 5) + 6)*  7)这样的中缀表达式要变成像123*+45*6+7*+这样的后缀表达式。

🍑步骤如下:

1、首先将中缀表达式的各个括号对都用不同的颜色清晰的标出来,像这样:
 

 2、然后把运算符符移动到相邻的最近的括号对的后边,(先移动里面的运算符,再移动外面的运算符,然后从前往后的进行移动)

比如对于(1 +(2 * 3))就是先把*号移动——》(1 +(23)*)

然后再是+号——》(1(23)*)+  

3、 最后再把括号去掉才变成了123*+45*6+7*+这种形式

总结:

1)先按照运算符的优先级对中缀表达式加括号,变成( ( a+(b*c) ) + ( ((d*e)+f) *g ) )

2)将运算符移到括号的后面,变成((a(bc)*)+(((de)*f)+g)*)+

3)去掉括号,得到abc*+de*f+g*+


后缀表达式求值:

那么如何像这种后缀表达式123*+45*6+7*+如何进行求值你呢?这就要借助我们的栈了。

1.建立一个空栈 stack。
2.遍历后缀表达式(此后将遍历到的字符称为 s)。
3.当 s 为数字时,将其压入 stack 栈。
4.当 s 为运算符时,弹出 stack 栈顶和次栈顶的两个数字,并将两个数字按照运算符 s 进行运算,然后将运算结果压入stack 栈 。
5.当遍历结束时,存留在栈 stack 中还剩下唯一一个数字,该数字便是运算结果。

下面用例子来解释一下:

举例说明:

假定待求值的后缀表达式为:6  5  2  3  + 8 * + 3  +  *,则其求值过程如下:

1)遍历表达式,遇到的数字首先放入栈中,此时栈如下所示:

 2)然后遇到了  +  这个运算符,那么就把当前的栈顶和次栈顶取出来,进行加法运算,但要注意运算时候次栈顶再前面,栈顶再后面 ,即次栈顶+栈顶的顺序来进行运算(其他的减乘除也都是按照次栈顶、栈顶这样的前后位置来进行运算的) 

3)再把刚才的运算结果5放到栈中,成为新的栈顶;

4)然后又继续向后走,把接下来的数字8入栈,成为栈顶  ;

5)遇到  * 号后,取出栈顶和次栈顶进行 * 的运算——》5,把新的运算结果作为新的栈顶放到栈中 ;

6)这样当遍历结束时,存留在栈 stack 中还剩下唯一一个数字,该数字便是运算结果;

代码分析:

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack();
        for (int i = 0; i < tokens.length; ++i) {
            String s = tokens[i]; 
            if (charge(s)) { // 如果是运算符,则进行运算操作
                int num1 = stack.pop();
                int num2 = stack.pop();
                switch(s) { // 对从栈顶和次栈顶取出的元素进行运算
                    case "+":
                        stack.push(num2 + num1); // 把运算的结果放回栈中
                        break;
                    case "-":
                        stack.push(num2 - num1);
                        break;
                    case "*":
                        stack.push(num2 * num1);
                        break;
                    case "/":
                        stack.push(num2 / num1);
                        break;        
                }
            }
            else { // 不是运算符,就存在栈中
                stack.push(Integer.parseInt(s)); // 因为我们的栈中储存的是字符,所以要用Integer中的parseInt进行类型转换
            }
        }
        return(stack.pop()); // 栈中最后一个元素给出栈
        
    }
    public boolean charge(String str) { // 判断当前字符串是否是有效的运算符
        if (str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/")) {
            return true;
        }
        return false;
    }
}


3.3 栈的压入、弹出序列

原题链接:

栈的压入和弹出——牛客 

详细的题解和思路可以看看这篇文章:

转载:栈的压入和弹出题解——牛客  

代码解析:
 

import java.util.*;
 
public class Solution {
    public static boolean IsPopOrder(int [] pushA,int [] popA) {
        int j = 0;
        // 用到了一个辅助栈
        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < pushA.length; i++) {
            stack.push(pushA[i]); // 把
            while (!stack.empty() && j < popA.length && stack.peek() == popA[j]) {
                stack.pop();
                ++j;
            }
        }
        if (stack.empty()) {
            return true;
        }
        return false;
    }
    
 
}


总结

今天的内容就介绍到这里,我们一下节内容再见!!!

 

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

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

相关文章

【产品设计】登录功能设计逻辑解析

登录功能是每个产品的基础功能&#xff0c;用户已经习以为常了&#xff0c;但对于产品经理来说&#xff0c;这是打开用户通往产品世界大门的“钥匙”&#xff0c;需要好好设计。 在用户看来&#xff0c;登录像是一个一次性的功能&#xff0c;很多 APP 在手机上登录过一次之后&a…

Linux学习记录——십구 构建进程间通信的信道方案

文章目录1、进程间通信介绍1、目的2、发展2、管道1、原理2、简单模拟实现3、总结3、匿名管道——控制进程4、命名管道1、原理2、模拟实现1、进程间通信介绍 之前所学都是单个进程&#xff0c;多个进程之间如何运转&#xff1f; 1、目的 数据传输&#xff1a;一个进程需要将它…

SpringSecurity之基本原理——过滤器链

前言 前面我们讲解了入门案例&#xff0c;很多小伙伴看完之后&#xff0c;应该也不知道他是如何实现的拦截。接下来&#xff0c;我们看一下SpringSecurity的基本原理是什么&#xff1f; 本质 其实&#xff0c;SpringSecurity的本质上就是一个过滤器链。在启动时&#xff0c;…

我的面试八股(JVM篇)

谈一谈Java内存区域和Java内存模型的理解&#xff1f; / Java内存区域和Java内存模型是一个东西吗&#xff1f; Java内存区域和Java内存模型不是一个东西&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; Java内存区域&#xff0c;也就是Java运行时数据区域。是…

Typora自定义主题分享 (Mac风、图片立体感...)

MarkDown 主题分享 文章目录MarkDown 主题分享Ligth-浅色主题主题效果展示安装方式Dark-深色主题主题效果展示安装方式关键字&#xff1a;Typora 、Mac、图片阴影、代码样式、表格 Ligth-浅色主题 主题效果展示 安装方式 下载 Typora 官网 Mo主题 下载地址将Mo.css样式修改为…

Docker容器部署

Docker容器1.Docker概念1.1.什么是Docker1.1.1.应用部署的环境问题1.1.2.Docker解决依赖兼容问题1.1.3.Docker解决操作系统环境差异1.1.4.小结1.2.Docker和虚拟机的区别1.3.Docker架构1.3.1.镜像和容器1.3.2.DockerHub1.3.3.Docker架构1.3.4.小结1.4.安装Docker2.Docker的基本操…

【无人机】采用最基本的自由空间路损模型并且不考虑小尺度衰落(多径多普勒)固定翼无人机轨迹规划(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

每日刷题记录(十四)

目录第一题&#xff1a;子集解题思路&#xff1a;代码实现&#xff1a;第二题&#xff1a;组合解题思路&#xff1a;代码实现&#xff1a;第三题&#xff1a;全排列解题思路&#xff1a;代码实现&#xff1a;第四题&#xff1a;全排列II解题思路&#xff1a;代码实现&#xff1…

2023年4月传统行业产品经理需要考NPDP吗?含金量高吗?

产品经理国际资格认证NPDP是新产品开发方面的认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年&#xff0c;是…

Linux内存管理(七):fixmap详解

源码基于:Linux 5.4 约定: 芯片架构:ARM64 CONFIG_ARM64_VA_BITS:39 CONFIG_ARM64_PAGE_SHIFT:12 0. 前言 内核启动首先会进入汇编阶段,mmu已经启动 (也就是说,当前SOC只能使用虚拟地址访问RAM),paging_init还没有完成调用,在内核启动过程需要访问某些特定的内核模…

SQL综合查询上

目录1、查询输出“高等数学”课程成绩前三名&#xff08;不考虑成绩有重复值的情况&#xff09;的学生的学号&#xff0c;姓名&#xff0c;课程名&#xff0c;系名&#xff0c;成绩。题目代码2、统计各门课程的重修人数&#xff08;包括grade为NULL&#xff09;&#xff0c;要求…

体验了一把ChatGPT4

不得不说ChatGPT对我的学习效率有极大的提升&#xff0c;它就像一位老师&#xff0c;不管有什么问题&#xff0c;都可以得到很好的答案。但是前段时间gpt3.5账号被封了&#xff0c;最近搞了个gpt4。市面上目前好像没啥可以白嫖的账号&#xff0c;基本都是免费使用几次&#xff…

C++11新特性有效总结

目录 语言可用性加强 (读现代C教程有感) nullptr constexpr if/switch 申明强化 &#xff08;C17开始&#xff09; 初始化参数列表 范围for迭代 两种类型推导方式 变长参数模板 SmartPointer Lambda 多线程 (并发与并行) 并发与并行的概念 C11中的并发并行 软件…

基于单片机的温室大棚环境监测系统设计

温室大棚对北方反季节蔬菜的种植具有重要意义。据了解全国各地温室大棚使用集中&#xff0c;但是大棚环境调控方式落后、管理落后、生产效率比较低。针对此问题本文提出了一种基于STM32单片机智能温室大棚控制系统方案&#xff0c;实现环境参数的自动检测&#xff0c;以达到智能…

C语言从入门到精通第2天(深度解析C语言数据类型及取值范围)

C语言基本数据类型及取值范围数据存储概述基本数据类型整型数的二进制表示浮点型数的二进制表示取值范围数据存储概述 C语言的变量有着不同的数据类型&#xff0c;每种数据类型的取值空间都是不同的&#xff0c;因此&#xff0c;不同数据类型的变量&#xff0c;其取值空间也不…

利用注解和反射解决代码冗余问题(改进版)

在优化代码的时候发现&#xff0c;传参存在着高度冗余&#xff0c;如果后面需要改参数&#xff0c;很不方便。 String pam1 "id" appKey "&sign" sign "&method" method "&access_token" token "&times…

光隔离器的工作原理及其应用

光隔离器也称为光隔离器或光耦合器&#xff0c;它是一种通过使用光将电信号或电压从一个电路传输到另一个电路的装置&#xff0c;同时它将两个电路彼此隔离。它可以通过隔离过压信号来防止高电压或快速变化的电压损坏组件。光隔离器可以承受高达10KV的输入至输出电压和高达10KV…

html+css+JavaScript+json+servlet的社区系统(手把手教学)

目录 课前导读&#xff1a; 一、系统前期准备 二、前端代码的编写 三、登陆页面简介 四、注册页面 五、社区列表页 六、社区详情页 七、社区发帖页 八、注销 九、访问链接 登陆页面http://175.178.20.77:8080/java106_blog_system/login.html 总结&#xff1a; 课前…

HTML5 <embed> 标签、HTML5 <figcaption> 标签

HTML5 <embed> 标签 实例 被嵌入的 flash 动画片&#xff1a; <embed src"helloworld.swf">尝试一下 浏览器支持 注意: 大多数现代浏览器已经弃用并取消了对浏览器插件的支持&#xff0c;所以如果您希望您的网站可以在普通用户的浏览器上运行&#xf…

【SpringCloud系列】开发环境下重写Loadbalancer实现自定义负载均衡

前言 spring-cloud-starter-netflix-ribbon已经不再更新了&#xff0c;最新版本是2.2.10.RELEASE&#xff0c;最后更新时间是2021年11月18日&#xff0c;详细信息可以看maven官方仓库&#xff1a;https://search.maven.org/artifact/org.springframework.cloud/spring-cloud-st…