一、概念
- 栈是一个先入后出(FILO)的有序列表。
- 栈限制线性表中元素的插入和删除只能在同一端进行,并将此端称为栈顶,而另一端为固定的一端,称为栈底。
二、栈的应用场景
- 子程序的调用:在跳往子程序之前,会先将下一个指令的地址存放到堆栈中,直到子程序执行完之后再将地址取出,以回到原来的程序中。
- 处理递归调用:和子程序调用类似,但会将参数、区域变量等数据也存入堆栈中。
- 表达式的转换与求值。
- 二叉树的遍历。
- 图的深度优先(depth-first)遍历。
三、代码实现
3.1 使用数组实现栈
package a04.arrayStack;
public class ArrayStack {
private int maxSize;
private int[] stack;
private int top = -1;
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[maxSize];
}
//栈满
public boolean isFull() {
return top + 1 == maxSize;
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//入栈
public void push(int value) {
if (isFull()) {
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
}
//出栈
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈空");
}
int result = stack[top];
top--;
return result;
}
//显示栈
public void show() {
if (isEmpty()) {
System.out.println("栈空");
return;
}
//从栈顶遍历
for (int i = top; i >= 0; i--) {
System.out.printf("%d\t", stack[i]);
}
System.out.println();
}
public static void main(String[] args) {
ArrayStack arrayStack = new ArrayStack(10);
for (int i = 0; i < 10; i++) {
arrayStack.push(i);
}
arrayStack.show();
for (int i = 0; i < 10; i++) {
arrayStack.pop();
}
arrayStack.show();
}
}
3.2 链表实现栈
package a04.arrayStack;
public class LinkedStack {
StackNode head;
int maxSize;
int size;
public LinkedStack(int maxSize) {
this.maxSize = maxSize;
}
//栈满
public boolean isFull() {
return size == maxSize;
}
//栈空
public boolean isEmpty() {
return size == 0;
}
//入栈
public void push(int num) {
if (isFull()) {
System.out.println("栈满");
}
StackNode node = new StackNode(num);
//考虑第一个节点的特殊性
if (head == null) {
head = node;
} else {
node.next = head;
head = node;
}
size++;
}
//出栈
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈空");
}
int result = head.num;
head = head.next;
size--;
//考虑最后一个节点的特殊性
if (size == 0) {
head = null;
}
return result;
}
//显示栈
public void show() {
if (isEmpty()) {
System.out.println("栈空");
return;
}
StackNode temp = head;
while (true) {
System.out.printf("%d\t",temp.num);
if (temp.next == temp ) {
break;
}
temp = temp.next;
}
System.out.println();
}
public static void main(String[] args) {
LinkedStack linkedStack = new LinkedStack(10);
for (int i = 0; i < 10; i++) {
linkedStack.push(i);
}
linkedStack.show();
for (int i = 0; i < 10; i++) {
linkedStack.pop();
}
linkedStack.show();
}
}
class StackNode {
int num;
StackNode next = this;
public StackNode(int num) {
this.num = num;
}
}
四、实战
4.1 使用栈来计算表达式
7 * 2 * 2 - 5 + 1 - 5 + 3 - 4
思路
- 使用两个栈来处理,一个数栈,一个运算符栈,遍历表达式并将其拆放入栈。
- 符号进栈时,若符号栈为空或者比栈内符号优先级高,就直接入栈
- 若优先级较栈内低,则将数栈中取两个数与栈内符号运算,然后将结果如数栈,将符号入符号栈。
- 当表达式遍历完毕,按顺序的将数栈与符号栈的符号进行运算,并将结果入数栈。
- 最后数栈只有1个值,即结果。
中缀表达式
package a04.calculator;
public class Calculator {
public static void main(String[] args) {
String expression = "30+2*6-2";
CalStack numStack = new CalStack(10);
CalStack opStack = new CalStack(10);
int result = 0;
String keepNumber = "";//用于处理多位数
for (int i = 0; i < expression.length(); i++) {
char temp = expression.charAt(i);
//如果是运算符
if (opStack.isOperator(temp)) {
//看符号栈是否空
if (opStack.isEmpty()) {
opStack.push(temp);
} else {
//符号栈非空
//对比符号优先级
if (opStack.priority(temp) <= opStack.priority(opStack.peek())) {
//当前符号优先级小
int a = numStack.pop();
int b = numStack.pop();
result = opStack.cal(a, b, opStack.pop());
numStack.push(result);
opStack.push(temp);
} else {
//当前符号优先级高
opStack.push(temp);
}
}
} else {
//如果不是最后一位数字,且下一个字符是数字,则先拼接,暂不入数栈
keepNumber = keepNumber + temp;
if (i != expression.length()-1) {
if(Character.isDigit(expression.charAt(i+1))){
continue;
}
}
numStack.push(Integer.valueOf(keepNumber));
keepNumber="";
}
// System.out.println("---------|"+temp+" |----------");
// System.out.print(" 数栈:");
// numStack.show();
// System.out.print(" 符号栈:");
// opStack.show();
}
while (true) {
if (opStack.isEmpty()) {
break;
}
result = opStack.cal(numStack.pop(), numStack.pop(), opStack.pop());
numStack.push(result);
}
System.out.println(result);
}
}
//数栈
class CalStack {
private int maxSize;
private int[] stack;
private int top = -1;
public CalStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[maxSize];
}
//栈满
public boolean isFull() {
return top + 1 == maxSize;
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//入栈
public void push(int value) {
if (isFull()) {
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
}
//出栈
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈空");
}
int result = stack[top];
top--;
return result;
}
//显示栈
public void show() {
if (isEmpty()) {
System.out.println("栈空");
return;
}
//从栈顶遍历
for (int i = top; i >= 0; i--) {
System.out.printf("%d\t", stack[i]);
}
System.out.println();
}
//返回运算符的优先级,数字越大, 则优先级越高
public int priority(int operator) {
if (operator == '*' || operator == '/') {
return 20;
} else if (operator == '+' || operator == '-') {
return 10;
} else {
//假定目前运算符只有+ - * /
return -1;
}
}
//判断是不是一个运算符
public boolean isOperator(int operator) {
return operator == '+' || operator == '-' || operator == '/' || operator == '*';
}
//计算方法
public int cal(int a, int b, int operator) {
int result = 0;
switch (operator) {
case '+':
result = a + b;
break;
case '-':
//注意顺序,栈是先进后出
result = b - a;
break;
case '*':
result = a * b;
break;
case '/':
result = b / a;
break;
}
return result;
}
//返回当前栈顶的值,不出栈
public int peek() {
return stack[top];
}
}
五、三种表达式
5.1 前缀表达式
- 波兰表达式,前缀表达式的运算符位于操作数之前。
- (3+4)*5-6对应的前缀表达式是-x+3456。
- 求值时,从右至左扫描表达式,先将数字全部入栈,遇到符号时,将数栈的两个值弹出并与符号计算,结果重新入数栈。
5.2 中缀表达式
- 是人类最熟悉的表达式,如(3+4)*5-6。
- 中缀表达式经常需要判断运算符的优先级。
- 对计算机来说不好操作,往往需要转换成另一种表达式进行运算,一般转换成后缀表达式。
5.3 后缀表达式
- 也称逆波兰表达式,将运算符置于操作数之后。
- (3+4)*5-6 对应的后缀表达式是34+5x6-。
- 求值时,从左向右扫描,遇到数字时入栈,遇到符号时,将数栈的两个值弹出并与符号计算,结果重新入数栈。
5.4 逆波兰计算器
package a04.calculator;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
//Reverse Polish Notation: 逆波兰表达式计算器
public class RPNCalculator {
public static void main(String[] args) {
//(3+4)*5-6 => 34+5x6-
String suffixExpression = "3 4 + 5 * 6 -";
//1.先放入ArrayList中。
List<String> rpnList = getListString(suffixExpression);
//2.将ArrayList传给一个方法,在方法中配合栈完成计算。
int result = calculate(rpnList);
System.out.println(result);
}
public static List<String> getListString(String suffixExpression) {
String[] split = suffixExpression.split(" ");
List<String> list = new ArrayList<>();
for (String item : split) {
list.add(item);
}
return list;
}
public static int calculate(List<String> rpnList) {
Stack<String> stack = new Stack<>();
for (String item : rpnList) {
//使用正则表达式判断是不是数字
if (item.matches("\\d+")) {
//是数字就入栈
stack.push(item);
} else {
//是符号就运算
int b = Integer.valueOf(stack.pop());
int a = Integer.valueOf(stack.pop());
int result = 0;
if (item.equals("+")) {
result = a + b;
} else if (item.equals("-")) {
result = a - b;
} else if (item.equals("*")) {
result = a * b;
} else if (item.equals("/")) {
result = a / b;
} else {
throw new RuntimeException("运算符有误");
}
stack.push(result + "");
}
}
return Integer.valueOf(stack.pop());
}
}
5.5 中缀转后缀
不要纠结思路的来源,如何转是已有的规范。
- 初始化两个栈,符号栈s1和存放过程的中间栈s2。
- 从左到右扫描表达式。
- 遇到操作数,压入s2
- 遇到符号,与s1栈内符号比较优先级:
- s1为空,或栈顶为(,则将符号压入s1.
- 优先级比s1栈顶高,也压入s1.
- 否则,将s1栈顶的运算符弹出并压入s2,并回到步骤4继续做比较。
- 遇到括号时
- 如果是(,压入s1
- 如果是),依次弹出s1栈顶的符号并压入s2,直到遇到左括号,并将此对括号丢弃。
- 扫描完表达式,将s1中剩余的符号依次压入s2,s2为后缀表达式。
package a04.calculator;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class InToSuf {
public static String inToSuf(List<String> infixList) {
//操作符栈
Stack<String> charStack = new Stack<>();
String result = "";
for (int i = 0; i < infixList.size(); i++) {
String item = infixList.get(i);
// System.out.println("i== " + i + " item= " + item);
// System.out.println(charStack);
//如果是操作数,拼入结果
if (item.matches("\\d+")) {
result = result + " " + item;
} else if (item.equals("+") || item.equals("-") || item.equals("*") || item.equals("/")) {
//如果是操作符
//如果符号栈为空或栈顶为(,则将符号压入符号栈
if (charStack.isEmpty() || charStack.peek().equals("(")) {
charStack.add(item);
} else if (priority(item) > priority(charStack.peek())) {
//优先级比栈顶高,也将符号压入符号栈
charStack.add(item);
} else {
result = result + " " + charStack.pop();
i--;
}
} else if (item.equals("(")) {
//如果是左括号,压入符号栈
charStack.add(item);
} else if (item.equals(")")) {
//如果是右括号,依次弹出s1栈顶的符号拼入结果,直到遇到左括号
while (true) {
if (charStack.peek().equals("(")) {
break;
}
result = result + " " + charStack.pop();
}
//并将此对括号丢弃
charStack.pop();
} else {
throw new RuntimeException("操作符异常");
}
}
//将s1中剩余的符号依次拼入结果
while(true){
if(charStack.isEmpty()){
break;
}
result = result + " " + charStack.pop();
}
return result;
}
//返回运算符的优先级,数字越大, 则优先级越高
public static int priority(String operator) {
if (operator.equals("*") || operator.equals("/")) {
return 20;
} else if (operator.equals("+") || operator.equals("-")) {
return 10;
} else {
//假定目前运算符只有+ - * /
return -1;
}
}
public static List<String> getListString(String infixExpression) {
String[] split = infixExpression.split(" ");
List<String> list = new ArrayList<>();
for (String item : split) {
list.add(item);
}
return list;
}
public static void main(String[] args) {
String infixExpression = "1 + ( ( 2 + 3 ) * 4 ) - 5";
List<String> infixList = getListString(infixExpression);
// System.out.println(infixList);
String result = inToSuf(infixList);
System.out.println(result);
}
}
Q.E.D.
Comments | 0 条评论