后入能先出,一文搞懂栈

目录

    • 什么是栈
    • 数组实现
    • 链表实现
    • 栈能这么玩
    • 总结

什么是栈

栈在我们日常编码中遇到的非常多,很多人对栈的接触可能仅仅局限在 递归使用的栈 和 StackOverflowException,栈是一种后进先出的数据结构(可以想象生化金字塔的牢房和生化角斗场的狗洞)。

image-20231102234445811

栈(stack)是一种运算受限的线性数据结构,它具有以下特点:

1. 运算受限: 栈限定仅在表尾进行插入和删除操作,这一端被称为栈顶,而另一端称为栈底。这限制了对栈的操作,只能按照后进先出(LIFO,Last-In-First-Out)的原则进行插入和删除操作。插入操作又称为进栈、入栈或压栈,它将新元素放到栈顶,使之成为新的栈顶元素;删除操作又称为出栈或退栈,它将栈顶元素删除,使其相邻的元素成为新的栈顶元素。

2. 线性表: 栈也是一种线性表,它表示数据元素之间的逻辑关系是线性的。虽然具体实现可以使用数组或链表等不同的物理存储结构,但逻辑上各个元素之间是相邻的,操作也是按照顺序进行的。

3. 栈顶和栈底: 栈的逻辑结构中有栈顶和栈底的概念。栈顶表示可以进行插入和删除操作的一端,通常与数组的末尾或链表的头部有关。栈底则是相对的另一端,用于限制操作的另一端。

4. 栈的应用: 栈在计算机科学和编程中有广泛的应用,例如程序执行调用堆栈、四则运算表达式求值、非递归算法实现、括号匹配问题、浏览器历史、内存分配、任务管理等的解决。掌握栈是非常重要的,它是必须了解的数据结构之一。

栈可以使用数组或链表来实现,选择合适的实现方式取决于具体的应用场景和性能需求。数组实现的栈通常更适合于需要固定大小的栈(当然也可以进行扩容),而链表实现的栈可以动态扩展,适用于不确定大小的栈。在栈的操作中,栈顶元素是非常关键的,因为它在插入和删除操作中起着重要作用。

总之,栈是一个非常有用的数据结构,它在计算机科学中扮演着重要的角色,了解它的特性和应用对于编程和算法设计至关重要。

对于一个栈的接口,我们简易定义如下:

public interface Stack<T> {void push(T item);      // 压栈T pop();               // 弹栈T peek();              // 获取栈顶元素boolean isEmpty();     // 判断栈是否为空int size();            // 返回栈的大小
}

数组实现

数组实现的栈用的比较多,我们经常刷题也会用数组去实现一个简单的栈去解决简单的问题。

结构设计

对于数组来说,我们模拟栈的过程很简单,因为栈是后进先出,我们很容易在数组的末尾进行插入和删除。所以我们选定末尾为栈顶。所以对于一个栈所需要的基础元素是 一个array[]数组和一个size表示大小,还需要一个负载因子表示数组的大小。

push入栈操作

  • 如果数组满了,需要扩容
  • size位置赋值, array[size++] = data;

image-20231105213018107

pop弹出栈并返回首位

  • 如果栈不为空,可以弹出。return array[--size];

如下图,当栈中还剩1,2,3,4执行pop操作,栈顶变为3的位置并且返回4

image-20231105213649409

peek返回栈顶

  • peek操作时返回栈顶不弹出,所以栈不为空时候return data[size-1]即可。

数组实现:

