一、题目描述
实现功能:输入命题公式的合式公式,求出公式的真值表,并输出该公式的主合取范式和主析取范式。
输入:命题公式的合式公式
输出:公式的主析取范式和主析取范式,输出形式为:“ mi ∨ mj ; Mi ∧ Mj” ,极小项和 ∨ 符号之间有一个空格,极大项和 ∧ 符号之间有一个空格;主析取范式和主合取范式之间用“ ; ”隔开,“ ; ”前后各有一个空格。 永真式的主合取范式为 1 ,永假式的主析取范式为 0 。
输入公式的符号说明:
! 非,相当于书面符号中的 “ ¬ ”
& 与,相当于书面符号中的 “ ∧ ”
| 或,相当于书面符号中的 “ ∨ ”
- 蕴含联结词,相当于书面符号中的 “ → ”
+ 等价联结词,相当于书面符号中的 “ ↔ ”
( 前括号
) 后括号
测试输入 期待的输出 时间限制 内存限制 额外进程 测试用例 1 以文本方式显示
- a&b↵
以文本方式显示
- m3 ; M0 ∧ M1 ∧ M2↵
无限制 64M 0 测试用例 2 以文本方式显示
- a|b↵
以文本方式显示
- m1 ∨ m2 ∨ m3 ; M0↵
无限制 64M 0
二、思路分析
求主范式,主要有两种方法。一个是真值表法,一个是等值演算法。在我看来,前者适合编程求解,后者适合我们平时手写计算。
所以这道题我用了真值表法。
具体过程是这样的:
1、读入命题公式Formula。
2、提取其中出现的变元,即p、q、r等。假设经过这一步我们得到总共有n个变元。
3、为变元从(0,0,...,0)到(1,1,...,1)依次赋真值,并且求出每一个赋值下命题公式的真值。
4、最后根据真值,输出它的主析取范式和主合取范式。
由于计算真值的过程中出现了一定的逻辑运算,可以考虑使用栈的方法来解决这其中遇到的逻辑问题。这题主要是在字符的处理上比较麻烦,但栈的调用方式都和我们之前在数据结构中做过的前缀转后缀或者四则运算都差不多。
代码我就不一段一段分出来,可读性很差,不如全放。注释我会适当地写在程序中。
其实说实话,讲道理,优快云上大段大段的代码应该没有人会看吧。要么看个思路就OK,要么直接整段copy了,反正我是这样的。
#include<bits/stdc++.h>
using namespace std;
#define N 4100
int result[N]; //result存储命题公式的真值
string Formula; //Formula是我们读入的命题公式
typedef struct Variant {
char character;
bool bool_value;
} Vrt; //character是变量名,bool_value是它当前的真值
Vrt v[11]; //这里是假设变元的数量不会超过11个
//operation函数的作用是进行两个真值之间的逻辑运算
void operation(bool *p1, bool p2, char op) {
switch (op) {
case '&': (*p1) = (*p1) && p2; break; //与
case '|': (*p1) = (*p1) || p2; break; //或
case '!': (*p1) = !(*p1); break; //非
case '-': (*p1) = ((*p1)==true && p2==false) ? false : true; break; //蕴含
case '+': (*p1) = ((*p1)==p2) ? true : false; break; //等价
}
}
//TypeDe可以判断字符的种类,0表示变元,大于0表示是逻辑运算符
int TypeDe(char c) {
if (c=='(') return 1;
if (c==')') return 4;
if (c=='!') return 3;
if (c=='&' || c=='|' || c=='-' || c=='+') {
return 2;
}
return 0;
}
int main(){
void operation(bool*, bool, char);
int TypeDe(char);
cin >> Formula;
int len = Formula.length(), n=0;
//提取公式里的所有变元,并且存入v(v是在开头声明的)
//为了方便,这里还把变元替换掉了
//例如(p-(p|q))&r被我转换成了(0-(0|1))&2,这个数字0或1或2不是真值的意思,而是这个变元在v中的顺序
{
int i=0;
while (i<len) {
if ( TypeDe(Formula[i])==0 ) {
if (n==0) {
v[n].character = Formula[i];
Formula[i] = n + '0';
n++;
i++;
continue;
}
int j=0, flag=1;
while (j<=n && flag==1) {
if ( Formula[i]==v[j].character ) {
flag=0; Formula[i] = j + '0';
break;
}
j++;
}
if (flag==1) {
v[n].character = Formula[i];
Formula[i] = n + '0';
n++;
}
}
i++;
}
}
//为变元赋值,并且依次算出公式的真值
for (int i=0;i<pow(2,n);i++) {
//为变量赋值
int i_extra = i;
for (int j=0;j<n;j++) {
v[n-j-1].bool_value = (i_extra%2==1) ? true : false;
i_extra/=2;
int k = (v[n-j-1].bool_value==true) ? 1 : 0;
}
//建立栈,类似四则运算,如果不了解可以去看看数据结构中栈是如何处理四则运算的
stack<char> op; //op用来存储运算符
stack<bool> value; //value用来存储真值
//计算过程
int k=0;
while (k<len) {
//如果不是运算符,直接存入value
if ( TypeDe(Formula[k])==0 ) {
value.push( v[Formula[k]-'0'].bool_value );
if (op.size() && op.top()=='!' && value.size()) {
bool b = !value.top();
op.pop(); value.pop();
value.push(b);
}
}
//否则就是运算符,分情况讨论
else {
//如果是左括号,直接入栈即可
if ( Formula[k]=='(' ) {
op.push(Formula[k]);
}
//否则就要判断是否需要计算
else if ( op.size() && ( (op.top()!='(' && Formula[k]==')') || (TypeDe(op.top())>=TypeDe(Formula[k])) ) ) {
//计算过程
while ( ( (op.top()!='(' && Formula[k]==')') || (TypeDe(op.top())>=TypeDe(Formula[k])) ) && op.size() ) {
char c = op.top(); op.pop();
bool b = value.top(); value.pop();
operation(&value.top(), b, c);
}
if ( Formula[k]!=')' ) {
op.push(Formula[k]);
}
else {
//特殊情况:如果op顶部是“非”运算,而且value顶部有真值,表明这个“非”和value是一个整体。此时对这个value取反,并且弹出这个“非”。
op.pop();
if (op.size() && op.top()=='!') {
bool b = !value.top();
op.pop(); value.pop();
value.push(b);
}
}
}
//不用算的话,当然直接入栈即可
else {
op.push(Formula[k]);
}
//特殊情况:处理连续出现的多个“非”运算符
if (op.size() && op.top()=='!') {
char c = '!'; op.pop();
if (op.size() && op.top()=='!') {
op.pop();
}
else op.push('!');
}
}
k++;
}
//补刀
if (op.size()) {
char c = op.top(); op.pop();
bool b = value.top(); value.pop();
operation(&value.top(), b, c);
}
//命题公式的真值是需要被存储起来的,后面输出会用,这里result[]就是存储用的
result[i] = (value.top()==true) ? 1 : 0;
}
//输出主析取范式,不要忘了永假式是输出0
int k=0;
for (int i=0;i<pow(2,n);i++) {
if (result[i]==1)
if (k==0) { k=1; cout << "m" << i; }
else { cout << " ∨ m" << i; }
}
if (k==1) { cout << " ; "; }
else { cout << 0 << " ; "; }
//输出主合取范式,不要忘了重言式是输出1
k=0;
for (int i=0;i<pow(2,n);i++) {
if (result[i]==0)
if (k==0) { k=1; cout << "M" << i; }
else { cout << " ∧ M" << i; }
}
if (k==0) { cout << 1; }
cout << endl;
return 0;
}
总的来说,如果用真值表法做这道题,你需要熟练地掌握:用栈来做四则运算。这看上去复杂,其实简单。
其他的细节,比如说字符串的处理啊,自己拟写一个可以进行逻辑运算的函数啊什么的,看上去简单,反而做起来细节多到没得说。