《Java小子怒闯数据结构九重天》第三重天——栈

简介: 自古以来数据结构界就分为九重天,据说冲破这九重天之后就可以去进攻算法界最终修炼最后成佬,受万人敬仰,现在讲的是第三重。

前言


自古以来数据结构界就分为九重天,据说冲破这九重天之后就可以去进攻算法界最终修炼最后成佬,受万人敬仰。


但是这谈何容易,因为每一重天都有神兽把守,想要冲破每一重天都必须收服守护的神兽才行。


守护九重天的神兽分别是:数组、字符串、栈、队列、链表、树、散列表、堆、图。可见他们的战斗力也是逐层增强的。想只凭靠自身的能力拿下他们谈何容易。


不过大家不必惊慌,我这里有一本上古秘籍《Java小子怒闯数据结构九重天》,里面有每一重天神兽的攻略。只要修炼者仔细钻研里面的每一篇,对九重天了如指掌之后,冲破这九重天也是易如反掌的。


今天为大家带来第三重天的攻略!

image.png


1.🌀栈的基础知识

栈(Stack):只允许在一端进行插入或删除的线性表

栈有两个概念是:栈底和栈顶。一般栈只允许在栈顶进行操作,这样就会让栈有一个特性——后进先出(LIFO:Last in, First out结构)。


栈的重要知识:


(1)栈是函数中定义的基本类型变量,对象的引用变量都在函数的栈内存中分配;

(2)栈内存特点,数据一执行完毕,变量会立即释放,节约内存空间;

(3)栈内存中的数据,没有默认初始化值,需要手动设置;


1.1 入栈


入栈push:入栈也称压栈,指的是栈的插入操作,在栈顶位置插入新的数据元素。


下图从左到右依次是入栈操作演示:


image.png

1.2 出栈


出栈pop:出栈也称弹栈,指的是栈的删除操作,删除栈顶位置的数据元素。


下图从左到右依次是出栈操作演示:

image.png

综合两幅图,我们就可以来看出来栈后进先出的操作。


2.🌀Java中实例化栈

在Java中为栈特地实现了一个类为Stack,它实现了一个标准的后进先出的栈,也定义了自己的一些方法。


初始化一个栈的写法:E表示数据类型,stack是你给栈起的名字。


Stack  stack = new Stack();


我们在声明Stack类对象之后就可以使用,类的实例化stack来调用Stack自带的一些方法了。这些方法我们后面会仔细讲到了。


但是我们在做题中需要用到栈结构时一般不使用Stack来实现栈结构。这是为什么呢?这又是谁说的呢?


其实这并不是某个Java大佬或者大家总结出来的习惯,而是Java的官方文档中自己提出来的。


我们可以在Java官网中查一下Stack得到以下结果:

image.png

我们对上面一大段英文进行翻译:


Stack类表示后进先出(LIFO)对象堆栈。它通过五个操作扩展了类向量,允许将向量作为堆栈处理。提供了常见的推送和弹出操作,以及一种查看堆栈顶部项目的方法、一种测试堆栈是否为空的方法,以及一种搜索堆栈中某个项目并发现其距离顶部有多远的方法。首次创建堆栈时,它不包含任何项。


Deque接口及其实现提供了一组更完整、更一致的后进先出堆栈操作,应该优先于此类使用。例如:


Deque stack = new ArrayDeque();


所以我们一般在做题或者使用到栈结构的时候一般都是使用下面形式来实例化栈的。


Deque stack = new ArrayDeque ();


那么具体为什么不使用Stack呢?


是因为Stack类继承于Vector类,而Vector是线程安全的,每个可能出现线程安全的方法上加了synchronized关键字,所以效率低。所以最后导致Stack的效率也比较低!

3.🌀Java中栈常用API

3.1 使用Stack类实现的栈


3.1.1 入栈


stack.push(value);


3.1.2 查看此堆栈顶部的对象,而不从堆栈中删除它


stack.peek();


3.1.3 删除此堆栈顶部的对象,并将该对象作为此函数的值返回


