数据结构与算法——逆波兰算法

cuixiaogang

逆波兰算法是什么?逆波兰算法是通过将“中缀表达式”转化“后缀表达式”(也称为“逆波兰表达式”)的方式,实现一个公式的自动计算。

各类表达式

  • 中缀表达式
  • 前缀表达式
  • 后缀表达式

中缀表达式是一个通用的算术或逻辑公式表示方法。在日常生活中,我们所识所见的表达式通常都是中缀表达式。中缀表达式的操作符是以中缀形式处于操作数的中间(例如:3 + 4),而前缀表达式则是需要将操作符前置(例如:+ 3 4),而同理,后缀表达式则是将操作符后置(例如:3 4 +)。在人们的生活中,中缀表达式可以很方便的帮助人们快速计算,但是在计算机的语言中,中缀表达式的计算就不会这哪方便了。为了解决这一问题,波兰逻辑学家J・卢卡西维兹(J・ Lukasewicz)于1929年首先提出了一种表达式的表示方法,就是现在的“后缀表达式”,所以人们也称这一表达式为“逆波兰表达式”。

算法介绍

中缀转化为后缀的算法

  1. 初始化两个栈:运算符栈S(opera)和操作数栈S(num);
  2. 从左往右扫描中缀表达式
  3. 如果是数字那么将其直接入栈到S(num)中
  4. 如果是操作,需要进一步判断
    1. 如果是左括号’(‘直接入栈到S(opera)中
    2. 如果是运算符(’+’、’-‘、’*’、’/‘),先判断栈S(opera)的栈顶的操作数的优先级(如果是空栈那么直接入栈到S(opera))
    3. 如果是左括号那么直接入栈到S(opera)中
    4. 如果栈顶是运算符,且栈顶运算符的优先级大于等于该运算符.那么将栈顶的运算符出栈,并入栈到S(num)中,重复步骤3,
    5. 如果栈顶运算符优先级小于该运算符,那么直接将该运算符入栈到S(opera)中
    6. 如果是右括号’)’,那么说明在S(opera)栈中一定有一个左括号与之对应(在你没输错的情况下),那么将opera中的运算符依次出栈,并入栈到S(num)中,直到遇到左括号’(‘(注意左括号不用入栈到S(num))
  5. 如果中缀表达式扫描完了,那么将S(opera)中的操作数依次出栈,并入栈到S(num)中就可以了,如果没有没有扫描完重复1-3步
  6. 后缀表达式存在在S(num)中,如果想看一下后缀表达式的内容,可以从栈S(num)中依次取出数据,然后倒序展示。

后缀表达式的计算

  1. 初始化一个数字栈
  2. 从左至右扫描后缀表达式
  3. 遇到数字时,将数字压入堆栈,
  4. 遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 op 栈顶元素),并将结果入栈;
  5. 重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。

代码实现

  • 封装类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
package Rpn;

