数据结构(Java)—— 栈(Stack)
1. 概念
栈
:一种特殊的线性表,其
只允许在固定的一端进行插入和删除元素操作
。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO
(
Last In First Out
)的原则。
压栈:栈的插入操作叫做进栈
/
压栈
/
入栈,
入数据在栈顶
。
出栈:栈的删除操作叫做出栈。
出数据在栈顶
。
如图所示:
2. 方法
2.1 栈的实例化
方法 | 描述 |
new Stack<E>() | 基于数组实现的 |
new LinkedList<E>() | 基于链表实现的 |
new Deque<E>() | 基于双向队列实现的 |
代码示例:
public static void main(String[] args) {
Stack<Integer> stack1 = new Stack<>();
LinkedList<Integer> stack2 = new LinkedList<>();
Deque<Integer> stack3 = new LinkedList<>();
stack1.push(1);
stack1.push(2);
stack1.push(3);
stack2.push(10);
stack2.push(20);
stack2.push(30);
stack3.push(11);
stack3.push(22);
stack3.push(33);
System.out.println("======基于数组实现的栈======");
System.out.println(stack1.peek());
System.out.println(stack1.pop());
System.out.println(stack1.peek());
System.out.println("======基于链表实现的栈======");
System.out.println(stack2.peek());
System.out.println(stack2.pop());
System.out.println(stack2.peek());
System.out.println("======基于双向队列实现的栈======");
System.out.println(stack3.peek());
System.out.println(stack3.pop());
System.out.println(stack3.peek());
}
运行结果如下:
2.2 栈的使用
栈的一些主要操作方法如下:
方法
| 功能 |
E push(E e)
| 将e入栈,并返回e |
E pop()
| 将栈顶元素出栈并返回 |
E peek()
| 获取栈顶元素 |
int size()
| 获取栈中有效元素个数 |
boolean empty()
| 检测栈是否为空 |
代码示例:
public static void main(String[] args) {
Stack<Integer> s = new Stack();
s.push(1);
s.push(2);
s.push(3);
s.push(4);
System.out.println("size: "+s.size()); // 获取栈中有效元素个数---> 4
System.out.println(s.peek()); // 获取栈顶元素---> 4
s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3
System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为3
if(s.empty()){
System.out.println("栈空");
}else{
System.out.println("size: "+s.size());
}
}
运行结果如下:
3. 栈的模拟实现
本文主要讲基于数组实现栈的功能
代码如下:
public class MyStack<E> {
public Object[] elem;
public int usedSize;
public MyStack() {
this.elem = new Object[10];
}
public void push(E val) {
if(isFull()) {
//扩容
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[usedSize] = val;
usedSize++;
}
public boolean isFull() {
return usedSize == elem.length;
}
public E pop() {
if(empty()) {
return null;
}
E oldVal = (E)elem[usedSize-1];
usedSize--;
return oldVal;
}
public E peek() {
if(empty()) {
return null;
}
return (E)elem[usedSize-1];
}
public boolean empty() {
return usedSize == 0;
}
public int size(){
return usedSize;
}
}
注意:
1. 要使用一个变量(usedSize)记录数组(elem)中的有效元素个数;
2. 当数组满了(usedSize == elem.length )时,要对数组进行扩容;
3.进行出栈(pop)或者查看栈顶元素(peek)时,应先判断数组是否为空(empty)
代码使用:
public static void main(String[] args) {
MyStack<Integer> s = new MyStack<>();
s.push(1);
s.push(2);
s.push(3);
s.push(4);
System.out.println("size: "+s.size()); // 获取栈中有效元素个数---> 4
System.out.println(s.peek()); // 获取栈顶元素---> 4
s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3
System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为3
if(s.empty()){
System.out.println("栈空");
}else{
System.out.println("size: "+s.size());
}
}
运行结果如下:
4. 应用场景
-
函数调用:当程序调用一个函数时,会将返回地址、局部变量等信息压入栈中,等到函数执行完毕后,再从栈顶弹出这些信息。
-
表达式求值:在计算过程中,括号匹配、递归算法等都利用了栈来存储中间结果,如前缀表达式的求值。
-
浏览器历史记录:浏览网页时,每次前进或后退都是通过栈来管理历史状态的,后访问的页面会被推到栈顶,最先访问的则位于栈底。
-
深度优先搜索(DFS):在图或树的遍历中,栈常用于保存待访问的节点,遵循“先进先出”的规则。
-
括号匹配:检查字符串中的括号是否配对,可以使用两个栈,一个存放左括号,另一个存放遇到的右括号,通过比较左右括号是否一一对应来判断匹配。
-
系统调用堆栈:操作系统中,每个进程都有一个调用堆栈,用于跟踪函数调用的上下文。