stack.pop();


3.1.4 判断栈是否为空,返回一个boolean类型的值


stack.empty();


3.2 使用Deque类实现的栈


官方推荐的是使用Deque的实现子类ArrayDeque来实现栈,不过平时我们使用到LinkedList这个实现子类也比较多。


Deque stack = new ArrayDeque();

Deque stack = new LinkedList();


ArrayDeque与LinkedList都实现了Deque接口,区别:


(1)它们的底层数据结构不同,ArrayDeque是基于数组,LinkedList是基于链表,ArrayDeque默认长度为16,当容量不够需要扩容(两倍),其扩容很费时间(需要复制原数组到新数组)。所以未知数据量时,尽量使用LinkedList

(2)ArrayDeque适合频繁访问,LinkedList适合频繁删除


ArrayDeque与LinkedList对应实现栈的方法:


ArrayDeque与LinkedList本身也有push(e),pop(),peek() 方法,功能与Stack中的一致。


在ArrayDeque与LinkedList中,isEmpty()与Stack中的empty()方法对应。


4.🌀Java实现栈

栈底层有两种结构来实现:数组与链表。那么我们自己能否使用数组与链表来实现一个栈呢?


这当然是可以的,接下来我们就分别使用数组与链表来实现栈结构。


4.1 用数组实现栈

public class MyStack {
    //定义栈的大小
    private int maxStack;
    //底层使用数组来实现数组
    private int[] stack;
    //表示栈顶所在的位置,默认情况下如果没有数据时,使用-1
    private int top = -1;
  //构造函数
    public MyStack(int maxStack) {
        this.maxStack = maxStack;
        stack=new int[maxStack];
    }
    //判断是否已经满栈
    public boolean isFull(){
        return this.top == this.maxStack-1;
    }
    //判断栈是否是空栈
    public boolean isEmpty(){
        return  this.top == -1;
    }
  //入栈
    public void push(int val){
        //在压栈之前要先判断是否栈满
        if (isFull()){
            throw  new RuntimeException("栈已满!");
        }
        top++;//指针到下一个位置
        stack[top]=val;//添加元素
    }
    //出栈
    public int pop(){
        //在弹栈之前要先判断是否为空栈
        if (isEmpty()){
            throw new RuntimeException("栈为空");
        }
        int value= stack[top];
        top--;
        return value;
    }
    // 查看栈顶元素,不删除
    public int peek(){
        if (isEmpty()){
            throw new RuntimeException("栈为空");
        }
        int value= stack[top];
        return value;
  }
    //查看栈中所有元素
    public void list(){
        System.out.println(Arrays.toString(stack));
    }
}
4.2 用链表实现栈
//定义链表节点,即每一个栈元素
class Node{
    public int val;
    public Node next;
    public Node(int val){
        this.val=val;
    }
}
public class MyStack{
  //底层使用链表实现栈
    Node head=null;
    //默认构造方法
    public MyStack(){
    }
    // 入栈
    public int push(int val){
        Node node=new Node(val);
        node.next = head;
        head = node;
        return head.val;
    }
    // 出栈
    public int pop(){
        if(empty()){
            throw new RuntimeException("栈为空");
        }
        int val=head.val;
        head=head.next;
        return val;
    }
    // 查看栈顶元素,不删除
    public int peek(){
        if(empty()){
            throw new RuntimeException("栈为空");
        }
        return head.val;
    }
    // 判断栈是否为空
    public boolean empty(){
        return head==null;
    }
}



5.🌀栈进阶练习

Leetcode 20. 有效的括号

微信图片_20220523225223.png

题解:


栈先入后出特点恰好与本题括号排序特点一致,即若遇到左括号入栈,遇到右括号时将对应栈顶左括号出栈,则遍历完所有括号后 stack 仍然为空则返回true。

