1. 栈(Stack)
1.1 概念
栈:一种特殊的线性表,只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中数据元素遵循后进先出LIFO(Last In First Out)的原则
压栈:栈的插入操作可以叫做进栈、压栈、入栈,入数据在栈顶
出栈:栈的删除操作叫做出栈,出数据在栈顶
1.2 栈的使用
方法 | 功能 |
Stack() | 构造一个空的栈 |
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(s.size());//获取栈中有效数据
System.out.println(s.peek());//查看栈顶元素
System.out.println(s.pop());//使栈顶元素出栈
System.out.println(s.empty());//检测栈是否为空
System.out.println(s.isEmpty());//检测栈是否为空,继承自Vector
}
System.out.println(s.isEmpty());
上面的isEmpty()方法,查看源码,虽然栈中没有,但是栈继承自Vector,在父类Vector中,有isEmpty方法
1.3 栈的模拟实现
import java.util.Arrays;
public class MyStack {
public int[] elem;
public int usedSize;
public MyStack() {
this.elem = new int[10];
}
public void push(int val) {
if(isFull()) {
//扩容
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[usedSize] = val;
usedSize++;
}
public boolean isFull(){
return usedSize == elem.length;
}
public int pop() {
if(empty()) {
return -1;
}
int oldVal = elem[usedSize-1];
usedSize--;
return oldVal;
}
public int peek() {
if(empty()) {
return -1;
}
return elem[usedSize-1];
}
public boolean empty() {
return usedSize == 0;
}
}
使用泛型实现
import java.util.Arrays;
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;
}
}
1.4 栈的应用场景
1.将递归转化为循环
//递归方式
public void printList(Node head) {
if(null != head) {
printList(head.next);
System.out.println(head.val + " ");
}
}
//运用栈的循环方式
public void printList(Node head) {
if(null == head) {
return;
}
Stack<Node> s = new Stack<>();
//将链表中的节点保存在栈中
Node cur = head;
while(null != cur) {
s.push(cur);
cur = cur.next;
}
//将栈中元素出栈
while(!s.empty()) {
System.out.println(s.pop().val + " ");
}
}
2. 括号匹配
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();//创建字符类型栈
for(int i = 0; i < s.length(); i++) {//遍历字符串,将字符串中字符取出
char ch = s.charAt(i);
//1.遇到左括号,入栈
if(ch == '(' || ch == '[' || ch == '{') {
stack.push(ch);
}else {
//2.遇到右括号
//先判断栈是否为空
if(stack.empty()) {
return false;
}else {
//3.取栈顶左括号看与当前右括号是否匹配
char chL = stack.peek();
if(chL == '(' && ch == ')' || chL == '[' && ch == ']' || chL == '{' && ch == '}') {
stack.pop();//若左右括号匹配,则栈顶元素出栈
}else {
return false;
}
}
}
}
return stack.empty();//最后若栈为空,返回true,栈不为空,返回false
}
}
3. 逆波兰表达式求值
解析:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
- 平常使用的算式则是一种中缀表达式,如
( 1 + 2 ) * ( 3 + 4 )
。 - 该算式的逆波兰表达式写法为
( ( 1 2 + ) ( 3 4 + ) * )
。
逆波兰表达式主要有以下两个优点:
- 去掉括号后表达式无歧义,上式即便写成
1 2 + 3 4 + *
也可以依据次序计算出正确结果。 - 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
将中缀表达式转换为后缀表达式的方法:
当逆波兰式运用到栈中,按照次序将数字入栈,若遇到运算符,则取出栈顶两个数字,先取出为运算符右边操作数,后取出为运算符左边操作数(这是为了避免当运算符为 - 或 / 时,顺序不同造成结果不同的问题),如下图:
代码:
public int evalRPN(String[] tokens) {
Stack<Integer> s = new Stack<>();
for (int i = 0; i < tokens.length; i++) {
String tmp = tokens[i];
if(!isOpearation(tmp)) {//判断当前字符串是否为运算符
Integer val = Integer.valueOf(tmp);//将字符串转换为数字
s.push(val);
}else {
Integer val2 = s.pop();
Integer val1 = s.pop();
switch(tmp) {
case "+":
s.push(val1+val2);
break;
case "-":
s.push(val1-val2);
break;
case "*":
s.push(val1*val2);
break;
case "/":
s.push(val1/val2);
break;
}
}
}
return s.pop();
}
public boolean isOpearation(String s) {
if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")) {
return true;
}
return false;
}
4. 出栈入栈次序匹配
public boolean IsPopOrder (int[] pushV, int[] popV) {
// write code here
Stack<Integer> s = new Stack<>();
int count = 0;
for(int i = 0; i < pushV.length; i++) {
s.push(pushV[i]);
//这个单独的循环,必须保证栈不为空,count不能越界,栈顶元素等于count下标处值
while(!s.empty() && count != popV.length && s.peek() == popV[count]){
s.pop();
count++;
}
}
return s.empty();
}
5. 最小栈
class MinStack {
Stack<Integer> s;//普通栈,泛型类型别忘记加!!!
Stack<Integer> ms;//最小栈
public MinStack() {//构造方法
s = new Stack<>();
ms = new Stack<>();
}
public void push(int val) {
s.push(val);
if(ms.empty()) {//若为第一次入栈操作,则最小栈无条件入栈
ms.push(val);
}else {
Integer peekVal = ms.peek();//若不是第一次,则需与最小栈栈顶元素进行比较
if(val <= peekVal) {
ms.push(val);
}
}
}
public void pop() {
if(s.empty()) {
return;
}
int popVal = s.pop();//包装类属于引用类型,不能直接==,所以此处用int接收,自动拆箱
if(popVal == ms.peek()) {
ms.pop();
}
}
public int top() {
if(s.empty()) {
return -1;
}
return s.peek();
}
public int getMin() {
if(ms.empty()) {
return -1;
}
return ms.peek();
}
}
1.5 栈、虚拟机栈、栈帧的区别
栈(Stack):是一种只允许在一端进行插入或删除的线性表,满足后进先出的特点
虚拟机栈:逻辑结构,是具有特殊作用的一块内存空间,主管Java程序的运行,它保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回
栈帧:函数从调用过程到结束的体现,一个函数从调用到销毁中占用的空间,内部的局部变量统一放在栈帧中。每个函数在运行时,JVM都会创建一个栈帧,然后将栈帧压入到虚拟机栈中,当函数调用结束时,该函数对应的栈帧会从虚拟机栈中出栈