算法数据结构:栈

301 阅读2分钟

1、什么栈

栈,可以想象成我们平时放盘子的时候,都是从下往上一个一个放;取得时候,是从上往下一个一个地依次取,不能从中间任意抽出。后进者先出,先进者后出(FILO),这就是典型的结构。这样的例子还有很多,比如:弹匣... 从栈的操作性上看,栈是一种操作受限的线性表,只允许在一端插入和删除数据。

当某个数据集合,只涉及在一端插入和删除数据,并且满足后进先出,先进后出的特性,我们就应该首选栈这种数据结构。

2、如何实现一个栈

栈主要包括两个操作,入栈和出栈,也就是在栈顶插入一个数据和从栈顶删除一个数据。

实际上,栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作顺序栈;用链表实现的栈,叫作链式栈。

2.1、数组实现

/**
 * @description: 数组实现栈
 * @author: erlang
 * @since: 2020-08-31 20:12
 */
public class ArrayStack<T> {
    /**
     * 数组
     */
    private Object[] arr;
    /**
     * 栈中的元素
     */
    private int count;
    /**
     * 栈的大小
     */
    private int limit;

    /**
     * 初始化 栈
     * @param limit
     */
    public ArrayStack(int limit) {
        arr = new Object[limit];
        count = 0;
        this.limit = limit;
    }

    /**
     * 入栈
     * @param value
     * @return
     */
    public boolean push(T value) {
        if (count == limit) {
            throw new RuntimeException("栈满了");
        }
        arr[count++] = value;
        return true;
    }

    public T pop() {
        if (count == 0) {
            throw new RuntimeException("栈空了");
        }
        return (T) arr[--count];
    }

    public boolean isEmpty() {
        return count == 0;
    }

    public static void main(String[] args) {
        ArrayStack<Integer> stack = new ArrayStack<>(7);
        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        stack.push(5);
        stack.push(6);
        stack.push(7);
        //stack.push(8);

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

    }
}

2.1、链表实现

/**
 * @description: 链表实现栈
 * @author: erlang
 * @since: 2020-08-31 20:12
 */
public class LinkedListStack<T> {

    public static class Node<T> {
        public T value;
        public Node<T> pre;
        public Node<T> next;

        public Node(T value) {
            this.value = value;
        }
    }

    public static class DoubleEndsQueue<T> {
        public Node<T> head;
        public Node<T> tail;

        public void addFromHead(T value) {
            Node<T> cur = new Node<>(value);
            if (head == null) {
                tail = cur;
            } else {
                cur.next = head;
                head.pre = cur;
            }
            head = cur;
        }

        public void addFromTail(T value) {
            Node<T> cur = new Node<>(value);
            if (head == null) {
                head = cur;
            } else {
                cur.pre = tail;
                tail.next = cur;
            }
            tail = cur;
        }

        public T popFromHead() {
            if (head == null) {
                return null;
            }
            Node<T> cur = head;
            if (head == tail) {
                head = null;
                tail = null;
            } else {
                head = head.next;
                cur.next = null;
                head.pre = null;
            }
            return cur.value;
        }

        public T popFromTail() {
            if (head == null) {
                return null;
            }
            Node<T> cur = tail;
            if (head == tail) {
                head = null;
                tail = null;
            } else {
                tail = tail.pre;
                cur.pre = null;
                tail.next = null;
            }
            return cur.value;
        }

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

    public DoubleEndsQueue<T> queue;
    public int count;
    public int limit;

    public LinkedListStack(int limit) {
        queue = new DoubleEndsQueue<>();
        this.limit = limit;
        count = 0;
    }

    public void push(T value) {
        if (limit == count) {
            throw new RuntimeException("栈满了");
        }
        count++;
        queue.addFromHead(value);
    }

    public T pop() {
        if (count == 0) {
            throw new RuntimeException("栈空了");
        }
        count--;
        return queue.popFromHead();
    }

    public boolean isEmpty() {
        return queue.isEmpty();
    }

    public static void main(String[] args) {
        LinkedListStack<Integer> stack = new LinkedListStack<>(7);
        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        stack.push(5);
        stack.push(6);
        stack.push(7);
        //stack.push(8);

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

    }
}

3、实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能

1)pop、push、getMin操作的时间复杂度都是 O(1)。

2)设计的栈类型可以使用现成的栈结构。

3.1、第一种设计方案

压入数据规则

假设当前数据为 value,先将其压入 stackData。然后判断 stackMin 是否为空:

  • 如果为空,则 value 也压入 stackMin。
  • 如果不为空,则比较 value 和 stackMin 的栈顶元素中哪一个更小:
    • 如果 value <= this.getMin(),则 value 压入 stackMin
    • 反之,则 stackMin 不压入任何内容
 public static class MinStack {
    public Stack<Integer> stackData;
    public Stack<Integer> stackMin;

    public MinStack() {
        stackData = new Stack<>();
        stackMin = new Stack<>();
    }

    public void push(int value) {
        if (this.stackData.isEmpty() || value <= this.getMin()) {
            this.stackMin.push(value);
        }
        this.stackData.push(value);
    }

    public int pop() {
        if (this.stackData.isEmpty()) {
            throw new RuntimeException("Your stack is empty.");
        }
        int value = this.stackData.pop();
        if (value == this.getMin()) {
            this.stackMin.pop();
        }
        return value;
    }

    public int getMin() {
        if (this.stackData.isEmpty()) {
            throw new RuntimeException("Your stack is empty.");
        }
        return this.stackMin.peek();
    }
}

3.2、第二种设计方案

压入数据规则

假设当前数据为 value,先将其压入 stackData。然后判断 stackMin 是否为空:

  • 如果为空,则 value 也压入 stackMin。
  • 如果不为空,则比较 value 和 stackMin 的栈顶元素中哪一个更小:
    • 如果 value <= this.getMin(),则 value 压入 stackMin;
    • 反之,则把 stackMin 栈顶元素重复压入 stackMin,即在栈顶元素上再压入一个栈顶元素。
public static class MinStack {
    public Stack<Integer> stackData;
    public Stack<Integer> stackMin;

    public MinStack() {
        stackData = new Stack<>();
        stackMin = new Stack<>();
    }

    public void push(int value) {
        if (this.stackData.isEmpty() || value <= this.getMin()) {
            this.stackMin.push(value);
        } else {
            int minValue = this.stackMin.peek();
            this.stackMin.push(minValue);
        }
        this.stackData.push(value);
    }

    public int pop() {
        if (this.stackData.isEmpty()) {
            throw new RuntimeException("Your stack is empty.");
        }
        int value = this.stackData.pop();
        this.stackMin.pop();
        return value;
    }

    public int getMin() {
        if (this.stackData.isEmpty()) {
            throw new RuntimeException("Your stack is empty.");
        }
        return this.stackMin.peek();
    }
}