数据结构与算法之栈
1. 基本知识
1.1 概念
栈是一种操作受限的数据结构,只能在一端插入和删除数据,因此只支持入栈和出栈操作,后进先出是它的最大特点。栈可以通过数组实现,也可以通过链表来实现,其出栈和入栈的时间复杂度都是O(1)。
1.2 分类
- 顺序栈:数组实现
- 链式栈:链表实现
1.3 基本操作
- 入栈 push(T data)
- 出栈 pop()
- 获取栈顶数据 peek()
1.3 实现
只需要维护一个栈顶指针,通过数组实现的顺序栈和通过链表实现的链式栈。
1.3.1 顺序栈
package com.baidu.test.construct.stack;
/**
* 顺序栈,后进先出
*/
public class ArrayStack<T> {
// 默认容量
private final static int DEFAULT_CAPACITY = 10;
// 元素集合
private Object[] datas;
// 容量
private int capacity;
// 长度
private int top;
public ArrayStack() {
this(DEFAULT_CAPACITY);
}
public ArrayStack(int capacity) {
this.datas = new Object[capacity];
this.capacity = capacity;
this.top = 0;
}
/**
* 入栈
* @param data
*/
public void push(T data) {
ensureCapacity(top+1);
datas[top++] = data;
}
/**
* 出栈
* @return
*/
public T pop() {
if (top == 0) {
return null;
}
return (T) datas[--top];
}
/**
* 获取栈顶元素
* @return
*/
public T getTop() {
return (T) datas[top-1];
}
public int size() {
return top;
}
public boolean empty() {
return top == 0;
}
/**
* 扩容
* @param size
*/
public void ensureCapacity(int size) {
if (size > capacity) {
System.out.println("容量不足,触发扩容...");
capacity = capacity * 2;
Object[] temp = new Object[capacity];
System.arraycopy(datas, 0, temp, 0, top);
datas = temp;
}
}
public static void main(String[] args) {
ArrayStack<Integer> stack = new ArrayStack<>();
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.getTop());
stack.push(9);
stack.push(10);
stack.push(11);
stack.push(12);
System.out.println("顺序栈长度:" + stack.size());
System.out.println("顺序栈是否为空:" + stack.empty());
System.out.print("遍历出栈:");
while (!stack.empty()) {
System.out.print(stack.pop() + ",");
}
}
}
1.3.2 链式栈
package com.baidu.test.construct.stack;
/**
* 链表栈
*/
public class LinkedListStack<T> {
// 头节点
private Node<T> head;
// 当前结点
private Node<T> current;
// 长度
private int size;
public LinkedListStack() {
this.head = this.current = new Node<T>(null, null);
this.size = 0;
}
/**
* 入栈,链表尾部入栈
* @param data
*/
public void push(T data) {
index(size-1);
current.next = new Node<T>(data, current.next);
size++;
}
/**
* 出栈,链表头部出栈
* @return
*/
public T pop() {
current = head;
Node<T> node = current.next;
current.next = current.next.next;
size--;
return node.data;
}
/**
* 获取栈顶位置
* @return
*/
public T getTop() {
return head.next.data;
}
public int size() {
return size;
}
public boolean empty() {
return size == 0;
}
private void index(int index) {
if (index < -1 || index > size -1) {
throw new IndexOutOfBoundsException();
}
if (index == -1) {
current = head;
return;
}
current = head.next;
int i=0;
while (i<index) {
current = current.next;
i++;
}
}
private static class Node<T> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
}
public static void main(String[] args) {
LinkedListStack<Integer> stack = new LinkedListStack<>();
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.getTop());
stack.push(9);
stack.push(10);
stack.push(11);
stack.push(12);
System.out.println("顺序栈长度:" + stack.size());
System.out.println("顺序栈是否为空:" + stack.empty());
System.out.print("遍历出栈:");
while (!stack.empty()) {
System.out.print(stack.pop() + ",");
}
}
}
2. 栈的应用
2.1 栈在括号匹配中的应用
package com.baidu.test.construct.stack;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
public class StackDemo {
/**
* 有效的括号:
* 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
* 有效字符串需满足:
* 左括号必须用相同类型的右括号闭合。
* 左括号必须以正确的顺序闭合。
*
* @param s
* @return
*/
private static boolean isValid(String s) {
Stack<Character> stack = new Stack();
Map<Character, Character> map = new HashMap<>();
map.put(')', '(');
map.put(']', '[');
map.put('}', '{');
int length = s.length();
for (int i=0; i< length; i++) {
char c = s.charAt(i);
if (map.containsKey(c)) {
Character pop = stack.isEmpty()? '$' : stack.pop();
if (map.get(c) != pop) {
return false;
}
} else if (map.containsValue(c)) {
stack.push(c);
}
}
return stack.isEmpty();
}
public static void main(String[] args) {
System.out.println(isValid("sfadfd[](sdfd){]"));
}
}
2.2 最小栈的实现
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
2.2.1 双栈实现
思路:
1.将第一个元素入栈。
2.新加入的元素如果大于栈顶元素,那么新加入的元素就不处理。
3.新加入的元素如果小于等于栈顶元素,那么就将新元素入栈。
4.出栈元素不等于栈顶元素,不操作。
5.出栈元素等于栈顶元素,那么就将栈顶元素出栈。
图解:
入栈 3
| | | |
| | | |
|_3_| |_3_|
stack minStack
入栈 5 , 5 大于 minStack 栈顶,不处理
| | | |
| 5 | | |
|_3_| |_3_|
stack minStack
入栈 2 ,此时右边的 minStack 栈顶就保存了当前最小值 2
| 2 | | |
| 5 | | 2 |
|_3_| |_3_|
stack minStack
出栈 2,此时右边的 minStack 栈顶就保存了当前最小值 3
| | | |
| 5 | | |
|_3_| |_3_|
stack minStack
出栈 5,右边 minStack 不处理
| | | |
| | | |
|_3_| |_3_|
stack minStack
出栈 3
| | | |
| | | |
|_ _| |_ _|
stack minStack
package com.baidu.test.construct.stack;
import java.util.Stack;
/**
* 最小栈
* 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
*
* push(x) —— 将元素 x 推入栈中。
* pop() —— 删除栈顶的元素。
* top() —— 获取栈顶元素。
* getMin() —— 检索栈中的最小元素
*
*/
public class MinStackByTwoStack {
private Stack<Integer> stack;
private Stack<Integer> minStack;
public MinStackByTwoStack() {
stack = new Stack();
minStack = new Stack();
}
public void push(int x) {
stack.push(x);
if (minStack.isEmpty()) { // 第一个元素最小
minStack.push(x);
} else {
int pop = minStack.peek(); // 取出最小栈栈顶数据
if (x <= pop) { // 当前数据小于等于最小栈栈顶数据,则将该数据压入最小栈栈顶
minStack.push(x);
}
}
}
public void pop() {
int pop = stack.pop();
int peek = minStack.peek();
if (pop == peek) { // 栈顶数据等于最小栈栈顶数据时,最小栈出栈
minStack.pop();
}
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
public static void main(String[] args) {
MinStackByTwoStack stack = new MinStackByTwoStack();
stack.push(2);
stack.push(3);
stack.push(10);
stack.push(7);
stack.push(8);
stack.push(1);
System.out.println("栈顶数据为:" + stack.top());
System.out.println("最小栈数据为:" + stack.getMin());
System.out.println("执行一次出栈操作");
stack.pop();
System.out.println("栈顶数据为:" + stack.top());
System.out.println("最小栈数据为:" + stack.getMin());
System.out.println("执行一次出栈操作");
stack.pop();
System.out.println("栈顶数据为:" + stack.top());
System.out.println("最小栈数据为:" + stack.getMin());
}
}
2.2.1 单栈实现
思路:
1.只需要一个栈了。
2.当有更小的值来的时候,我们只需要把之前的最小值入栈,当前更小的值再入栈即可。
3.当这个最小值要出栈的时候,下一个值便是之前的最小值了。
图解:
入栈 3
| | min = 3
| |
|_3_|
stack
入栈 5
| | min = 3
| 5 |
|_3_|
stack
入栈 2
| 2 | min = 2?
| 5 |
|_3_|
stack
如果只用一个变量就会遇到一个问题,如果把 min 更新为 2,那么之前的最小值 3 就丢失了。怎么把 3 保存起来呢?把它在 2 之前压入栈中即可。
入栈 2 ,同时将之前的 min 值 3 入栈,再把 2 入栈,同时更新 min = 2
| 2 | min = 2
| 3 |
| 5 |
|_3_|
stack
入栈 6
| 6 | min = 2
| 2 |
| 3 |
| 5 |
|_3_|
stack
出栈 6
| 2 | min = 2
| 3 |
| 5 |
|_3_|
stack
出栈 2
| 2 | min = 2
| 3 |
| 5 |
|_3_|
stack
package com.baidu.test.construct.stack;
import java.util.Stack;
public class MinStackWithOneStack {
private Stack<Integer> stack;
private int min;
public MinStackWithOneStack() {
this.stack = new Stack<>();
min = Integer.MAX_VALUE;
}
public void push(int x) {
if (x <= min) { // 如果当前数据小于等于最小值,则把当前最小值先于当前数组压入
stack.push(min);
min = x;
}
stack.push(x);
}
public void pop() {
if (stack.pop() == min) { // 如果弹出栈顶位置等于最小值,则再一次弹出数据设为最小值
min = stack.pop();
}
}
public int top() {
return stack.peek();
}
public int getMin() {
return min;
}
public static void main(String[] args) {
MinStackWithOneStack stack = new MinStackWithOneStack();
stack.push(2);
stack.push(3);
stack.push(10);
stack.push(7);
stack.push(8);
stack.push(1);
System.out.println("栈顶数据为:" + stack.top());
System.out.println("最小栈数据为:" + stack.getMin());
System.out.println("执行一次出栈操作");
stack.pop();
System.out.println("栈顶数据为:" + stack.top());
System.out.println("最小栈数据为:" + stack.getMin());
System.out.println("执行一次出栈操作");
stack.pop();
System.out.println("栈顶数据为:" + stack.top());
System.out.println("最小栈数据为:" + stack.getMin());
}
}
2.2.3 基于链表实现
思路:
在 Node 节点中增加一个 min 字段,这样的话每次加入一个节点的时候,我们同时只要确定它的 min 值即可。
package com.baidu.test.construct.stack;
public class MinStack {
private Node head;
public MinStack() {
this.head = new Node(null, null, null);
}
public void push(int x) {
if (head.next == null) {
head.next = new Node(x, head.next, x);
} else {
head.next = new Node(x, head.next, Math.min(head.next.min, x));
}
}
public void pop() {
if (head.next != null) {
head.next = head.next.next;
}
}
public int top() {
return head.next.data;
}
public int getMin() {
return head.next.min;
}
private static class Node {
private Integer data;
private Node next;
private Integer min; // 最小值
public Node(Integer data, Node next, Integer min) {
this.data = data;
this.next = next;
this.min = min;
}
}
public static void main(String[] args) {
MinStack stack = new MinStack();
stack.push(2);
stack.push(3);
stack.push(10);
stack.push(7);
stack.push(8);
stack.push(1);
System.out.println("栈顶数据为:" + stack.top());
System.out.println("最小栈数据为:" + stack.getMin());
System.out.println("执行一次出栈操作");
stack.pop();
System.out.println("栈顶数据为:" + stack.top());
System.out.println("最小栈数据为:" + stack.getMin());
System.out.println("执行一次出栈操作");
stack.pop();
System.out.println("栈顶数据为:" + stack.top());
System.out.println("最小栈数据为:" + stack.getMin());
}
}
2.3 实现简单计算器
思路:
1.利用两个栈实现,一个栈保存原始字符,一个栈保存括号中的元素,
2.括号中的数据计算完毕压入原始栈,
3.遍历原始栈得到最终结果
该问题因为有括号,因此比较复杂,如果没有括号的话,就可以简单:
1.两个栈,一个数据栈,一个操作符栈。
package com.baidu.test.construct.stack;
import java.util.Stack;
/**
* 实现一个基本的计算器来计算一个简单的字符串表达式的值。
*
* 字符串表达式可以包含左括号 ( ,右括号 ),加号 + ,减号 -,非负整数和空格
*/
public class Calculator {
public static int calculate(String s) {
if (s == null && s.length() == 0) {
throw new RuntimeException("非法数据");
}
Stack<String> stack = new Stack<>();
int length = s.length();
for(int i=0; i<length; i++) {
char c = s.charAt(i);
if (c == ' ') {
continue;
}
if (')' != c) {
stack.push(c+"");
} else {
calculate(stack);
}
}
calculate(stack);
return Integer.parseInt(stack.pop());
}
private static void calculate(Stack<String> stack) {
int sum = 0;
Stack<String> tempStack = new Stack<>();
while (!stack.isEmpty()) {
String pop = stack.pop();
if ("(".equals(pop)) {
break;
}
tempStack.push(pop);
}
int x = Integer.parseInt(tempStack.pop());
while (!tempStack.isEmpty()) {
String p = tempStack.pop();
int y = Integer.parseInt(tempStack.pop());
sum = calculate(x, p , y);
x = sum;
}
stack.push(sum + "");
}
private static int calculate(int x, String p, int y) {
switch (p) {
case "+":
return x + y;
case "-":
return x - y;
default:
return 0;
}
}
public static void main(String[] args) {
System.out.println(calculate("(1+(4+5+2)-3)+(6+8)"));
}
}