网上针对调度场算法的参考资料挺多挺详细的;且针对调度场算法处理的中缀表达式转换为前缀/后缀表达式问题也有其他处理方法,有兴趣的可以自行了解。
参考资料 :
算法 - 调度场算法(Shunting Yard Algorithm)-腾讯云开发者社区-腾讯云 (tencent.com)
前缀、中缀、后缀表达式(逆波兰表达式) - chensongxian - 博客园 (cnblogs.com)
前缀表达式、中缀表达式和后缀表达式 - 乘月归 - 博客园 (cnblogs.com)
一、前情提要
前缀表达式(波兰式) | 运算符在操作数之前 |
中缀表达式(常见) | 运算符在操作数中间 |
后缀表达式(逆波兰式) | 运算符在操作数之后 |
在说到调度场算法之前,首先引入上面三个算法表达式概念。
调度场算法主要用于将中缀表达式转换为前缀/后缀表达式。
这里我们先看一下 ( A - B ) * C + D / E 的转换样例:
转换为前缀表达式:
- 先计算 A - B,转换为 - A B
- - A B 再与 C 计算,转换为 * - A B C
- 再计算 D / E,转换为 / D E
- 最后将两者相加,转换为 + * - A B C / D E
转换为后缀表达式:
- 先计算 A - B,转换为 A B -
- A B - 再与 C 计算,转换为 A B - C *
- 再计算 D / E,转换为 D E /
- 最后将两者相加,转换为 A B - C * D E / +
二、算法思想
这里主要用到栈。
一般来说,根据运算符的优先级,我们采取思路如下:
依次按顺序扫描读入:遇到操作数时直接输出;遇到操作符时,根据前缀/后缀表达式的比较规则比较该操作符与栈顶操作符的优先级并作出对应操作(将栈顶操作符输出并使之出栈,直到栈空或不满足条件为止,然后将操作符入栈)。处理完成后再按照规定顺序读出。
而在这里,还需特别说明一下对括号的处理:
括号的优先级一般都是最高的,但在调度场算法中,如果简单的把括号当作一种拥有最高优先级的运算符来处理是不行的也不对的。
这里需要在原先思路的基础上,把左右括号拿出来特别处理;同时需要清楚的是:前缀/后缀表达式中是没有括号的,所以括号也不应该被输出。
下面以中缀表达式转换为后缀表达式的思路为例:
(1) 初始化两个栈:操作符栈symbol和储存中间结果的栈operand;
(2) 从左至右扫描中缀表达式;
(3) 遇到操作数时,将其压入operand;
(4) 遇到操作符时,比较其与symbol栈顶操作符的优先级:
(4-1) 如果symbol为空,或栈顶操作符为左括号“(”,则直接将此操作符入栈;
(4-2) 否则,若优先级比栈顶操作符的高,也将操作符压入symbol;
(4-3) 否则,将symbol栈顶的操作符弹出并压入到operand中,再次转到(4-1)与symbol中新的 栈顶操作符相比较;
(5) 遇到括号时:
(5-1) 如果是左括号“(”,则直接压入symbol;
(5-2) 如果是右括号“)”,则依次弹出symbol栈顶的操作符,并压入operand,直到遇到左括为 止,此时将这一对括号丢弃;
(6) 重复步骤(2)至(5),直到表达式的最右边;
(7) 将symbol中剩余的操作符依次弹出并压入operand;
(8) 依次弹出operand中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。
注:前缀表达式的转换流程和后缀是基本一致的,主要是体现在下面三个地方的区别改动:
- 转换为后缀表达式时,是从左往右遍历中缀表达式;前缀表达式是从右往左遍历,且在这个过程中遇到的左右括号应该相反处理,如代码中的ReverseExp函数;
- 在进行操作符与栈顶操作符的优先级判断中,后缀表达式是操作符优先级大于栈顶操作符优先级时直接将操作符入栈;前缀表达式是大于等于;
- 在得到最终的中间结果栈后(代码中的operand)依次弹出operand的元素并输出即为前缀表达式;operand的依次弹出结果的逆序为后缀表达式。
具体可参考下面的ConvertPolish和ConvertReversePolish两个函数对照不同之处。
代码中还添加对操作符优先级的定义和判断返回函数、操作符的判断函数、根据前缀表达式从右往左遍历要求对字符串的翻转函数。
代码用例仅针对包含加减乘除和左右括号的中缀表达式。
#include<iostream>
#include<stack>
using namespace std;
//返回操作符优先级
int Priority(char sym)
{
int priority;
switch (sym)
{
case'+':priority = 1; break;
case'-':priority = 1; break;
case'*':priority = 2; break;
case'/':priority = 2; break;
default:priority = 0; break;
}
return priority;
}
//判断字符是否为操作符
bool isOperator(char ch)
{
if (ch == '(' || ch == ')' || ch == '+' || ch == '-' || ch == '*' || ch == '/')
return true;
return false;
}
//翻转字符串,且要求将左右括号置换一下
string ReverseExp(string exp)
{
string exp_;
for (int i = exp.length() - 1; i >= 0; i--) {
if (exp[i] == '(')
{
exp_ += ')';
}
else if (exp[i] == ')')
{
exp_ += '(';
}
else
{
exp_ += exp[i];
}
}
return exp_;
}
//将中缀表达式转换为前缀表达式(从右到左)
stack<char> ConvertPolish(string exp)
{
stack<char>symbol;
stack<char>operand;
//翻转表达式
exp = ReverseExp(exp);
int i = 0;
char ch, sym;
ch = exp[i];
while (ch != '\0')
{
//若为操作符
if (isOperator(ch))
{
//遇到括号时,若为左括号,直接压入symbol
if (ch == '(')
{
symbol.push(ch);
i++;
ch = exp[i];
}
//若为右括号,依次弹出symbol的栈顶元素并压入operand,直到遇到左括号为止
else if (ch == ')')
{
while (symbol.top() != '(')
{
operand.push(symbol.top());
symbol.pop();
}
symbol.pop();
i++;
ch = exp[i];
}
//若为有列出优先级的操作符
else
{
//如果symbol为空,或栈顶操作符为左括号“(”,则直接将此操作符入栈
if (symbol.empty() == true || symbol.top() == '(')
{
symbol.push(ch);
}
//否则,若该操作符优先级小于栈顶操作符,将symbol栈顶的操作符弹出并压入到operand中,再将该操作符与symbol中新的栈顶操作符相比较
//若该操作符优先级大于等于栈顶操作符,直接将此操作符入栈
else
{
sym = symbol.top();
while (Priority(sym) > Priority(ch))
{
operand.push(symbol.top());
symbol.pop();
if (symbol.empty())
{
break;
}
sym = symbol.top();
}
symbol.push(ch);
}
i++;
ch = exp[i];
}
}
//若为操作数。直接压入operand
else
{
operand.push(ch);
i++;
ch = exp[i];
}
}
//将symbol中剩余的操作符依次取出并压入operand中
while (!symbol.empty())
{
operand.push(symbol.top());
symbol.pop();
}
return operand;
}
//将中缀表达式转换为后缀表达式(从左到右)
stack<char> ConvertReversePolish(string exp)
{
stack<char>symbol;
stack<char>operand;
stack<char>result;
int i = 0;
char ch, sym;
ch = exp[i];
while (ch != '\0')
{
//若为操作符
if (isOperator(ch))
{
//遇到括号时,若为左括号,直接压入symbol
if (ch == '(')
{
symbol.push(ch);
i++;
ch = exp[i];
}
//若为右括号,依次弹出symbol的栈顶元素并压入operand,直到遇到左括号为止
else if (ch == ')')
{
while (symbol.top() != '(')
{
operand.push(symbol.top());
symbol.pop();
}
symbol.pop();
i++;
ch = exp[i];
}
//若为有列出优先级的操作符
else
{
//如果symbol为空,或栈顶操作符为左括号“(”,则直接将此操作符入栈
if (symbol.empty() == true || symbol.top() == '(')
{
symbol.push(ch);
}
//否则,若该操作符优先级小于等于栈顶操作符,将symbol栈顶的操作符弹出并压入到operand中,再将该操作符与symbol中新的栈顶操作符相比较
//若该操作符优先级大于栈顶操作符,直接将此操作符入栈
else
{
sym = symbol.top();
while (Priority(sym) >= Priority(ch))
{
operand.push(symbol.top());
symbol.pop();
if (symbol.empty())
{
break;
}
sym = symbol.top();
}
symbol.push(ch);
}
i++;
ch = exp[i];
}
}
//若为操作数。直接压入operand
else
{
operand.push(ch);
i++;
ch = exp[i];
}
}
//将symbol中剩余的操作符依次取出并压入operand中
while (!symbol.empty())
{
operand.push(symbol.top());
symbol.pop();
}
//逆序输出
while (!operand.empty())
{
result.push(operand.top());
operand.pop();
}
return result;
}
int main()
{
string exp;
cout << "请输入中缀表达式:";
cin >> exp;
cout << "转换得到的前缀表达式为:";
stack<char>prefix_res = ConvertPolish(exp);
while (!prefix_res.empty())
{
char r;
r = prefix_res.top();
prefix_res.pop();
cout << r;
}
cout << endl;
cout << "转换得到的后缀表达式为:";
stack<char>suffix_res = ConvertReversePolish(exp);
while (!suffix_res.empty())
{
char r;
r = suffix_res.top();
suffix_res.pop();
cout << r;
}
return 0;
}
运行结果如下: