对比Queue 来说 栈(Stack)是一种后进先出(LIFO:Last In First Out)的数据结构。(相当于把前面的通道给封死)。
Stack
是这样一种数据结构:只能不断地往Stack
中压入(push)元素,最后进去的必须最早弹出(pop)来。
Stack
只有入栈和出栈的操作:
- 把元素压栈:
push(E)
; - 把栈顶的元素“弹出”:
pop(E)
; - 取栈顶元素但不弹出:
peek(E)
。
为什么Java的集合类没有单独的Stack
接口呢?因为有个遗留类名字就叫Stack
,出于兼容性考虑,所以没办法创建Stack
接口,只能用Deque
接口来“模拟”一个Stack
了。
当我们把Deque
作为Stack
使用时,注意只调用push()
/pop()
/peek()
方法,不要调用addFirst()
/removeFirst()
/peekFirst()
方法,这样代码更加清晰。
Stack的作用
Stack在计算机中使用非常广泛,JVM在处理Java方法调用的时候就会通过栈这种数据结构维护方法调用的层次。例如
static void main(String[] args) {
foo(123);
}
static String foo(x) {
return "F-" + bar(x + 1);
}
static int bar(int x) {
return x << 2;
}
JVM会创建方法调用栈,每调用一个方法时,先将参数压栈,然后执行对应的方法;当方法返回时,返回值压栈,调用方法通过出栈操作获得方法返回值。
因为方法调用栈有容量限制,嵌套调用过多会造成栈溢出,即引发StackOverflowError
:
public class Main {
public static void main(String[] args) {
increase(1);
}
static int increase(int x) {
return increase(x) + 1;
}
}
另一个Stack
的用途,对整数进行进制的转换就可以利用栈。
例如,我们要把一个int
整数12500
转换为十六进制表示的字符串
首先我们准备一个空栈;
然后计算12500÷16=781…4,余数是4
,把余数4
压栈;
然后计算781÷16=48…13,余数是13
,13
的十六进制用字母D
表示,把余数D
压栈;
然后计算48÷16=3…0,余数是0
,把余数0
压栈;
最后计算3÷16=0…3,余数是3
,把余数3
压栈。
得到
当商是0
的时候,计算结束,我们把栈的所有元素依次弹出,组成字符串30D4
,这就是十进制整数12500
的十六进制表示的字符串。
练习1:请利用Stack把一个给定的整数转换为十六进制:
import java.util.*;
public class Main {
public static void main(String[] args) {
String hex = toHex(12500);
if (hex.equalsIgnoreCase("30D4")) {
System.out.println("测试通过");
} else {
System.out.println("测试失败");
}
}
static String toHex(int n) {
Deque<String> hex =new LinkedList<>();
String string = "";
var connectString = new StringBuilder();
for (; n>0 ; n/=16) {
hex.push(Integer.toHexString(n%16));
}
for(String s:hex){
connectString.append(s);
}
return connectString.toString();
}
}
练习2:请利用Stack把字符串中缀表达式编译为后缀表达式,然后再利用栈执行后缀表达式获得计算结果:
(在编写程序的时候,我们使用的带括号的数学表达式实际上是中缀表达式,即运算符在中间,例如:1 + 2 * (9 - 5)
。但是计算机执行表达式的时候,它并不能直接计算中缀表达式,而是通过编译器把中缀表达式转换为后缀表达式,例如:1 2 9 5 - * +
。)
package StackDemo;
import java.util.*;
public class testMain {
public static void main(String[] args) {
String exp = "1 + 2 * (9 - 5)";
SuffixExpression se = compile(exp);
float result = se.execute();
System.out.println(exp + " = " + result + " " + (result == 1 + 2 * (9 - 5) ? "✓" : "✗"));
}
static SuffixExpression compile(String exp) {
// TODO:
Deque<Character> deque = new LinkedList<>();
char[] charExp = exp.toCharArray();
String out = "";
for (char ch : charExp) {
if(ch == ' ')continue;
if (ch>='0'&&ch<='9'){//1295
out += ch;
continue;
}
if(ch=='('){
deque.push(ch);
}
if (ch == '+' || ch =='-') {
while(!deque.isEmpty() && (deque.peek() != '(')) {
out += deque.pop();
}
deque.push(ch);
continue;
}
if (ch == '*' || ch =='/') {
while(!deque.isEmpty() && (deque.peek() == '*' || deque.peek() == '/')) {
out += deque.pop();
}
deque.push(ch);
continue;
}
if (ch == ')') {
while(!deque.isEmpty() && deque.peek() != '(') {
out += deque.pop();
}
deque.pop();
continue;
}
}
while(!deque.isEmpty()) out += deque.pop();
System.out.println(out);
return new SuffixExpression(out);
}
}
class SuffixExpression {
private String exp;
public SuffixExpression(String exp) {
this.exp = exp;
}
public float execute() {
// TODO:
Deque<Float> cs= new LinkedList<>();
char[] cexp = exp.toCharArray();
for (int i = 0; i < cexp.length; i++) {
char ch = cexp[i];
if (ch>='0' && ch<='9') {
cs.push(Float.valueOf(ch - '0'));
continue;
} else {
cs.push(calculate(ch, cs.pop(), cs.pop()));
}
}
System.out.println(exp);
return cs.pop();
}
public float calculate(char op, Float f1, Float f2) {
if (op == '+') return f1 + f2;
else if (op == '-') return f2 -f1;
else if (op == '*') return f1 * f2;
else if (op == '/') return f2/f1;
else return Float.valueOf(-0);
}
}