Java栈和队列的快速入门

一、栈 Stack

1、概念

在 Java 中,栈(Stack)是一种后进先出(LIFO)的数据结构,用于存储元素。在栈中,只有栈顶的元素是可见的和可访问的,其他元素都被隐藏起来,直到栈顶的元素被移除或弹出。Java 中的 java.util.Stack 类实现了这种栈的数据结构,并且是线程安全的,继承自 Vector 类。

压栈:栈的插入操作叫做进栈 / 压栈 / 入栈, 入数据在栈顶 。
出栈:栈的删除操作叫做出栈。 出数据在栈顶

Deque和Stack

Stack 和 Deque 都是用于存储元素的数据结构,但它们在设计理念、API 方法和使用场景上有所不同。

不同

Stack:
Stack 类是 Java 1.0 引入的一个老类,继承自 Vector,属于遗留类。它遵循后进先出(LIFO)原则。
由于 Stack 继承自 Vector,它具有线程安全特性,但这也带来了额外的同步开销。

Deque:
Deque(双端队列)接口是在 Java 1.6 中引入的,它提供了更现代和灵活的设计。
Deque 接口支持在队列的两端进行插入和删除操作,因此它可以作为栈(LIFO)或队列(FIFO)使用。

线程安全性

Stack:
Stack 是线程安全的,因为它是 Vector 的子类,而 Vector 是同步的。
但是,线程安全带来了额外的同步开销,可能会影响性能。
Deque:
Deque 接口本身并不提供线程安全的实现,但它可以通过使用 Collections.synchronizedDeque 来实现

线程安全。

LinkedList 和 ArrayDeque 等实现类不是线程安全的,但它们通常比 Stack 更高效。

使用场景

Stack:
适用于需要标准栈操作的场景,特别是在需要线程安全性的情况下。
但由于其性能和设计的限制,现代代码中较少使用 Stack。
Deque:
适用于需要灵活操作的场景,既能用作栈也能用作队列。
通常情况下,推荐使用 Deque 来替代 Stack,因为它的实现更高效且功能更强大。

总结

虽然 Stack 和 Deque 都可以用来实现栈的功能,但 Deque 提供了更现代、更高效的设计,并且支持更多的操作。在大多数情况下,推荐使用 Deque 来替代 Stack。

API 方法

Stack:
push(E item): 将元素压入栈顶。
pop(): 从栈顶弹出元素。
peek(): 查看栈顶元素但不弹出。
empty(): 检查栈是否为空。
search(Object o): 搜索元素并返回其位置(从栈顶开始计数)。

Deque:
push(E e): 将元素压入栈顶(等同于 addFirst(E e))。
pop(): 从栈顶弹出元素(等同于 removeFirst())。
peek(): 查看栈顶元素但不弹出(等同于 peekFirst())。
addFirst(E e): 在队首插入元素。
addLast(E e): 在队尾插入元素。
removeFirst(): 移除队首元素。
removeLast(): 移除队尾元素。

2、基本操作

在这里插入图片描述

3、常用方法

方法功能
Stack()构造一个空的栈
E push(E e)将e入栈,并返回
E pop()将栈顶元素出栈并返回
E peek()获取栈顶元素
int size()获取栈中有效元素个数
boolean empty()检测栈是否为空

4、举例

import java.util.Stack;
 
public class StackExample {
    public static void main(String[] args) {
        Stack<String> stack = new Stack<>();
        
        // 压栈操作
        stack.push("Java");
        stack.push("Python");
        stack.push("C++");
        
        // 弹栈操作
        String topLanguage = stack.pop();
        System.out.println("弹出栈顶元素:" + topLanguage);
        
        // 查看栈顶元素
        String currentTop = stack.peek();
        System.out.println("当前栈顶元素:" + currentTop);
        
        // 判空操作
        if (stack.isEmpty()) {
            System.out.println("栈为空");
        } else {
            System.out.println("栈不为空");
        }
    }
}

5、分析

执行过程:

压栈操作:

stack.push("Java");:将 “Java” 压入栈中。
stack.push("Python");:将 “Python” 压入栈中。
stack.push("C++");:将 “C++” 压入栈中。

此时,栈中的元素从栈底到栈顶依次是:“Java” -> “Python” -> “C++”。

弹栈操作:

String topLanguage = stack.pop();:弹出栈顶元素 “C++”,并将其赋值给 topLanguage。
System.out.println("弹出栈顶元素:" + topLanguage);:打印出 “弹出栈顶元素:C++”。
此时,栈中的元素从栈底到栈顶依次是:“Java” -> “Python”。

