Java栈和队列

目录

    • 1.栈的概念
    • 2.栈、虚拟机栈、栈帧的区别
    • 3.括号匹配问题(栈)
    • 4.逆波兰表达式求值(栈)
    • 5.栈的弹出序列是否正确(栈)
    • 6.最小栈问题(栈)
    • 7.队列的概念
    • 8.循环队列
    • 9.栈和队列的实例化
    • 10.用队列实现栈
    • 11.用栈实现队列

1.栈的概念

栈是操作受限的特殊的线性表(顺序存储和链式存储),即有顺序栈和链栈。
栈的特点是:先进后出,只允许在固定的一端进行插入和删除。
栈的方法有:

方法 功能
Stack() 构造一个空的栈
push(E e) 入栈
pop() 出栈
peek() 获取栈顶元素
size() 得到栈的大小
empty() 判断栈是否为空

2.栈、虚拟机栈、栈帧的区别

栈:是一种数据结构
虚拟机栈:是Java运行程序的一块内存
栈帧:是在虚拟机栈上给函数开辟的内存空间

3.括号匹配问题(栈)

题力扣20. 有效的括号:给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。 有效字符串需满足:
左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左括号。

分析:
把给定的输入字符串依次压栈,如果遇到右括号就弹出栈进行匹配。
不匹配一共有3种情况,①左括号多了,当输入字符串读完的时候,栈还没空;②右括号多了,当栈为空的时候,还读入了右括号;③不匹配,右括号和出栈的左括号不匹配
代码:

class Solution {
    public boolean isValid(String s) {
        Deque<Character> stack = new ArrayDeque<>();//实例化栈
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if (ch == '(' || ch == '{' || ch == '[') {
                //入栈
                stack.push(ch);
            }else { //ch为右括号
                if (stack.isEmpty()) {
                    return false;
                }
                char ch2 = stack.peek(); //左括号
                if (ch == ')' && ch2 == '(' || ch == '}' && ch2 == '{' || ch == ']' && ch2 == '[') {
                    stack.pop();
                }else {
                    return false; //不匹配
                }
            }
        }
        if (stack.isEmpty()) {
            return true;
        }
        //左括号多了
        return false;
    }
}

4.逆波兰表达式求值(栈)

题:力扣150. 逆波兰表达式求值 给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
有效的算符为 ‘+’、‘-’、‘*’ 和 ‘/’ 。
每个操作数(运算对象)都可以是一个整数或者另一个表达式。
两个整数之间的除法总是 向零截断 。
表达式中不含除零运算。
输入是一个根据逆波兰表示法表示的算术表达式。
答案及所有中间计算结果可以用 32 位 整数表示。

分析:逆波兰式又叫后缀表达式,将中缀表达式转换成后缀表达式,利用栈来进行求值,例如将 9+(3-1)*3+10/2 转换成后缀表达式,根据运算符的优先级顺序结果为:931-3*+102/+
那么计算后缀表达式的值,利用栈,将数组压入栈中,如果遇到符号,那么就弹出2个数字进行符号运算,得到的结果又压栈。
代码:

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<String> stack = new Stack<>(); //实例化栈
        for (int i = 0; i < tokens.length; i++) {
            if (!judeYun(tokens[i])) {//不是运算符
                stack.push(tokens[i]);
            }else {//是运算符
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int ret = count(tokens[i],num1,num2);
                String s = String.valueOf(ret);
                stack.push(s);
            }
        }
        return Integer.parseInt(stack.pop());
    }
    private boolean judeYun(String s) {
        if (s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")) {
            return true;
        }
        return false;
    }
    private int count(String s,int num1, int num2) {
        switch (s){
            case "+": return num1 + num2;
            case "-": return num1 - num2;
            case "*": return num1 * num2;
            case "/": return num1 / num2;
            default: return 0;
        }
    }
}

5.栈的弹出序列是否正确(栈)

题:力扣剑指 Offer 31. 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。

