Java栈和队列·下

Posted 晓星航

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java栈和队列·下相关的知识,希望对你有一定的参考价值。

Java栈和队列·下

大家好,我是晓星航。今天为大家带来的是 Java栈和队列·下 的讲解!😀

继上一个讲完的栈后,我们这次开始讲解队列!

2. 队列(Queue)

2.1 概念

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头(Head/Front)

在idea中看Queue(队列的底层逻辑代码)时 按下 alt + 7 可以查看它包含的所有方法:

2.2 实现

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。


class Node 
    public int val;
    public Node next;
    public Node(int val) 
        this.val = val;
    


public class MyQueue 
    public Node head;
    public Node last;

    /**
     * 尾插法
     * @param val
     */
    public void offer(int val) 
        Node node = new Node(val);
        if (head == null) 
            head = node;
            last = node;
         else 
            last.next = node;
            last = last.next;
        
    

    public int poll() 
        if (isEmpty()) 
            throw new RuntimeException("队列为空");
        
        int oldVal = head.val;
        head = head.next;
        return oldVal;
    
    public boolean isEmpty() 
        return this.head == null;
    

    public int peek() 
        if (isEmpty()) 
            throw new RuntimeException("队列为空");
        
        return head.val;
    



下面为测试代码:

public class TestDemo 
    public static void main(String[] args) 
        MyQueue queue = new MyQueue();
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        System.out.println(queue.peek());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    

Queue中方法总结:
add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素

2.3 相似方法的区别

1、add()和offer()区别:

add()和offer()都是向队列中添加一个元素。一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,调用 add() 方法就会抛出一个 unchecked 异常,而调用 offer() 方法会返回 false。因此就可以在程序中进行有效的判断

2、poll()和remove()区别:

remove() 和 poll() 方法都是从队列中删除第一个元素。如果队列元素为空,调用remove() 的行为与 Collection 接口的版本相似会抛出异常,是新的 **poll() 方法在用空集合调用时只是返回 null。**因此新的方法更适合容易出现异常条件的情况。

3、element() 和 peek() 区别:

element() 和 peek() 用于在队列的头部查询元素。与 remove() 方法类似,在队列为空时, element() 抛出一个异常,而 peek() 返回 null。

2.4 循环队列

实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列通常使用数组实现。

数组下标循环的小技巧

    1. 下标最后再往后(offset 小于 array.length): index = (index + offset) % array.length
  1. 下标最前再往前(offset 小于 array.length): index = (index + array.length - offset) % array.length

如何区分空与满

  1. 通过添加 size 属性记录 使用size和数组长度比较,确定满或者空
  1. 使用标志位flag,最开始falg = false,

    入队列时,每放一个元素,falg就置为true

    出队列时,每出一个元素,falg就置为false

    当front和rear相遇时,falg为true则队列为满,falg为false则队列为空。

  1. 浪费一个格子来区分空和满,每次存放元素之前,都先检查一下rear的下一个是不是front。如果是,那么就是满的

空:frontNext == rear

满:rearNext == front

3. 双端队列 (Deque)

3.1 概念

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。

双端队列的所有方法:

4.java中的栈和队列

栈的方法:

普通队列方法:

双端队列方法:

5. 栈和队列面试题

  1. 括号匹配问题。OJ链接
import java.util.Stack;
class Solution 
    public boolean isValid(String s) 
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < s.length(); i++) 
            char ch = s.charAt(i);
            if (ch == '(' || ch == '[' || ch == '' ) 
                //如果是左括号直接入栈
                stack.push(ch);
             else 
                //如果是右括号
                if (stack.empty()) 
                    //右括号多
                    System.out.println("右括号多!");
                    return false;
                
                char top = stack.peek();
                if (top == '(' && ch == ')' || top == '[' && ch == ']' || top == '' && ch == '') 
                    //如果左括号和右括号匹配 则弹出这个左括号
                    stack.pop();
                 else 
                    //左右括号不匹配
                    System.out.println("左右括号不匹配");
                    return false;
                
            
        
        if (!stack.empty()) 
            //左括号多
            System.out.println("左括号多!");
            return false;
        
        return true;
    

