栈
栈是一种只允许在序列末端操作的数据结构。栈的末端也称之为栈顶。
栈的插入操作是将新元素压入栈顶,称为入栈。栈的删除操作是删除栈顶元素,称为出栈。由此可以看出,栈的特点是:后入栈的元素先出栈,先入栈的元素后出栈。
栈常用的操作有:入栈、出栈、取栈顶元素等。其接口定义如下:

1 public interface Stack<E> {
2
3 void push(E e);
4
5 E pop();
6
7 E getTop();
8
9 boolean isEmpty();
10
11 int size();
12
13 void clear();
14
15 }
顺序栈
顺序栈是采用顺序存储结构的栈。可以用一个数组来表示顺序栈,并指定栈顶位于数组末端。但是,数组的大小是预先确定的,存满之后无法继续插入新元素。所以在栈满时需要重新分配更大的空间进行扩容。
顺序栈中包括以下变量:
datas:存储元素的数组。
top:栈顶位标(栈顶元素的下一个位置)。top值也表示当前栈中的元素个数。
size:初始存储容量。
increment:数组扩容时增加的存储容量。
下图是一个初始容量为5,增量为3的栈:

可以看到其中已经有4个元素,此时top=4表示下一个入栈的元素应该放在datas[4]位置。
入栈
入栈操作是将元素放入top指向的位置,然后top自增。

可以看到,此时top的值为5,表示如果还有元素入栈,该元素需要放在datas[5]位置。而datas数组当前容量为5,并没有datas[5]位置,即不能再有新元素入栈,也就是栈已满。
所以,栈满的条件是:top指针的值等于datas数组当前容量,即top == datas.length。此时,如果再有元素入栈,需要先对栈进行扩容。因此,在每次入栈操作中,应该先判断栈是否需要扩容,之后再将元素压入栈中。
扩容操作可以理解为:重新定义一个datas数组,容量为原datas数组的容量加上容量增量increment,并将原datas数组中的所有元素放入新定义的datas数组中。

可以看到,扩容之后datas数组容量变为8,此时有了datas[5]位置,新元素可以入栈。

1 @SuppressWarnings("unchecked")
2 @Override
3 public void push(E e) {
4 // top == datas.length表示栈满,需要扩容
5 if (top == datas.length) {
6 // 重新定义新的数组,容量为datas.length + increment
7 E[] newbase = (E[]) new Object[datas.length + increment];
8 // 将原datas数组中的元素复制进新的数组中
9 System.arraycopy(datas, 0, newbase, 0, datas.length);
10 // datas数组为新的数组
11 datas = newbase;
12 }
13 // 将元素放入datas[top]中,并执行top++
14 datas[top++] = e;
15 }
出栈
出栈操作是获取栈顶元素,并将栈顶位置置为null,最后让top自减。

可以看到,原来的栈顶元素6被取出,top的值减1变为5,指向datas[5]位置。

1 @Override
2 public E pop() {
3 // top自减,指向栈顶元素的位置,并获取栈顶元素
4 E e = datas[--top];
5 // 将当前位置置为null,表示栈顶元素被取出
6 datas[top] = null;
7 return e;
8 }
取栈顶元素
取栈顶元素仅仅是获取当前栈顶的元素,并没有将该元素弹出栈外。

可以看到,当前的栈顶元素是5。

1 @Override
2 public E getTop() {
3 return datas[top - 1];
4 }
获取当前栈中元素的个数
top的值除了表示下一个元素入栈的位置,还可以表示当前栈中元素的个数。

当前top == 5,表示下一个元素入栈后存放的位置是datas[5],也表示当前栈中有5个元素。

1 @Override
2 public int size() {
3 return top;
4 }
判断栈是否为空
栈空的条件是:栈中元素的个数为0,即top == 0。

1 @Override
2 public boolean isEmpty() {
3 return top == 0;
4 }
栈的应用
栈具有后进先出的特点,所以栈一般用于结果需要倒序输出,或者是匹配最近的单位等。
数制转换
十进制数转换成N进制数过程为:将十进制数除以n,得到商和余数;再继续将商除以n,得到下一个商和余数;直到商为0。最后将余数从低位到高位排列,即为转换后的结果。
例如将十进制数1348转换为八进制数:
1. 1348 ÷ 8 = 168 ...... 4
2. 168 ÷ 8 = 21 ...... 0
3. 21 ÷ 8 = 2 ...... 5
4. 2 ÷ 8 = 0 ...... 2
5. 得到的余数分别是4、0、5、2,从低位到高位排列,得到十进制数1348转换为八进制数的结果为2504。
可以看到得到的余数最终要倒序输出才得到最终结果,所以可以先用栈存储余数,之后将栈中的余数弹出,就得到倒序后的结果。

