文章目录
数据结构与算法|第五章:栈
1.项目环境
- jdk 1.8
- github 地址:https://github.com/huajiexiewenfeng/data-structure-algorithm
- 本章模块:chapter04
2.栈是什么?
一种受限制的线性表,只允许在表的一端进行删除(出栈)和插入(入栈)的操作,满足先进后出,后进先出的特点;如果有符合这种特点的场景,就可以使用栈,比较熟悉的设计就是 JVM 中的虚拟机栈,本地方法栈等等。
从功能上来看,虽然数组和链表都可以取代栈,但是栈的意义就在于限制
,数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,自然也就更容易出错。
3.使用数组实现栈
3.1 代码实现
栈主要包括两个操作
- 入栈(push)
- 出栈(pop)
/**
* 使用数组来实现一个栈
*/
public class ArrayStack {
private String[] items;// 存储栈元素数组
private Integer length;// 栈的深度
private Integer count;// 栈目前的元素个数
public ArrayStack(Integer length) {
this.items = new String[length];
this.length = length;
this.count = 0;
}
/**
* 入栈
*/
public void push(String item) {
if (count >= length) {
throw new RuntimeException("超过栈的最大深度:" + length);
}
items[count] = item;
count++;
}
/**
* 出栈
*/
public String pop() {
if (count == 0) {
throw new RuntimeException("空栈");
}
String item = items[count - 1];
items[count - 1] = null;
count--;
return item;
}
@Override
public String toString() {
return "ArrayStack{" +
"items=" + Arrays.toString(items) +
", length=" + length +
", count=" + count +
'}';
}
}
测试
/**
* 测试 {@link ArrayStack} 示例
*/
public class ArrayStackDemo {
public static void main(String[] args) {
//演示栈溢出异常
exception();
//演示正常的入栈和出栈过程
pushAndPop();
}
private static void pushAndPop() {
ArrayStack arrayStack = new ArrayStack(5);
for (int i = 0; i < 5; i++) {
arrayStack.push("元素" + i);
System.err.println(arrayStack.toString());
}
for (int i = 0; i < 5; i++) {
String item = arrayStack.pop();
System.err.println("弹出元素:" + item);
System.err.println(arrayStack.toString());
}
}
private static void exception() {
ArrayStack arrayStack = new ArrayStack(5);
for (int i = 0; i < 6; i++) {
arrayStack.push("元素" + i);
System.err.println(arrayStack.toString());
}
}
}
两个方法需要分开演示
使用 err 打印的原因是 out 和 err 打印的信息顺序会不一样,异常信息输出使用的是 err
- exception 执行结果
ArrayStack{items=[元素0, null, null, null, null], length=5, count=1}
ArrayStack{items=[元素0, 元素1, null, null, null], length=5, count=2}
ArrayStack{items=[元素0, 元素1, 元素2, null, null], length=5, count=3}
ArrayStack{items=[元素0, 元素1, 元素2, 元素3, null], length=5, count=4}
ArrayStack{items=[元素0, 元素1, 元素2, 元素3, 元素4], length=5, count=5}
Exception in thread "main" java.lang.RuntimeException: 超过栈的最大深度:5
at com.huajie.chapter04.ArrayStack.push(ArrayStack.java:24)
at com.huajie.chapter04.ArrayStackDemo.exception(ArrayStackDemo.java:35)
at com.huajie.chapter04.ArrayStackDemo.main(ArrayStackDemo.java:11)
- pushAndPop 执行结果
ArrayStack{items=[元素0, null, null, null, null], length=5, count=1}
ArrayStack{items=[元素0, 元素1, null, null, null], length=5, count=2}
ArrayStack{items=[元素0, 元素1, 元素2, null, null], length=5, count=3}
ArrayStack{items=[元素0, 元素1, 元素2, 元素3, null], length=5, count=4}
ArrayStack{items=[元素0, 元素1, 元素2, 元素3, 元素4], length=5, count=5}
弹出元素:元素4
ArrayStack{items=[元素0, 元素1, 元素2, 元素3, null], length=5, count=4}
弹出元素:元素3
ArrayStack{items=[元素0, 元素1, 元素2, null, null], length=5, count=3}
弹出元素:元素2
ArrayStack{items=[元素0, 元素1, null, null, null], length=5, count=2}
弹出元素:元素1
ArrayStack{items=[元素0, null, null, null, null], length=5, count=1}
弹出元素:元素0
ArrayStack{items=[null, null, null, null, null], length=5, count=0}
Exception in thread "main" java.lang.RuntimeException: 空栈
at com.huajie.chapter04.ArrayStack.pop(ArrayStack.java:36)
at com.huajie.chapter04.ArrayStackDemo.main(ArrayStackDemo.java:19)
3.1 复杂度
空间复杂度:是固定申请的数组长度,所以空间复杂度为 O(1)。
时间复杂度:只有出栈和入栈两个操作,而且都是针对的栈顶元素,所以时间复杂度也是 O(1)。
4.JVM 虚拟机栈
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈的栈元素。
每个方法的调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
栈帧中的操作栈(Operand Stack)也是一个后人先出(LIFO)栈的结构,在方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是入栈和出栈的过程。
示例:
public class JVMStackDemo {
public static void main(String[] args) {
int a = 3;
int b = 5;
int c = 1;
int res = 0;
int result = 0;
res = add(a, b);
result = res + c;
System.out.println(result);
}
public static int add(int a, int b) {
int sum = 0;
sum = a + b;
return sum;
}
}
从代码中可以看到 main() 调用了 add() 方法,我们使用画图来演示栈的数据结构。
5.LeetCode 相关题目
题目:
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串示例 1:
输入: “()”
输出: true
示例 2:输入: “()[]{}”
输出: true
示例 3:输入: “(]”
输出: false
示例 4:输入: “([)]”
输出: false
示例 5:输入: “{[]}”
输出: true来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-parentheses
题解
-
使用 Java 中的 Stack 进行处理
-
如果 map 中包含 这个 char 字符,就 push 到 Stack 中。
-
如果不包含,就 pop 弹出栈顶的元素,判断该元素是否为当前元素括号的反括号
-
stack.empty() -> 返回 stack 是否为空
class Solution {
private static Map<Character, Character> map = new HashMap();
static {
map.put('{', '}');
map.put('[', ']');
map.put('(', ')');
}
public boolean isValid(String s) {
char[] chars = s.toCharArray();
Stack<Character> stack = new Stack<>();
for (char c : chars) {
if (map.containsKey(c)) {
stack.push(c);//压栈
}else if (stack.empty() || c != map.get(stack.pop())) {
return false;
}
}
return stack.empty();
}
}
6.浏览器前进后退功能
我们在使用浏览器的时候,都会使用前进和后退功能,比如我先后访问了 5 个页面,分别标记为 1、2、3、4、5,现在想看第 3 个页面,那么后退 2 次,如果又想看第 4 个页面,再前进 1 次。
我们看看用栈如何实现上述效果,我们分别用两个栈来记录访问数据,分别是 前进栈 和 后退栈 。
用户依次访问 5 个页面,将页面信息依次 push 到后退栈中。
后退 2 次
前进 1 次
7.小结
栈是一种操作受限的线性表,只支持入栈和出栈操作。后进先出是它最大的特点。栈既可以通过数组实现,也可以通过链表来
实现。不管基于数组还是链表,入栈、出栈的时间复杂度都为O(1)。
8.参考
- 极客时间 -《数据结构与算法之美》王争
- 《深入理解Java虚拟机》 周志明