Description
uncle-lu迷上了逻辑值运算。但uncle-lu算的头都晕了,也没算出个所以然。他只好找到你,让你帮他解决这个问题。
Input
一个字符串(串长小于255)表达逻辑式子,内只包含true,false,or,and,not和空格,(不包含括号和xor),优先级同pascal.(not->and->or),同级左边先算,如果逻辑式有误则输出 error。
Output
运算结果:true 或者 false ,如果无法得到结果的输出error
Sample Input 1
true or false and false
Sample Output 1
true
这是典型的中缀表达式,可以用栈做。先考虑最简单的情况,逻辑表达式从左往右的运算符优先级依次递增,把运算符存进符号栈,逻辑值存进数据栈,就可以存储整个逻辑表达式——这时栈底至栈顶的运算符优先级都是依次递增的,我们就可以通过弹栈把整个表达式的值计算出来。但是现实不会如此简单,往往会有高优先级运算符在低优先级运算符之前入栈,故我们需要在存储表达式时添加一些操作:如果读到一个运算符,在入栈前,先判断此时栈顶元素的优先级是否不小于该运算符,是则先计算局部表达式并且弹栈,再入栈,否则直接入栈。这样就能保证最后留在栈中的运算符满足从栈底到栈顶优先级依次递增。为什么读到和栈顶元素相同的运算符也要先计算局部表达式再入栈?——因为题目要求优先级相同时左边先算。
这里我采取的入栈方式是,先让栈顶指针自增,再存入元素,并且栈底从下标1开始。这样有什么好处呢?栈顶指针数值即是栈中元素的个数,在写本题代码时较为直观和方便。我用bool数组存储逻辑值,用int数组存储运算符——1表示or,2表示and,3表示not。这样做的好处是:计算局部表达式时可以直接进行逻辑运算,运算符优先级大小的比较形象直观地转换为数值大小的比较。
这题对输入字符串的处理也是令我颇有收获,欲知具体请看代码。
#include <iostream>
#include<string>
using namespace std;
bool data[256]={0};//数据栈
int sign[256]={0};//符号栈
int top_d=0,top_s=0;//两个栈的栈顶指针
void Pop()//弹栈函数,即计算表达式的函数
{
switch (sign[top_s]) {//读取符号栈顶元素
case 1:
if(top_d<2){//要进行or运算,数据却少于2个,无法计算
cout<<"error";
exit(0);
}
//否则进行局部计算,2个数据变为1个
data[top_d-1]=data[top_d]||data[top_d-1];
top_d--;//数据栈元素减少1个
break;
case 2:
if(top_d<2){//要进行and运算,数据却少于2个,无法计算
cout<<"error";
exit(0);
}
//同理
data[top_d-1]=data[top_d]&&data[top_d-1];
top_d--;
break;
case 3:
if(!top_d){//要进行not运算,却没有数据,无法计算
cout<<"error";
exit(0);
}
//否则进行局部计算,数据个数没有改变
data[top_d]=!data[top_d];
}
top_s--;//计算一次,符号栈弹栈一次
}
int main() {
string str;
while(cin>>str)//cin的>>运算符重载在读入结束符EOF时会返回0,否则返回cin本身
{
if(str=="true") data[++top_d]=true;
else if(str=="false") data[++top_d]=false;
else if(str=="or"){
while(top_s)//or优先级是最低的,必须先计算or左边的局部表达式
Pop(); //除非or之前没有运算符
sign[++top_s]=1;//计算完了即可入栈
}
else if(str=="and"){
while(top_s&&sign[top_s]>=2)//如果and之前出现优先级不小于and的运算符
Pop(); //必须先计算左边局部表达式
sign[++top_s]=2;
}
else if(str=="not") sign[++top_s]=3;//not优先级最高,直接入栈
}
while(top_s)//计算经过处理的表达式
Pop();
//如果表达式正常,最后数据栈应只有1个元素
if(top_d==1) cout<<boolalpha<<data[1];
else cout<<"error";//否则说明运算符太少,无法计算
return 0;
}
由于我在本地调试时无法输出结束符EOF,无法进行调试,故我使用了以下这种方法处理字符串。至于为什么不使用gets函数读取整行字符串,因为C++11已经把gets列为警告了,C++14则彻底删除了gets,所以使用C++string类的getline方法会比较稳定,不挑平台(有的平台不支持gets,会报错“未定义gets函数”)。
#include <iostream>
#include<string>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
using namespace std;
bool data[256]={0};
int sign[256]={0};
int top_d=0,top_s=0;
void Pop()
{
switch (sign[top_s]) {
case 1:
if(top_d<2){
cout<<"error";
exit(0);
}
data[top_d-1]=data[top_d]||data[top_d-1];
top_d--;
break;
case 2:
if(top_d<2){
cout<<"error";
exit(0);
}
data[top_d-1]=data[top_d]&&data[top_d-1];
top_d--;
break;
case 3:
if(!top_d){
cout<<"error";
exit(0);
}
data[top_d]=!data[top_d];
}
top_s--;
}
int main() {
//接收原表达式
string origin;
getline(cin,origin);
char s[256];
strcpy(s,origin.c_str());
//以s为源串、以" "为分隔符分割字符串,获取运算符和逻辑值
char *sst=strtok(s," ");
string str;
while(sst!=NULL)//字符串无可分割时strtok会返回NULL
{
str=sst;
if(str=="true") data[++top_d]=true;
else if(str=="false") data[++top_d]=false;
else if(str=="or"){
while(top_s)
Pop();
sign[++top_s]=1;
}
else if(str=="and"){
while(top_s&&sign[top_s]>=2)
Pop();
sign[++top_s]=2;
}
else if(str=="not") sign[++top_s]=3;
sst=strtok(NULL," ");//在上次分割的基础上继续分割
}
while(top_s)
Pop();
if(top_d==1) cout<<boolalpha<<data[1];
else cout<<"error";
return 0;
}