数据结构与算法(三)--栈

news2024/9/23 21:20:45

一、前言

前两篇文章我们学习了第一个数据结构,数组,且从底层通过java实现了数组的构建和增删改查的操作功能,并且通过resize操作使我们的数组可以动态的扩容或者缩容。且我们知道数组最大的优点就是在索引有语义的情况下,查询和修改操作会非常的快,反之我们就得遍历寻找元素。我们还了解了时间复杂度,渐进复杂度,分摊复杂度和复杂度震荡等知识,分析动态数组相关操作的时间复杂度并且优化了resize操作。那么从这一章我们要学习另一种线性数据结构-栈。

二、栈(Stack)

  • 栈也是一种线性结构
  • 相比数组,栈对应的操作是数组的子集
  • 我们只能从一端添加元素,也只能从同一端取出元素
  • 这一端我们称为栈顶

当我们这样限定我们的数组形成了栈这样一个数据结构之后,它却可以在我们计算机组成逻辑有着非常非常重要的作用。
在这里插入图片描述
然后我们只能往栈顶这一端放入数据,这个放数据的过程通常我们称之为入栈
在这里插入图片描述
需要注意的是,3只能放在2的上面,不能放在1和2的中间或者1的下面,这就是我们只能往栈顶这一端放入数据这个的体现。
如果我们现在想从栈取出元素,我们只能取出先取出3这个元素,因为3目前在栈顶,我们拿不到2和1,甚至从用户角度根本看不到2和1这两个元素。用户只能看到栈顶的元素,我们将元素从栈顶取出的过程叫作出栈。且出栈也体现了只能从同一端(栈顶)取出元素

  • 从放入和取出的过程我们可以看到,栈是一种后进先出的数据结构(LIFO-Last In First Out)

我们从刚才的例子可以看到,3是我们最后放入栈的元素,但却是第一个从栈被取出的元素

三、栈的应用

  1. 无处不在的undo操作(撤销)

原理就是栈的应用。
例如我现在在文本里输入“沉迷”,对于这个动作,我们编辑器就会记录下来,这个记录动作其实就是把动作放进栈中,然后打入“学习”,接着本来打入“无法自拔”结果打成了“不法自拔”,由于打错也算一个动作这个时候就会把这些入栈过程体现为:
在这里插入图片描述
然后你意识到打错了,这个时候撤销,你执行撤销操作的过程就是从栈中拿出栈顶的元素,通过栈顶的元素来确认你最近的操作是什么,所以这次撤销操作就是把“不法自拔”从栈拿出然后删掉:
在这里插入图片描述
然后就可以继续重复之前的操作从而打入正确的文本。

  1. 程序调用的系统栈
    事实上,在我们程序调用的过程中经常会出现在一个逻辑的中间先终止,然后跳到另外的逻辑去执行,例如子函数的调用,这个过程中计算机就需要使用一个系统栈,其实就是栈的数据结构来记录我们程序的调用过程
    例如,下面有三个函数,其中A在第二行代码执行B(),B()在第二行代码执行C(),C()直接执行完普通的三行代码,没有子函数调用。那么对于我们计算机来说当执行A第二行代码的时候,此时程序暂时中断,转而去执行B函数,此时我们的系统栈就会记录一个信息叫作A2,代表之前这个程序执行到A的第二行了,这里进行了中断去执行B函数了;同理执行B第二行代码的时候,转而去执行C函数暂时中断B函数,系统栈会记录一个信息叫B2
    在这里插入图片描述
    当C函数执行完了后,计算机就会遇到个问题,下面需要去执行什么,答案就是查看系统栈,对于系统栈来说,栈顶是B2,它就知道执行到B函数第二行的时候中断了,我就可以跳回到B2这个位置继续执行了,此时B2出系统栈并且删除,同理执行完B后看到系统栈还有A2,跳回到A2这个位置继续执行完整个程序。此时系统栈是空的,整个过程才算执行完毕。
    在这里插入图片描述

这就是我们编程的时候进行子过程调用的时候,当一个子过程完成后可以自动的回到上层函数调用中断的位置,继续执行下去的原因,因为背后有一个系统栈,它可以记录每一次调用过程中中断的那个点。
基于这个原理的算法例如递归就是类似这样实现的。

四、栈的实现

上一节说了栈的定义和应用,其实大家会发现栈的应用非常重要,但是它实现起来其实非常简单,我们只需要实现以下方法即可:
Stack<E>

  • void push(E) 入栈
  • E pop() 从栈中拿出元素,出栈
  • E peek() 查看当前栈顶元素,也可以叫top()
  • int gitSize() 查看栈里一共有多少元素
  • boolean isEmpty() 查看栈是否为空