题目描述的很清楚,左右括号如果不匹配,无非是以下几种情况:

1、左括号多余右括号

2、右括号多余左括号

3、左右括号顺序不想匹配

在使用代码将这几种情况考虑完全后,便可轻易通过测试。

思路如下:

如果是左括号我们直接使其进栈,如果是右括号我们先判断右括号是否比左括号多,再看其是否相匹配,匹配则弹出相对应的左括号继续下一个括号的判断,如果不匹配则返回不匹配错误,在右括号全部判断完毕后,我们判断一下栈此时是否为空,如果为空则我们所有的括号都匹配成功即正确,如果不为空则是左括号比右括号要多,我们就返回false。

  1. 用队列实现栈。OJ链接
public class MyStack 
    private Queue<Integer> qu1;
    private Queue<Integer> qu2;
    public MyStack() 
        qu1 = new LinkedList<>();
        qu2 = new LinkedList<>();
    

    public void push(int x) 
        if (!qu1.isEmpty()) 
            qu1.offer(x);
         else if (!qu2.isEmpty())
            qu2.offer(x);
         else 
            qu1.offer(x);
        
    

    public int pop() 
        if (empty()) 
            return -1;
        
        if (!qu1.isEmpty()) 
            int size = qu1.size();
            for (int i = 0; i < size - 1; i++) 
                int val = qu1.poll();
                qu2.offer(val);
            
            return qu1.poll();
        
        if (!qu2.isEmpty()) 
            int size = qu2.size();
            for (int i = 0; i < size - 1; i++) 
                int val = qu2.poll();
                qu1.offer(val);
            
            return qu2.poll();
        
        return -1;
    

    public int top() 
        if (empty()) 
            return -1;
        
        if (!qu1.isEmpty()) 
            int val = -1;
            int size = qu1.size();
            for (int i = 0; i < size; i++) 
                val = qu1.poll();
                qu2.offer(val);
            
            return val;
        
        if (!qu2.isEmpty()) 
            int val = -1;
            int size = qu2.size();
            for (int i = 0; i < size; i++) 
                val = qu2.poll();
                qu1.offer(val);
            
            return val;
        
        return -1;
    

    public boolean empty() 
        return qu1.isEmpty() && qu2.isEmpty();
    

1、入栈的时候,入到不为空的队列,刚开始都为空指定入到一个队列。

2、出栈的时候,找到不为空的队列,出size-1个元素到另一个队列,剩下这个元素就是出栈的元素。

注意:这里有一个小问题我们很容易疏忽,就是在使用top和pop方法进行移动时我们qu1和qu2中的元素个数时变化的,因此我们的qu1.size() qu2.size()也会跟着变化,为了避免这个情况,我们在for循环的外面自己定义一个Int size = qu1.size(); int size = qu2.size();在之后的for循环中我们只需要让i小于size即可,此时的size就不是一个会变动的值了。

  1. 用栈实现队列。OJ链接
class MyQueue 

    public Stack<Integer> stack1;
    public Stack<Integer> stack2;
    public MyQueue() 
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    

    public void push(int x) 
        stack1.push(x);
    

    public int pop() 
        if (empty()) 
            return -1;
        
        if (stack2.empty()) 
            while (!stack1.isEmpty()) 
                stack2.push(stack1.pop());
            
        
        return stack2.pop();
    

    public int peek() 
        if (empty()) 
            return -1;
        
        if (stack2.empty()) 
            while (!stack1.isEmpty()) 
                stack2.push(stack1.pop());
            
        
        return stack2.peek();
    

    public boolean empty() 
        return stack1.isEmpty() && stack2.isEmpty();
    

思路:1、入队的时候,统一入到第1个栈