//逆波兰算法
public class Rpn {

private Stack opera;
private Stack num;

public Rpn()
{

this.opera = new Stack(100);
this.num = new Stack(100);
}

public int calculate(String equation)
{
//将中缀表达式转化为后缀表达式
equation = this.toRpnEquation(equation);

//将后缀表达式转化成一个数组,每个元素是一个操作结点
String[] equationArray = equation.split(" ");

//通过后缀表达式进行计算
for (String chunk : equationArray) {
if (chunk.matches("\\d+")) {
this.num.push(chunk);
} else {
if (this.num.isEmpty()){
System.out.println("后缀表达式出错了:"+chunk);
break;
}
int num1 = Integer.parseInt(this.num.pop());
if (this.num.isEmpty()){
System.out.println("后缀表达式出错了:"+chunk);
break;
}
int num2 = Integer.parseInt(this.num.pop());
int result;
switch (chunk) {
case "+":
result = num1 + num2;
break;
case "-":
result = num2 - num1;
break;
case "*":
result = num2 * num1;
break;
case "/":
result = num2 / num1;
break;
default:
System.out.println("出错了,未知的计算操作式:"+chunk);
result = 0;
break;
}
this.num.push(String.valueOf(result));
}
}
return Integer.parseInt(this.num.pop());
}

/**
* 将中缀表达式转化为后缀表达式
* 1. 从左往右扫描中缀表达式
* 2. 如果是数字那么将其直接入栈到数组num中
* 3. 如果是操作,需要进一步判断
* a. 如果是左括号'('直接入栈到数组opera中
* b1. 如果是运算符('+'、'-'、'*'、'/'),先判断数组opera的栈顶的操作数的优先级(如果是空栈那么直接入栈到数组opera),
* b2. 如果是左括号那么直接入栈到数组opera中,
* b3. 如果栈顶是运算符,且栈顶运算符的优先级大于等于该运算符.那么将栈顶的运算符出栈,并入栈到数组num中,重复步骤3,
* b4. 如果栈顶运算符优先级小于该运算符,那么直接将该运算符入栈到opera中
* c. 如果是右括号')',那么说明在opera数组中一定有一个左括号与之对应(在你没输错的情况下),那么将opera中的运算符依次出栈,并入栈到num中,直到遇到左括号'('(注意左括号不用入栈到num)
* 4. 如果中缀表达式扫描完了,那么将opera中的操作数依次出栈,并入栈到num中就可以了,如果没有没有扫描完重复1-3步
*
* @param equation String
*
* @return String
**/
protected String toRpnEquation(String equation)
{
String[] equationArray;
String chunk;

//数字栈
Stack num = new Stack(100);

//操作符栈
Stack opera = new Stack(100);

while (equation.length() > 0) {
equationArray = this.fetchChunk(equation);
chunk = equationArray[0];
if (chunk.matches("\\d+")) {
num.push(chunk);
} else {
switch (chunk) {
case "(":
opera.push(chunk);
break;
case "+":
case "-":
case "*":
case "/":
while (true) {
if (opera.isEmpty()) {
opera.push(chunk);
break;
} else if ("(" == opera.peek()) {
opera.push(chunk);
break;
} else if (this.level(opera.peek()) >= this.level(chunk)) {
String op = opera.pop();
num.push(op);
} else {
opera.push(chunk);
break;
}
}
break;
case ")":
while (true) {
String top = opera.pop();
if (top.equals("(")) {
break;
}
num.push(top);
}
break;
}
}
equation = equationArray[1];
}

//将opera栈中的数据全部插入到num栈中
while (!opera.isEmpty()) {
num.push(opera.pop());
}

String rpnEquation = "";
while (!num.isEmpty()) {
rpnEquation = num.pop() + " " + rpnEquation;
}
return rpnEquation;
}

/**
* 返回一个操作符的优先级
*
* @param operator int 操作符
*
* @return int
**/
protected int level(String operator)
{
int level = 0;
switch (operator) {
case "+":
case "-":
level = 1;
break;
case "*":
case "/":
level = 2;
break;
default:
level = 0;
break;
}
return level;
}


/**
* 取出一个单元
*
* @param equation String[] 表达式
*
* @return String[]
**/
protected String[] fetchChunk(String equation)
{
if (0 == equation.length()) {
return null;
}
String chunk = equation.substring(0, 1);
equation = equation.substring(1);
if (chunk.matches("\\d+")) {
while (equation.length() > 0) {
if (equation.substring(0, 1).matches("\\d+")) {
chunk = chunk + equation.substring(0, 1);
equation = equation.substring(1);
} else {
break;
}
}
}

String[] result = new String[2];
result[0] = chunk;
result[1] = equation;
return result;
}


//栈
protected class Stack {

private int max;
private String[] data;
private int top = -1;

public Stack(int max)
{
this.max = max;
this.data = new String[max];
}

/**
* 数据入栈
*
* @param value String 入栈的数据
*
* @return void
**/
public void push(String value)
{
if (this.isFull()) {
throw new RuntimeException("栈已满");
}
this.data[++this.top] = value;
}

/**
* 数据出栈
*
* @return String
**/
public String pop()
{
if (this.isEmpty()) {
return null;
}
String value = this.data[this.top--];
return value;
}

/**
* 偷看一下栈顶数据
*
* @return String
**/
public String peek()
{
if (this.isEmpty()) {
return null;
}
return this.data[this.top];
}

/**
* 判断是否为空
*
* @return boolean
**/
public boolean isEmpty()
{
return this.top == -1;
}

/**
* 判断是否已满
*
* @return boolean
**/
protected boolean isFull()
{
return (this.top + 2) == this.max;
}
}
}
  • 代码执行
1
2
3
4
5
6
7
8
9
10
11
12
13
package Rpn;

public class RpnMain {
public static void main(String[] args)
{
Rpn rpn = new Rpn();
//String equation = "11+22*3-6+(1+1)*3";
String equation = "2*(9+6/3-5)+4";

System.out.println("表达式 "+equation+" 的结果为:");
System.out.println(rpn.calculate(equation));
}
}
  • 执行结果

image.png