从用户的角度看,支持这些操作就好,和数组差不多,用户不想担心怎样resize的,他只需要知道数组是动态的,我可以通过数组添加删除元素是正常的就好了。对于栈亦是如此
对于具体底层实现,用户不关心,实际底层有多种实现方式,但是这里我们只采用基于动态数组的方式实现的栈。由于栈本身主要就是上面说的那5种操作,我们完全可以将Stack本身定义为一个接口,接口定义上面5个操作,然后写一个具体的实现类叫作ArrayStack通过数组的方式具体实现这5个操作。
ps:此次数组栈的实现方式需要前面文章动态数组的实现的基础,所以最好需要去查看动态数组的实现这篇博客,有了动态数组的实现基础,我们会发现数组栈的实现就非常简单:
1、首先我们需要编写Stack接口,定义5个基础操作,代码如下:

public interface Stack<T> {
	int getSize();
	boolean isEmpty();
	void push(T t);
	T pop();
	T peek();
}

2、编写ArrayStack类实现Stack接口,实现5个操作的方法,代码如下:

import com.mbw.array.DynamicArray;

public class ArrayStack<T> implements Stack<T> {
	private DynamicArray<T> dynamicArray;

	public ArrayStack(int capacity){
		this.dynamicArray = new DynamicArray<>(capacity);
	}

	public ArrayStack(){
		this.dynamicArray = new DynamicArray<>();
	}

	@Override
	public int getSize() {
		return dynamicArray.gitSize();
	}

	@Override
	public boolean isEmpty() {
		return dynamicArray.isEmpty();
	}

	@Override
	public void push(T t) {
		dynamicArray.addLast(t);
	}

	@Override
	public T pop() {
		return dynamicArray.removeLast();
	}

	@Override
	public T peek() {
		return dynamicArray.getLast();
	}

	@Override
	public String toString() {
		return "ArrayStack{" +
				"top = " + peek() +
				'}';
	}
}

其实这里说一下我一开始实现的误解的点,我认为栈顶等于数组第一个位置,所以我认为push和pop对应的不应该是addLast和removeLast,而是addFirst和removeFirst.peek也是getFirst()。但是这样说明我们对栈的定义是有错误的,其实我们只需要保证放入元素和去除元素是同一端即可,此时这个"同一端"不管是从数组头还是数组尾,其实都是可以的。那么我们当然选择addLast和removeLast啊,addFirst和removeFirst时间复杂度铁打的O(n),而addLast和removeLast从均摊复杂度角度分析其实是O(1),效率更高。
然后我们可以试一下我们编写的栈:

public static void main(String[] args) {
		ArrayStack<String> stringArrayStack = new ArrayStack<>();
		stringArrayStack.push("沉迷");
		stringArrayStack.push("学习");
		stringArrayStack.push("不法");
		String pop = stringArrayStack.pop();
		System.out.println(pop);
		stringArrayStack.push("无法自拔");
		System.out.println(stringArrayStack.peek());
		System.out.println(stringArrayStack.toString());
	}

在这里插入图片描述
对于toString很多小伙伴可能会有疑惑为什么不把栈的所有元素打出来,其实从用户角度而言,栈这个数据结构用户只需关注栈顶的元素,我们从原则上也不应该把栈的中间元素给展示出来。不过自己学习的话可以将成员变量array的详情打印出来便于我们查看分析问题。这里就不做过多讲述。
而对于复杂度分析,除了之前说的push和pop从均摊复杂度角度分析都是O(1),而peek(),getSize(),isEmpty()这几个操作很明显都是O(1)的,所以栈的性能是非常良好的。

五、栈的另一个应用–括号匹配(编译器)

想必很多干开发的朋友会很熟悉,例如我们编程的时候使用小括号,中括号的逻辑,或者我们写块逻辑,无论是for,if,while或者定义一个函数或者类都需要使用大括号,经常会有这种括号套括号的情况,在这种情况下,如果我们的括号匹配不成功的话,那我们的编译器就会报错,而这个原理就是应用栈。
本章我们会通过leetcode的一个题目通过编程的方式完成这个应用—20. 有效的括号
那么这题我们来看看如何通过栈来解决该问题:
首先假设字符串为([{}])
我们先从头开始,将字符串为左括号的入栈。如下图:

在这里插入图片描述
直到遍历字符串到第一个右括号的时候,这个时候出栈,将栈的第一个元素和当前字符做匹配,以此类推,如果最后都匹配上了,栈一定为空。如果栈不为空,说明有多的左括号。
而一旦没匹配上,则直接返回false。例如下图大括号和栈顶的中括号不匹配:
在这里插入图片描述

总结一句话就是:
栈顶元素反映了在嵌套的层次关系中,最近的需要匹配的元素
接着我们掌握了原理,就可以编程解决了,这里我们不使用我们自定义的ArrayStack解决,而是通过java.util.Stack解决,其实熟悉的小伙伴会发现我们自己编写的ArrayStack和Stack的方法是一致的。
那么逻辑如下

class Solution {
	public boolean isValid(String s) {
	    //如果字符串长度不是偶数,那么肯定不匹配,返回false
		if (s.length() % 2 == 1){
			return false;
		}
		Stack<Character> stack = new Stack<>();
		for (int i = 0; i < s.length(); i++) {
			char c = s.charAt(i);
			//如果是左括号则入栈
			if (c == '(' || c == '[' || c == '{') {
				stack.push(c);
			} else {
				//如果此时栈是空的,那么说明没有左括号只有右括号,那肯定是不匹配的,直接返回false
				if (stack.isEmpty()) {
					return false;
				}
				if (!validatePopAndChar(stack, c)) {
					return false;
				}
			}
		}
		//for循环结束后如果栈还有元素那么说明存在未匹配多余的左括号,返回false,否则返回true
		return stack.isEmpty();
	}

	private boolean validatePopAndChar(Stack<Character> stack, char c) {
	     //不是左括号,出栈和左括号匹配
		Character pop = stack.pop();
		if (c == ')' && pop != '(') {
			return false;
		}
		if (c == ']' && pop != '[') {
			return false;
		}
		return c != '}' || pop == '{';
	}
}

最后可以通过。
在这里插入图片描述

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

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

相关文章

VS2013任意一个项目配置Tiff环境

1.包含目录库目录 2.链接器输出tiff.lib 3.文件夹里放一些东西

北邮22级信通院数电:Verilog-FPGA(1)实验一“跑通第一个例程” 过程中遇到的常见问题与解决方案汇总(持续更新中)

北邮22信通一枚~ 跟随课程进度更新北邮信通院数字系统设计的笔记、代码和文章 持续关注作者 迎接数电实验学习~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院数电实验_青山如墨雨如画的博客-CSDN博客 目录 问题一&#xff1a;Verilog代码没有跑通 报…

如何维护物流中心电力系统?这个方法太炸裂了

物流中心是现代供应链管理中的关键环节之一&#xff0c;它承担着货物存储、分拣和分发的任务。而物流中心的正常运营离不开可靠的电力供应。 配电柜&#xff0c;作为电力系统的关键组成部分&#xff0c;负责分配电能到各个设备和区域&#xff0c;因此其运行状态至关重要。为了确…

Docker核心原理与实操

第一章、Docker基本概念 1、概念&#xff1a;Docker是一种容器技术&#xff0c;可以解决软件跨环境迁移问题。 2、实现原理&#xff1a;是一个分层复用的文件系统&#xff1b;每一层都是一个独立的软件&#xff1b; …

前端JS中的异步编程与Promise

&#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 一、JavaScript的异步编步机制 二、事件循环&#xff08;Event Loop&#xff09;和任务队列&#xff08;Task Queue…

【LeetCode热题100】--1.两数之和

1.两数之和 方法一&#xff1a;最直观的方法就是暴力破解&#xff0c;就是枚举数组中的每一个数x&#xff0c;寻找数组中是否存在target-x class Solution {public int[] twoSum(int[] nums, int target) {int n nums.length;for(int i0;i<n;i){for(int j i1 ;j<n;j){…

基于SpringBoot的旅游系统

基于SpringBootVue的旅游系统、前后端分离 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 【主要功能】 角色&#xff1a;管理员、用户 用户&#xff1a;浏览旅游…

动态代理是什么?应用场景在哪?

一、什么是动态代理 代理&#xff0c;是一种设计模式&#xff0c;提供了对目标对象额外的访问方式&#xff0c;即可以通过代理访问目标对象&#xff0c;这样可以在不修改原目标对象的前提下&#xff0c;提供额外的方式进行访问&#xff0c;以保护原有的真实访问对象而根据创建…

人人组队与人机组队的风险

