“字符串表达式求值的编程实现”
问题描述及求解过程
要求:给定一个含有字符 ‘0’-‘9’、小数点‘.’、加‘+’、乘‘*’、除‘/’、左括号‘(’、右括号‘)’以及空格的字符串,编写程序对其解析,并给出对应的数学表达式的值。
比如,对于字符串“(-3.5)+1.5/1.2*1.5”,计算结果应该为-1.625。
编程实现字符串表达式求值问题主要步骤如下:
1.解析输入字符串,将其转化为“中缀表达式”;
2.将“中缀表达式”转化为“后缀表达式”;
3.对转化得到的后缀表达式进行求值。
下面,将逐个分析各环节。
从输入到中缀表达式
“将输入的表达式字符串转化为中缀表达式”,这一步的主要工作有两个:
1.判断输入字符串的正确性及合法性;
2.将字符串分割成“原子”单位,即操作数(浮点数)和操作符,并依次压入某个栈中。
比如,字符串“(-3.5)+1.5/1.2*1.5”应该分割为:
( -3.5 ) + 1.5 / 1.2 * 1.5
其中,红色框与蓝色框分别指示其内容是“操作符”还是“操作数”。
一个表达式字符串是“合法”的,必须满足如下的条件:
1.‘+’、‘*’、‘/’以及‘-’作为减法操作符时,前后必须都是操作数。
2.操作数字符只能是‘0’-‘9’或者‘.’,规定负数必须有括号,例如(-3+5)*2、2*(-5)、(-5)*3合法,但是-3+5是不合法的。
3.每个操作数中‘.’最多只能出现一次。
4.操作数中不能出现空格,小数点后面不能出现空格,首字符不能含空格。
5.操作数与操作符之间、括号和操作数或操作符之间可以有空格。
6.操作数后面不能直接跟左括号‘(’。
7.左括号后不能直接跟‘+’、‘*’、‘/’、‘)’、‘.’,但能够跟(不超过一个)‘-’。
8.右括号后不能直接跟‘(’、‘.’、‘0’-‘9’。
9.整个表达式的左右括号数必须相等。
10.在表达式的任何位置,出现在其左边的左括号数不能小于出现在其左边的右括号数。
通过划分输入状态,得出下表:
当前状态state |
输入字符char |
下一状态nextstate |
动作 |
op |
"0,1,2...9" |
non_dot |
value+=char,polar=1 |
"+,-,*,/" |
wrong |
停机报错 |
|
"(" |
l_par |
par_num++;Stack.push(char) |
|
空格 |
non_dot |
|
|
其它 |
wrong |
停机报错 |
|
non_dot |
"." |
dot |
value+=char |
"+,-,*,/" |
op |
val=polar?atof(value):-atof(value); Stack.push(val); Stack.push(char); |
|
"0,1,2...9" |
non_dot |
value+=char |
|
"(" |
wrong |
停机报错 |
|
空格 |
下一个非空格字符为"),+,-,*,/"时正确 |
|
|
其它 |
wrong |
停机报错 |
|
")" |
r_par |
val=polar?atof(value):-atof(value); Stack.push(val); Stack.push(char); par_num--;若par_num<0,报错 |
|
dot |
"0,1,2...9" |
dot |
value+=char,data=1 |
"." |
wrong |
停机报错 |
|
"+,-,*,/"且data=1 |
op |
val=polar?atof(value):-atof(value); Stack.push(val); Stack.push(char); |
|
")"且data=1 |
r_par |
val=polar?atof(value):-atof(value); Stack.push(val); Stack.push(char); |
|
其它 |
wrong |
停机报错 |
|
l_par |
"0,1,2...9" |
non_dot |
value+=char |
"(" |
l_par |
l_parnum++;Stack.push(char) |
|
空格 |
non_dot |
|
|
其它 |
wrong |
停机报错 |
|
r_par |
"+,-,*,/" |
op |
val=polar?atof(value):-atof(value); Stack.push(val); Stack.push(char); |
")" |
r_par |
r_parnum++; |
|
空格 |
r_par |
|
|
其它 |
wrong |
停机报错 |
|
space(伪状态,用于分析) |
"." |
wrong |
|
" "空格 |
non_dot |
进行比较判断 |
|
"+,-,*,/" |
op |
|
|
"(" |
l_par |
l_parnum++;Stack.push(char) |
|
")" |
r_par |
r_parnum--; |
|
初始状态:l_par或non_dot |
|||
末尾操作:如果l_parnum!=r_parnum,报错,state=nextstate |
根据上述状态,编写如下判定函数:
bool is_legal(string str);
数据结构设计
在上一部分的分析中,栈中有时需要存放操作数(浮点数),有时却需要存放操作符(字符、字符串)。考虑到数据类型的复杂性,在实现时采用OO的思想,声明一种自定义数据类型StackEle。该类中包含一个布尔类型的成员isop。该成员的值为true时,表明该元素为操作符;否则,该元素为操作数。
StackEle类的具体声明点击这里。
接下来,为StackEle类派生两个子类DataStackEle和OpStackEle。前者对应操作数元素,后者对应操作符元素。
于是,栈中的元素可以统一声明为StackEle类的指针。对于每一个DataStackEle类的指针,其isop需初始化为false,对于每一个OpStackEle类的指针,其is_op需初始化为true。
需要注意的是,考虑到我们需要使用StackEle类的指针访问其两个子类DataStackEle类和OpStackEle类的数据,因此在声明StackEle类时同时声明了getdata和getop两个虚函数。但是,在子类的声明中实现时,要有所区分。使用DataStackEle类的指针调用getop函数是没有意义的;反之,使用OpStackEle类的指针调用getdata函数也是没有意义的。
最后,声明一个堆栈类Stack用以存放指针。具体声明点击这里。
中缀表达式到后缀表达式
由于运算符有不同的优先级级别,以及表达式中括号的存在,我们需要将中缀表达式转化为后缀表达式。此时我们可以将合法的输入式的内容压入一个栈stack中,此过程需要借助另一个临时栈op_stack。代码点击这里。
注意:
执行完毕后,即可将输入的表达式转化为后缀表达式形式:
-3.5 1 1.2 / 1.5 * +
后缀表达式求值
这是计算的最后一步,主要计算过程如下:首先,将stack中的内容逆向压入另一个栈rev_stack;接下来,复用栈stack完成求值。该步的主要特点为:
#include
#include
#include
using namespace std;
//堆栈元
class StackEle {
public:
StackEle() { isop = false; }
StackEle(bool n) { isop = n; }
~StackEle() {};
virtual double getdata()=0;
virtual char getop()=0;
virtual void setdata(double v) {}
virtual void settype(char n) {}
protected:
bool isop;
public:
bool is_op() { return isop; }
void set_isop(bool v) { isop = v; }
};
//存储运算数的堆栈
class DataStackEle :public StackEle {
public:
DataStackEle() { data = 0; this->isop = false; }
DataStackEle(double d) { data = d; this->isop = false; }//
~DataStackEle() {}
private:
double data;
public:
double getdata() { return data; }
char getop() { return 0; }
void setdata(double v) { data = v; }
};
class OpstackEle :public StackEle {
public:
OpstackEle() { this->isop = true; }
OpstackEle(char o) { type = o; this->isop = true; }//???
~OpstackEle() {}
private:
char type;
public:
char getop() { return type; }
double getdata() { return 0; }
void settype(char n) { type = n; }
};
//存储运算式的堆栈
class Stack {
public:
Stack() {
Node =NULL;
};
Stack(StackEle * s) {
Node = new node;
Node->ele = s;
Node->next = NULL;
};
~Stack() {
while (Node != NULL)
{
node *N = Node;
Node = Node->next;
delete N;
}
}
protected:
struct node
{
StackEle *ele;
node *next;
};
node *Node;
public:
node*pos(int n)
{
node* c = NULL;
node* d = Node;
int i = 0, m;
m = count() - n - 1;
while (inext;
d = c;
}
return d;
}
int count() {
int n = 0;
node* c = Node;
while (c)
{
n++;
c = c->next;
}
return n;
}
void push(StackEle* s) {
node *c;
if (Node == NULL)
{
Node = new node;
Node->ele = s;
Node->next = NULL;
}
else
{
c = new node;
c->ele = s;
c->next = Node;
Node = c;
}
};
StackEle *pop() {
StackEle *c;
c = Node->ele;
node *x = Node;
if (Node->next != NULL)
{
Node = Node->next;
delete x;
}
else
{
Node = NULL;
}
return c;
};
StackEle *top() {
StackEle *c;
if (Node != NULL)
{
c = Node->ele;
return c;
}
else
{
return NULL;
}
};
bool empty() {
if (Node == NULL)
return true;
else
return false;
};
};
bool is_legal(string str);
int main()
{
string S ;
Stack stack,op_stack;//op_stack表示临时存储op的堆栈。stack 用于存储中缀表达式
OpstackEle *op=NULL;//作为一个临时变量,用于建立堆栈节点
DataStackEle *data=NULL;//作为一个临时变量,用于建立堆栈节点以及后续的计算
Stack rev_stack;//用于转置
StackEle *op_s=NULL, *data1=NULL, *data2=NULL;//用于接收临时op栈弹出的元素,方便查看
int n;
string num=" ";
int i = 0;
while (getline(cin, S))
{
i++;
n = S.length();
if (is_legal(S))
{
for (int i = 0; i < n; i++)
{
if (S[i] <= '9'&&S[i] >= '0'||S[i]=='.')
{
num += S[i];
}
else
{
if (num!=" ")
{
data = new DataStackEle;
data->setdata(atof(num.data()));
data->set_isop(false);
stack.push(data);
num = " ";
}
switch (S[i])
{
case '+':
case '-':
{
if (!op_stack.empty())
{
op_s = op_stack.top();
while (op_s->is_op() == true)
{
stack.push(op_stack.pop());
if (op_stack.empty())
{
break;
}
else
op_s = op_stack.top();
}
}
op = new OpstackEle;
op->settype(S[i]);
op->set_isop(true);
op_stack.push(op);
break;
}
case '*':
case '/':
{
if (!op_stack.empty())
{
op_s = op_stack.top();
if (op_s->getop() == '*' || op_s->getop() == '/')
{
stack.push(op_s);
op_stack.pop();
}
}
op = new OpstackEle;
op->settype(S[i]);
op->set_isop(true);
op_stack.push(op);
break;
}
case'(':
{
if (S[i + 1] == '-')
{
num +='0';
}
op = new OpstackEle;
op->settype(S[i]);
op->set_isop(false);
op_stack.push(op);
}
case ')':
{
if (!op_stack.empty())
{
op_s = op_stack.top();
while (op_s->getop() != '(')
{
op_s = op_stack.pop();
stack.push(op_s);
if (op_stack.empty()==true)
{
break;
}
}
}
break;
}
default:
break;
}
}
}
if (num != " ")
{
data = new DataStackEle;
data->setdata(atof(num.data()));
data->set_isop(false);
stack.push(data);
num = " ";
}
while (!op_stack.empty())
{
StackEle *x = op_stack.pop();
if(x->getop()!='(')
stack.push(x);
}
while (!stack.empty())
{
StackEle *x = stack.pop();
if (x->getop()!='(')
rev_stack.push(x);
}
while (!rev_stack.empty())//计算结果
{
op_s = rev_stack.pop();
if (op_s->is_op()==0)
{
stack.push(op_s);
}
else
{
data2 = stack.pop();
data1 = stack.pop();
double val;
if (op_s->getop() == '+')
val = data1->getdata() + data2->getdata();
if (op_s->getop() == '-')
val = data1->getdata() - data2->getdata();
if (op_s->getop() == '*')
val = data1->getdata() * data2->getdata();
if (op_s->getop() == '/')
val = data1->getdata() / data2->getdata();
data1->setdata (val) ;
data1->set_isop(false);
stack.push(data1);
}
}
}
else
{
cout << "第"<getdata()<= '1'&&str[0] <= '9') || str[0] == '('||str[0]=='-'||str[0]==' ')
{
for (int i = 0; i