第3章 栈和队列

3-1 栈和栈的应用

  • 栈(Stack)也是一种数据结构
  • 相比数组,栈对应的操作是数组的子集
  • 只能从一端添加元素,也只能从一端取出元素(这一端称为栈顶)
    在这里插入图片描述
  • 栈是一种后进先出(LIFO,Last In First Out)的数据结构
  • 在计算机的世界里,栈拥有着不可思议的作用
    • 无处不在的Undo操作(撤销)
    • 程序调用的系统栈
      在这里插入图片描述
      在这里插入图片描述

3-2 栈的基本实现

  • 从用户的角度看,支持这些操作就好
  • 具体底层实现,用户不关心
  • 实际底层有多种实现方式
    在这里插入图片描述
public interface Stack<E> {
    int getSize();
    boolean isEmpty();
    void push(E e);
    E pop();
    E peek();
}

※ Array类使用的是自己的实现,具体的实现在数组有关文章中,请自行查看

  • 在Array类中添加如下两个方法,方便我们Stack类的实现
    public E getLast(){
        return get(size - 1);
    }

    public E getFirst(){
        return get(0);
    }
  • 具体的Stack类(用数组实现)
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();
    }
}
  • 时间复杂度分析
方法时间复杂度
push()O(1) 均摊
pop()O(1) 均摊
peek()O(1)
getSize()O(1)
isEmpty()O(1)

3-3 栈的另一个应用:括号匹配

解决LeetCode上的问题20

思路:逐个遍历字符串,碰到一个左括号就入栈,当遇到右括号时判断是否和栈顶括号匹配,如果匹配就出栈,否则该字符串不符合要求,返回false,当字符串遍历结束时,如果栈为空,则字符串符合要求。栈顶元素反映了在嵌套的层次关系中,最近的需要匹配的元素。

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;

                char topChar = stack.pop();
                if(c == ')' && topChar != '(')
                    return false;
                if(c == ']' && topChar != '[')
                    return false;
                if(c == '}' && topChar != '{')
                    return false;
            }
        }
        return stack.isEmpty();
    }

    public static void main(String[] args) {

        System.out.println((new Solution()).isValid("()[]{}"));
        System.out.println((new Solution()).isValid("([)]"));
    }
}

3-4 关于LeetCode的一些说明

  • 美国LeetCode,体量较国内更多,功能更全,题解丰富
  • 中国力扣,大部分题目已经汉化但仍有小部分为英文题,关于题目的讨论较少

LeetCode提交Java代码的格式:

//自动生成的模板
class Solution {
public:
	//参数为包含有括号的字符串
    bool isValid(string s) {
        //Your Code
    }
};

※ 如果代码中想使用自己编写的其他类可以将类写成内部类

3-5 数组队列

  • 队列也是一种数据结构
  • 相比数组,队列对应的操作是数组的子集
  • 只能从一端(队尾)添加元素,且只能从另一端(队首)取出元素
    在这里插入图片描述
  • 队列是一种先进先出(First In First Out,FIFO)的数据结构
public interface Queue<E> {

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

    private Array<E> array;

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

    public ArrayQueue(){
        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 enqueue(E e){
        array.addLast(e);
    }

    @Override
    public E dequeue(){
        return array.removeFirst();
    }

    @Override
    public E getFront(){
        return array.getFirst();
    }
}

※ Array类使用的是自己的实现,具体的实现在数组有关文章中,请自行查看

  • 时间复杂度分析
方法时间复杂度
enqueue()O(1) 均摊
dequeue()O(n)
front()O(1)
getSize()O(1)
isEmpty()O(1)

3-6 循环队列

  • 问题引出:
    由于出队时将首元素移除后需要将剩余元素依次向前挪动一位,因此删除队首元素的时间复杂度为O(n)。那我们可不可以不挪动其它元素呢?
    在这里插入图片描述
    在这里插入图片描述

循环队列的实现

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 boolean isEmpty(){
        return front == tail;
    }

    @Override
    public int getSize(){
        return size;
    }

    @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 ret = data[front];
        data[front] = null;
        front = (front + 1) % data.length;
        size --;
        if(size == getCapacity() / 4 && getCapacity() / 2 != 0)
            resize(getCapacity() / 2);
        return ret;
    }

    @Override
    public E getFront(){
        if(isEmpty())
            throw new IllegalArgumentException("Queue is empty.");
        return data[front];
    }

    private void resize(int newCapacity){

        E[] newData = (E[])new Object[newCapacity + 1];
        for(int i = 0 ; i < size ; i ++)
            newData[i] = data[(i + front) % data.length];

        data = newData;
        front = 0;
        tail = size;
    }
}
方法时间复杂度
enqueue()O(1) 均摊
dequeue()O(1) 均摊
front()O(1)
getSize()O(1)
isEmpty()O(1)

3-7 数组队列和循环队列的比较

测试两种方式实现的队列的效率

import java.util.Random;

public class Main {

    // 测试使用q运行opCount个enqueueu和dequeue操作所需要的时间,单位:秒
    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;

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

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

结果:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值