2、出队的时候,同一出第2个栈里面的元素,如果第2个为空,那么把第一个栈里面所有的元素,全部倒过来,然后再出栈顶的元素。

  1. 实现一个最小栈。OJ链接
class MinStack 
    private Stack<Integer> stack;
    private Stack<Integer> minStack;
    public MinStack() 
        stack = new Stack<>();
        minStack = new Stack<>();
    

    public void push(int val) 
        stack.push(val);
        if (!minStack.empty()) 
            int top = minStack.peek();
            //比较 小于等于的话 也要放进来
            if (val <= top) 
                minStack.push(val);
            
         else 
            minStack.push(val);
        
    

    public void pop() 
        int popVal = stack.pop();
        if (!minStack.empty()) 
            int top = minStack.peek();
            if (top == popVal) 
                minStack.pop();
            
        
    

    public int top() 
        return stack.peek();
    

    public int getMin() 
        return minStack.peek();
    

思路:这里我们采取了使用两个栈(一个普通栈 一个最小栈)来比较的方法,例如我们在push元素时,普通栈我们是直接放进去的,而最小栈我们则是通过比较,如果要放的元素比我们最小栈栈顶的元素小或等于我们便在最小栈也放入一份。

在pop弹出栈顶元素时我们同样是直接弹出普通栈的栈顶元素,然后比较这个弹出的元素和最小栈栈顶元素的大小是否相等,如果相等我们则还需要再pop一次最小栈的栈顶元素。

top方法和我们stack栈中的peek方法一样,我们直接返回stack的peek方法即可。

getMin方法是返回栈中最小元素,我们这里有两个栈,而最小栈的原理就是将最小的元素通过压栈(头插)的方式进入最小栈,因此我们最小栈的最小值永远是栈顶的元素,我们直接返回最小栈的栈顶元素即可。

  1. 设计循环队列。OJ链接
class MyCircularQueue 
    public int[] elem;
    public int front;//队头下标
    public int rear;//队尾下标
    public MyCircularQueue(int k) 
        this.elem = new int[k + 1];
    

    /**
     * 入队操作
     * @param value
     * @return
     */
    public boolean enQueue(int value) 
        if (isFull()) 
            return false;
        
        this.elem[rear] = value;
        //rear++;
        rear = (rear + 1) % elem.length;
        return true;
    

    /**
     * 出队操作
     * @return
     */
    public boolean deQueue() 
        if (isEmpty()) 
            return false;
        
        front = (front + 1) % elem.length

Java集合与数据结构 栈和队列

Java集合与数据结构 栈和队列

栈(Stack)

概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶

基本方法


示例代码如下:

public class TestDemo 
    public static void main(String[] args) 
        Stack<Integer> stack = new Stack<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        //stack.peek() 拿到栈顶元素
        System.out.println(stack.peek());//3
        //弹出栈顶元素
        System.out.println(stack.pop());//3
        System.out.println(stack.peek());//2
        System.out.println(stack.pop());//2
        System.out.println(stack.pop());//1
        System.out.println(stack.empty());//t
        System.out.println(stack.isEmpty());//t
    

该代码的执行结果如下:

栈的习题

出栈顺序不可能

一个栈的入栈序列是a,b,c,d,e则栈的不可能的输出序列是:()
A edcba B decba C dceab D abcde
解答:
栈中的数据元素遵守后进先出,对于A选项是可以作为栈的输出序列的,进a ,进b,进c,进d,进e,出e,出d,出c,出b,出a,所以出栈顺序为edbca;对于B选项是可以作为栈的输出序列的,进a,进b,进c,进d,出d,进e,出e,出c,出b,出a,所以出栈顺序为decba;对于D选项也是可以作为栈的输出序列的,进a,出a,进b,出b,进c,出c,进d,出d,进e,出e,所以出栈顺序为abcde;而C选项的出栈顺序可能为dceba,绝不可能为dceab。

中缀表达式转后缀表达式(逆波兰式)