无论人与人之间还是人与机之间&#xff0c;只要有协同就会有风险。其原因主要是&#xff1a;协同可能导致合作伙伴之间的利益冲突&#xff0c;协同合作还可能引发信息共享的风险&#xff0c;协同可能面临合作对象的信任问题&#xff0c;协同合作还可能受到外部环境的影响等等。…

Java基于SpringBoot的闲一品交易平台

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 大家好&#xff0c;我是程序员徐师兄、今天给大家谈谈基于android的app开发毕设题目&#xff0c;以及基于an…

销售更喜欢的CRM软件系统

企业在实施CRM系统后&#xff0c;大多数销售人员都会抵触。他们认为这是一款麻烦且用来监视他们的工具。其实&#xff0c;CRM可以帮助他们更好地完成销售工作。那么&#xff0c;什么样的CRM系统销售更喜欢&#xff1f; 销售人员最关心的是如何提高自己的业绩和收入&#xff0c…

亲测!好用的电子期刊制作工具

宝贝们&#xff0c;今天来给大家分享一个超级实用的网站&#xff01;电子期刊制作&#xff0c;完全不用再求人&#xff01;简单易操作&#xff0c;分分钟让你成为制作达人&#xff01; ​无论你是想制作一个精美的个人简历&#xff0c;还是想制作一个有吸引力的企业期刊&#…

性能测试之压力测试

文章目录 一.基本介绍二.性能指标三.下载安装JMeter1.下载安装包2.启动JMeter 四.使用JMeter1.模拟用户请求2.填写测试地址3.接收测试结果4.结果解释 一.基本介绍 压力测试考察当前软硬件条件下系统所能承受的最大负荷并找到系统瓶颈所在。压测是为了系统在线上的处理能力和稳定…

24.98万起,新一代AITO问界M7值得买吗?

监制 | 何玺 排版 | 叶媛 问界汽车新品来袭。 9月12日下午&#xff0c;问界汽车为全新的M7系列车型举行了发布会。华为常务董事余承东&#xff0c;在全网一片“遥遥领先”呼声的烘托下&#xff0c;上台发表演讲&#xff0c;详细介绍了M7的全面升级和各大亮点。 01 新一代AI…

今日事今日毕,推荐五款无广告的免费软件

​ 大家好&#xff0c;我是互联网的搬运工&#xff0c;今天继续给大家带来五款没有广告的免费软件。 1.视频编辑——Shotcut ​ Shotcut 是一款免费、开源、跨平台的视频编辑软件&#xff0c;支持多种音频和视频格式和编码&#xff0c;无需导入即可进行本地编辑&#xff0c;支…

【java】【SpringBoot】【一】基础篇 SpringBoot工程创建和实现ssm/ssmp整合

目录 一、快速上手SpringBoot 1 创建SpringBoot入门程序 1.1 创建一个empty Project 1.2 查看maven版本&#xff0c;配置 1.3 创建模块 &#xff08;springboot选择Spring Initializr&#xff09; 1.4 编写BookController类&#xff08;基于rest的MVC控制器&#xff09; …

【深度学习】Pytorch 系列教程(二):PyTorch数据结构:1、Tensor(张量): GPU加速(GPU Acceleration)

目录 一、前言 二、实验环境 三、PyTorch数据结构 0、分类 1、张量&#xff08;Tensor&#xff09; 1. 维度&#xff08;Dimensions&#xff09; 2. 数据类型&#xff08;Data Types&#xff09; 3. GPU加速&#xff08;GPU Acceleration&#xff09; 一、前言 ChatGP…

【DevOps系列】Docker数据卷(volume)详解

【DevOps系列】Docker数据卷&#xff08;volume&#xff09;详解 文章目录 【DevOps系列】Docker数据卷&#xff08;volume&#xff09;详解一、概述二、数据卷三、为什么使用数据卷volume数据卷的作用&#xff1a;数据卷的特点&#xff1a; 四、数据卷volume基本操作4.1 创建数…

YOLOv5训练自定义模型 训练

说明&#xff1a; 1、训练过程请参考官网&#xff1a;https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data 2、本课使用的是YOLOv5 6.1版本&#xff0c;其他版本训练过程可能有不同&#xff0c;请以官网为准 3、硬件&#xff1a;Windows 11 、GPU GeForce 3070Ti…

element-ui tree组件实现在线增删改

这里要实现一个tree 增删改 <!--oracle巡检项--> <template><div class"oracle_instanceType"><el-row type"flex" align"middle" justify"space-between"><iclass"el-icon-s-fold iBox"click&q…