栈和队列stack&queue

栈stack

栈的类型:顺序栈,链式栈

顺序栈:底层基于数组组成,大小固定,入栈出栈的时候直接对数组尾部进行操作,不需要移动元素,所以速度较快,时间复杂度为O ( 1 ) 。但是当栈满时,需要扩容,而扩容右比较耗性能。

链式栈:采用链表作为底层,时间复杂度也是O ( 1 ),虽然更耗空间,但大小不固定。

综合情况
由于顺序栈扩容要将已满的顺序栈复制入一个更大的空栈则需要花费大量的时间,因而,在不确定有多大规模的数据需要存入栈中时,建议采用链式表作为存储结构。
另一方面来说,链式栈存储指针域需要花费更多空间,因而在得知入栈数据规模又希望节省存储空间时,采用顺序栈。

栈的动作方法

  入栈(压栈):push()

  出栈(退栈):pop()

  查看栈顶元素:peek()、getTop()等

  为空:isEmpty()

以链式栈为例,实现栈的实现方法

public class myLinkedStack {
    Node head;  //头节点,栈顶
    int size;   //栈存储的数据量

    private static class Node{
        Object data;
        Node next;

        Node(Object obj){
            data = obj;
        }
    }

    public myLinkedStack(){
        head = null;
        size = 0;
    }

    public void push(Object obj){
        Node p = new Node(obj);
        p.next = head; //新结点的指针域指向原栈顶
        head = p;    //将新结点设置为栈顶
        size++;
    }

    public Object pop() throws Exception{
        if (head == null){
            throw new Exception("stack is empty");
        }
        Object obj = head.data;  //保存栈顶数据元素
        head = head.next;   //将原栈顶的后方元素设置为栈顶
        size--;
        return obj;
    }

    public Object getTop() throws Exception{
        if (head == null){
            throw new Exception("stack is empty");
        }
        return head.data;
    }

    public boolean isEmpty(){
        return head == null;
    }
}

java中的stack接口

java中的stack类继承子vector接口,是相对较老的接口,不推荐使用。在Stack类中,被赋予了出栈,入栈,判断栈顶元素,栈空,计算某元素离栈顶的位置5个操作方法。

既然有了Stack类,那为什么我们在使用的过程中经常用到的是Collection包下的deque而不是Stack?

主要还是因为stack集成自vector接口,尽管线程安全,但是开销较大,同时,vector接口下的stack底层基于数组实现,需要扩容,又增加了一定的开销,所以同vector接口一样逐渐被放弃,从而改为集成自Deque接口。

官方:Deque接口及其实现提供了一组更完整和一致的LIFO堆栈操作,应优先于Stack使用。
关于线程安全,应该使用java.util.concurrent包下的类。

java中的Deque接口

Deque是支持在两端插入和删除元素的线性集合。可将其称为"双端队列",双端队列也可以用作LIFO(后进先出)堆栈。
作为栈使用时,可使用入栈push,出栈pop,peek看到栈顶元素,isEmpty空栈等方法

void push​(E e);
E pop();
E peek();
boolean isEmpty();

继承子Deque接口的两个实现类ArrayDeque和LinkList

ArrayDeque

ArrayDeque基于数组实现栈,java为他提供了一下成员,方法

成员

 transient Object[] elements;    //存储数据的数组
    transient int head;       //头部标记
    transient int tail;       //尾部标记

构造函数

设置初始容量为16,2)给定整数构建双端队列,3)根据给出的集合及其元素构建双端队列

public ArrayDeque() {
        this.elements = new Object[16];
    }

    public ArrayDeque(int var1) {
        this.elements = new Object[var1 < 1 ? 1 : (var1 == 2147483647 ? 2147483647 : var1 + 1)];
    }

    public ArrayDeque(Collection<? extends E> var1) {
        this(var1.size());   //集合大小
        this.copyElements(var1);   //复制元素
    }

入栈

public void push(E var1) {
        this.addFirst(var1);  //调用在首部插入的方法
    }
    public void addFirst(E var1) {
        if (var1 == null) {
            throw new NullPointerException();   
        } else {
            Object[] var2 = this.elements;    
            var2[this.head = dec(this.head, var2.length)] = var1;   //dec方法是:让第一个参数标识的下标往前挪动一个单位(带循环的挪动),因而此方法是,将新数据添加至头标记的前一个位置
            if (this.head == this.tail) {   //如果头标记和尾标记碰头,也即数组已存满,则扩容
                this.grow(1);    //扩容方法见2.5
            }
        }
    }

出栈

 public E pop() {
        return this.removeFirst();   //调用首部删除方法
    }
    public E removeFirst() {
        Object var1 = this.pollFirst(); 
        if (var1 == null) {
            throw new NoSuchElementException();
        } else {
            return var1;
        }
    }
    public E pollFirst() {
        Object[] var1;
        int var2;
        Object var3 = elementAt(var1 = this.elements, var2 = this.head);   //获取头标记指向元素
        if (var3 != null) {   //若非空
            var1[var2] = null;   //将其置空
            this.head = inc(var2, var1.length);   //往后移动头标记(循环移动)
        }

        return var3;
    }

