四、栈
1、概念
栈和队列类似,不同的是栈遵循 LIFO
(last-in-first-out
)的原则,即最后一个入栈的元素,最先被取出
来
2、常用方法
- push。入栈
- pop。出栈
- peek。看一下栈顶的元素
3、示例
【1】数组栈
class ArrayStack<T> {
private final int length;
private Object[] array;
private int top; // 指向栈顶的前一个位置
public ArrayStack(int length) {
this.length = length;
this.array = new Object[this.length];
this.top = -1;
}
public void push(T element) {
if (isFull()) {
return;
}
array[++top] = element;
}
@SuppressWarnings("unchecked")
public T pop() {
if (isEmpty()) {
return null;
}
return (T)array[top--];
}
@SuppressWarnings("unchecked")
public T peek() {
if (isEmpty()) {
return null;
}
return (T)array[top];
}
public boolean isFull() {
return top == length - 1;
}
public boolean isEmpty() {
return top == -1;
}
@Override
public String toString() {
if (isEmpty()) {
return "[]";
}
StringBuilder sBuilder = new StringBuilder("[");
for (int i = top; i > 0; i--) {
sBuilder.append(array[i]).append(',').append(' ');
}
sBuilder.append(array[0]).append(']');
return sBuilder.toString();
}
}
【2】链表栈
class LinkedStack<T> {
private final int length;
private Node<T> top;
private int size;
public LinkedStack(int length) {
this.length = length;
this.top = null;
this.size = 0;
}
public void push(T element) {
if (isFull()) {
return;
}
Node<T> add = new Node<T>(element);
add.next = top;
top = add;
this.size++;
}
public T pop() {
if (isEmpty()) {
return null;
}
Node<T> node = this.top;
this.top = node.next;
this.size--;
return node.element;
}
public T peek() {
if (isEmpty()) {
return null;
}
return this.top.element;
}
public boolean isFull() {
return size == length;
}
public boolean isEmpty() {
return this.top == null;
}
private static class Node<T> {
private T element;
private Node<T> next;
public Node(T element) {
this.element = element;
}
@Override
public String toString() {
return (null != element) ? element.toString() : "null";
}
}
@Override
public String toString() {
if (isEmpty()) {
return "[]";
}
StringBuilder sBuilder = new StringBuilder("[");
Node<T> node = this.top;
while (true) {
sBuilder.append(node.element);
if (null == node.next) {
break;
}
node = node.next;
sBuilder.append(',').append(' ');
}
sBuilder.append(']');
return sBuilder.toString();
}
}
4、应用
【1】中缀表达式
中缀表达式即我们日常计算时使用的表达式算法,例如 1 + 2 * ( 3 / 4.5 )
即运算符在运算量的中间。虽然对于人类来说计算方便,但是对于计算机计算时却比较麻烦
计算方式:
-
使用2个栈结构,一个保存数字(数栈),另一个保存运算符(符号栈)
-
从左向右遍历中缀表达式,当遇到数字时,放到数栈中
-
当遇到运算符时,如果符号栈为空,则直接入符号栈
-
如果符号栈不为空,则需要判断符号栈顶的运算符和当前运算符的优先级(左小括号优先级别最低):
如果当前运算符的优先级大于栈顶的符号,就把当前运算符放到符号栈中
如果当前运算符的优先级小于或等于栈顶的符号,则取出栈顶符号,同时从数栈中取出两个数字,第一个数字作为 (加/减/乘/除)数 ,第二个数字作为 被(加/减/乘/除)数 ,将计算结果放到数栈中,同时将当前运算符入符号栈
-
当遇到小括号时,如果是左小括号,则直接入符号栈,如果是右小括号,则需要依次取出一个运算符和两个数字,计算后,将结果放到数栈中,重复此操作,直到遇到左括号,并将其出栈为止
-
最后:从符号栈中取出栈顶符号,同时从数栈中取出两个数字,第一个数字作为 (加/减/乘/除)数 ,第二个数字作为 被(加/减/乘/除)数 ,将计算结果放到数栈中,重复此操作,直到符号栈空为止。最终数栈会留下一个元素,即计算结果
示例:
import java.util.Stack;
class InfixExpression {
public static double calc(String expression) {
if (null == expression || 0 == expression.length()) {
return 0;
}
// 替换空白字符,包括空格、制表符、换页符等
expression = expression.replaceAll("\\s", "");
if (0 == expression.length()) {
return 0;
}
return split(expression);
}
private static double split(String expression) {
Stack<String> digitStack = new Stack<String>();
Stack<String> operatorStack = new Stack<String>();
String digit = "";
for (int i = 0, len = expression.length(); i < len; i++) {
char ch = expression.charAt(i);
if (isOperator(ch)) {
if (!"".equals(digit)) {
// 先判断是否有数字
digitStack.push(digit);
digit = "";
}
if (operatorStack.isEmpty()) {
// 符号栈为空
operatorStack.push(String.valueOf(ch));
} else {
if ('(' == ch) {
operatorStack.push(String.valueOf(ch));
} else if (')' == ch) {
char operator = operatorStack.pop().charAt(0);
while ('(' != operator) {
String digit1 = digitStack.pop();
String digit2 = digitStack.pop();
double calc = calc(digit1, digit2, operator);
digitStack.push(Double.toString(calc));
operator = operatorStack.pop().charAt(0);
}
} else {
char pop = operatorStack.pop().charAt(0);
// 当前运算符的算数优先级 大于 符号栈顶的运算符的算数优先级
if (getPriority(ch) > getPriority(pop)) {
operatorStack.push(String.valueOf(pop));
} else {
String digit1 = digitStack.pop();
String digit2 = digitStack.pop();
double calc = calc(digit1, digit2, pop);
digitStack.push(Double.toString(calc));
}
operatorStack.push(String.valueOf(ch));
}
}
} else {
digit += ch;
}
}
if (!"".equals(digit)) {
// 再判断一次是否有数字
digitStack.push(digit);
}
return result(digitStack, operatorStack);
}
/**
* 加减乘除 和 小括号都是运算符
*/
private static boolean isOperator(char ch) {
return '+' == ch || '-' == ch || '*' == ch || '/' == ch || '(' == ch || ')' == ch;
}
private static double calc(String digit1, String digit2, char operator) {
switch (operator) {
case '+':
return Double.valueOf(digit2) + Double.valueOf(digit1);
case '-':
return Double.valueOf(digit2) - Double.valueOf(digit1);
case '*':
return Double.valueOf(digit2) * Double.valueOf(digit1);
case '/':
return Double.valueOf(digit2) / Double.valueOf(digit1);
default:
return 0;
}
}
private static int getPriority(char operator) {
switch (operator) {
case '(':
return 0;
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
throw new IllegalArgumentException("运算符不正确!");
}
}
private static double result(Stack<String> digitStack, Stack<String> operatorStack) {
while (!operatorStack.isEmpty()) {
// 符号栈不为空
String digit1 = digitStack.pop();
String digit2 = digitStack.pop();
char operator = operatorStack.pop().charAt(0);
double calc = calc(digit1, digit2, operator);
digitStack.push(Double.toString(calc));
}
return Double.valueOf(digitStack.pop()); // 最终数栈只留下一个元素:计算结果
}
}
【2】前缀表达式(波兰表达式)
前缀表达式即运算符在运算量的前面,例如:+ 1 * 2 / 3 4.5
中缀表达式转前缀表达式:
-
使用2个栈结构,一个保存数字(数栈),另一个保存运算符(符号栈)
-
从右向左遍历中缀表达式,当遇到数字时,放到数栈中
-
当遇到运算符时,如果符号栈为空,则直接入符号栈
-
如果符号栈不为空,则需要判断符号栈顶的运算符和当前运算符的优先级(右小括号优先级别最低):
如果当前运算符的优先级大于或等于栈顶的符号,就把当前运算符放到符号栈中
如果当前运算符的优先级小于栈顶的符号,则取出符号栈顶的符号,放入数栈中,同时将当前运算符入符号栈
-
当遇到小括号时,如果是右小括号,则直接入符号栈,如果是左小括号,则需要取出符号栈顶的符号,放入数栈中,重复此操作,直到遇到右括号,并将其出栈为止
-
最后将符号栈中的符号依次出栈,并入数栈中
计算方式:
- 反转数栈中的元素
- 使用1个临时栈结构
- 依次出栈波兰表达式中的元素,如果是数字则入临时栈
- 如果是符号,则从临时栈中取出两个数字,第一个数字作为 被(加/减/乘/除)数 ,第二个数字作为 (加/减/乘/除)数 ,将计算结果放到临时栈中,重复此操作,直到所有元素都出栈为止。最终临时栈会留下一个元素,即计算结果
示例:
import java.util.Stack;
class PrefixExpression {
public static double calc(String expression) {
if (null == expression || 0 == expression.length()) {
return 0;
}
expression = expression.replaceAll("\\s", "");
if (0 == expression.length()) {
return 0;
}
return split(expression);
}
private static double split(String expression) {
Stack<String> digitStack = new Stack<String>();
Stack<String> operatorStack = new Stack<String>();
String digit = "";
for (int i = expression.length() - 1; i >= 0; i--) {
// 从右向左遍历
char ch = expression.charAt(i);
if (isOperator(ch)) {
if (!"".equals(digit)) {
// 先判断是否有数字
digitStack.push(digit);
digit = "";
}
if (operatorStack.isEmpty()) {
// 符号栈为空
operatorStack.push(String.valueOf(ch));
} else {
if (')' == ch) {
operatorStack.push(String.valueOf(ch));
} else if ('(' == ch) {
char operator = operatorStack.pop().charAt(0);
while (')' != operator) {
digitStack.push(String.valueOf(operator));
operator = operatorStack.pop().charAt(0);
}
} else {
char pop = operatorStack.pop().charAt(0);
if (getPriority(ch) >= getPriority(pop)) {
operatorStack.push(String.valueOf(pop));
} else {
digitStack.push(String.valueOf(pop));
}
operatorStack.push(String.valueOf(ch));
}
}
} else {
digit = ch + digit; // 当前数字放到临时保存的数字前面
}
}
if (!"".equals(digit)) {
// 再判断一次是否有数字
digitStack.push(digit);
}
while (!operatorStack.isEmpty()) {
// 将运算符入到数栈中
digitStack.push(operatorStack.pop());
}
return result(digitStack);
}
/**
* 加减乘除 和 小括号都是运算符
*/
private static boolean isOperator(char ch) {
return '+' == ch || '-' == ch || '*' == ch || '/' == ch || '(' == ch || ')' == ch;
}
private static double calc(String digit1, String digit2, char operator) {
switch (operator) {
case '+':
return Double.valueOf(digit1) + Double.valueOf(digit2);
case '-':
return Double.valueOf(digit1) - Double.valueOf(digit2);
case '*':
return Double.valueOf(digit1) * Double.valueOf(digit2);
case '/':
return Double.valueOf(digit1) / Double.valueOf(digit2);
default:
return 0;
}
}
private static int getPriority(char operator) {
switch (operator) {
case ')':
return 0;
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
throw new IllegalArgumentException("运算符不正确!");
}
}
private static double result(Stack<String> digitStack) {
printNotation(digitStack);
Stack<String> temp = new Stack<String>();
while (!digitStack.isEmpty()) {
// 反转波兰表达式
temp.push(digitStack.pop());
}
Stack<String> tempDigitStack = new Stack<String>();
while (!temp.isEmpty()) {
String pop = temp.pop();
char first = pop.charAt(0);
if (isOperator(first)) {
String digit1 = tempDigitStack.pop();
String digit2 = tempDigitStack.pop();
double calc = calc(digit1, digit2, first);
tempDigitStack.push(Double.toString(calc));
} else {
tempDigitStack.push(pop);
}
}
return Double.valueOf(tempDigitStack.pop());
}
private static void printNotation(Stack<String> digitStack) {
for (int i = digitStack.size() - 1; i >= 0; i--) {
System.out.print(digitStack.get(i) + " ");
}
System.out.println();
}
}
【3】后缀表达式(逆波兰表达式)
逆波兰表达式(reverse Polish notation,RPN)把运算量写在前面,把运算符写在后面
例如:1 2 3 4.5 / * +
中缀表达式转逆波兰表达式:
-
使用2个栈结构,一个保存数字(数栈),另一个保存运算符(符号栈)
-
从左向右遍历中缀表达式,当遇到数字时,放到数栈中
-
当遇到运算符时,如果符号栈为空,则直接入符号栈
-
如果符号栈不为空,则需要判断符号栈顶的运算符和当前运算符的优先级(左小括号优先级别最低):
如果当前运算符的优先级大于栈顶的符号,就把当前运算符放到符号栈中
如果当前运算符的优先级小于或等于栈顶的符号,则取出符号栈顶的符号,放入数栈中,同时将当前运算符入符号栈
-
当遇到小括号时,如果是左小括号,则直接入符号栈,如果是右小括号,则需要取出符号栈顶的符号,放入数栈中,重复此操作,直到遇到左括号,并将其出栈为止
-
最后将符号栈中的符号依次出栈,并入数栈中
-
反转数栈中的元素
计算方式:
- 使用1个临时栈结构
- 依次出栈逆波兰表达式中的元素,如果是数字则入临时栈
- 如果是符号,则从临时栈中取出两个数字,第一个数字作为 (加/减/乘/除)数 ,第二个数字作为 被(加/减/乘/除)数 ,将计算结果放到临时栈中,重复此操作,直到所有元素都出栈为止。最终临时栈会留下一个元素,即计算结果
示例:
import java.util.Stack;
class RPN {
public static double calc(String expression) {
if (null == expression || 0 == expression.length()) {
return 0;
}
expression = expression.replaceAll("\\s", "");
if (0 == expression.length()) {
return 0;
}
return split(expression);
}
private static double split(String expression) {
Stack<String> digitStack = new Stack<String>();
Stack<String> operatorStack = new Stack<String>();
String digit = "";
for (int i = 0, len = expression.length(); i < len; i++) {
char ch = expression.charAt(i);
if (isOperator(ch)) {
if (!"".equals(digit)) {
// 先判断是否有数字
digitStack.push(digit);
digit = "";
}
if (operatorStack.isEmpty()) {
// 符号栈为空
operatorStack.push(String.valueOf(ch));
} else {
if ('(' == ch) {
operatorStack.push(String.valueOf(ch));
} else if (')' == ch) {
char operator = operatorStack.pop().charAt(0);
while ('(' != operator) {
digitStack.push(String.valueOf(operator));
operator = operatorStack.pop().charAt(0);
}
} else {
char pop = operatorStack.pop().charAt(0);
if (getPriority(ch) > getPriority(pop)) {
operatorStack.push(String.valueOf(pop));
} else {
digitStack.push(String.valueOf(pop));
}
operatorStack.push(String.valueOf(ch));
}
}
} else {
digit += ch;
}
}
if (!"".equals(digit)) {
// 再判断一次是否有数字
digitStack.push(digit);
}
while (!operatorStack.isEmpty()) {
// 将运算符入到数栈中
digitStack.push(operatorStack.pop());
}
Stack<String> temp = new Stack<String>();
while (!digitStack.isEmpty()) {
// 反转数栈
temp.push(digitStack.pop());
}
return result(temp);
}
/**
* 加减乘除 和 小括号都是运算符
*/
private static boolean isOperator(char ch) {
return '+' == ch || '-' == ch || '*' == ch || '/' == ch || '(' == ch || ')' == ch;
}
private static double calc(String digit1, String digit2, char operator) {
switch (operator) {
case '+':
return Double.valueOf(digit2) + Double.valueOf(digit1);
case '-':
return Double.valueOf(digit2) - Double.valueOf(digit1);
case '*':
return Double.valueOf(digit2) * Double.valueOf(digit1);
case '/':
return Double.valueOf(digit2) / Double.valueOf(digit1);
default:
return 0;
}
}
private static int getPriority(char operator) {
switch (operator) {
case '(':
return 0;
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
throw new IllegalArgumentException("运算符不正确!");
}
}
private static double result(Stack<String> digitStack) {
printNotation(digitStack);
Stack<String> tempDigitStack = new Stack<String>();
while (!digitStack.isEmpty()) {
String pop = digitStack.pop();
char first = pop.charAt(0);
if (isOperator(first)) {
String digit1 = tempDigitStack.pop();
String digit2 = tempDigitStack.pop();
double calc = calc(digit1, digit2, first);
tempDigitStack.push(Double.toString(calc));
} else {
tempDigitStack.push(pop);
}
}
return Double.valueOf(tempDigitStack.pop());
}
private static void printNotation(Stack<String> digitStack) {
for (int i = digitStack.size() - 1; i >= 0; i--) {
System.out.print(digitStack.get(i) + " ");
}
System.out.println();
}
}