class Solution {
    public boolean isValid(String s) {
        Stack stack= new Stack();
        for(char c : s.toCharArray()) {
            if(c == '(' || c == '{' || c == '[') {
                stack.push(c);
            }
            else if(c == ')' && !stack.empty()) {
                if(stack.pop() != '(')
                return false;
            }else if(c == '}' && !stack.empty()) {
                if(stack.pop() != '{')
                return false;
            }else if(c == ']' && !stack.empty()) {
                if(stack.pop() != '[')
                return false;
            }else return false;
        }
        if(stack.empty()) return true;
        else return false;
    }
}

输出:

image.png


结语

恭喜你修炼到这里,你已经基本有了收服神兽栈的能力。栈也是是我们到进攻算法界最重要的能力之一。大家要多加思考它的结构原理才能更好的运用。


相关文章
|
23天前
|
存储 算法 Java
惊!Java程序员必看:JVM调优揭秘,堆溢出、栈溢出如何巧妙化解?
【8月更文挑战第29天】在Java领域,JVM是代码运行的基础,但需适当调优以发挥最佳性能。本文探讨了JVM中常见的堆溢出和栈溢出问题及其解决方法。堆溢出发生在堆空间不足时,可通过增加堆空间、优化代码及释放对象解决;栈溢出则因递归调用过深或线程过多引起,调整栈大小、优化算法和使用线程池可有效应对。通过合理配置和调优JVM,可确保Java应用稳定高效运行。
101 4
|
1月前
|
Java
【Java集合类面试二十六】、介绍一下ArrayList的数据结构?
ArrayList是基于可动态扩展的数组实现的,支持快速随机访问,但在插入和删除操作时可能需要数组复制而性能较差。
|
1月前
|
存储 设计模式 算法
JAVA中的常见数据结构
JAVA中的常见数据结构
|
9天前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
|
11天前
|
存储 C语言
数据结构基础详解(C语言): 栈与队列的详解附完整代码
栈是一种仅允许在一端进行插入和删除操作的线性表,常用于解决括号匹配、函数调用等问题。栈分为顺序栈和链栈,顺序栈使用数组存储,链栈基于单链表实现。栈的主要操作包括初始化、销毁、入栈、出栈等。栈的应用广泛,如表达式求值、递归等场景。栈的顺序存储结构由数组和栈顶指针构成,链栈则基于单链表的头插法实现。
|
13天前
|
Java
【数据结构】栈和队列的深度探索,从实现到应用详解
本文介绍了栈和队列这两种数据结构。栈是一种后进先出(LIFO)的数据结构,元素只能从栈顶进行插入和删除。栈的基本操作包括压栈、出栈、获取栈顶元素、判断是否为空及获取栈的大小。栈可以通过数组或链表实现,并可用于将递归转化为循环。队列则是一种先进先出(FIFO)的数据结构,元素只能从队尾插入,从队首移除。队列的基本操作包括入队、出队、获取队首元素、判断是否为空及获取队列大小。队列可通过双向链表或数组实现。此外,双端队列(Deque)支持两端插入和删除元素,提供了更丰富的操作。
14 0
【数据结构】栈和队列的深度探索,从实现到应用详解
|
1月前
栈的几个经典应用,真的绝了
文章总结了栈的几个经典应用场景,包括使用两个栈来实现队列的功能以及利用栈进行对称匹配,并通过LeetCode上的题目示例展示了栈在实际问题中的应用。
栈的几个经典应用,真的绝了
|
17天前
|
Linux C++ Windows
栈对象返回的问题 RVO / NRVO
具名返回值优化((Name)Return Value Optimization,(N)RVO)是一种优化机制,在函数返回对象时,通过减少临时对象的构造、复制构造及析构调用次数来降低开销。在C++中,通过直接在返回位置构造对象并利用隐藏参数传递地址,可避免不必要的复制操作。然而,Windows和Linux上的RVO与NRVO实现有所不同,且接收栈对象的方式也会影响优化效果。
|
1月前
|
负载均衡 网络协议 安全
DKDP用户态协议栈-kni
DKDP用户态协议栈-kni
|
1月前
|
存储 安全 编译器
缓冲区溢出之栈溢出(Stack Overflow
【8月更文挑战第18天】
55 3