1 public static String convert(int n, int radix) {
2 Stack<Integer> stack = new ArrayStack<Integer>();
3 while (n != 0) {
4 stack.push(n % radix);
5 n /= radix;
6 }
7 String result = "";
8 while (! stack.isEmpty()) {
9 result += stack.pop();
10 }
11 return result;
12 }
输入n=1348,进制radix=8,得到结果为:

括号匹配
假设括号序列中只允许出现括号(“(”、“)”、“[”、“]”、“{”、“}”),其出现的个数和顺序是任意的。对括号序列进行匹配,规则如下:
a. 如果为空串,返回true。
b. 如果当前右括号与前一个左括号不匹配(例如括号序列“([)]”,可以看到第一个出现的右括号是“)”,而前一个左括号是“[”,不匹配),返回false。
c. 右括号多余(例如括号序列“)[]”,右括号前没有左括号,是多余的),返回false。
d. 左右括号个数不一致(例如括号序列“([]”,左括号多余;括号序列“[])”,右括号多余),返回false。
可以用栈来实现该方法:
1. 从左向右扫描括号序列,若为左括号则压入栈中。若为右括号则先判断栈是否为空,为空表示当前右括号前没有左括号,表示右括号是多余的;不为空则弹出栈顶的左括号,判断是否与当前右括号匹配。
2. 扫描完括号序列后,判断栈是否已空,没有空表示左括号个数比右括号多,即存在多余的左括号,返回false。

1 public static boolean match(String str) {
2 // 空串直接返回true
3 if (str.equals("")) return true;
4 Stack<Character> stack = new ArrayStack<Character>();
5 for (int i = 0; i < str.length(); i++) {
6 char c = str.charAt(i);
7 if (c == '(' || c == '[' || c == '{') { // 左括号直接入栈
8 stack.push(c);
9 } else if (c == ')') {
10 // 先判断栈是否为空,为空表示该“)”多余。不为空则弹出栈顶的左括号,判断是否为“(",不为“(”表示括号不匹配
11 if (stack.isEmpty() || stack.pop() != '(') return false;
12 } else if (c == ']') {
13 // 先判断栈是否为空,为空表示该“]”多余。不为空则弹出栈顶的左括号,判断是否为“[",不为“[”表示括号不匹配
14 if (stack.isEmpty() || stack.pop() != '[') return false;
15 } else {
16 // 先判断栈是否为空,为空表示该“}”多余。不为空则弹出栈顶的左括号,判断是否为“{",不为“{”表示括号不匹配
17 if (stack.isEmpty() || stack.pop() != '{') return false;
18 }
19 }
20 // 判断栈是否为空,不为空表示左括号个数比右括号多,存在多余的左括号
21 return stack.isEmpty();
22 }

