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