C++实现逻辑表达式的真值表建立与等价判定
逻辑表达式真值表的建立与等价判断
首先设计真值表的ADT:
class trueTable{
vector<string> logicElement; //组成元素
vector<vector<int>> table; //真值表
int basicNum;
int column;
public:
bool cmp(trueTable T){
int k1=this->logicElement.size(),k2=T.logicElement.size();
if(T.column!=this->column) return 0;
int n1=this->basicNum,n2=T.basicNum;
if(n1!=n2) return 0;
for(int i=0;i<n1;i++){ //基本元素不同
if(this->logicElement[i]!=T.logicElement[i]){
return 0;
}
}
for(int i=0;i<column;i++){
if(this->table[i][k1-1]!=T.table[i][k2-1]) return 0;
}
return 1;
}
void cal_not(string s){ //!
logicElement.push_back("!"+s); //推入新元素名称
int n=search(s);
for(int i=0;i<column;i++){ //做非运算
table[i].push_back(1-table[i][n]);
}
return;
}
int And(int a,int b){ //与运算
if(a==1 && b==1) return 1;
return 0;
}
void cal_and(string s1,string s2){ //&
logicElement.push_back(s1+"&"+s2);
int n1=search(s1),n2=search(s2);
for(int i=0;i<column;i++){
table[i].push_back(And(table[i][n1],table[i][n2]));
}
return ;
}
int Or(int a,int b){
return !(a==0 && b==0);
}
void cal_or(string s1,string s2){ //|
logicElement.push_back(s1+"|"+s2);
int n1=search(s1),n2=search(s2);
for(int i=0;i<column;i++){
table[i].push_back(Or(table[i][n1],table[i][n2]));
}
return ;
}
int If(int a,int b){
return !(a==1 && b==0);
}
void cal_if(string s1,string s2){ //->
logicElement.push_back(s1+"->"+s2);
int n1=search(s1),n2=search(s2);
for(int i=0;i<column;i++){
table[i].push_back(If(table[i][n1],table[i][n2]));
}
return ;
}
bool add(string s){ //添加初始元素
if(-1==search(s)){
logicElement.push_back(s);
return 1;
}
return 0;
}
int search(string s){ //按名称查找下标,注意排除外括号
int k=logicElement.size();
if(s[0]=='('){
bool f=1;
for(int i=1;i<k && f;i++){
if(s[i]==')' && i!=k-1) f=0; //说明不是外括号
}
if(f){ //去掉外括号
s=s.substr(1,s.size()-2);
}
}
for(int i=0;i<k;i++){
if(s==logicElement[i]) return i;
}
return -1;
}
void reOrder(){ //基本元素重排序
int k=logicElement.size();
for(int i=0;i<k-1;i++){
for(int j=k-1;j>i;j--){
if(logicElement[j]<logicElement[j-1]){ //冒泡排成字符表序
string tmp=logicElement[j];
logicElement[j]=logicElement[j-1];
logicElement[j-1]=tmp;
}
}
}
return ;
}
void initialLise(){
int k=logicElement.size();
this->basicNum=k;
this->column=1;
if(k==0) this->column=0;
else {
for(int i=0;i<k;i++){
this->column*=2;
}
}
vector<int> v;
initialLise_r(v,k); //初始化基本元素的真值表
reOrder();
return ;
}
void initialLise_r(vector<int> v,int k){
vector<int> v1=v;
if(k==1){
for(int i=0;i<2;i++){
v1.push_back(i);
this->table.push_back(v1);
v1=v;
}
return ;
} else {
for(int i=0;i<2;i++){
v1.push_back(i);
initialLise_r(v1,k-1);
v1=v;
}
return ;
}
}
void printTable(){
int k=logicElement.size();
for(int i=0;i<this->column+1;i++){
for(int j=0;j<k;j++){
if(!i){
cout<<logicElement[j]<<" ";
}
else{
cout<<table[i-1][j]<<" ";
int len=logicElement[j].size()-1;
if(len)
for(int p=0;p<len;p++){
cout<<" ";
}
}
}
cout<<endl;
}
}
};
封装了相应的功能,放弃了将命题本身封装为一个类,这样做的好处是直观且易于维护,编写起来效率高,但还不是最好的选择,还能再进行高度封装。
主程序部分:
#include <iostream>
#include <string>
#include <stack>
#include <sstream>
#include <vector>
#include <cmath>
#include <stack>
using namespace std;
char test;
class trueTable{
vector<string> logicElement; //组成元素
vector<vector<int>> table; //真值表
int basicNum;
int column;
public:
bool cmp(trueTable T){
int k1=this->logicElement.size(),k2=T.logicElement.size();
if(T.column!=this->column) return 0;
int n1=this->basicNum,n2=T.basicNum;
if(n1!=n2) return 0;
for(int i=0;i<n1;i++){ //基本元素不同
if(this->logicElement[i]!=T.logicElement[i]){
return 0;
}
}
for(int i=0;i<column;i++){
if(this->table[i][k1-1]!=T.table[i][k2-1]) return 0;
}
return 1;
}
void cal_not(string s){ //!
logicElement.push_back("!"+s); //推入新元素名称
int n=search(s);
for(int i=0;i<column;i++){ //做非运算
table[i].push_back(1-table[i][n]);
}
return;
}
int And(int a,int b){ //与运算
if(a==1 && b==1) return 1;
return 0;
}
void cal_and(string s1,string s2){ //&
logicElement.push_back(s1+"&"+s2);
int n1=search(s1),n2=search(s2);
for(int i=0;i<column;i++){
table[i].push_back(And(table[i][n1],table[i][n2]));
}
return ;
}
int Or(int a,int b){
return !(a==0 && b==0);
}
void cal_or(string s1,string s2){ //|
logicElement.push_back(s1+"|"+s2);
int n1=search(s1),n2=search(s2);
for(int i=0;i<column;i++){
table[i].push_back(Or(table[i][n1],table[i][n2]));
}
return ;
}
int If(int a,int b){
return !(a==0 && b==1);
}
void cal_if(string s1,string s2){ //->
logicElement.push_back(s1+"->"+s2);
int n1=search(s1),n2=search(s2);
for(int i=0;i<column;i++){
table[i].push_back(If(table[i][n1],table[i][n2]));
}
return ;
}
bool add(string s){ //添加初始元素
if(-1==search(s)){
logicElement.push_back(s);
return 1;
}
return 0;
}
int search(string s){ //按名称查找下标,注意排除外括号
int k=logicElement.size();
if(s[0]=='('){
bool f=1;
for(int i=1;i<k && f;i++){
if(s[i]==')' && i!=k-1) f=0; //说明不是外括号
}
if(f){ //去掉外括号
s=s.substr(1,s.size()-2);
}
}
for(int i=0;i<k;i++){
if(s==logicElement[i]) return i;
}
return -1;
}
void reOrder(){ //基本元素重排序
int k=logicElement.size();
for(int i=0;i<k-1;i++){
for(int j=k-1;j>i;j--){
if(logicElement[j]<logicElement[j-1]){ //冒泡排成字符表序
string tmp=logicElement[j];
logicElement[j]=logicElement[j-1];
logicElement[j-1]=tmp;
}
}
}
return ;
}
void initialLise(){
int k=logicElement.size();
this->basicNum=k;
this->column=1;
if(k==0) this->column=0;
else {
for(int i=0;i<k;i++){
this->column*=2;
}
}
vector<int> v;
initialLise_r(v,k); //初始化基本元素的真值表
reOrder();
return ;
}
void initialLise_r(vector<int> v,int k){
vector<int> v1=v;
if(k==1){
for(int i=0;i<2;i++){
v1.push_back(i);
this->table.push_back(v1);
v1=v;
}
return ;
} else {
for(int i=0;i<2;i++){
v1.push_back(i);
initialLise_r(v1,k-1);
v1=v;
}
return ;
}
}
void printTable(){
int k=logicElement.size();
for(int i=0;i<this->column+1;i++){
for(int j=0;j<k;j++){
if(!i){
cout<<logicElement[j]<<" ";
}
else{
cout<<table[i-1][j]<<" ";
int len=logicElement[j].size()-1;
if(len)
for(int p=0;p<len;p++){
cout<<" ";
}
}
}
cout<<endl;
}
}
};
const int opt[7][7]={0,2,2,2,2,2,2,1,1,1,2,2,1,1,1,2,1,2,2,1,1,1,1,1,1,2,1,1,3,2,2,2,2,0,2,1,1,1,1,3,1,1,1,2,2,2,2,1,1}; //运算符优先级,1是大于,0是等于,2是小于,3是error
/*
# & | ! ( ) ->
# 0 2 2 2 2 2 2
& 1 1 1 2 2 1 1
| 1 2 1 2 2 1 1
! 1 1 1 1 2 1 1
( 3 2 2 2 2 0 2
) 1 1 1 1 3 1 1
-> 1 2 2 2 2 1 1
*/
int searchFor(string s){ //返回符号的数组下标
if(s=="#") return 0;
if(s=="&") return 1;
if(s=="|") return 2;
if(s=="!") return 3;
if(s=="(") return 4;
if(s==")") return 5;
if(s=="->" || s=="-") return 6;
return -1;
}
void logicConstruct(string s,trueTable &T){
stack<string> ele;
stack<string> op;
op.push("#");
s=s+"#";
int k=s.length();
for(int i=0;i<k;i++){ //初始化
switch(s[i]){
case '#':
case '&':
case '|':
case '!':
case '-':
case '>':
case '(':
case ')':
case ' ':break;
default:{ //添入基本元素
T.add(s.substr(i,1));
}
}
}
T.initialLise();
for(int i=0;i<k;i++){
if(s[i]==' ') continue;
string tmp=op.top();
if(searchFor(s.substr(i,1))==-1){
ele.push(s.substr(i,1));
} else
switch(tmp[0]){
case '#':{
switch(opt[0][searchFor(s.substr(i,1))]){
case 0:{
op.pop();
break;
};
case 2:{
if(s[i]=='-'){
op.push(s.substr(i,2));
i+=1;
} else {
op.push(s.substr(i,1));
}
}
}
break;
};
case '&':{
switch(opt[1][searchFor(s.substr(i,1))]){
case 1:{
op.pop();
string str[2];
str[1]=ele.top();
ele.pop();
str[0]=ele.top();
ele.pop();
T.cal_and(str[0],str[1]);
ele.push(str[0]+"&"+str[1]);
i=i-1;
break;
};
case 2:{
if(s[i]=='-'){
op.push(s.substr(i,2));
i+=1;
} else {
op.push(s.substr(i,1));
}
}
}
break;
};
case '|':{
switch(opt[2][searchFor(s.substr(i,1))]){
case 1:{
op.pop();
string str[2];
str[1]=ele.top();
ele.pop();
str[0]=ele.top();
ele.pop();
T.cal_or(str[0],str[1]);
ele.push(str[0]+"|"+str[1]);
i=i-1;
break;
};
case 2:{
if(s[i]=='-'){
op.push(s.substr(i,2));
i+=1;
} else {
op.push(s.substr(i,1));
}
}
}
break;
}
case '!':{
switch(opt[3][searchFor(s.substr(i,1))]){
case 1:{
op.pop();
string str;
str=ele.top();
ele.pop();
T.cal_not(str);
ele.push("!"+str);
i=i-1;
break;
};
case 2:{
if(s[i]=='-'){
op.push(s.substr(i,2));
i+=1;
} else {
op.push(s.substr(i,1));
}
}
}
break;
}
case '-':{
switch(opt[6][searchFor(s.substr(i,1))]){
case 1:{
op.pop();
string str[2];
str[1]=ele.top();
ele.pop();
str[0]=ele.top();
ele.pop();
T.cal_if(str[0],str[1]);
ele.push(str[0]+"->"+str[1]);
i=i-1;
break;
};
case 2:{
if(s[i]=='-'){
op.push(s.substr(i,2));
i+=1;
} else {
op.push(s.substr(i,1));
}
}
}
break;
}
case '(':{
switch(opt[4][searchFor(s.substr(i,1))]){
case 0:{
op.pop();
string s=ele.top();
ele.pop();
ele.push("("+s+")");
break;
};
case 2:{
if(s[i]=='-'){
op.push(s.substr(i,2));
i+=1;
} else {
op.push(s.substr(i,1));
}
}
}
break;
}
case ')':{
break;
}
case ' ':break;
}
}
return;
}
int main(){
trueTable tableP,tableQ;
string s;
cout<<"请输入表达式p:";
getline(cin,s);
logicConstruct(s,tableP);
tableP.printTable();
cout<<"请输入表达式q:";
getline(cin,s);
logicConstruct(s,tableQ);
tableQ.printTable();
if(tableP.cmp(tableQ)) cout<<"逻辑等价";
else cout<<"逻辑不等价";
return 0;
}
ADT建立的主要思路
首先确定将命题与真值分开存储到两个动态数组中,先扫一遍输入的逻辑表达式,将其中的基本命题添加至真值表中并进行排序,方便后续比较。
再根据基本命题的个数确定真值表的行数 column
,同时利用递归函数代替循环的嵌套初始化真值表,将基本命题的真值表构造出来。利用 initialLise
函数初始化该真值表即可。
然后编写各种逻辑运算的处理函数,处理完毕后将新的组合命题和真值添加至表中,真值只需要在原有数组的某尾直接添加即可,行数不变,列数随着命题数量的增加而增加。
表达式的分析运用到了栈处理运算符与中缀表达式的思路,因此最后一个完成计算的命题一定是输入的复合命题,比较真值表时只需要比较最后一列即可。
处理真值与命题的对应关系运用的是搜索函数,但由于有括号的存在,被存储的命题最外层是没有括号的,这就会导致括号包裹后的命题无法被识别,因此搜索函数需要处理最外层的括号(如果存在的话)。同样运算符的优先级用到了矩阵来表示。
由于条件运算符 ->
的程度为2,C++不能用 switch
处理字符串,因此处理运算符在矩阵的下标时需要额外关照 ->
,为其设置一个额外的判准 -
,同时处理的如果是条件运算符则用于遍历的指针 i
需要跳跃两格,因此要在末尾额外加一。
switch
的处理逻辑不同会导致程序的编写思路不同,如果用于分类栈顶符号,好处是可以避免二次分类判断需要调用的逻辑运算函数,坏处是要额外判断当前读到的符号是否特殊,如占两位的"->" 需要指针额外前进一格,空格需要被忽略,是否非运算符而是命题的名称(这里小偷一个懒默认命题的名称长度为1);若用于分类当前读到的符号 s[i]
则需要二次分类判定调用的逻辑运算函数,比较优先级时由于视角不同设定的判准也会有所改变,如遇到右括号在默认语法合法的情况下(其实是懒得写出错)可以给栈顶命题加上括号(这个很重要,会影响输出命题名称的严谨性),而在前一种情况中是不可能分类出右括号的。
当遇到括号时右括号始终不推入栈,而是一直比较直到碰到左括号消除为止。
运算符的优先级矩阵是非严格等级排列的,而只有一个相对等级,这是因为从左向右运算也是一个潜在的优先级判准,以及界限符的一些特殊性质也有所影响。
string[i]
的写法在C++中会返回一个 char
类型的数据,不能直接用于栈,因此需要用到 string.substr(pos,n)
返回从下标"pos"开始的n个字母组成的子串。同时C++中的类编写没有JS那么方便,键值对的键不是字符串而是变量,要想通过字符串检索下标只能通过STL的 map
或者编写分类函数来实现,不是很方便。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程