计算器
实现一个计算器功能,首先需要让计算器能够“读懂”输入的表达式。
算术表达式的表示形式按运算符的位置分,可以分为三种:中缀表达式、前缀表达式、后缀表达式。
计算器程序通过扫描算术表达式得到运算数、运算符,以及运算的先后顺序。通常情况下会借助一个数栈和一个符号栈实现。
中缀表达式
中缀表达式是指运算符位于运算数之间的表示形式。
1. 计算机从左往右扫描中缀表达式:
a. 如果是数字,压入数栈。
b. 如果是运算符:如果符号栈为空,则直接将当前运算符压入符号栈。否则:如果符号栈栈顶符号不为“(”,且其优先级不低于当前运算符,则弹出栈顶运算符op,从数栈中依次弹出两个数,先弹出的为num2,后弹出的为num1,执行num1 op num2并将结果压入数栈。循环执行该操作直到符号栈为空或者栈顶符号为“(”或其优先级低于当前运算符。将当前运算符压入符号栈中。
c. 如果是“(”,压入符号栈中。
d. 如果是“)”,则弹出栈顶符号op。如果op不为“(”,则从数栈中依次弹出两个数,先弹出的为num2,后弹出的为num1,执行num1 op num2并将结果压入数栈,继续弹出下一个栈顶符号。循环执行该操作直到op为“(”。
2. 中缀表达式扫描完后,若符号栈不为空,则弹出栈顶符号op,从数栈中依次弹出两个数,先弹出的为num2,后弹出的为num1,执行num1 op num2并将结果压入数栈,继续弹出下一个栈顶符号。循环执行该操作直到符号栈为空。
3. 符号栈为空后,数栈只剩下一个元素,即为最终结果。弹出该元素即可。
例如,对于10 * ( 1 + 3 - 4 + 1 ):
1. 定义数栈和符号栈。
2. 扫描中缀表达式:
| 序号 | 描述 | 数栈 | 符号栈 |
| a | 扫描到“10”,直接压入数栈 | [10 | [ |
| b | 扫描到“*”,当前符号栈为空,直接压入符号栈 | [10 | [* |
| c | 扫描到“(”,直接压入符号栈 | [10 | [*, ( |
| d | 扫描到“1”,直接压入数栈 | [10, 1 | [*, ( |
| e | 扫描到“+”,符号栈的栈顶符号为“(”,直接压入符号栈 | [10, 1 | [*, (, + |
| f | 扫描到“3”,直接压入数栈 | [10, 1, 3 | [*, (, + |
| g |
扫描到“-”: 1. 符号栈的栈顶符号为“+”,弹出栈顶符号“+”,数栈依次弹出“3”和“1”,执行“1 + 3”,将结果“4”压入数栈 2. 符号栈的栈顶符号为“(”,结束循环 3. 将“-”压入符号栈 |
[10, 4 | [*, (, - |
| h | 扫描到“4”,直接压入数栈 | [10, 4, 4 | [*, (, - |
| i |
扫描到“+”: 1. 符号栈的栈顶符号为“-”,弹出栈顶符号“-”,数栈依次弹出“4”和“4”,执行“4 - 4”,将结果“0”压入数栈 2. 符号栈的栈顶符号为“(”,结束循环 3. 将“+”压入符号栈 |
[10, 0 |
[*, (, + |
| j | 扫描到“1”,直接压入数栈 | [10, 0, 1 | [*, (, + |
| k |
扫描到“)”: 1. 弹出栈顶符号“+” 2. 当前符号不是“(”,所以数栈依次弹出“1”和“0”,执行“0 + 1”,将结果“1”压入数栈,弹出下一个栈顶符号“(” 3. 当前符号是“(”,结束循环 |
[10, 1 | [* |
3. 符号栈不为空,需要依次弹出并进行运算:
| 序号 | 描述 | 数栈 | 符号栈 |
| a | 符号栈不为空,弹出栈顶符号“*”,数栈依次弹出“1”和“10”,执行“10 * 1”,将结果“-10”压入数栈 | [10 | [ |
4. 弹出数栈最后一个元素“10”,即为运算的最终结果。

1 public static int calculateInfix(String infix) {
2 Stack<Integer> numStack = new ArrayStack<Integer>();
3 Stack<Operator> opStack = new ArrayStack<Operator>();
4 int num1, num2;
5 Operator op;
6 for (String s : infix.split(" ")) {
7 if (s.equals("+") || s.equals("-")) {
8 while (! opStack.isEmpty()) {
9 // +、-的优先级最低,所以如果栈顶符号不为“(”,都需要进行运算
10 if (opStack.getTop() == Operator.left) break;
11 // 弹出栈顶符号
12 op = opStack.pop();
13 // 先弹出的数为num2
14 num2 = numStack.pop();
15 // 后弹出的数为num1
16 num1 = numStack.pop();
17 // 执行num1 op num2,并将结果压入数栈
18 numStack.push(calculate(num1, op, num2));
19 }
20 opStack.push(Operator.parse(s));
21 } else if (s.equals("*") || s.equals("/")) {
22 while (! opStack.isEmpty()) {
23 op = opStack.getTop();
24 // *、/的优先级高于+、-,所以如果栈顶符号不为“(”、“+”、“-”,都需要进行运算
25 if (op == Operator.left || op == Operator.add || op == Operator.subtract) break;
26 // 弹出栈顶符号
27 op = opStack.pop();
28 // 先弹出的数为num2
29 num2 = numStack.pop();
30 // 后弹出的数为num1
31 num1 = numStack.pop();
32 // 执行num1 op num2,并将结果压入数栈
33 numStack.push(calculate(num1, op, num2));
34 }
35 opStack.push(Operator.parse(s));
36 } else if (s.equals("(")) {
37 // “(”直接压入符号栈
38 opStack.push(Operator.left);
39 } else if (s.equals(")")) {
40 // 弹出栈顶符号
41 op = opStack.pop();
42 while (op != Operator.left) {
43 // 先弹出的数为num2
44 num2 = numStack.pop();
45 // 后弹出的数为num1
46 num1 = numStack.pop();
47 // 执行num1 op num2,并将结果压入数栈
48 numStack.push(calculate(num1, op, num2));
49 // 弹出下一个栈顶符号
50 op = opStack.pop();
51 }
52 } else {
53 // 数字直接压入数栈
54 numStack.push(Integer.parseInt(s));
55 }
56 }
57 while (! opStack.isEmpty()) {
58 // 弹出栈顶符号
59 op = opStack.pop();
60 // 先弹出的数为num2
61 num2 = numStack.pop();
62 // 后弹出的数为num1
63 num1 = numStack.pop();
64 // 执行num1 op num2,并将结果压入数栈
65 numStack.push(calculate(num1, op, num2));
66 }
67 // 符号栈为空时,数栈只剩下一个元素,即为最终结果
68 return numStack.pop();
69 }