查看栈顶元素

public E peek() {
        return this.peekFirst();
    }
    public E peekFirst() {
        return elementAt(this.elements, this.head);  //定位头标记元素并返回
    }

扩容

private void grow(int var1) {
        int var2 = this.elements.length;  //数组长度
        int var4 = var2 < 64 ? var2 + 2 : var2 >> 1;   //原数组长度小于64则为原数组长度+2;否则减半
        int var3;
        if (var4 < var1 || (var3 = var2 + var4) - 2147483639 > 0) {
            var3 = this.newCapacity(var1, var4);   //新数组大小可看作:var1和var4中更大的那个+原数组大小
            //若var4小于给出的期望增长大小,则新大小为原数组大小+var4
        }

        Object[] var5 = this.elements = Arrays.copyOf(this.elements, var3);   //拷贝至新数组
        if (this.tail < this.head || this.tail == this.head && var5[this.head] != null) {  //调整数据位置
            int var6 = var3 - var2;
            System.arraycopy(var5, this.head, var5, this.head + var6, var2 - this.head);
            int var7 = this.head;

            for(int var8 = this.head += var6; var7 < var8; ++var7) {
                var5[var7] = null;
            }
        }

    }

    private int newCapacity(int var1, int var2) {
        int var3 = this.elements.length;
        int var4;
        if ((var4 = var3 + var1) - 2147483639 > 0) {   //超过最大整数
            if (var4 < 0) {
                throw new IllegalStateException("Sorry, deque too big");
            } else {
                return 2147483647;
            }
        } else if (var1 > var2) {  //若参数1>参数2,则新容量为:原数组大小+参数1
            return var4;
        } else {   //若参数1<参数2,则新容量为:原数组大小+参数2
            return var3 + var2 - 2147483639 < 0 ? var3 + var2 : 2147483639;
        }
    }

总结

1.栈为"先进后出表",通常用来临时存储接下来可能还会马上用到的元素。
2.顺序栈和链式栈的出入栈操作没有效率上的差别,而顺序栈固定大小,扩容操作带来较大开销,故通常考虑链式栈而不考虑顺序栈。
3.使用ArrayDeque(顺序)或LinkedList(链式)来代替Stack,需要线程安全时使用ConcurrentLinkedDeque

栈的应用

典型应用:有效的括号

输入:s = "{[]}"
输出:true

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

class Solution {
    public boolean isValid(String s) {
        if(s.length()%2==1){return false;}
        if(s.isEmpty()){return true;}
            
        Deque<Character> stack=new LinkedList<Character>();
        for(char c:s.toCharArray()){
            if(c=='(')
            //压栈
                stack.push(')');
            else if(c=='{')
                stack.push('}');
            else if(c=='[')
                stack.push(']');
            else if(stack.isEmpty()||c!=stack.pop())
                return false;
        }
        if(stack.isEmpty())
            return true;
        return false;
    }
}

典型应用:用栈实现队列

class MyQueue {
    private Deque<Integer> deque  = new LinkedList<>();
    private Deque<Integer> deque2  = new LinkedList<>();
    private int front;

    public MyQueue() {
    }
    
    public void push(int x) {
        //如果为空将本元素设置为栈顶元素,在本应用中永远是栈1进行压栈,出栈的永远是栈2
        if(deque.isEmpty()){
            front = x;
        }f
        deque.push(x);
    }
    
    public int pop() {
        //当栈2有值时,直接返回栈2的顶元素,当栈2没有值时,再将栈1的全部元素加入栈2
        if(deque2.isEmpty()){
           while(!deque.isEmpty()){
                deque2.push(deque.pop());
           }
        }
       return deque2.pop();
    }
    
    public int peek() {
        //当栈2不为空是返回栈2的顶元素,当栈2为空时,将栈1的全部元素加入栈2
        if(!deque2.isEmpty()){
            return deque2.peek();
        }
        return front;
    }
    
    public boolean empty() {
        return deque.isEmpty() && deque2.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();
 */

典型应用:双队列实现栈

class MyStack {    
   private Queue<Integer> a;//输入队列 private Queue<Integer> b;//输出队列 public MyStack() { a = new LinkedList<>(); b = new LinkedList<>(); } public void push(int x) {
      //a始终为空,先将x插入队列
      //将b中的所有元素再插进去,再将b和a交换,保证a始终为空,就是先了栈结构 a.offer(x);
// 将b队列中元素全部转给a队列 while(!b.isEmpty()) a.offer(b.poll()); // 交换a和b,使得a队列没有在push()的时候始终为空队列 Queue temp = a; a = b; b = temp; } public int pop() { return b.poll(); } public int top() { return b.peek(); } public boolean empty() { return b.isEmpty(); } }

声明:

 ————————————————
版权声明:本文为CSDN博主「Sakura_lht」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_45608306/article/details/101120948

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

 
posted @ 2021-10-12 18:14  高频率巨炮  阅读(81)  评论(0)    收藏  举报