当时第一次看到这道题,整个人是懵的,毫无思路。即使是第二次做也是问题一堆,这题其实不算很难,但是综合了数据结构的很多知识。说实话,并不是要你学的很精,只要你对这些数据结构知识有些了解就能写出来,但是无奈本人还是磕磕绊绊写了4个小时,应该还是本人太菜了。这道题并不难,但是很有含金量。
好了,先看题:
“陆金金金金先生(!!)被冬哥告知,在计算器上键入的一个一元一次方程中,只包含整数、小写字母及+、-、=这三个数学符号(当然,符号"─"既可作减号,也可作负号)。方程中并没有括号,也没有除号,方程中的字母表示未知数。”永远滴神
“编写程序,解输入的一元一次方程,将解方程的结果(精确至小数点后三位)输出至屏幕。
你可假设对键入的方程的正确性的判断是由另一个程序员在做,或者说可认为键入的一元一次方程均为合法的,且有唯一实数解。”
例如:6a-5+1=2-2a
输出:a=0.750
- 如何在计算机里求解一元一次方程?(字符串)
①一个一元一次方程都可转化为ax-b=0的形式,因此x=b/a。
例如对6a-5+1=2-2a 移项再合并 8a-6=0。则a=6/8
②如何得到a和b?
对于ax-b,令x=1,得到a-b的值sum,再令x=0,得到-b,则 a=sum-b。
③如何处理移项与合并?
使用字符串存储输入数据,若遇到’=’号,变为’-(‘号,最后再加上’)’。
6a-5+1=2-2a -> 6a-5+1-(2-2a)
处理负号:若遇上负号在开头或等号后边或括号右边,则可以在负号前加一个0.(便于逆波兰表达式处理) 例...=-2+1 -> ...-(0-2+1) -6-5+1... -> 0-6-5+1... 6-(-5+1) ->6-(0-5+1)
处理系数:若遇到变量前有数字,则加上’*’号,例6a -> 6*a(理由同上)
- 如何在计算机里处理四则运算式(中缀表达式)?(栈、队列、关联容器)
①表示成计算机理解的算式形式:逆波兰式
使用逆波兰表达式。逆波兰表达式也叫后缀表达式,简单的说,它是把一则四则运算算式变成一种形式,这种形式中运算符都写在数字的后面。例如6a-5+1=2-2a,我们暂且让a=1(这是由意义的),然后像上面说的把整式移到一边。即:
6-5+1-2+2 这就是一则简单的四则运算表达式。
6 5 1 2 2 - + - + 这就是逆波兰表达式,运算符写在数字后面。
再给出一些例子:
0-6+12*1-(0) ----> 0 6 12 1 - + * 0 - (逆波兰式中无括号!)
0-1+1*1-3-(1-3) ----> 0 1 1 1 - + * 3 1 3 - - -
②计算逆波兰表达式
从头开始遍历逆波兰式,遇到运算符就把这个运算符前的两个数进行这个运算符对应的运算后保留在原位(栈后对栈前,队列则是前对后)继续遍历直到只剩最后一个数即为结果。
例如
6 5 1 2 2 - + - + 执行2-2
6 5 1 0 + - + 执行 0+1
6 5 1 - + 执行1-5
6 -4 + 执行-4+6
2
于是,6-5+1-2+2=2
PS:
为什么要将看似简单的中缀表达式转换为复杂的逆波兰式?原因就在于这个简单是相对人类的思维结构来说的,对计算机而言中序表达式是非常复杂的结构。相对的,逆波兰式在计算机看来却是比较简单易懂的结构。因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。
- 解题思路:
①用字符串保存输入的算式,遇到未知数赋值为1和0,遇到等号和负号进行对应的处理。
②得到两个未知数赋值为1和0的四则运算算式
③把这两个算式转化为逆波兰式(运用栈来处理,队列来保存。遇到数字加入队列,遇到运算符压入栈,若栈顶运算符优先级大于当前运算符将栈全部出栈直到为空或栈顶运算符优先级小于当前运算符,将当前运算符压入栈。注意括号出栈但是步加入队列。最后全部输出,得到的队列即为逆波兰式)
④计算这两个逆波兰式,得到结果sum和b。(运用栈来计算,对于逆波兰式从头遍历,遇到数字加入栈,遇到运算符取两个栈顶元素运算后再压入栈直到最后栈中只剩一个元素为结果。)a=sum-b
⑤x=b/a。保留三位小数输出。
要点1:将字符串添加空格,使用c++ string输入流来遍历字符串。因为若有连续的数字,例如35-1+2中的35难以处理,因此我们将这一字符串变为 _35_1_+_2_的样子,string输入流会以自动以空格分割输入数据和判断类型,因此遍历使就能遍历到整体的35。注意这样遍历的时候数据都为string类型。因此判断字符要用string[0] 后面计算逆波兰式若为连续数字要让这个数n + 数n-1*10
要点2:精度问题。计算的函数和运算都建议使用double双精度类型。
要点3:数据类型问题。队列为queue<string>,栈为stack<double>(运算)和stack<string>(存储运算符)
要点4:判断运算符优先级:使用6x6(-,+,*,/,(,))邻接矩阵,优先级行列相同则为1,低则为-1,括号对括号特殊为0。
例如 ‘+’(0)对’-’(1)优先级相同为1,’+’(0)对’*’(2)优先级低为-1,’(‘对一切符号都低,’)’对一切符号都相同(保证不入栈)。则矩阵[0][1]=1 矩阵[0][2]=-1。
建议建立一个运算符对数字的映射,在python里可以用字典实现。在c++里建议使用Map数据类型。Map<char,int>建立一个字符对数字的映射。
好了,说了那么多,AC代码奉上!
#include<iostream>
#include<queue>
#include<string>
#include<stack>
#include<map>
#include<sstream>
#include<ctype.h>
#include<iomanip>
using namespace std;
char var;
string express,express0="", express1="";
map<char, int>op_to_index;
int priority[6][6] = {
1,1,-1,-1,-1,1,
1,1,-1,-1,-1,1,
1,1,1,1,-1,1,
1,1,1,1,-1,1,
-1,-1,-1,-1,-1,0,
1,1,1,1,1,1
};
bool isop(char ch) {
if (ch == '(' || ch == ')' || ch == '+' || ch == '-' || ch == '*' || ch == '/')return true;
return false;
}
bool compare(char top, char next)
{
if (priority[op_to_index[top]][op_to_index[next]] == -1)return true;
if (priority[op_to_index[top]][op_to_index[next]] == 0)return true;
return false;
}
double calculate_num(double a, double b, char op)
{
if (op == '+')return a + b;
else if (op == '-')return a - b;
else if (op == '*')return a * b;
else if (op == '/')return a / b;
}
double calculation(queue<string> back_express)
{
stack<double>stack_res;
while (!back_express.empty())
{
string temp = back_express.front();
back_express.pop();
if (isop(temp[0]))
{
char op = temp[0];
double b = stack_res.top();
stack_res.pop();
double a = stack_res.top();
stack_res.pop();
stack_res.push(calculate_num(a, b, op));
}
else stack_res.push(stoi(temp));
}
return stack_res.top();
}
void print_postfix(queue<string> back_express) {
cout << "逆波兰表达式为:" << endl;
while (!back_express.empty()) {
cout << back_express.front() << ' ';
back_express.pop();
}
cout << endl;
}
double reverse_poland(string express)
{
queue<string>back_express;
stack<char>stack_op;
for (int i = 0; i < express.length(); i++)
{
if (isop(express[i]))
{
express.insert(i, " ");
i++;
express.insert(i + 1, " ");
}
}
stringstream sin(express);
string word;
while (sin >> word)
{
if (isop(word[0]))
{
if (stack_op.empty())stack_op.push(word[0]);
else if (compare(stack_op.top(), word[0]))stack_op.push(word[0]);
else
{
while (!stack_op.empty() && !compare(stack_op.top(), word[0]))
{
string temp = ""; temp += stack_op.top();
stack_op.pop();
back_express.push(temp);
}
if (word[0] != ')')stack_op.push(word[0]);
else {
if (stack_op.top() == '(')stack_op.pop();
}
}
}
else
back_express.push(word);
}
while (!stack_op.empty())
{
if (stack_op.top() == ')' || stack_op.top() == '(') {
stack_op.pop(); continue;
}
string temp = ""; temp += stack_op.top();
stack_op.pop();
back_express.push(temp);
}
//print_postfix(back_express);
return calculation(back_express);
}
void treat(string express)
{
if (express[0] == '-') {
express0 += '0';
express1 += '0';
}
for (int i=0; i < express.length(); i++)
{
if (isalpha(express[i]) && !isop(express[i]))
{
var=express[i];
if (i == 0 || (i > 0 && !isdigit(express[i - 1]))) {
express0 += '0';
express1 += '1';
}
else
{
express0 += "*";
express0 += '0';
express1 += '*';
express1 += '1';
}
}
else if (express[i] == '=')
{
express0 += '-';
express0 += '(';
express1 += '-';
express1 += '(';
if (express[i + 1] == '+' || express[i + 1] == '-')
{
express0 += '0';
express1 += '0';
}
}
else if (express[i] == '(' && (express[i + 1] == '-' || express[i + 1] == '+'))
{
express0 += '(';
express0 += '0';
express1 += '(';
express1 += '0';
}
else
{
express0 += express[i];
express1 += express[i];
}
}
express0 += ')';
express1 += ')';
//cout << express0 << endl;
//cout << express1 << endl;
double b = reverse_poland(express0);
double sum = reverse_poland(express1);
/*cout << sum << " " << b << endl;*/
double a = sum - b;
double final = double(-b) / double(a);
cout << fixed << setprecision(3) <<var<<"="<< final << endl;
}
int main()
{
op_to_index['+'] = 0;
op_to_index['-'] = 1;
op_to_index['*'] = 2;
op_to_index['/'] = 3;
op_to_index['('] = 4;
op_to_index[')'] = 5;
cin >> express;
treat(express);
}
参考博客:
逆波兰表达式│算法与数据结构 - 知乎 (zhihu.com)