查看栈顶元素:

String currentTop = stack.peek();:查看当前栈顶元素 “Python”,并将其赋值给 currentTop。
System.out.println("当前栈顶元素:" + currentTop);:打印出 “当前栈顶元素:Python”。

判空操作:

if (stack.isEmpty()) { ... } else { ... }:判断栈是否为空。
因为栈中还有 “Java” 和 “Python” 两个元素,所以 stack.isEmpty() 返回 false。
System.out.println("栈不为空");:打印出 “栈不为空”。

结果

弹出栈顶元素:C++
当前栈顶元素:Python
栈不为空

二、队列

1、概念

在 Java 中,队列(Queue)是一种先进先出(FIFO)的数据结构,用于存储元素。队列在 java.util 包中有多种实现,如 LinkedList、ArrayDeque 和 PriorityQueue。只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头(Head/Front)
在这里插入图片描述

2、常用方法

方法功能
boolean offer(E e)入队列
E pop()检索并删除队列的头
peek()获取队头元素
int size()获取队列中有效元素个数
boolean isEmpty()检测队列是否为空

3、举例

注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。

public static void main(String[] args) {
    Queue<Integer> q = new LinkedList<>();
    q.offer(1);
    q.offer(2);
    q.offer(3);
    q.offer(4);
    q.offer(5); // 从队尾入队列
    System.out.println(q.size());
    System.out.println(q.peek()); // 获取队头元素
    q.poll();
    System.out.println(q.poll()); // 从队头出队列,并将删除的元素返回
    if(q.isEmpty()){
    System.out.println("队列空");
    }else{
    System.out.println(q.size());
    }
}

4、分析:

初始化队列并入队列:

Queue<Integer> q = new LinkedList<>();
q.offer(1);
q.offer(2);
q.offer(3);
q.offer(4);
q.offer(5); // 从队尾入队列

此时,队列中的元素从队头到队尾依次是:1 -> 2 -> 3 -> 4 -> 5。

获取队列的大小:

System.out.println(q.size());

队列中有 5 个元素,所以输出结果是:

5

获取队头元素:

System.out.println(q.peek()); // 获取队头元素

队头元素是 1,所以输出结果是:

1

从队头出队列:

q.poll();

从队头删除元素 1,此时队列中的元素从队头到队尾依次是:2 -> 3 -> 4 -> 5。

再次从队头出队列,并返回删除的元素:

System.out.println(q.poll());

从队头删除元素 2,并返回这个元素,所以输出结果是:

2

检查队列是否为空,并输出队列的大小:

if(q.isEmpty()){
    System.out.println("队列空");
}else{
    System.out.println(q.size());
}

此时队列中有 3 个元素(3, 4, 5),所以 q.isEmpty() 返回 false,输出队列的大小:

3

综上所述,代码的输出结果是:

5
1
2
3

三、力扣算法快速入门

232. 用栈实现队列

力扣题
图解:
在这里插入图片描述

代码:

class MyQueue {

    Stack<Integer> stackIn;
    Stack<Integer> stackOut;

    /** 初始化两个栈 stackIn 和 stackOut。stackIn 用于存放入队的元素,stackOut 用于存放出队的元素。 */
    public MyQueue() {
        stackIn = new Stack<>(); // 负责进栈
        stackOut = new Stack<>(); // 负责出栈
    }
    
    /**将元素 x 压入 stackIn 栈中。由于栈是先进后出的,所以 stackIn 中的元素顺序与队列的入队顺序一致 */
    public void push(int x) {
        stackIn.push(x);
    }
    
    /** 从队列的头部移除并返回元素。首先调用 dumpstackIn() 方法将 stackIn 中的所有元素转移到 stackOut 中(如果 stackOut 为
    空),然后从 stackOut 中弹出栈顶元素,这个元素就是队列的头部。 */
    public int pop() {    
        dumpstackIn();
        return stackOut.pop();
    }
    
    /** 返回队列的头部元素,但不移除它。首先调用 dumpstackIn() 方法确保 stackOut 中有元素,然后返回 stackOut 的栈顶元素。*/
    public int peek() {
        dumpstackIn();
        return stackOut.peek();
    }
    
    /** 检查队列是否为空。队列为空的条件是 stackIn 和 stackOut 都为空。 */
    public boolean empty() {
        return stackIn.isEmpty() && stackOut.isEmpty();
    }

    // 如果stackOut为空,那么将stackIn中的元素全部放到stackOut中
    private void dumpstackIn(){
        if (!stackOut.isEmpty()) return; 
        while (!stackIn.isEmpty()){
                stackOut.push(stackIn.pop());
        }
    }
}

225. 用队列实现栈

力扣题
在这里插入图片描述
使用一个队列:

class MyStack {

    Queue<Integer> queue;

    public MyStack() {
        queue = new LinkedList<>();
    }

    //每 offer 一个数(A)进来,都重新排列,把这个数(A)放到队列的队首
    public void push(int x) {
        queue.offer(x);
        int size = queue.size();
        //移动除了 A 的其它数
        while (size-- > 1)
            queue.offer(queue.poll());
    }

    public int pop() {
        return queue.poll();
    }

    public int top() {
        return queue.peek();
    }

    public boolean empty() {
        return queue.isEmpty();
    }
}

使用两个队列:

class MyStack {
    // Deque 接口继承了 Queue 接口
    // 所以 Queue 中的 add、poll、peek等效于 Deque 中的 addLast、pollFirst、peekFirst
    Deque<Integer> que1; // 和栈中保持一样元素的队列
    Deque<Integer> que2; // 辅助队列
    /** Initialize your data structure here. */
    public MyStack() {
        que1 = new ArrayDeque<>();
        que2 = new ArrayDeque<>();
    }
    
    /** Push element x onto stack. */
    public void push(int x) {
        que1.addLast(x);
    }
    
    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        int size = que1.size();
        size--;
        // 将 que1 导入 que2 ,但留下最后一个值
        while (size-- > 0) {
            que2.addLast(que1.peekFirst());
            que1.pollFirst();
        }

        int res = que1.pollFirst();
        // 将 que2 对象的引用赋给了 que1 ,此时 que1,que2 指向同一个队列
        que1 = que2;
        // 如果直接操作 que2,que1 也会受到影响,所以为 que2 分配一个新的空间
        que2 = new ArrayDeque<>();
        return res;
    }
    
    /** Get the top element. */
    public int top() {
        return que1.peekLast();
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return que1.isEmpty();
    }
}

20.有效的括号

力扣题目
在这里插入图片描述

class Solution {
    public boolean isValid(String s) {
        int n=s.length();  
        if(n%2!=0){         //剪枝操作
            return false;
        }
         Deque<Character> deque = new LinkedList<>();
         char  ch;
         for(int i=0;i<s.length();i++){
            ch=s.charAt(i);
            if(ch=='('){                //拿左,往栈里放右。
                deque.push(')');
            }else if(ch=='['){
                deque.push(']');
            }else if(ch=='{'){
                deque.push('}');
            }else if(deque.isEmpty()||ch!=deque.peek()){    //处理了不相等的情况,也处理了右多左少的情况。
                return false;
            }else{
                deque.pop();        //上面处理了两种情况,那这里就是左右相等,匹配的情况,所以,直接弹出栈顶
             }
         }
         return deque.isEmpty();    //如果不为空,则是左多右少。
    }
}
1047. 删除字符串中的所有相邻重复项

力扣题目
使用栈

class Solution {
    public String removeDuplicates(String s) {
        //创建栈,ArrayDeque的删除会更快
      ArrayDeque<Character> deque = new ArrayDeque<>();
        //将字符串转为字符数组,使用增强for循环,也可使用普通for循环
      for(char str:s.toCharArray()){
        //如果栈为空,或者头部元素等于相邻元素
        if(deque.isEmpty()||deque.peek()!=str){
        //将str放入栈中
            deque.push(str);
        }else{
            deque.pop();
        }
      }
      //转换回字符串
      String str="";
      while(!deque.isEmpty()){
      //因为是反着加入到栈中的,所以反着拿出来。
        str=deque.pop()+str;
      }
      return str;
    }
}

使用字符串当作栈

class Solution {
    public String removeDuplicates(String s) {
        // 将 res 当做栈
        // 也可以用 StringBuilder 来修改字符串,速度更快
        // StringBuilder res = new StringBuilder();
        StringBuffer res = new StringBuffer();
        // top为 res 的长度
        int top = -1;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // 当 top > 0,即栈中有字符时,当前字符如果和栈中字符相等,弹出栈顶字符,同时 top--
            if (top >= 0 && res.charAt(top) == c) {
                res.deleteCharAt(top);
                top--;
            // 否则,将该字符 入栈,同时top++
            } else {
                res.append(c);
                top++;
            }
        }
        return res.toString();
    }
}

感谢

感谢大佬Java 【数据结构】 栈(Stack)和队列(Queue)【神装】的教学,本文大多搬运自此文。
感谢代码随想录的部分图文。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值