学 无 止 境 , 与 君 共 勉 。
相关系列
介绍
栈(stack)是限制插入和删除只能在一个位置上的数据结构,该位置是栈的末端,叫做栈的顶(top),它是属于后进先出(LIFO)的数据结构。对栈的基本操作有push(入栈)和pop(出栈),前者相当于在栈的末端插入数据,后者相当于删除最后插入的数据。栈的大小通常比较小,可用于临时数据结构。通常我们通过数组实现一个栈。
身边的栈
在JAVA中我们每次方法的调用其实就是入栈操作,把这个方法的返回地址(使内部方法运行完后知道返回到哪里去)和参数(隔离各个方法的参数)压入栈中,方法调用结束后方法出栈,获取到方法的返回值,传给外部方法,并释放栈空间。还有对异常处理 e.printStackTrace()
的实质就是打印异常调用的堆栈信息。
操作
Push(入栈)
top 指向了栈顶,如果是个空栈,top = -1;新数据入栈的时候执行2步操作:
- 先将top指向下一位置: top++;
- 然后将新插入的数据放入top的位置;
注意如果顺序相反了,新插入的数据会替换掉原先栈顶的数据,top指向的数据为空数据
pop(出栈)
移除并返回栈顶的数据;同样有2步操作:
- 获取顶部数据;
- top--;
这里同样不能颠倒顺序,如果先将top减1了,获取到的数据为移除后的栈顶数据;
通过数组实现栈
public class StackUseArray<E>{
// 最大容量
private int maxSize;
// 数据数组
private Object[] array;
// 执行栈顶
private int top;
public StackUseArray(int maxSize) {
this.maxSize = maxSize;
this.array = new Object[maxSize];
this.top = -1;
}
/**
* 顶端插入数据
*
* @param data 数据
*/
public void push(E data) {
if (isFull()) {
System.out.println("栈已满,不能再添加了!!!");
return;
}
this.array[++this.top] = data;
}
/**
* 移除顶端数据,并返回移除的数据
*
* @return data
*/
@SuppressWarnings("unchecked")
public E pop() {
if (isEmpty()) {
System.out.println("栈已空了,不能再删除了!!!");
return null;
}
return (E) this.array[this.top--];
}
/**
* 查询顶部数据
*
* @return data
*/
@SuppressWarnings("unchecked")
public E peek() {
if (isEmpty()) {
System.out.println("这是个空栈!!!");
return null;
}
return (E) this.array[this.top];
}
/**
* 检查是否为空
*
* @return boolean
*/
public boolean isEmpty() {
return this.top == -1;
}
/**
* 检查是否已经满了
*
* @return boolean
*/
public boolean isFull() {
return this.top == (this.maxSize - 1);
}
}
应用场景
我们通过IDE编写代码时,对于大量嵌套代码有时候会漏掉或者多写一个括号,这时会显示错误信息。这里通过栈的方式去验证文本中括号的格式合法性。
思路:读取文本,每次遇到左括号就入栈,每次遇到右括号就从栈顶弹出左括号去和当前右括号配对,校验是否合法。当所有文本全部读取完成后,查看栈是否为空,不为空,说明文本中少了右括号
- 实现
public class BracketChecker {
/**
* 左括号,需要入栈
*/
private static final String LEFT_BRACKET = "([{";
/**
* 右括号,用于校验,每次遇到需要校验和出栈的左括号是否匹配
*/
private static final String RIGHT_BRACKET = ")]}";
/**
* 用于匹配校验,每个右括号对应它的左括号
*/
private static final Map<Character, Character> MATCH_MAP = new HashMap<Character, Character>() {{
put(')', '(');
put(']', '[');
put('}', '{');
}};
/**
* 要校验的文本
*/
private String text;
public BracketChecker(String text) {
this.text = text;
}
/**
* 执行校验
*
* @return boolean
*/
public boolean check() {
int stackSize = this.text.length();
StackUseArray<Character> stack = new StackUseArray<>(stackSize);
// 当前字符
char curChar;
for (int i = 0; i < stackSize; i++) {
curChar = this.text.charAt(i);
if (LEFT_BRACKET.indexOf(curChar) > -1) {
// 左括号入栈
stack.push(curChar);
System.out.println("push:" + curChar);
} else if (RIGHT_BRACKET.indexOf(curChar) > -1) {
// 右括号,出栈匹配
System.out.println("curChar:" + curChar + ", match:" + stack.peek());
if (!stack.pop().equals(MATCH_MAP.get(curChar))) {
System.out.println("Error:" + curChar + " at " + i);
return false;
}
}
}
// 校验栈是否已空
if (!stack.isEmpty()) {
System.out.println("Error: mission " + MATCH_MAP.get(stack.peek()));
return false;
}
return true;
}
public static void main(String[] args) {
BracketChecker bracketChecker = new BracketChecker("(a{b[c]d}e)");
System.out.println("验证格式:" + bracketChecker.check());
}
}
- 运行结果
push:(
push:{
push:[
curChar:], match:[
curChar:}, match:{
curChar:), match:(
验证格式:true
扩展,使用链表去实现栈
对比
- 在通过数组实现栈中,
push
和pop
是通过数组操作来完成的。
array[++top] = data;
data = array[top--];
- 用链表存储数据的情况下,我们通过头插法的方式创建单链表来实现入栈和出栈
linkList.insertFirst(data);
linkList.deleteFirst;
- 因为链表没有大小限制,所以链表实现的栈也没有大小限制
实现
public class StackUseLink<E> {
private LinkList<E> linkList;
public StackUseLink() {
this.linkList = new LinkList<>();
}
/**
* 顶端插入数据
*
* @param data 数据
*/
public void push(E data) {
linkList.insertFirst(data);
}
/**
* 移除顶端数据,并返回移除的数据
*
* @return data
*/
public E pop() {
return linkList.deleteFirst();
}
/**
* 检查是否为空
*
* @return boolean
*/
public boolean isEmpty() {
return false;
}
/**
* 内部类实现头插法单链表
*
* @param <E>
*/
private static class LinkList<E> {
Node<E> first;
void insertFirst(E data) {
first = new Node<>(data, first);
}
E deleteFirst() {
if (isEmpty()) {
System.out.println("删除失败,已经空了");
return null;
}
Node<E> delNode = first;
first = first.next;
delNode.next = null;
return delNode.data;
}
boolean isEmpty() {
return first == null;
}
/**
* 链表节点
*
* @param <E>
*/
private static class Node<E> {
/**
* 数据
*/
E data;
/**
* 指向下一个节点
*/
Node<E> next;
Node(E data, Node<E> next) {
this.data = data;
this.next = next;
}
}
}
}
总结
- 只允许访问一个数据项,即最后插入的数据项,叫做栈的顶(top)
- 属于后进先出(LIFO)
- 通常很小,时临时的数据机构
- push(进栈)
- pop(出栈)
- 可以通过数组或者链表的方式实现
访问源码
本系列所有代码均上传至Github上,方便大家访问
日常求赞
创作不易,如果各位觉得有帮助,求点赞支持