认识栈和队列

一.栈

1.1 栈的概念

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

栈在生活中是十分常见的,比如经常乘坐的电梯,就满足先进后出的特点

 

1.2 栈的方法

下图为栈的继承关系

 栈只有无参构造,所以栈都初始化为

Stack<T> stack=new Stack();

下面看看栈的方法吧

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

 1.3 栈的模拟实现

1.3.1 用数组实现栈

为了满足栈先进后出的规则,使用数组实现栈时,栈顶应该是数组尾部

下面给出用数组模拟实现栈的代码

 

public class ArrayListStack {
    int[] elem;
    int size;
    static final int DEFAULTCAPACITY=10;
    public ArrayListStack() {
        elem=new int[DEFAULTCAPACITY];
    }
    public int push(int data){
        if(size==elem.length) {
            ensureCapacity(elem.length*2);
        }
        elem[size++]=data;
        return data;
    }
    private void ensureCapacity(int capacity){
        elem= Arrays.copyOf(elem,capacity);
    }
    public int pop(){
        if(size==0) {
            throw new NullException("栈内没有元素");
        }
        return elem[--size];
    }
    public int peek(){
        if(size==0) {
            throw new NullException("栈内没有元素");
        }
        return elem[size-1];
    }
    public boolean empty(){
        return size==0;
    }
}

1.3.2 用链表实现栈

如果使用双向链表实现栈,既可以将last作为栈顶,也可以将head作为栈顶,push和pop操作的时间复杂度都是O(1)

 如果使用单向链表实现栈,最好将head作为栈顶,push和pop时间复杂度为O(1)

如果将last作为栈顶,pop操作必须要找到last的前驱,时间复杂度是O(n)

 因为双向链表实现栈比较简单,下面给出单向链表模拟实现栈的代码

public class LinkedListStack {
    class Node{
        int val;
        Node prev;
        Node next;

        public Node(int val) {
            this.val = val;
        }
    }
    Node head;
    public int push(int data) {
        Node node=new Node(data);
        if(head==null) {
            head=node;
            return data;
        }
        node.next=head;
        head=node;
        return data;
    }
    public int pop(){
        if(head==null) {
            throw new NullException("栈内没有元素");
        }
        int data=head.val;
        head=head.next;
        return data;
    }
    public int peek() {
        if(head==null) {
            throw new NullException("栈内没有元素");
        }
        return head.val;
    }

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

}
//异常定义
public class NullException extends RuntimeException{
    public NullException() {
    }

    public NullException(String message) {
        super(message);
    }
}

1.4 栈的练习

1.4.1 栈的压入、弹出序列

栈的压入,弹出序列

题目如下:

 这道题的解题思路比较简单,我们只需要用栈来模拟元素入栈,弹出的过程即可。

基本过程:

用下标i遍历pushV,用下标j遍历popV,i每遍历一个元素,都要将当前元素入栈

将pushV[i]入栈后有两种情况:

1.栈顶元素!=popV[j],说明当前元素没有被弹出,i继续向后遍历即可

 

2. 栈顶元素==popV[j],说明当前pushV[i]入栈后立即被弹出,直接弹出栈顶元素即可

 但是又出现一个问题:

所以要使用while循环继续弹出栈顶元素

 

最后,i遍历完pushV数组后,如果栈为空,说明所有的元素已被弹出,即入栈和出栈数组是匹配的,否则不匹配

实例代码如下

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
      Stack<Integer> st=new Stack<>();
      for(int i=0,j=0;i<pushA.length;i++) {
         st.push(pushA[i]);
         //此处不能用if,可能会一直弹出栈顶元素
         while(!st.empty()&&st.peek()==popA[j]) {
            st.pop();
            j++;
         }
      }
      if(st.empty()) return true;
      return false;
    }
}

1.4.2 最小栈

最小栈 

题目描述

这道题有两个难点:

1. 怎么保存最小的元素?

2. 当最小元素被弹出时怎么更新最小元素?

可以使用另一个栈专门保存当前入栈的最小元素,举例:

 用minSt栈顶保存当前在栈内的最小元素

 

当元素入栈时分为三种情况:

1.2.入栈元素大于minSt栈顶元素,只入st,不入minSt

 

2.入栈元素小于minSt栈顶元素/minSt为空栈--入栈

 

第三种情况就是入栈元素==minSt栈顶元素,这种情况我们先来看看pop方法再议

pop元素分为两种情况

1. 要pop的元素是minSt的栈顶元素,两个栈同时pop

 2. 要pop的元素不是minSt的栈顶元素,只pop普通栈

 这样我们就实现了pop元素的同时更新最小元素

现在再来看push的第三种情况:如果要入栈的元素==minSt的栈顶元素

