Soptq

Soptq

Probably a full-stack, mainly focusing on Distributed System / Consensus / Privacy-preserving Tech etc. Decentralization is a trend, privacy must be protected.
twitter
github
bilibili

调度场算法

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

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

—— 维基百科

算法#

文字版#

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

状态机#

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

Mermaid Loading...

函数就是 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 实现#

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

#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;
}
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。