分析:这个题目在选择题也经常出现,通过入栈的序列来判断给出的弹出栈的序列是否合法。压入的序列在数组pushed中,弹出序列在数组poped中,如果即将压入的数字,等于弹出数字中指向的下标对应的值就压入并且弹出。

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Deque<Integer> stack = new LinkedList<>(); //实例化队列
        int j = 0;
        for (int i = 0; i < pushed.length; i++) {
            stack.push(pushed[i]);
            while (!stack.isEmpty() && j < popped.length && popped[j] == stack.peek()) {
                j++;
                stack.pop(); //相等的那么出栈
            }
        }
        if (!stack.isEmpty() || j < popped.length) {
            return false;
        }
        return true;
    }
}

6.最小栈问题(栈)

题:力扣155. 最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。

分析:要快速得到栈的最小元素,那么就实例化2个栈,一个是普通栈,另外一个是实时存放最小元素的栈。即借助一个辅助栈minStack用于存储获取stack普通栈的最小值。

class MinStack {
    public Stack<Integer> stack;
    public Stack<Integer> minStack;

    public MinStack() {
        stack = new Stack<>(); //普通栈
        minStack = new Stack<>(); //最小栈
    }
    
    public void push(int val) {
        if (stack.isEmpty()) {
            stack.push(val);
            minStack.push(val);
            return;
        }
        stack.push(val);
        if (val <= minStack.peek()) {
            minStack.push(val);
        }
    }
    
    public void pop() {
        int x = stack.pop();
        if (x == minStack.peek()) {
            minStack.pop();
        }
    }
    
    public int top() {
        if (stack.isEmpty()) {
            return -1;
        }
        return stack.peek();
    }
    
