栈和队列

栈 (Stack)

栈是一种线性结构;相比数组,栈对应的操作是数组的子集;只能从一端加元素,也只能从一端取出元素;这一端称为栈顶。
先进后出的数据结构:
在这里插入图片描述

栈的应用:

  • 无处不在的撤销(ctrl+z)
    在这里插入图片描述

  • 程序调用的系统栈
    在这里插入图片描述
    计算机执行A方法时,执行到第二行,发现要先去执行方法B,此时系统就要先记录一个信息A2(表示方法A执行到了第二行,就去执行B了);同理,执行B方法的时候,执行到第二行时,发现又要去执行C方法,又在栈中记录B2;然后程序去执行C,执行完后,程序就要去看一下系统栈了,发现栈顶是B2,取出B2,然后顺着B2之前执行到的位置往下执行,执行完B后;程序又去栈中查看栈顶元素,发现是A2,然后又执行A2,执行完A后,然后就又去栈中查看,发现已经没有元素了,说明已经执行完了。

栈的实现(数组的方式)

Stack<E>

  • void push(E) 添加元素。入栈 。 时间复杂度O(1) 如果触发了resize,均摊之后,依然是O(1)
  • E pop() 取出元素 。 出栈。 O(1) 如果触发了resize,均摊之后,依然是O(1)
  • E peek() 查看栈顶元素是什么 O(1)
  • int getSize() 栈里几个元素 O(1)
  • boolean isEmpty() 栈是否为空 o(1)

使用上一篇,编写的动态数组类实现栈,当然也能使用其他数据结构,比如链表。

栈接口:

public interface Stack<E> {
    int getSize();
    boolean isEmpty();
    void push(E e);
    E pop();
    E peek();
}

实现:

public class ArrayStack<E> implements Stack<E> {
    private Array<E> array;

    public ArrayStack(int capacity){
        array=new Array<>(capacity);
    }
    public ArrayStack(){
        array=new Array<>();
    }

    @Override
    public int getSize() {
        return array.getSize();
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }
    //使用动态数组实现栈的时候才存在容量这个概念(也可以使用链表)
    public int getCapacity(){
        return array.getCapacity();
    }
    @Override
    public void push(E e) {
      array.addLast(e);
    }

    @Override
    public E pop() {
        return array.removeLast();
    }

    @Override
    public E peek() {
        return array.getLast();
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder=new StringBuilder();
        stringBuilder.append("Stack:");
        stringBuilder.append("[");
        for (int i=0;i<array.getSize();i++){
            stringBuilder.append(array.get(i));
            if (i!=array.getSize()-1)
                stringBuilder.append(",");
        }
        //top表示右边是栈顶
        stringBuilder.append("]top");
        return stringBuilder.toString();
    }
}

栈的具体应用

括号匹配——编译器
https://leetcode-cn.com/problemset/all/
题库的第二十题。

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 c = s.charAt(i);
            if (c=='(' || c=='{' || c=='[')
                stack.push(c);
            else {
                if (stack.isEmpty())
                    return false;
                Character pop = stack.pop();
                if (c==')' && pop!='(')
                    return false;
                if (c=='}' && pop!='{')
                    return false;
                if (c==']' && pop!='[')
                    return false;
            }
        }
        //匹配完之后,如果栈里还有元素,没人和它匹配,也是不行的
        return stack.isEmpty();
    }
}

队列(Queue)

队列也是一种线性结构;
相比数组,队列对应的操作是数组的子集;
只能从一端(队尾)添加元素,只能从另一端(队首)取出元素;先进先出的数据结构;

队列的实现(数组方式)

Queue<E>

  • void enqueue(E) 入队 O(1) 均摊
  • E dequeue() 出队 O(n) 性能消耗大
  • E getFront() 查看队首元素 O(1)
  • int getSize() 获取队列的元素个数 O(1)
  • boolean isEmpty() 队列是否为空 O(1)

队列接口:

public interface Queue<E> {
    void enqueue(E e);
    E dequeue();
    E getFront();
    int getSize();
    boolean isEmpty();
}

队列实现:

public class ArrayQueue<E> implements Queue<E> {
    private Array<E> array;

    public ArrayQueue(){
        array=new Array<>();
    }

    public ArrayQueue(int capacity){
        array=new Array<>(capacity);
    }

    /**
     * 入队
     * @param e
     */
    @Override
    public void enqueue(E e) {
         array.addLast(e);
    }
    /**
     * 出队
     * @return
     */
    @Override
    public E dequeue() {
        return array.removeFirst();
    }
    /**
     * 查看队首元素
     * @return
     */
    @Override
    public E getFront() {
        return array.getFirst();
    }
    /**
     * 获取队列的元素个数
     * @return
     */
    @Override
    public int getSize() {
        return array.getSize();
    }
    /**
     * 队列是否为空
     * @return
     */
    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }
    //使用动态数组实现栈的时候才存在容量这个概念(也可以使用链表)
    public int getCapacity(){
        return array.getCapacity();
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder=new StringBuilder();
        stringBuilder.append("Queue:");
        stringBuilder.append("front[");
        for (int i=0;i<array.getSize();i++){
            stringBuilder.append(array.get(i));
            if (i!=array.getSize()-1)
                stringBuilder.append(",");
        }
        stringBuilder.append("]tail");
        return stringBuilder.toString();
    }

    public static void main(String[] args) {
        ArrayQueue<Integer> arrayQueue=new ArrayQueue();
        for (int i=0;i<10;i++){
            arrayQueue.enqueue(i);
            System.out.println(arrayQueue);
            if (i%3==2){
                arrayQueue.dequeue();
                System.out.println(arrayQueue);
            }
        }
    }
}

