2 栈
-
定义
线性表中的一种特殊的数据结构,数据只能从固定的一端插入,另一端封闭。
-
栈顶:
只能从一端添加元素,也只能从一端取出元素,这一端称之为栈顶。 -
特点: LIFO
栈是一种后进先出的数据结构。 -
栈的分类:
- 顺序栈:
采用数组实现,数据在物理结构上保持连续性。 - 链表栈:
用线性表的链式结构存储,数据在物理结构上非连续。
2.1 栈的实际应用
- undo撤销操作
使用word等文本编辑器时会有一个栈记录输入的信息。输入错误时撤销操作可以使错误字出栈、然后使正确的字入栈。 - 程序调用的系统栈
函数A运行到一半,调用函数B; B运行到一半,调用函数C;
A2指函数A运行到了第二行,B2指函数B运行到了第二行。当函数C顺序执行完毕之后,该执行哪一个函数呢,拿出栈顶B2继续执行。
当B函数执行完毕之后,拿出栈顶A2,执行A函数,执行完之后,发现栈为空,整个程序执行完毕。
子程序、子逻辑的调用可以帮助理解递归。
2.2 栈的实现(以动态数组栈为例)
定义一个接口说明需要实现的相关方法:
- 栈的实现相关方法
- 获取长度
- 判断是否为空
- 入栈、出栈
- 获取栈顶元素
public interface Stack<E>{
int getSize();
boolean isEmpty();
void push(E e);
E pop();
E peek();
}
栈的声明和使用:
public class StackMain {
public static void main(String[] args) {
ArrayStack<Integer> stack=new ArrayStack<>();
for(int i=0;i<5;i++){
stack.push(i);
System.out.println(stack);
}
stack.pop();
System.out.println(stack);
}
}
输出结果为:
2.3 链表栈的实现
package DataStructure;
public class LinkedListStack<E> implements Stack<E> {
private LinkedList<E> list;
public LinkedListStack(){
list = new LinkedList<>();
}
@Override
public int getSize(){
return list.getSize();
}
@Override
public boolean isEmpty(){
return list.isEmpty();
}
@Override
public void push(E e){
list.addFirst(e);
}
@Override
public E pop(){
return list.removeFirst();
}
@Override
public E peek(){
return list.getFirst();
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("Stack: top ");
res.append(list);
return res.toString();
}
public static void main(String[] args) {
LinkedListStack<Integer> stack = new LinkedListStack<>();
for(int i = 0 ; i < 5 ; i ++){
stack.push(i);
System.out.println(stack);
}
stack.pop();
System.out.println(stack);
}
}
2.4 栈的时间复杂度分析
- 数组栈:
- 时间复杂度:
出栈和入栈操作可能导致数组容量发生变化导致最坏情况下时间复杂度为O(1),均摊时间复杂度为O(1),其他操作时间复杂度均为O(1); - 空间复杂度:
出栈和入栈操作只涉及一两个临时变量的存储空间,所以空间复杂度为O(1);
- 链表栈:
- 时间复杂度
出栈、入栈操作只是顶部元素操作,可以在链表头对节点进行操作。时间复杂度为O(1); - 空间复杂度
同上,空间复杂度为O(1);
2.5 应用例题:括号匹配
- 我们假设表达式中只包含三种括号,圆括号 ()、方括号 [] 和花括号 {},并且它们可以任意嵌套。
- 我们用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能够匹配,比如 “(” 跟 “)” 匹配,“[” 跟 “]” 匹配,“{” 跟 “}” 匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。
- 当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明有未匹配的左括号,为非法格式。
左括号入栈,右括号出栈,栈顶元素反映了在嵌套的层次关系中,最近的需要匹配的括号。
import java.util.Scanner;
import java.util.Stack;
public class BracketsSolution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (int i=0;i<s.length();i++){
char c=s.charAt(i);
if(c=='('||c=='['||c=='{')
stack.push(c);
else{
if(stack.isEmpty())
return false;
char top=stack.pop();
if(c==')'&&top!='(')
return false;
if(c==']'&&top!='[')
return false;
if(c=='}'&&top!='{')
return false;
}
}
return stack.isEmpty();
}
public static void main(String[] args) {
BracketsSolution bs=new BracketsSolution();
Scanner sc=new Scanner(System.in);
System.out.println("请输入字符:");
String c=sc.next();
System.out.println(bs.isValid(c));
}
}