如下图,如果不入minSt栈,当我们要弹出-1时,根据刚才pop方法的逻辑,两个栈的-1均会被弹出,这时st栈内最小元素是-1,但它却并未被保存在minSt的栈顶

所以第三种情况也要入栈

 实例代码如下

class MinStack {
    Stack<Integer> st;
    Stack<Integer> minSt;
    public MinStack() {
      st=new Stack<>();
      minSt=new Stack<>();
    }
    
    public void push(int val) {
      if(minSt.empty()||val<=minSt.peek()) {
          minSt.push(val);
      }
      st.push(val);
    }
    
    public void pop() {
      if(!st.empty()){
          if(st.pop().equals(minSt.peek())) {
              minSt.pop();
          }
      }
    }
    
    public int top() {
       return st.peek();
    }
    
    public int getMin() {
        return minSt.peek();
    }
}

二. 队列

2.1 队列的概念

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

 

 2.2 队列的方法

在Java中,队列是一个接口,是由LinkedList实现的

 上图中的Deque是双端队列,下文会提到

由于先进先出的局限性,队列的方法并不多

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

实际上Queue还有add和remove方法,与offer方法不同的是,add方法在堆空间不够时,会抛异常,但是offer方法只会返回false

 remove方法当队列为空时,同样会抛异常,poll方法当队列为空时会返回null

 2.3 队列的实现

可以使用双链表实现队列

如果让last当队尾,head当队头,push和pop方法时间复杂度为O(1);如果让last当队头,head当队尾,push和pop方法的时间复杂度同样是O(1)

 

也可以用单链表实现队列 

如果让last当队尾,head当队头,push和pop方法时间复杂度均为O(1)

相反,如果让last当队头,pop元素时需要找到last的前驱,时间复杂度为O(n)

下面用单链表模拟实现队列

public class SingleQueue {
    class Node {
        int val;
        Node prev;
        Node next;

        public Node(int val) {
            this.val = val;
        }
    }
    Node head;
    Node last;
    int size;
    public boolean offer(int data) {
        Node node=new Node(data);
        if(head==null) {
            head=node;
            last=node;
        } else {
            last.next=node;
            last=node;
        }
        size++;
        return true;
    }
    public int poll() {
        if(head!=null) {
            int data=head.val;
            head=head.next;
            size--;
            return data;
        }
        return -1;//将-1当做Queue为空的标志
    }
    public int peek() {
        if(head!=null) {
            return head.val;
        }
        return -1;//将-1作为队列为空的标记
    }
    public int size(){
        return size;
    }
    public boolean isEmpty(){
        return head==null;
    }
}

 2.4 循环队列

实际上数组也可以实现队列,但这种队列呈环形。
那么环形队列的方法是怎么实现的呢?
offer方法---队尾插入元素,队尾向前移动
poll方法---队头向前移动,原先队头的元素可以被覆盖

 那么当队尾==数组长度时,这个队列并不一定是满的,比如下面这种情况

 所以在插入元素时,last=(last+1)%len;

删除元素时同样,head=(head+1)%len;

那么又出现一个问题:队列为空或队列是满的时候,last==head,怎么区分这两种情况呢?

方式1:使用size来记录队列长度

方式2:浪费一个位置(见下图)

 

 下面我们实现一下循环队列,下面的链接可以验证正确性       

​​​​​​循环队列

class MyCircularQueue {
    private int[] arr;
    int head;
    int last;
    public MyCircularQueue(int k) {
        arr=new int[k+1];
    }
    
    public boolean enQueue(int value) {
       if(isFull()) {
           return false;
       }
       arr[last]=value;
       last=(last+1)%arr.length;
       return true;
    }
    
    public boolean deQueue() {
       if(isEmpty()) {
           return false;
       }
       head=(head+1)%arr.length;
       return true;
    }
    
    public int Front() {
        if(isEmpty()) return -1;
       return arr[head];
    }
    
    public int Rear() {
        if(isEmpty()) return -1;
        int index=(last-1+arr.length)%arr.length;
       return arr[index];
    }
    
    public boolean isEmpty() {
        return head==last;
    }
    
    public boolean isFull() {
        return (last+1)%arr.length==head;
    }
}

 2.5 双端队列

双端队列( deque )是指允许两端都可以进行入队和出队操作的队列
deque “double ended queue” 的简称。

 

Deque 是一个接口,使用时必须创建 LinkedList 的对象。

 实际上,栈和队列都可以用Deque来实现,所以Deque的适用场景要比栈和队列多

Deque<Integer> stack = new ArrayDeque<>(); // 双端队列的线性实现,可以代替栈
Deque<Integer> queue = new LinkedList<>(); // 双端队列的链式实现,可以代替队列
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不 会敲代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值