调度场算法可以把一个中缀表达式转换为后缀表达式,后缀表达式可以很轻松的被转换为二叉表达树。

中缀表示法(或中缀记法)是一个通用的 算术 或 逻辑 公式表示方法, 操作符 是以中缀形式处于 操作数 的中间(例:3 + 4)。与 前缀表达式 (例:+ 3 4 )或 后缀表达式 (例:3 4 + )相比,中缀表达式不容易被 电脑 解析,但仍被许多 程序语言 使用,因为它符合人们的普遍用法。

——维基百科

算法

文字版

  • 当还有记号可以读取时:
    • 读取一个记号。
    • 如果这个记号表示一个数字,那么将其添加到输出队列中。
    • 如果这个记号表示一个函数,那么将其压入栈当中。
    • 如果这个记号表示一个函数参数的分隔符(例如,一个半角逗号 , ):
      • 从栈当中不断地弹出操作符并且放入输出队列中去,直到栈顶部的元素为一个左括号为止。如果一直没有遇到左括号,那么要么是分隔符放错了位置,要么是括号不匹配。
    • 如果这个记号表示一个操作符,记做o1,那么:
      • 只要存在另一个记为o2的操作符位于栈的顶端,并且 如果o1是左结合性的并且它的运算符优先级要小于或者等于o2的优先级,或者如果o1是右结合性的并且它的运算符优先级比o2的要低,那么将o2从栈的顶端弹出并且放入输出队列中(循环直至以上条件不满足为止);
      • 然后,将o1压入栈的顶端。
    • 如果这个记号是一个左括号,那么就将其压入栈当中。
    • 如果这个记号是一个右括号,那么:
      • 从栈当中不断地弹出操作符并且放入输出队列中,直到栈顶部的元素为左括号为止。
      • 将左括号从栈的顶端弹出,但并不放入输出队列中去。
      • 如果此时位于栈顶端的记号表示一个函数,那么将其弹出并放入输出队列中去。
      • 如果在找到一个左括号之前栈就已经弹出了所有元素,那么就表示在表达式中存在不匹配的括号。
  • 当再没有记号可以读取时:
    • 如果此时在栈当中还有操作符:
      • 如果此时位于栈顶端的操作符是一个括号,那么就表示在表达式中存在不匹配的括号。
      • 将操作符逐个弹出并放入输出队列中。
  • 退出算法。

状态机

我把上面的一大串文字梳理一下,其实就是这么一个状态机。为了简单一点,没有考虑表达式错误的情况。

开始结束开始加入输出队列加入操作符栈栈弹出到队列直到栈顶部为左括号栈弹出到队列直到栈顶部为左括号栈弹出到队列直到栈顶部操作符有限度低于当前操作符弹出左括号,不放进输出队列结束数字函数左括号右括号栈顶端为函数函数分隔符栈顶端不为函数操作符优先度高于栈顶操作符操作符优先度小于等于栈顶操作符可读栈弹出到队列不可读

函数就是 sin(x), tan(x) 这种。

引用一下维基百科的例子

我们输入一个 3 + 4 * 2 / (1 - 5)^ (2 ^ 3)