import java.util.EmptyStackException;public class SeqStack<T> implements Stack<T> {private T array[];private int size;private static final int DEFAULT_CAPACITY = 10;public SeqStack() {this.size = 0;array = (T[]) new Object[DEFAULT_CAPACITY];}@Overridepublic void push(T data) {if (size == array.length) {// 如果数组已满,扩展数组resizeArray();}array[size++] = data;}@Overridepublic T pop() {if (isEmpty()) {throw new EmptyStackException();}// 下面可以写成 return array[--size];T data = array[size - 1];size--;return data;}@Overridepublic T peek() {if (isEmpty()) {throw new EmptyStackException();}return array[size - 1];}@Overridepublic boolean isEmpty() {return size == 0;}@Overridepublic int size() {return size;}private void resizeArray() {int newCapacity = (int) (array.length * 2);T[] newArray = (T[]) new Object[newCapacity];for (int i = 0; i < size; i++) {newArray[i] = array[i];}array = newArray;}
}

链表实现

栈可以使用数组或链表来实现,两种思路如下:

  1. 链表尾部作为栈顶: 在数组实现中,栈的操作是在尾部进行插入和删除。链表中即使使用尾指针可以提高尾部插入效率,但删除操作仍然需要查找前驱节点。要实现高效的删除操作,需要使用双向链表,这增加了整个结构的复杂性。
  2. 链表头部作为栈顶: 在这种实现中,栈的设计不带头节点的单链表(不需要哑结点),所有操作都在链表的头部进行。头部插入删除都很方便效率比较高,编写代码也很简单。

基础结构

public class LinkedStack<T> implements Stack<T> {private Node<T> top;private int size;public LinkedStack() {top = null;size = 0;}private static class Node<T> {T data;Node<T> next;public Node(T data) {this.data = data;this.next = null;}}//其他方法
}

push入栈

与不带头结点单链表头插入一致

  • 创建新节点
  • 新节点的next指向栈顶节点top
  • 栈顶节点top指向新节点,表示这个节点为新的栈顶节点
  • size++

部分操作流程如下图

image-20231105221137828

pop弹出

与不带头结点单链表头插入一致

  • 判断是否为空
  • 记录头结点top的值data
  • 头结点top指向top.next
  • size–,返回前面记录的值data

部分操作流程如下图

image-20231105222129654

peek返回栈顶

不为空的时候返回 top.data即可

链表实现:

import java.util.EmptyStackException;public class LinkedStack<T> implements Stack<T> {private Node<T> top;private int size;public LinkedStack() {top = null;size = 0;}private static class Node<T> {T data;Node<T> next;public Node(T data) {this.data = data;this.next = null;}}@Overridepublic void push(T item) {Node<T> newNode = new Node<>(item);newNode.next = top;top = newNode;size++;}@Overridepublic T pop() {if (isEmpty()) {throw new EmptyStackException();}T data = top.data;top = top.next;size--;return data;}@Overridepublic T peek() {if (isEmpty()) {throw new EmptyStackException();}return top.data;}@Overridepublic boolean isEmpty() {return size == 0;}@Overridepublic int size() {return size;}
}

栈能这么玩

既然上面详细讲解设计栈,这里来两道栈非常经典非常经典的例题(非常高频,很容易忘,又很重要,普通问题就不放的)

力扣20有效的括号:

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

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

示例 :

输入: "()[]{}"
输出: true

示例 :

输入: "([)]"
输出: false

分析:
括号类的问题是经典栈类问题,肯定要想到用栈处理。判断一个字符串满不满足一个有效的字符串,就要看它是不是都能组成对。

从单个括号对来说,((,))都是不满足的,只有()才可满足,即一左一右。

从多个括号对来说 {[(字符串还可接受任意无限([,{的括号。但是如果向左的括号只能先接收)括号(变成{[)。

从上面可以看作一种相消除的思想。例如(({[()()]}))字符串遍历时候可以这样处理:

  • (({[(下一个)消掉成(({[
  • (({[(下一个)消掉成(({[
  • (({[下一个]消掉成(({
  • (({下一个}消掉成((
  • ((下一个)消掉成(
  • (下一个)消掉成 这样就满足题意

每次操作的时候都判断剩余有效括号最顶部那个括号是否能够和遍历的相消除,这个过程利用栈判断当前是加入栈还是消除顶部,到最后如果栈为空说明满足,否则不满足,当然具体括号要对应,具体实现代码为:

public boolean isValid(String s) {Stack<Character> stack = new LinkedStack<Character>();for (int i = 0; i < s.length(); i++) {char te = s.charAt(i);if (te == ']') {if (!stack.isEmpty() && stack.pop() == '[')continue;else {return false;}} else if (te == '}') {if (!stack.isEmpty() && stack.pop() == '{')continue;else {return false;}} else if (te == ')') {if (!stack.isEmpty() && stack.pop() == '(') {continue;} else {return false;}} else {stack.push(te);}}return stack.isEmpty();
}

当然,JDK自带的栈用起来不快,可以用数组优化:

public boolean isValid(String s) {char a[] = new char[s.length()];int index = -1;for (int i = 0; i < s.length(); i++) {char te = s.charAt(i);if (te == ']') {if (index >= 0 && a[index] == '[')index--;else {return false;}} else if (te == '}') {if (index >= 0 && a[index] == '{')index--;else {return false;}} else if (te == ')') {if (index >= 0 && a[index] == '(')index--;else {return false;}} else {a[++index] = te;}}return index == -1;
}

力扣32最长有效括号(困难)

题目描述:给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。

示例 :

输入: “(()”
输出: 2
解释: 最长有效括号子串为 “()”

示例 :

输入: “)()())”
输出: 4
解释: 最长有效括号子串为 “()()”

方案一暴力

这种题核心思想就是使用栈模拟。本题的话更简单一点因为只有()两种括号,使用暴力的时候就可以循环每次找到最长的有效括号。而括号匹配的时候可以直接终止的情况是)右括号多出无法匹配。

例如())(到第三个不可能和前面相连。如果来(只需要期待后面能够来),一个)可以和一个(组成一对,消除栈中的一个(

当然,在具体的实现上,我们用数组模拟栈,实现代码为:

public int longestValidParentheses(String s) {char str[] = s.toCharArray();//字符数组int max = 0;for (int i = 0; i < str.length - 1; i++) {int index = -1;if (max >= str.length - i)break;for (int j = i; j < str.length; j++) {if (str[j] == '(') {index++;} else {if (index < 0) {i = j;break;} else {index--;}}if (index == -1 && (j - i + 1 > max)) {max = j - i + 1;}}}return max;
}

这个复杂度太高,我们看看如何用栈优化。

方案二栈优化

如何将这道题从一个O(n^2)的时间复杂度优化到O(n)?这其实非常简单,只需要注意处理的过程。让我们首先考虑一些可能的最大情况。

  • ( ) ) ( ) ( ( ) ( ) ) 最大为后面部分(空格分开)
  • ( ) ( ) ( ( ( ) 最大为前面部分
  • ( ( ( ( ( ( ) ( ) ( ) ( ) 最大为后面部分

在处理这道题时,我们会注意到不同类型的括号可能会有一些区别:
(:左括号一旦出现那么他就期待一个)进行匹配,但它的后面可能有)并且在这中间有很多其他括号对。
):右扩号有两种情况:

  • 一种是当前已经超过左括号前面已经不可能连续了。例如( ) ) ( )第三个括号出现已经使得整个串串不可能连续,最大要么在其左面要么再其右面。 你可以理解其为一种清零初始机制。
  • 另一种情况)就是目标栈中存在(可与其进行匹配。匹配之后要叠加到消除后平级的数量上,并且判断是否是最大值。(下面会解释)

具体实现的思路上,就是使用一个int数组标记当前层级(栈深)有正确的括号数量。 模拟一次栈行为从左向右,遇到)太多(当前栈中不存在(进行匹配)就将数据清零重新开始。这样一直到最后。你可以把它看成台接,遇到(就上一个台阶并清零该新台阶,遇到)就下一个台阶并且把数量加到下降后的台阶上。具体可以看下面图片模拟的过程:
( ) ( ( ) ( ) ( ( ) ) )

image-20231105224429516

具体实现代码为:

public static int longestValidParentheses(String s) {int max = 0;int value[] = new int[s.length() + 1];int index = 0;for (int i = 0; i < s.length(); i++) {if (s.charAt(i) == '(') {index++;value[index] = 0;} else {//")"if (index == 0) {value[0] = 0;} else {value[index - 1] += value[index--] + 2;//叠加if (value[index] > max)//更新max = value[index];}}}return max;
}

用栈也可以实现,但是效率比数组略低:

public int longestValidParentheses(String s) {int maxans = 0;Stack<Integer> stack = new Stack<>();stack.push(-1);for (int i = 0; i < s.length(); i++) {if (s.charAt(i) == '(') {//(将当前的 stack.push(i);} else {stack.pop();if (stack.empty()) {stack.push(i);} else {//i-stack.peek就是i是出现的总个数 peek是还没匹配的个数maxans = Math.max(maxans, i - stack.peek());}}}return maxans;
}

总结

到这里,本文对栈的介绍就结束了,相信你可以手写个栈并且可以小试牛刀解决括号匹配问题!当然栈能解决的问题还有很多比如接雨水问题、二叉树非递归遍历等等,有些重要的还会再总结。

系列仓库地址:https://github.com/javasmall/bigsai-algorithm
csdn专栏:数据结构与算法专栏

写一篇原创不易,还请点赞、收藏、关注三连支持一下!

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

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

相关文章

洛谷P5731 【深基5.习6】蛇形方阵java版题解

import java.util.Arrays; import java.util.Scanner;// 给出一个不大于9的正整数n&#xff0c;输出nn的蛇形方阵。 public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt();int[][] a new int[n][n];int total…

MySQL中表格的自我复制,与复制表格

先创建一个空表&#xff0c;my_tab01 CREATE TABLE my_tab01(id INT ,name VARCHAR(32),sal DOUBLE,job VARCHAR(32),deptno INT); SELECT * FROM my_tab01;准备一张有数据的表格&#xff1a; 将另一张表格的数据插入到my_tab01的表格中&#xff1a; -- 演示如何自我复制 --…

前端项目导入vue和element

1.安装nodejs 下载链接https://cdn.npmmirror.com/binaries/node/v18.18.0/node-v18.18.0-x64.msi 进入cmd 命令行模式 管理员身份运行 输入 &#xff08;node -v&#xff09;能看到版本号 npm config set prefix "C:\Program Files\nodejs" 默认路径 npm config…

刚柔相济铸伟业 ——访湖南顺新金属制品科技有限公司董事长张顺新

时代在变&#xff0c;唯初心不改。 精致、谦虚、谨慎、儒雅、温和——他就是张顺新&#xff0c;湖南顺新金属制品科技有限公司、湖南顺新供应链管理有限公司董事长&#xff0c;民建长沙市委常委&#xff0c;民建湖南省环资委副主任&#xff0c;省、市民建企联会常务副会长&…

基于SSM+Vue的随心淘网管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

官方Redis视图化工具Redisinsight

一、下载最新版本的 docker pull redislabs/redisinsight mkdir /data/redisinsight docker run -d -u root -p 8001:8001 -v /etc/localtime:/etc/localtime -v /data/redisinsight:/db --restartunless-stopped redislabs/redisinsight:latest 二、浏览器打开 http://192…

自动化测试(Java+eclipse)教程

webdriver环境配置 1.下载chromedriver到本地&#xff08;一定要选择和自己浏览器相对应的版本chromedriver下载地址&#xff09; 2.加入到环境变量path中 webdriver工作原理 创建web自动化测试脚本 1.Maven项目创建 File->New->project->(搜索maven)选择maven pr…

爱家房产网站源码 爱家房产网商业版 微信互动营销整合+手机触屏版+经纪人分销

房产网站源码手机访问自动转手机版修改修复如下&#xff1a; 1&#xff0c;修复手机版首页标题头部名称 2&#xff0c;修复手机版首页频道导航按钮 3&#xff0c;新增手机版广告位置显示方式 4&#xff0c;修复手机版首页内容显示样式 5&#xff0c;手机版头部背景颜色ic…

什么是观察者模式?用 Python 如何实现 Observer(观察者或发布订阅)对象行为型模式?

什么是观察者模式&#xff1f; 观察者模式&#xff08;Observer pattern&#xff09;是一种行为型设计模式&#xff0c;它允许对象之间建立一种一对多的依赖关系&#xff0c;当一个对象的状态发生变化时&#xff0c;其相关依赖对象都会得到通知并自动更新。 在观察者模式中&am…

三天打鱼两天晒网

文章目录 前言一、题目描述 二、题目分析 三、解题 程序运行代码 前言 本系列为选择结构编程题&#xff0c;点滴成长&#xff0c;一起逆袭。 一、题目描述 二、题目分析 三、解题 程序运行代码 #include<stdio.h> int main(){int n;scanf("%d",&n);i…

Go 接口:Go中最强大的魔法,接口应用模式或惯例介绍

Go 接口&#xff1a;Go中最强大的魔法,接口应用模式或惯例介绍 文章目录 Go 接口&#xff1a;Go中最强大的魔法,接口应用模式或惯例介绍一、前置原则二、一切皆组合2.1 一切皆组合2.2 垂直组合2.2.1 第一种&#xff1a;通过嵌入接口构建接口2.2.2 第二种&#xff1a;通过嵌入接…

【应用前沿】360QPaaS 精彩亮相首届中国航空制造设备博览会 | 数智航空

近日&#xff0c;首届“中国航空制造设备博览会”&#xff08;CAEE2023&#xff09;在宁波国际会展中心顺利召开&#xff0c;本届大会以“数智产融 开放发展”为主题&#xff0c;以“新技术、新产品、新服务、新企业”为定位&#xff0c;以特色化、专业化、品牌化、高端化为方向…

AVL树详解

目录 AVL树的概念 旋转的介绍 单旋转 双旋转 旋转演示 具体实现 通过高度判断的实现 通过平衡因子判断的实现 AVL树的概念 AVL树是一种自平衡的平衡二叉查找树&#xff0c;它是一种高效的数据结构&#xff0c;可以在插入和删除节点时保持树的平衡&#xff0c;从而保证…

在HTML单页面中,使用Bootstrap框架的多选框如何提交数据

1.引入Bootstrap CSS和JavaScript文件&#xff1a;确保在HTML页面的标签内引入Bootstrap的CSS和JavaScript文件。可以使用CDN链接或者下载本地文件。 <link rel"stylesheet" href"https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css&q…

04【保姆级】-GO语言指针

之前我学过C、Java、Python语言时总结的经验&#xff1a; 先建立整体框架&#xff0c;然后再去抠细节。先Know how&#xff0c;然后know why。先做出来&#xff0c;然后再去一点点研究&#xff0c;才会事半功倍。适当的囫囵吞枣。因为死抠某个知识点很浪费时间的。对于GO语言&a…

16.字符连接

#include<stdio.h> #include <cstring> int main(){char s1[44];char s2[33];scanf("%s",s1);scanf("%s",s2);strcat(s1,s2) ;printf("连接两个字符为&#xff1a;%s ",s1); return 0;}

HashMap源码分析(一)

存储结构 说明&#xff1a;本次讲解的HashMap是jdk1.8中的实现&#xff0c;其他版本可能有差异 内部是由Node节点数组组成&#xff0c;Node节点之间又由链表或红黑树组成。 图是网上找的&#xff0c;实在不想画 属性介绍 //存储数据的数组&#xff0c;初次使用时初始化&…

基于CSP的运动想象EEG分类任务实战

基于运动想象的公开数据集&#xff1a;Data set IVa (BCI Competition III)1 数据描述参考前文&#xff1a;https://blog.csdn.net/qq_43811536/article/details/134224005?spm1001.2014.3001.5501 EEG 信号时频空域分析参考前文&#xff1a;https://blog.csdn.net/qq_4381153…

xdcms漏洞合集-漏洞复现

目录 xdcms v3.0.1漏洞 环境搭建 代码审计 目录总览 配置文件总览 登陆处sql注入 漏洞分析 漏洞复现 注册处sql注入漏洞 漏洞分析 漏洞复现 getshell 任意文件删除 xdcms订餐网站管理系统v1.0漏洞 简介 环境搭建 全局变量的覆盖 漏洞分析 漏洞复现 后台任意…

6个机器学习可解释性框架

1、SHAP SHapley Additive explanation (SHAP)是一种解释任何机器学习模型输出的博弈论方法。它利用博弈论中的经典Shapley值及其相关扩展将最优信贷分配与局部解释联系起来. 举例&#xff1a;基于随机森林模型的心脏病患者预测分类 数据集中每个特征对模型预测的贡献由Shap…