中缀表达式转后缀表达式的方法:
1.遇到操作数:直接输出(添加到后缀表达式中)
2.栈为空时,遇到运算符,直接入栈
3.遇到左括号:将其入栈
4.遇到右括号:执行出栈操作,并将出栈的元素输出,直到弹出栈的是左括号,左括号不输出。
5.遇到其他运算符:加减乘除:弹出所有优先级大于或者等于该运算符的栈顶元素,然后将该运算符入栈。
6.最终将栈中的元素依次出栈,输出。

这里我们再介绍一种简单方法:

手动实现栈

public class MyStack 
    private int[] elem;
    private int top;//既可以代表下标:这个位置就是当前可以存放数据的下标
    // 也可以代表当前有多少个元素

    public MyStack() 
        this.elem = new int[10];
    

    public boolean isFull() 
        return this.top == this.elem.length;
    

    public int push(int item) 
        if(isFull()) 
            throw new RuntimeException("栈为满");
        
        this.elem[this.top] = item;
        this.top++;
        return this.elem[this.top-1];
    

    /*
     * 弹出栈顶元素 并且删除
     * @return
     */
    public int pop() 
        if(empty()) 
            //return -1;
            throw new RuntimeException("栈为空");
        
        this.top--;
        return this.elem[this.top];
    

    /*
     * 拿到栈顶元素不删除
     * @return
     */
    public int peek() 
        if(empty()) 
            //return -1;
            throw new RuntimeException("栈为空");
        
        return this.elem[this.top-1];
    

    public boolean empty() 
        return this.top == 0;
        //return size() == 0;
    

    public int size() 
        return this.top;
    

    public static void main(String[] args) 
        MyStack myStack = new MyStack();
        myStack.push(12);
        myStack.push(23);
        myStack.push(34);
        myStack.push(45);
        myStack.push(56);
        System.out.println(myStack.peek());
        System.out.println(myStack.pop());
        System.out.println(myStack.peek());
        System.out.println(myStack.empty());
    

该代码执行效果如下:

队列(Queue)

概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾(Tail/Rear)
出队列:进行删除操作的一端称为队头(Head/Front)

基本方法


示例代码如下:

public class QueueTestDemo 
    public static void main(String[] args) 
        //普通的队列
        Queue<Integer> queue = new LinkedList<>();
        //此时调用的add方法,默认从队尾入队
        queue.add(1);
        queue.offer(2);
        queue.offer(3);
        queue.offer(4);
        //得到队头元素,不删除
        System.out.println(queue.peek());
        //拿出队头元素
        System.out.println(queue.poll());
        System.out.println(queue.peek());
        System.out.println(queue);
    

此代码的执行结果如下:

链表实现队列

class Node
    public int val;
    public Node next;

    public Node(int val) 
        this.val = val;
    

public class MyQueueByLinkedList 
    public Node first;//队头
    public Node last;//队尾

    public boolean offer(int val) 
        Node node = new Node(val);
        //判断是否第一次插入
        if (this.first == null) 
            this.first = node;
            this.last = node;
        else 
            this.last.next = node;
            this.last = node;
        
        return true;
    
    /*
     * 拿出队头元素
     */
    public int poll() throws RuntimeErrorException
        if (isEmpty()) 
            throw new RuntimeException("队列为空~");
        
        int ret = this.first.val;
        this.first = this.first.next;
        return ret;
    
    public boolean isEmpty() 
        if (this.last == null && this.first == null) 
            return true;
        
        return false;
    
    /*
     * 得到队头元素不删除
     */
    public int peek() throws RuntimeErrorException
        if (isEmpty()) 
            throw new RuntimeException("队列为空~");
        
        int ret = this.first.val;
        return ret;
    

    public static void main(String[] args) 
        MyQueueByLinkedList myQueue = new MyQueueByLinkedList();
        myQueue.offer(1);
        myQueue.offer(2);
        myQueue.offer(3);
        myQueue.offer(4);
        //得到队头元素,不删除
        System.out.println(myQueue.peek());
        //拿出队头元素
        System.out.println(myQueue.poll());
        System.out.println(myQueue.peek());
    

