- 原题参见严蔚敏版《数据结构题集(C语言版)》P148 5.1
- 简单来说,就是写一个程序判断类似于(A|~A)&(B|~B)的逻辑表达式是重言式,矛盾式,还是两者都不是。
一、杂乱的知识点笔记
识别逻辑表达式的符号形式并建立二叉树有两种策略:自底向上的算符优先法和自顶向下分割,先序遍历建立二叉树的方法。
- 自底向上的算符优先法:我个人的理解是,类比通过栈计算算术表达式的算法。一个栈负责存叶子节点,一个栈负责存逻辑运算符。找到两个叶子节点,同时找到当前优先级相对高的符号,组成一棵子树,然后将这棵子树的根节点作为一个新的叶子节点。不断出栈入栈,最后形成一棵代表逻辑表达式的二叉树。运算符之间的优先级关系可以用一个写定的二维数组表示。
- 自顶向下分割,先序遍历建立二叉树的方法:例如(A|~A)&(B|~B),其先序序列可以为“& | A ~ A | B ~ B”,如果能由逻辑表达式得到这样一个对应的先序序列,然后按照先序遍历建成二叉树就行。
- 为什么要采用二叉树去表示逻辑表达式?:如果是算数表达式,用二叉树来表示就会显得非常繁琐,讲道理,一边处理就可以一边计算了。反观逻辑表达式,它和算术表达式的不同就很能说明问题,逻辑表达式里的一个命题A,可以为TRUE或FALSE,为了证明这个表达式是否为重言式,就得来回给表达式中的命题带入TRUE或FALSE计算结果,这时候将逻辑表达式建立为一个二叉树就能很方便处理:通过递归。
- 先,中,后序三种遍历中,用后序序列遍历计算最好:例如(A|~A)&(B|~B),假如它的先,中,后序序列分别是:“& | A ~ A | B ~ B”、“A | ~ A & B | ~ B”、“A A ~ | B B ~ | &”。首先,因为括号是不会出现在二叉树中的,中序序列是没法体现出之前括号给定的优先级关系,所以中序不适合计算。先序和后序能体现括号给定的优先级。先序序列需要从右往左找运算符计算,比如B和~B要向左找‘|’,而后序是从左往右寻,先A,第二个A做‘~’运算,然后前面两个做‘|’运算。显然从左往右更符合我们日常计算的习惯。
二、混乱的大致实现思路
首先要解决的问题是,怎么用先序遍历的方式建立二叉树?我考虑过能不能写一个函数将逻辑函数字符串转换成先序序列,然后直接用之前写的先序建立一个二叉树。然后我发现这个方式有两个难点:括号的存在对符号优先级的影响;单纯一个先序不足以建立一棵二叉树。对此毫无思路的我,看到了书中的提示:
以二叉树表示表达式的递归定义如下:若表达式为数或简单的变量,则相应二叉树中仅有一个根结点,其数据域存放该表达式信息;若表达式=(第一操作数)(运算符)(第二操作数),则相应的二叉树中以左子树表示第一操作数;右子树表示第二操作数;根结点的数据域存放运算符(若为一元运算符,则左子树为空)。操作数本身又为表达式。
然后我就打算强行写一个递归函数,每次调用的目的在于寻找第一操作数,运算符和第二操作数,将这三个组合成一棵子二叉树,并返回运算符(根节点)的指针。写的时候发现有很多特殊情况要处理,然后就补充了大量的if语句。最后完成并调试很多遍后,虽然书中的例子我都输入通过了,我依旧觉得我这个算法有问题,不完善。
在建立好二叉树后,考虑如何计算表达式的值并证明该式子是否为重言式。我的想法是去维护一个一维数组,这个数组记录了所有命题和命题的取值(0 or 1,也就是FALSE还是TRUE),每次计算都会去查询这个数组,当前计算的命题对应的取值。我考虑过指针数组,但感觉逻辑表达式中命题经常重名,命题相同但在二叉树中的节点位置不同,存储起来也很麻烦,还不如一维数组加查找。
最后证明逻辑表达式是否为重言式,意味着我很有可能要产生一个真值表,产生一个真值表就意味着我需要在3中提到一维数组中做0和1的全排列,我唯一想到的方法是用递归,每次进入递归前改一下。
最后的最后我偷懒没写释放树申请空间的代码。
三、不靠谱的代码实现
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX_A_LEN 30
#define MAX_S_NUM 20
typedef struct LSNode{
//二叉树节点
char f;
int b;
struct LSNode* LSubNode;
struct LSNode* RSubNode;
}LSNode;
typedef struct CC{
char f;
int b;
}CC;
typedef struct CCTABLE{
//对应命题的一维数组的结构定义
CC count[MAX_S_NUM];
int total;
}CCTABLE;
LSNode* CLS(char* LogicStatent,int length,char Prec,CCTABLE* C);
LSNode* CreateNode(char c,CCTABLE* C);
void freeTree(LSNode* T);//偷懒没写的释放申请空间函数
int Judge_S(LSNode* T,CCTABLE* C,int mark);
int Caculate_S(LSNode* T,CCTABLE* C);
int main()
{
int slu;
LSNode* HEAD;
CCTABLE* C;
C=malloc(sizeof(CCTABLE));
C->total=0;
char LogicStatent[MAX_A_LEN];
memset(LogicStatent,'\0',MAX_A_LEN);
gets(LogicStatent);
HEAD=CLS(LogicStatent,strlen(LogicStatent),'\0',C);
slu=Judge_S(HEAD,C,0);
if(slu==-1){
printf("Satisfactible\n");
}
else if(slu==0){
printf("False forever\n");
}
else if(slu==1){
printf("True forever\n");
}
return 0;
}
int Judge_S(LSNode* T,CCTABLE* C,int mark)//判断函数,返回值-1代表可变式,0为矛盾式,1为重言式
{
int slu;
static int preslu=-2;
if(mark==C->total){
slu=Caculate_S(T,C);
if(preslu==-2) preslu=slu;
else if(slu!=preslu){
preslu=-1;
}
}
else{
C->count[mark].b=0;
Judge_S(T,C,mark+