二叉树在表达式中的应用-命题逻辑表达式的真值表计算程序
对于一个给定的逻辑表达式输出其真值表。具体就是借助二叉树来表示逻辑表达式。
1、程序说明文档
本计算程序用于命题逻辑式的计算。 功能概述:按照一定格式(格式稍后说明)输入命题逻辑式,即可得到真值表。 使用说明: 1、为了方便输入,我们将原本的逻辑运算符号进行了修改。在输入表达式时,请将对应符号转换成我们所要求的符号。下面是对应表列: 逻辑非替代符! 合取替代符号* 析取替代符号/ 蕴含替代符号: 等价替代符号= 输入时只需要对公式的符号直接代换输入即可。 2、运算程序仅支持使用大小写字母表示命题变项,且运算过程对大小写敏感。 3、运算公式中仅允许小括号的出现,并且允许小括号的嵌套使用。 4、如果要结束程序,请使用end命令。命令对大小写不敏感。 5、程序正处于测试阶段,希望使用者能提供漏洞测试数据。 6、表达式输入过程中不允许使用任何的不必要的空格,且每次仅仅允许计算一个表达式 7、尽管程序理论上可以输入2*26个不同的命题变元进行运算,但由于其结果指数增长的特性,请不要随意尝试。否则后果自负。
2、源代码:(这里将基础功能暂时封装成类)
1 #include<cstring> 2 #include<cstdio> 3 #include<cstring> 4 #include<ctype.h> 5 #include<string> 6 #include<algorithm> 7 using namespace std; 8 9 class LogicalExpression 10 { 11 private: 12 int maxn; 13 const char optr[6]="!*/:=";//运算符 14 int *lch=nullptr; 15 int *rch=nullptr; 16 char *op=nullptr; 17 int nc=0;//结点个数 18 int MaxnEstimate(char* AimString); 19 int Build_Tree(char* s,int x,int y); 20 int GetValue(char* p,int* v,int root); 21 public: 22 LogicalExpression(){}; 23 ~LogicalExpression(){}; 24 void SetMaxn(char* AimString); 25 void BuildLogicalExpressionTree(char* EX); 26 void PresentTree(); 27 void ValueChart(); 28 void Clear(); 29 }; 30 31 int LogicalExpression::GetValue(char* p,int* v,int root) 32 { 33 char ch=op[root]; 34 //printf("%c\n",ch); 35 switch(ch) 36 { 37 case '!':return 1-GetValue(p,v,rch[root]); 38 case '*':return (GetValue(p,v,lch[root])&&GetValue(p,v,rch[root]))?1:0; 39 case '/':return (GetValue(p,v,lch[root])||GetValue(p,v,rch[root]))?1:0; 40 case ':':return (GetValue(p,v,lch[root])==1&&GetValue(p,v,rch[root])==0)?0:1; 41 case '=':return (GetValue(p,v,lch[root])==GetValue(p,v,rch[root]))?1:0; 42 } 43 //运行到此处说明是到达叶子 44 //printf("->%d\n",v[strchr(p,ch)-p]); 45 return v[strchr(p,ch)-p]; 46 } 47 48 void LogicalExpression::ValueChart() 49 { 50 //统计命题个数 51 char temp[nc]; 52 memset(temp,0,sizeof(temp)); 53 int u=0; 54 for(int i=1;i<=nc;i++) 55 { 56 char ch=op[i]; 57 if(strchr(temp,ch)==NULL&&strchr(optr,ch)==NULL)temp[u++]=ch; 58 } 59 sort(temp,temp+u); 60 for(int t=0;t<u;t++)printf("%3c",temp[t]); 61 printf(" ANS\n"); 62 //创建真值分配数组 63 int value[u]; 64 memset(value,0,sizeof(value)); 65 //i=[0,(1<<n)-1]循环 66 for(int i=0;i<(1<<u);i++) 67 { 68 int c=i; 69 for(int k=u-1;k>=0;k--) 70 { 71 value[k]=c%2; 72 c/=2; 73 } 74 //分解i为二进制&&分配到各个命题 75 int ans=GetValue(temp,value,1); 76 for(int t=0;t<u;t++)printf("%3d",value[t]); 77 printf("%3d\n",ans); 78 } 79 // 80 return; 81 } 82 83 void LogicalExpression::BuildLogicalExpressionTree(char* EX) 84 { 85 memset(lch,0,sizeof(lch)); 86 memset(rch,0,sizeof(rch)); 87 memset(op,0,sizeof(op)); 88 int lenth=strlen(EX); 89 Build_Tree(EX,0,lenth); 90 return; 91 } 92 93 int LogicalExpression::MaxnEstimate(char* AimString) 94 { 95 int lenth=strlen(AimString); 96 int num=0; 97 for(int i=0;i<lenth;i++) 98 if(AimString[i]=='('||AimString[i]==')')num++; 99 return lenth-num+1; 100 } 101 102 void LogicalExpression::SetMaxn(char* AimString) 103 { 104 maxn=MaxnEstimate(AimString); 105 lch = new int[maxn+1]; 106 rch = new int[maxn+1]; 107 op = new char[maxn+1]; 108 nc=0; 109 return; 110 } 111 112 void LogicalExpression::Clear() 113 { 114 if(lch!=nullptr){delete[] lch;lch=nullptr;} 115 if(rch!=nullptr){delete[] rch;rch=nullptr;} 116 if(op !=nullptr){delete[] op ;op =nullptr;} 117 maxn=nc=0; return; 118 } 119 120 int LogicalExpression::Build_Tree(char* s,int x,int y) 121 { 122 int c[]={-1,-1,-1,-1,-1}; 123 int u; 124 if(y-x==1)//仅一个字符,建立单独结点 125 { 126 u=++nc; 127 lch[u]=rch[u]=0; 128 op[u]=s[x]; 129 return u; 130 } 131 // 132 int p=0; 133 for(int i=x;i<y;i++)//寻找括号外的运算符 134 { 135 if(s[i]=='(')p++; 136 else if(s[i]==')')p--; 137 else if(!p&&strchr(optr,s[i])!=NULL) c[strchr(optr,s[i])-optr]=i; 138 } 139 // 140 int pst=-1; 141 for(int k=4;k>=0;k--) if(c[k]!=-1) {pst=c[k];break;} 142 //寻找在括号外优先级最低的二目运算符 143 if(pst==-1) return Build_Tree(s,x+1,y-1); 144 //整个式子都被包裹在括号里 145 u=++nc; 146 if(s[pst]=='!') lch[u]=-1;//右子树为-1说明是'!'运算 147 else lch[u]=Build_Tree(s,x,pst); 148 rch[u]=Build_Tree(s,pst+1,y); 149 op[u]=s[pst]; 150 return u; 151 } 152 153 void LogicalExpression::PresentTree() 154 { 155 printf("VertexNum==%d\n--------\n",nc); 156 printf("NUM:"); 157 for(int i=1;i<nc+1;i++)printf("%3d",i); 158 printf("\nVER:"); 159 for(int i=1;i<nc+1;i++)printf("%3c",op[i]); 160 printf("\nLCH:"); 161 for(int i=1;i<nc+1;i++)printf("%3d",lch[i]); 162 printf("\nRCH:"); 163 for(int i=1;i<nc+1;i++)printf("%3d",rch[i]); 164 printf("\n"); return; 165 } 166 167 /************************************************************/ 168 169 int main() 170 { 171 //freopen("input.txt","r",stdin); 172 //freopen("ans.txt","w",stdout); 173 LogicalExpression psd; 174 char aim[100]; 175 memset(aim,0,sizeof(aim)); 176 printf(">>"); 177 while(~scanf("%s",aim)) 178 { 179 if(tolower(aim[0])=='e'&&tolower(aim[1])=='n'&&tolower(aim[2])=='d')break; 180 psd.SetMaxn(aim); 181 psd.BuildLogicalExpressionTree(aim); 182 //psd.PresentTree(); 183 psd.ValueChart(); 184 psd.Clear(); 185 memset(aim,0,sizeof(aim)); 186 printf(">>"); 187 } 188 return 0; 189 }
3、有一说一,这次的算法并不让我觉得很好。算法的思路是参考了一些资料确定下来的。
首先是上述代码的参考源代码。由于之前只接触过一次类似的问题,所以这一次保留了一些前辈提供的模板。
1 const int maxn = 1000 + 10; 2 char str[maxn]; 3 int lch[maxn + 1], rch[maxn + 1]; char op[maxn + 1]; //每个结点的左右子结点编号和字符 4 int nc = 0; //结点数 5 int build_tree(char* s, int x, int y) 6 { 7 int i, c1=-1, c2=-1, p=0; 8 int u; 9 if(y-x == 1) //仅一个字符,建立单独结点 10 { 11 u = ++nc; 12 lch[u] = rch[u] = 0; 13 op[u] = s[x]; 14 return u; 15 } 16 17 for (i = x; i < y; i++) //寻找根节点的位置 18 { 19 switch (s[i]) 20 { 21 case '(': p++; break; 22 case ')': p--; break; 23 case '+': 24 case '-': if (!p) c1 = i; break; 25 case '*': 26 case '/': if (!p) c2 = i; break; 27 } 28 } 29 if (c1 < 0) c1 = c2; //找不到括号外的加减号,就用乘除号 30 if(c1 < 0) return build_tree(s, x+1, y-1); //整个表达式被一对括号括起来 31 u = ++nc; 32 lch[u] = build_tree(s, x, c1); 33 rch[u] = build_tree(s, c1+1, y); 34 op[u] = s[c1]; 35 return u; 36 }
上述这段代码在解决这种问题上十分容易理解。在修改这段代码时,需要时刻关注优先级和符号结合特征的问题。在四则运算中,符号的都是二目运算符且只有两个优先级。但是,在逻辑运算中却有两种结合特性的运算符以及五个不同的优先级。上述代码依旧借助了“编号化”的思想,使得在建立模拟二叉树的过程中能够提前估算二叉树规模。这在我封装好的类中有所体现。
代码的来源:https://www.cnblogs.com/lfri/p/9985993.html
第二个参考资料是一篇论文。《一种利用二叉树来实现逻辑表达式自动推导的算法》来自信阳师范学院学报第18卷第2期2005年4月(自然科学版)。
论文对于这一部分的说法十分简洁,这里通过这次课题来进行补充。该程序之后的设计也会参考论文之后的讲解。但是由于采用的数据结构的差异,我不会完全按照论文的讲解去做。
OK