    public int getMin() {
        if (minStack.isEmpty()) {
            return -1;
        }
        return minStack.peek();
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

7.队列的概念

队列是一种操作首先的线性表,只允许在一端进行插入,另一端进行删除,具有先进先出的特性,插入的那端叫做队头,删除的这端叫做队尾。
队列中的操作方法:

方法 功能
offer(E e) 入队列
poll() 出队列
peek() 获取队头元素
size() 得到队列的大小
empty() 判断队列是否为空

队列也有链式存储和线性存储,其中线性存储通常实现的是循环队列,队列也有一种叫双端队列Deque。

8.循环队列

循环队列通常是队列的顺序存储,当采用顺序存储存储队列的时候,就会造成底层数组出现“虚满”,从而提出循环队列的思想。那么循环队列如何区分队空和队满呢?【3种方法】
①添加计数器
②牺牲一个内存空间
③使用标记
通常采用得是第二种方法,那么此时:
队空:rear == front
堆满:rear + 1 == front

力扣622. 设计循环队列
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。

代码:

class MyCircularQueue {
    public int[] elem;
    public int front; //队头
    public int rear; //队尾

    public MyCircularQueue(int k) {
        elem = new int[k+1]; //牺牲一个内存单元
    }
    
    public boolean enQueue(int value) {
        //入队要判断队列是否满
        if (isFull()) {
            return false;
        }
        //此时队列不满
        elem[rear] = value;
        rear = (rear + 1 + elem.length) % elem.length;
        return true;
    }
    
    public boolean deQueue() {
        if (isEmpty()) {
            return false;
        }
        front = (front + 1 + elem.length) % elem.length;
        return true;
    }
    
    public int Front() {
        if (isEmpty()) {
            return -1;
        }
        return elem[front];
    }
    
    public int Rear() {
        if (isEmpty()) {
            return -1;
        }
        int index = rear - 1 >= 0 ? rear - 1 : elem.length - 1;
        return elem[index];
    }
    
    public boolean isEmpty() {
        if (rear == front) {
            return true;
        }
        return false;
    }
    
    public boolean isFull() {
        if ((rear + 1 + elem.length) % elem.length == front) {
            return true;
        }
        return false;
    }
}

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue obj = new MyCircularQueue(k);
 * boolean param_1 = obj.enQueue(value);
 * boolean param_2 = obj.deQueue();
 * int param_3 = obj.Front();
 * int param_4 = obj.Rear();
 * boolean param_5 = obj.isEmpty();
 * boolean param_6 = obj.isFull();
 */

9.栈和队列的实例化

在Java种,Queue是一个接口,底层是通过链表实现的。

        //栈的实例化
        Stack<Integer> stack = new Stack<>(); //不太常用
        //双端队列实现栈
        Deque<Integer> stack1 = new LinkedList<>();
        Deque<Integer> stack2 = new ArrayDeque<>();
        //队列的实例化
        Queue<Integer> queue = new LinkedList<>();
//        Queue queue1 = new ArrayList<>();  报错

Deque是一个接口,栈和队列均可以使用该接口。

10.用队列实现栈

力扣225. 用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

分析:模拟入栈的时候,哪个队列不为空的时候就入哪个队列,当要出栈的时候,就把不为空的队列的元素移到为空的那个队列,并且把最后一个元素留下输出,相当于出栈。【相互倒】

代码:

import java.util.LinkedList;
import java.util.Queue;

//225. 用队列实现栈
class MyStack {
    //定义2个队列
    public Queue<Integer> queue1;
    public Queue<Integer> queue2;
    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }
    
    public void push(int x) {
        if (queue1.isEmpty() && queue2.isEmpty()) {
            queue1.offer(x);
        }else if (queue1.isEmpty()) {
            queue2.offer(x);
        }else {
            queue1.offer(x);
        }
    }
    
    public int pop() {
        if (queue1.isEmpty() && queue2.isEmpty()) {
            return -1;
        }
        if (!queue1.isEmpty()) {
            int size = queue1.size();
           while (size - 1 != 0) {
               queue2.offer(queue1.poll());
               size--;
           }
           return queue1.poll();
        }
        if (!queue2.isEmpty()) {
            int size = queue2.size();
            while (size - 1 != 0) {
                queue1.offer(queue2.poll());
                size--;
            }
            return queue2.poll();
        }
        return -1;
    }
    
    public int top() {
        if (queue1.isEmpty() && queue2.isEmpty()) {
            return -1;
        }
        if (!queue1.isEmpty()) {
            int size = queue1.size();
            while (size - 1 != 0) {
                queue2.offer(queue1.poll());
                size--;
            }
            int val = queue1.poll();
            queue2.offer(val);
            return val;
        }
        if (!queue2.isEmpty()) {
            int size = queue2.size();
            while (size - 1 != 0) {
                queue1.offer(queue2.poll());
                size--;
            }
            int val = queue2.poll();
            queue1.offer(val);
            return val;
        }
        return -1;
    }
    
    public boolean empty() {
        if (queue1.isEmpty() && queue2.isEmpty()) {
            return true;
        }
        return false;
    }
}

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

11.用栈实现队列

力扣232. 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

分析:一个栈专门用来入元素,当要出元素的时候,把专门用来入元素的栈的元素全部移动到栈2中,以后每次出队列就在栈2中出,直至栈2为空,再把栈1的元素拿过来【专注自己的功能事】

代码:

import java.util.Deque;
import java.util.LinkedList;

class MyQueue {
    public Deque<Integer> satck1;
    public Deque<Integer> satck2;
    public MyQueue() {
        satck1 = new LinkedList<>();
        satck2 = new LinkedList<>();
    }
    
    public void push(int x) {
        //选取一个栈,从一而终
        satck1.push(x);
    }
    
    public int pop() {
        if (empty()) {
            return -1;
        }
        if (!satck2.isEmpty()) {
            return satck2.pop();
        }
        while (!satck1.isEmpty()) {
            satck2.push(satck1.pop());
        }
        return satck2.pop();
    }
    
    public int peek() {
        if (empty()) {
            return -1;
        }
        if (!satck2.isEmpty()) {
            return satck2.peek();
        }
        while (!satck1.isEmpty()) {
            satck2.push(satck1.pop());
        }
        return satck2.peek();
    }
    
    public boolean empty() {
        return satck1.isEmpty() && satck2.isEmpty();
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

你可能感兴趣的:(Java数据结构,算法,java,数据结构)