输入 动作 输出队列 运算符栈 提示
3 将符号加入输出队列 3    
+ 将符号压入操作符堆栈 3 +  
4 将符号加入输出队列 3 4 +  
* 将符号压入操作符堆栈 3 4 * + *号的优先级高于+号
2 将符号加入输出队列 3 4 2 * +  
/ 将堆栈中元素弹出,加入输出队列 3 4 2 * + /号和*号优先级相同
/ 将符号压入操作符堆栈 3 4 2 * / + /号的优先级高于+号
( 将符号压入操作符堆栈 3 4 2 * ( / +  
1 将符号加入输出队列 3 4 2 * 1 ( / +  
- 将符号压入操作符堆栈 3 4 2 * 1 - ( / +  
5 将符号加入输出队列 3 4 2 * 1 5 - ( / +  
) 将堆栈中元素弹出,加入输出队列 3 4 2 * 1 5 − ( / + 循环直到找到(号
) 将堆栈元素弹出 3 4 2 * 1 5 − / + 括号匹配结束
^ 将符号压入操作符堆栈 3 4 2 * 1 5 − ^ / + ^号的优先级高于/号
2 将符号加入输出队列 3 4 2 * 1 5 − 2 ^ / +  
^ 将符号压入操作符堆栈 3 4 2 * 1 5 − 2 ^ ^ / + ^号为从右至左求值
3 将符号加入输出队列 3 4 2 * 1 5 − 2 3 ^ ^ / +  
END 将栈中所有数据加入输出队列 3 4 2 * 1 5 − 2 3 ^ ^ / +    

CPP 实现

把上面的算法理解了之后实现应该就非常简单了。下面这个代码没有实现函数,实现了数字和操作符。但理解了结构,实现一个函数应该还是不难。

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
#include <iostream>
#include <string>
#include <stack>
#include <vector>
#include <stdexcept>
#include <cmath>

#define is_operator(c) (c == ‘+’ || c == ‘-‘ || c == ‘/‘ || c == ‘*’ || c == ‘%’ || c == ‘^’)
#define is_left_operator(c) (c == ‘+’)
#define is_others(c) (c == ‘(‘ || c == ‘)’)

using namespace std;

// 解析树结构体
struct ParseTree{
	string data; // 存的数据
	int result = -1; // 子树计算的结果
//	int diff = -1;	// 自动求导可能会用上,这里用不上
	struct ParseTree* left = nullptr;
	struct ParseTree* right = nullptr;
};

// 操作符栈
stack<char> op_stack;
// 解析树数组
stack<ParseTree*> parse_stack;
// 输出队列
vector<string> o_vector;

// 这里定义的五种类型,分别是 数字,函数,操作符,默认,其他。
// 函数在这篇里没有实现,只实现了操作符。但结构都在这里了,实现一个函数问题应该不大
enum CharType {Number, Function, Operator, Default, Others};

// 字符转数字
int char2int(char c){
	return c - 0;
}

//初始化全局变量
void initVariables() {
	while (!op_stack.empty()) {
		op_stack.pop();
	}
	while (!parse_stack.empty()) {
		parse_stack.pop();
	}
	o_vector.clear();
}

// + == - < * == / == % < ^
// 1    1   2    2    2
// 得到操作符的优先级
int getPriority(char op1) {
	if (op1 == ( || op1 == ))
		return 0;
	if (op1 == + || op1 == -)
		return 1;
	if (op1 == * || op1 == / || op1 == %)
		return 2;
	if (op1 == ^)
		return 3;
	throw invalid_argument( received invalid operator );
}

//得到字符所属的类别
CharType getType(char c) {
	CharType type = Default;
	if (0 <= c && c <= 9) {
		type = Number;
	}
	if (is_operator(c)) {
		type = Operator;
	}
	if (is_others(c)) {
		type = Others;
	}
	return type;
}

//将操作符栈的顶部转移到输出队列
void move2queue() {
	if (op_stack.empty()) {
		throw invalid_argument( cannot pop a empty stack );
	}
	char top = op_stack.top();
	string temp(1, top);
	o_vector.push_back(temp);
	op_stack.pop();
}

//将一个数字 push 到输出队列
void push_accu(int accu) {
	if (accu < 0)
		return;
	string temp = to_string(accu);
	o_vector.push_back(temp);
}

//调度场主要算法
bool shunting_yard(string input){
	int length = (int)input.length();
	// 这个 Flag 主要用来标记是否是连续的数字,连续的数字要合并为一个数字。
	bool flagContinuousNumber = false;
	int accumulator = -1;
	
	// Begin Loop
	for (int i = 0; i < length; i++){
		CharType type = getType(input[i]);
		if (type == Default){
			flagContinuousNumber = false;
			push_accu(accumulator);
			accumulator = -1;
			continue;
		}
		if (type == Number) {
			if (flagContinuousNumber) { // last one is a number
				accumulator = accumulator * 10 + char2int(input[i]);
			} else { // last one is not a number, but this one is.
				flagContinuousNumber = true;
				accumulator = char2int(input[i]);
			}
			continue;
		} else {
			flagContinuousNumber = false;
			push_accu(accumulator);
			accumulator = -1;
		}
		if (type == Operator) {
			while(!op_stack.empty()) {
				if (getPriority(input[i]) <= getPriority(op_stack.top())) {
					move2queue();
				} else {
					break;
				}
			}
			op_stack.push(input[I]);
			continue;
		}
		if (type == Others) {
			if (input[I] == () {
				op_stack.push(input[I]);
			}
			if (input[I] == )) {
				while (op_stack.top() != () {
					move2queue();
				}
				op_stack.pop();
			}
			continue;
		}
	}
	
	if (flagContinuousNumber)
		push_accu(accumulator);
	
	while (!op_stack.empty()) {
		char top = op_stack.top();
		if (top == ( || top == )) {
			return false;
		}
		string temp(1, top);
		o_vector.push_back(temp);
		op_stack.pop();
	}
	
	return true;
}


int main(){
	initVariables();
//	const string input = "(1+2) * (3 * (4+5))"; // 81
	const string input = "4+ 2 ^ 3 ^ 2 + 4 / 2"; // 516
	shunting_yard(input);
	return 0;
}


发现存在错别字或者事实错误?请麻烦您点击 这里 汇报。谢谢您!