数组队列的问题

出栈的时候时间复杂度O(n),太高了,出一个,后面的元素都得往前移动一位;

循环队列:
维护两指针front tail

  • 空队列
    在这里插入图片描述
  • 有元素入队,只需要tail往后移动一位就行
    在这里插入图片描述
    在这里插入图片描述
  • 有元素出队。front向后移动一位就行。
    在这里插入图片描述
  • 为什么叫循环队列呢?
    当元素不断入队,如下,此时tail不能++了
    在这里插入图片描述
    由于队首元素出队了,有空间,没有被后面的元素挤掉,对于数组来说,前面可能还有可以利用的空间,这就是循环队列的来由。
    把这个数组看做一个环,7之后的索引其实是0。之前一直说tail++,更准确的说tail的索引是:(当前的索引+1)%数组长度。

取余运算:取余只能整数除以整数,若除数比被除数大,被除数就是余数;若除数比被除数小,被除数就除以除数直到剩下的数比除数小,则这个数就是余数;4%7=4//4÷7=0……4,余4,因为4根本除不了7,被除数小于除数

所以上图的tail应该指向0:
在这里插入图片描述
如果继续添加元素:tail正常的往后移动一位即可
在这里插入图片描述
再继续添加一位:
此时tail就要往下移动,就会出现tail==front的情况,但是此时队列不为空,那么tail==front就会即可表示队列为空又可以表示队列为满,这不是我们想看到的情况。
所以定义tail+1==front的条件为队列满,也就是说如果数组只剩下一个空间了,就是满了,有意识的浪费一个空间。
在这里插入图片描述
更准确的说条件是:(当前的索引+1)%数组长度==front 队列满
在这里插入图片描述
满了,触发扩容。

实现循环队列

实现循环队列就是为了弥补出队操作的复杂度是O(n),现在变成了O(1)
在这里插入图片描述

public class LoopQueue<E> implements Queue<E> {
    private E[] data;
    private int front,tail;
    private int size;
    public LoopQueue(int capacity){
        //因为循环队列有一个元素会被我们有意的浪费掉
        data= (E[]) new Object[capacity+1];
        front=0;
        tail=0;
        size=0;
    }
    public LoopQueue(){
        this(10);
    }
    public int getCapacity(){
        //因为循环队列有一个元素会被我们有意的浪费掉
        return data.length-1;
    }
    @Override
    public void enqueue(E e) {
        //队列满了,扩容
        if ((tail+1)%data.length==front)
            resize(getCapacity()*2);
        data[tail]=e;
        tail=(tail+1) % data.length;
        size++;
    }
    @Override
    public E dequeue() {
        //队列为空
        if (isEmpty())
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
        //队首出队
        E datum = data[front];
        data[front]=null;
        //维护指针front
        front=(front+1) % data.length;
        //维护size
        size--;
        //缩容
        if(size==getCapacity() / 4 && getCapacity() /2 !=0 )
            resize(getCapacity() / 2);
        return datum;
    }
    @Override
    public E getFront() {
        //队列为空
        if (isEmpty())
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
        return data[front];
    }
    @Override
    public int getSize() {
        return size;
    }
    @Override
    public boolean isEmpty() {
        return front==tail;
    }
    private void resize(int newCapacity){
        E[] newData=(E[])new Object[newCapacity+1];
        for (int i=0;i<size;i++)
            //newData[0]对应的应该是data[front]
            //newData[1]对应的应该是data[front+1]
            //(front+i) % data.length 取余是怕产生数组越界
            newData[i]=data[(front+i) % data.length];
        data=newData;
        front=0;
        tail=size;
    }
    @Override
    public String toString() {
        StringBuilder res=new StringBuilder();
        res.append(String.format("Queue: size= %d , capacity= %d\n",size,getCapacity()));
        res.append("front [");
        for (int i=front;i!=tail;i=(i+1) % data.length){
            res.append(data[i]);
            if ((i+1) % data.length !=tail)
                res.append(",");
        }
        res.append("] tail");
        return res.toString();
    }
    public static void main(String[] args) {
        LoopQueue<Integer> arrayQueue=new LoopQueue();
        for (int i=0;i<10;i++){
            arrayQueue.enqueue(i);
            System.out.println(arrayQueue);
            if (i%3==2){
                arrayQueue.dequeue();
                System.out.println(arrayQueue);
            }
        }
    }
}

数组队列和循环队列的比较

public class Main {
    private static double testQueue(Queue<Integer> q, int opCount){
        long startTime = System.nanoTime();

        Random random=new Random();
        for (int i=0;i<opCount;i++)
            q.enqueue(random.nextInt(Integer.MAX_VALUE));
        for (int i=0;i<opCount;i++)
            q.dequeue();

        long endTime = System.nanoTime();

        return (endTime-startTime) / 1000000000.0;
    }

    public static void main(String[] args) {
        //循环队列和数组队列比较

        int opCount=100000;

        Queue<Integer> arrayQueue=new ArrayQueue();
        System.out.println("ArrayQueue , time: "+testQueue(arrayQueue,opCount )+"s");

        Queue<Integer> loopQueue=new LoopQueue();
        System.out.println("LoopQueue , time: "+testQueue(loopQueue,opCount )+"s");
    }
}

结果:
ArrayQueue , time: 3.8745792s
LoopQueue , time: 0.0156017s

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值