二叉树在表达式中的应用-命题逻辑表达式的真值表计算程序

对于一个给定的逻辑表达式输出其真值表。具体就是借助二叉树来表示逻辑表达式。

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 }
Line 190

 

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

 

posted @ 2020-03-14 14:02  SavenNeer  阅读(958)  评论(0编辑  收藏  举报