一个算数表达式的前缀中缀和后缀
目录
1. 关于前缀、中缀和后缀
- 中缀表达式:就是我们常见的算数表达式,有优先级和括号,例如:3+4*(4+5 )。这个对于我们来说很好理解,但是对于计算机来说就比较麻烦。
- 前缀表达式:前缀表达式是一种没有括号的算术表达式,与中缀表达式不同的是,其将运算符写在前面,操作数写在后面。为纪念其发明者波兰数学家Jan Lukasiewicz,前缀表达式也称为“波兰式”。例如,- 1 + 2 3,它等价于1-(2+3)。
- 后缀表达式:不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则,如:(2 + 1) * 3 , 即2 1 + 3 *
2. 中缀转前缀、后缀的算法思想及代码实现
2.1 中缀转前缀
中缀转前缀算法思想:
1. 初始化两个栈s1,s2
2. 从右向左扫描中缀表达式,若是数字,将数字压入栈s1
3. 若遇到运算符,则将其与栈s2栈顶的运算符优先级作比较
3.1. 若栈s2为空,则将其压入栈
3.2. 如果栈s2不为空,且不是右括号,且和栈s2的栈顶的运算符优先级相同或比其高,则将这个运算符压入栈s2
3.3. 如果遇到的运算符比栈顶运算符优先级低,则将从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入栈s1,然后回到3.1
4. 如果遇到右括号,将右括号压入栈s2
5. 如果遇到左括号,则s2栈顶元素与右括号做比较,如果不是则从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入栈s1。
6. 如果s2不为空,则从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入栈s1,直到s2为空
7. 如果最终结果中只有栈s1只有一个元素,则这个结果即为正确结果。
中缀转前缀的代码实现如下图所示:
import java.math.BigDecimal;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Stack;
public class CenterToFront {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (true) {
System.out.print("请输入:");
String s = sc.nextLine();
String result = toFront(s);
if (result != "出错") {
System.out.println("结果是:" + result);
}
else {
System.out.println(s);
}
}
}
static String operator = "+-*/"; // 用于判断是否是操作符
// 用于判断优先级
static HashMap<Character, Integer> priority = new HashMap<>();
static {
priority.put('+', 1);
priority.put('-', 1);
priority.put('*', 2);
priority.put('/', 2);
priority.put(')', 0);
}
public static String toFront (String str) {
ArrayDeque<Character> s1 = new ArrayDeque<>(); // 用于储存运算符
ArrayDeque<Double> s2 = new ArrayDeque<>(); // 用于储存数字
// 这里的这个栈不使用ArrayDeque是
// 因为当ArrayDeque存入Object元素时,
// 会将同一种类型的元素放在一块,这是一个坑点
Stack<Object> s3 = new Stack<>();
char ch, temp; // ch储存每次遍历时,temp后面用于暂时储存字符,后面会用到
// 用于储存数字的索引,从右向左遍历是frontDouble存储的是数字
int frontDouble = -1;
int len = str.length(); // 用于储存字符串的字符串的长度
for (int i = len - 1; i >= 0; i--) {
ch = str.charAt(i); // charAt()方法可以返回字符串索引处的字符
if (Character.isDigit(ch)) { // Character的isDigit()可以用来判断数字是否是字符
frontDouble = readFrontDouble(str, i); // 该方法用于算该数字从索引处到往前遍历找到第一个非数字的索引
if (frontDouble == -1) {
return "出错";
}
// String类的substring(int a, int b)用于返回该字符串从a到b-1处的一个子字符串
// Double.parseDouble(String str) 将一个自负串解析成一个double型数字
double d = Double.parseDouble(str.substring(frontDouble, i + 1));
if ((int)d == d)
s3.add(String.valueOf((int)d));
else
s3.add(String.valueOf(d));
s2.push(d);
i = frontDouble;
}
/**
* 查看ch是否是运算符
* 若是运算符则与s1栈顶的运算符作比较
* 1. 若栈顶元素为空,则将ch压入栈s1
* 2. 若栈顶元素不为空,且ch的优先级比s1栈顶优先级高
* 则将ch压入栈s1
* 3. 若栈顶元素不为空,且ch的优先级比s1栈顶优先级低
* 则从栈s2中弹出两个数、从栈s1中弹出一个运算符,
* 然后做运算,将元素所得的结果压入栈s2
*/
else if (operator.indexOf(ch) != -1) {
while (!s1.isEmpty() && s1.peek() != ')'
&& priority.get(ch) < priority.get(s1.peek())) {
double d1 = s2.pop();
double d2 = s2.pop();
s3.push(s1.peek().toString());
s2.push(cal(d1, d2, s1.pop()));
}
s1.push(ch);
}
// 如遇到右括号,直接压入栈
else if (ch == ')')
s1.push(ch);
/**
* 如果遇到左括号,并且s1栈顶元素不是右括号
* 则从s2栈顶弹出两个数字,从s1栈顶弹出一个运算符
* 做运算,将结果存入栈s2
* 循环上述过程知道s1栈顶元素是左括号,然后将左括号弹出
*
*/
else if (ch == '(') {
while (s1.peek() != ')') {
double d1 = s2.pop();
double d2 = s2.pop();
s3.push(s1.peek().toString());
s2.push(cal(d1, d2, s1.pop()));
// 如果没遇到左括号,但s1栈已经是空的了,那么肯定出错了
if (s1.isEmpty()) {
return "出错";
}
}
s1.pop();
}
// 忽略掉空格
else if (ch == ' ') {
continue;
}
// 有其他字符肯定出错
else {
return "出错";
}
}
/**
* 字符串遍历完成后若s1栈不为空(一般来说坑定不为空)
* 则从栈s1中弹出一个符号,从s2栈顶弹出两个数字做运算
* 后压入栈s2,直到栈s1为空
*/
while (!s1.isEmpty()) {
double d1 = s2.pop();
double d2 = s2.pop();
s3.push(s1.peek().toString());
double d3 = cal(d1, d2, s1.pop());
s2.push(d3);
}
System.out.print("前缀是:");
while(!s3.isEmpty()) {
System.out.print(s3.pop() + " ");
}
System.out.println();
// 若最后栈s2中还有超过一个元素,则证明出错了
double result = s2.pop();
if (!s2.isEmpty())
return "出错";
if ((int) result == result)
return String.valueOf((int)result);
else{
return String.valueOf(result);
}
}
/**
* 此方法用于计算的d1 op d2 的结果
*/
private static double cal(double d1, double d2, char op)
throws ArithmeticException{
switch (op) {
case '+':
return d1 + d2;
case '-':
return d1 - d2;
case '*':
return d1 * d2;
case '/':
if (d1 == 0) {
return 1;
}
return d1 / d2;
}
return 1;
}
/**
* 这是一个读取数字位置的方法
* 该方法可以从右向左的读取一个数字,然后返回该数字在字符串中开始的下标
*/
private static int readFrontDouble(String str, int start) {
int flag = -1; // 用于记录小数点
char ch; // 用于记录每次遍历的字符
for (int i = start; i >= 0; i--) {
ch = str.charAt(i);
if (ch == '.') { //如果第一次出现小数点,则记录小数点位置,如果不是那么肯定出错
if (flag != -1) {
return -1;
}
else {
flag = i;
}
// 如果该字符是减号,若该字符是第一位(i == 0),则该减号是负号,或者如果该字符的前一个字符不是数字,证明也是负号
} else if (ch == '-' && ((i > 0
&& !Character.isDigit((str.charAt(i-1))))
|| i == 0)) {
return i;
// 如果是非数字的肯定该数字已经找到了
}else if (!Character.isDigit(ch))
return i + 1;
else if (i == 0) {
return 0;
}
}
return -1;
}
}
2.2 中缀转后缀
中缀转后缀的计算方法与中缀转前缀的方法特别相似,只是中缀转后缀是从前到后的。
1. 初始化两个栈s1,s2
2. 从左向右扫描中缀表达式,若是数字,将数字压入栈s1
3. 若遇到运算符,则将其与栈s2栈顶的运算符优先级作比较
3.1. 若栈s2为空,则将其压入栈
3.2. 如果栈s2不为空,且不是左括号,且和栈s2的栈顶的运算符优先级相同或比其高,则将这个运算符压入栈s2
3.3. 如果遇到的运算符比栈顶运算符优先级低,则将从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入栈s1,然后回到3.1
4. 如果遇到左括号,将左括号压入栈s2
5. 如果遇到右括号,则s2栈顶元素与左括号做比较,如果不是则从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入栈s1。
6. 如果s2不为空,则从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入栈s1,直到s2为空
7. 如果最终结果中只有栈s1只有一个元素,则这个结果即为正确结果。
// 前缀转中缀的注释已经相当的清晰了,所以这里就不在赘述了
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Stack;
public class CenterToLast {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while(true) {
System.out.println("请输入:");
String str = sc.nextLine();
System.out.println(toSuffix(str));
}
}
static String operator = "+-*/"; // 用于判断是否是操作符
// 用于判断优先级
static HashMap<Character, Integer> priority = new HashMap<>();
static {
priority.put('+', 1);
priority.put('-', 1);
priority.put('*', 2);
priority.put('/', 2);
}
private static String toSuffix(String str) {
int len = str.length();
char c, tempChar;
ArrayDeque<Character> s1 = new ArrayDeque<Character>();
ArrayDeque<Double> s2 = new ArrayDeque<Double>();
Stack<Object> s3 = new Stack<>();
double number;
int lastIndex = -1;
for (int i = 0; i < len; ++i) {
c = str.charAt(i);
if (Character.isDigit(c)) {
lastIndex = readDouble(str, i);
number = Double.parseDouble(str.substring(i, lastIndex));
s2.push(number);
i = lastIndex - 1;
if ((int) number == number)
s3.push((int)number);
else
s3.push(number);
}else if (c == '-' && i == 0) {
lastIndex = readDouble(str, i + 1);
number = Double.parseDouble(str.substring(i, lastIndex));
s2.push(number);
i = lastIndex - 1;
if ((int) number == number)
s3.push((int) number);
else
s3.push(number);
}else if (i > 0 && c == '-' &&
(str.charAt(i-1) == '('
|| operator.indexOf(str.charAt(i-1)) != -1)) {
lastIndex = readDouble(str, i + 1);
number = Double.parseDouble(str.substring(i, lastIndex));
s2.push(number);
i = lastIndex - 1;
if ((int) number == number)
s3.push((int)number);
else
s3.push(number);
}
else if (operator.indexOf(c) != -1) {
while (!s1.isEmpty() && s1.peek() != '('
&& priority.get(c) < priority.get(s1.peek())) {
System.out.print(s1.peek() + " ");
double num1 = s2.pop();
double num2 = s2.pop();
s3.push(s1.peek());
s2.push(calc(num2, num1, s1.pop()));
}
s1.push(c);
} else if (c == '(') {
s1.push(c);
} else if (c == ')') {
while ((tempChar = s1.pop()) != '(') {
System.out.print(tempChar + " ");
double num1 = s2.pop();
double num2 = s2.pop();
s3.push(s1.peek());
s2.push(calc(num2, num1, tempChar));
if (s1.isEmpty()) {
return "出错";
}
}
} else if (c == ' ') {
continue;
} else {
return "出错";
}
}
while (!s1.isEmpty()) {
tempChar = s1.pop();
s3.push(tempChar);
double num1 = s2.pop();
double num2 = s2.pop();
s2.push(calc(num2, num1, tempChar));
}
double result = s2.pop();
if (!s2.isEmpty())
return "出错";
System.out.print("后缀是:");
while (!s3.isEmpty()) {
System.out.print(s3.pop() + " ");
}
System.out.println();
if ((int) result == result)
return String.valueOf(((int)result));
else
return String.valueOf(result);
}
/**
* 获取是double值得最后一位索引
*/
public static int readDouble(String str, int start) {
int len = str.length();
int dotIndex = -1;
char ch;
for (int i = start; i < len ; i++) {
ch = str.charAt(i);
if(ch == '.') {
if(dotIndex != -1)
return -1;
else if(i == len - 1)
return -1;
else
dotIndex = i;
} else if(!Character.isDigit(ch)) {
if (dotIndex == -1 || i - dotIndex > 1)
return i;
else
return -1;
}
else if(i == len - 1) {
return len;
}
}
return -1;
}
/**
* 计算两个数的结果
* 并返回
*/
private static double calc (double num1, double num2, char op)
throws IllegalArgumentException{
switch (op) {
case '+':
return num1 + num2;
case '-':
return num1-num2;
case '*':
return num1 * num2;
case '/':
if (num2 == 0)
throw new ArithmeticException("除数不能为0");
return num1 / num2;
default:
return 0;
}
}
}