该代码的执行结果为:

循环队列

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

手动实现循环队列手动实现循环队列力扣链接

public class MyCircularQueue 
    private int front;
    private int rear;
    private int[] elem;


    public MyCircularQueue(int k) 
        this.elem = new int[k+1];
        this.front = 0;
        this.rear = 0;

    

    /*
     * 判断队列是否为空
     * @return
     */
    public boolean isEmpty() 
        return this.front == this.rear;
    


    /*
     * 判断队列是否已满
     * @return
     */
    public boolean isFull() 
        return (this.rear + 1) % this.elem.length == this.front;
    

    /*
     * 入队
     * @param value
     * @return
     */
    public boolean enQueue(int value) 
        if (isFull()) 
            return false;
        
        //放到数组的rear下标
        this.elem[this.rear] = value;
        this.rear = (this.rear + 1) % this.elem.length;
        return true;
    


    /*
     * 出队
     * @return
     */
    public boolean deQueue() 
        if (isEmpty()) 
            return false;
        
        this.front = (this.front + 1) % this.elem.length;
        return true;
    

    /*
     * 得到队头元素
     * @return
     */
    public int Front() 
        if (isEmpty()) 
            return -1;
        
        int ret = this.elem[this.front];
        return ret;
    

    /*
     * 得到队尾元素
     * @return
     */
    public int Rear() 
        int index;
        if (isEmpty()) 
            return -1;
        
        if (this.rear == 0) 
            index = this.elem.length - 1;
         else 
            index = this.rear-1;
        
        return this.elem[index];
    

该代码执行通过。

用队列实现栈

队列实现栈力扣链接

题目描述:
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

class MyStack 
    private Queue<Integer> qu1 = new LinkedList<>();
    private Queue<Integer> qu2 = new LinkedList<>();
    /** Initialize your data structure here. */
    public MyStack() 

    
    /** Push element x onto stack. */
    public void push(int x) 
        if(!qu1.isEmpty()) 
            qu1.offer(x);
         else if (!qu2.isEmpty()) 
            qu2.offer(x);
         else 
            qu1.offer(x);
        
    
    /** Removes the element on top of the stack and returns that element. */
    public int pop() 
        if(empty()) 
            return -1;
        
        if(!qu1.isEmpty()) 
            int size = qu1.size();
            for(int i=0;i<size-1;i++)
                qu2.offer(qu1.poll());
            
            return qu1.poll();
         else 
            int size = qu2.size();
            for(int i=0;i<size-1;i++)
                qu1.offer(qu2.poll());
            
            return qu2.poll();
        
    
    /** Get the top element. */
    public int top() 
         if(empty()) 
            return -1;
        
        if(!qu1.isEmpty()) 
            int size = qu1.size();
            int cur = -1;
            for(int i=0;i<size;i++)
                cur = qu1.poll();
                qu2.offer(cur);
            
            return cur;
         else 
            int size = qu2.size();
            int cur = -1;
            for(int i=0;i<size;i++)
                cur = qu2.poll();
                qu1.offer(cur);
            
            return cur;
        
    
    /** Returns whether the stack is empty. */
    public boolean empty() 
        return qu1.isEmpty() && qu2.isEmpty();
    

该代码执行通过。

用栈实现队列

栈实现队列力扣链接

题目描述:
仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty)
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾 int pop() 从队列的开头移除并返回元素 int peek()
返回队列开头的元

以上是关于Java栈和队列·下的主要内容,如果未能解决你的问题,请参考以下文章

Java集合与数据结构 栈和队列

数据结构之栈和队列及其Java实现

数据结构 Java 版详解栈和队列的实现

数据结构 Java 版详解栈和队列的实现

Java栈和队列·上

数据结构 Java数据结构 栈和队列 以及LeetCode相关面试题