2.栈和队列

一.栈的基本实现

Stack(E)

  • void push(E) 推入栈
  • E pop() 移出栈
  • E peek() 得到栈顶元素
  • int getSize() 栈中元素个数
  • boolean isEmpty 栈是否为空

我们的栈的实现是基于上一篇博文的动态数组的。
文件结构:

.
├── Array.iml
├── out
│   └── production
└── src
    ├── Array.java
    ├── ArrayStack.java
    ├── Main.java
    └── Stack.java

新建接口文件Stack.java:

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

编写ArrayStack.java:

public class ArrayStack<E> implements Stack<E> {
    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();
    }

    /**
     * 不是接口中的一部分, 因为用动态数组实现的栈才有这个方法
     * @return
     */
    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 res = new StringBuilder();
        res.append("Stack:");
        res.append("[");
        for(int i=0; i < array.getSize(); i++){
            res.append(array.get(i));
            if(i != array.getSize()-1){
                res.append(",");
            }
        }
        res.append("] top");   //表示末尾是top
        return res.toString();
    }
}

其中peek方法调用的getLast方法,在我们原本的动态数组中没有定义的, 我们补充一下。
Array.py,在get方法后面补充上:

 E get(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("Get failed.Index is illegal.");
        }
        return data[index];
    }

    E getLast(){
        return get(size - 1);
    }

    E getFirst(){
        return get(0);
    }

最后Main.java中测试

public class Main {

    public static void main(String[] args) {
        ArrayStack<Integer> stack = new ArrayStack<>();

        for(int i=0; i<5; i++){
            stack.push(i);
            System.out.println(stack);
        }


        stack.pop();
        System.out.println(stack);
    }
}

运行结果:

Stack:[0] top
Stack:[0,1] top
Stack:[0,1,2] top
Stack:[0,1,2,3] top
Stack:[0,1,2,3,4] top
Stack:[0,1,2,3] top

栈的时间复杂度分析:

ArrayStack<E>

- void push(E)        O(1) 均摊复杂度
- E pop()             O(1) 均摊复杂度
- E peek()            O(1) 
- int getSize()       O(1) 
- boolean isEmpty()   O(1) 

二.栈的一个应用:括号的匹配

列举几个栈的应用:

- undo操作    (编辑器)
- 系统调用栈   (操作系统)
- 括号匹配     (编译器)

我们来详解一下括号匹配的应用原理

拿leetcode上的一个简单算法题为例:

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

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

示例 1:

输入: "()"
输出: true
示例 2:

输入: "()[]{}"
输出: true
示例 3:

输入: "(]"
输出: false
示例 4:

输入: "([)]"
输出: false
示例 5:

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

解答:

import java.util.Stack;  //java提供的标准库的栈, 接口与我们自定义的一样

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("{}()[]");
    }
}

python的答案

class Solution:
    def isValid(self, s):
        """
        :type s: str
        :rtype: bool
        """
        stack = []
        for c in s:
            if c == '(' or c == '[' or c == '{':
                stack.append(c)
            else:
                if len(stack) == 0:
                    return False
                topchar = stack.pop()
                if c == ')' and topchar != '(':
                    return False
                if c == ']' and topchar != '[':
                    return False
                if c == '}' and topchar != '{':
                    return False
        return len(stack)==0


三. 数组队列

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

队列的实现

Queue<E>

- void enqueue(E)   入队
- E dequeue()       出队
- E getFront()      查看队首元素
- int getSize()
- boolean isEmpty()

依然基于我们的自定义动态数组实现.
新建接口文件Queue.java

public interface Queue<E> {
    void enqueue(E e);

    E dequeue();

    E getFront();

    int getSize();

    boolean isEmpty();
}

ArrayQueue.java:

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();
    }

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

    //测试
    public static void main(String[] args) {
        ArrayQueue<Integer> queue = new ArrayQueue<>();
        for (int i = 0; i < 10; i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}

测试结果:

Queue:front [0] tail
Queue:front [0,1] tail
Queue:front [0,1,2] tail
Queue:front [1,2] tail
Queue:front [1,2,3] tail
Queue:front [1,2,3,4] tail
Queue:front [1,2,3,4,5] tail
Queue:front [2,3,4,5] tail
Queue:front [2,3,4,5,6] tail
Queue:front [2,3,4,5,6,7] tail
Queue:front [2,3,4,5,6,7,8] tail
Queue:front [3,4,5,6,7,8] tail
Queue:front [3,4,5,6,7,8,9] tail

时间复杂度分析

ArrayQueue<E>
- void enqueue<E>     O(1)均摊复杂度
- E dequeue()         O(n)
- E front()           O(1)
- int getSize()       O(1)
- boolean isEmpty()   O(1)

四. 循环队列

dequeue的时间复杂度是O(n)导致了队列的局限性。我们使用循环队列。


具体实现:

新建LoopQueue.java

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(20);
    }

    public int getCapacity() {
        return data.length - 1;
    }

    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;
    }


    @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) {  // 3/4的空间是空的, 我们缩容
            resize(getCapacity() / 2);
        }
        return ret;
    }

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

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("Queue: size = %d, capacity = %d\n", size, data.length));
        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> queue = new LoopQueue<>();

        for (int i = 0; i < 10; i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}

复杂度分析

LoopQueue<E>
- void enqueue<E>     O(1)均摊复杂度
- E dequeue()         O(1)均摊复杂度
- E front()           O(1)
- int getSize()       O(1)
- boolean isEmpty()   O(1)

可以看到LoopQueue对比ArrayQueue在dequeue方法的时间复杂度,由O(n)变为O(1).

LoopQueue与ArrayQueue对比测试

Main.java

import java.util.Random;

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;

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

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

运行结果:

ArrayQueue, time: 42.833585979
LoopQueue, time: 0.019467203

性能差距很大。

猜你喜欢

转载自blog.csdn.net/weixin_41207499/article/details/80851328