《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


结语

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


相关文章
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
140 9
|
26天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
24 1
|
2月前
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
47 1
|
2月前
|
存储 Java
告别混乱!用Java Map优雅管理你的数据结构
【10月更文挑战第17天】在软件开发中,随着项目复杂度增加,数据结构的组织和管理至关重要。Java中的Map接口提供了一种优雅的解决方案,帮助我们高效、清晰地管理数据。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,有效提升了代码质量和维护性。
92 2
|
2月前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
67 2
|
11天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
40 5
|
13天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
33 5
|
29天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
29天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
46 6
|
1月前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。