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