[离散] 编程求命题公式真值表
2017-03-18 09:41
169 查看
[离散] 编程求命题公式真值表
概述
真值表是离散数学中的一个重要概念,由真值表我们能求得任意命题公式的主析取范式和主合取范式。下面我们先来回顾一下真值表的概念:将命题公式A在所有赋值下取值情况列成表,称作A的真值表
由真值表的定义我们不难得出我们将要做些什么,
分析命题公式A
对命题公式进行赋值并求值
输出真值表
本文将利用C语言编写一个程序,实现上述过程。
离散 编程求命题公式真值表
概述
分析问题
人求解的思路
抽象人的思路
实现细节
建树
节点的存储
递归建树
计算公式的真值
打印真值表
参考代码
分析问题
要求真值表,就必须分析命题公式,将输入的命题公式,如p&!q|(q>p),转化为计算机能理解的对象,方便后续求解。
注:这里用 & 表示合取, 用 | 表示析取,用 > 表示蕴含,用 = 表示等价,!表示命题的否定
人求解的思路
一个解析命题公式最直接的也是最朴素的想法就是模拟,也就是模拟你是怎么想的,让计算机按照你分析公式的思路进行分析。这样,我们就需要仔细想想,我们在分析命题公式,求真值表的时候都在做些什么呢 ?
例:给定如下命题公式
((p>q)&(q>r))>(p>r),求它的真值表
读者不妨自己在纸上写下这个公式,然后自己想一想,理清思路
一个常见的思路
给定一个赋值,从左到右按顺序求出每个子公式的值,最终得到公式的值
如,假设
p =1,q = 0, r = 1
计算
p>q得到 0,计算
q>r得到1,计算
p>r得到1
最后计算
(0 & 1 ) > 1得到1
我们分析这个思路,会发现,这里3步骤里计算哪个表达式选择往往因人而异,同时现实中很可能根本就是随意选取,这样是不利于计算机实现的。
如果将第三步限定为从左到右选取,那么这个思路,我们要按顺序解析出每个形如
A[符号]B的命题,还要记下子命题之间的运算符,并且还需要用别的方法,搞清楚我们最后算总命题的顺序。
不是不能做,也能做,有兴趣的也可以尝试编写对应代码试一试。不过在这里,我要介绍另一种利用递归的方法。
给出参考思路:
我们在看到这个公式的时候,首先寻找了优先级最低的符号(即>),然后将这个表达式分成了两份。
观察分好的表达式,如果是形如
A[符号]B的形式 ,则直接计算,如果不是进入下一步。
对新的子式重复做 步骤1,直到分成的每个子式都最简
然后按顺序反回去计算整个表达式的值
熟悉递归的朋友,可能一下就会发现这就是个递归,将大问题拆分成子问题,用子问题的返回值,对父问题进行解答。
抽象人的思路
如果我们用一个树来表达我们的拆解过程,那么可以得到下面一颗非完全二叉树,我们的拆解过程,就是一个构建树的过程。还是之前的例子:
((p>q)&(q>r))>(p>r),通过递归,我们得到了这样一颗树。
我们接下了的所有操作都将以这个树为基础,所以构建这个颗树极为关键,是整个算法的核心
下面给出完整思路(忽略实现细节)
建树(建树思路前面给出)。
从树的叶子节点开始,自下而上,同层优先,进行计算。
得到根节点的值,然后重新赋值
回到步骤2,直到真值表构建完毕
有了这个思路框架,我们就可以开始编写具体的代码,并补充一些其他需要注意的东西。
实现细节
建树
节点的存储
定义一个结构体 node 存储节点信息,除叶子节点外,每个节点都有至少有一个左儿子或右儿子(当然大部分是都有),这些节点要存储的信息如下:一个字符串存储命题公式
一个字符存储,两个儿子之间的运算符
一个int型整数存储该节点存储的命题公式的真值
递归建树
考虑递归结束的条件:当当前命题公式不含符号时
当当前命题公式为命题的否定时
按照运算的优先级,从低到高枚举第一个出现的符号,按符号将公式分成两个子式①
优先级:
=<
><
|<
&<
!<
()
考虑一些特殊情况
第一个满足①的符号,包括在括号里②
命题公式以
!开头③
对于第一种,我们需要先检测公式第一个字符是不是右括号
如果是,我们找到对应的左括号,并以左括号旁边的括号为分界点分出子式。
如果不是,按原样处理。可以证明,如果第一个符号不是右括号或取反符号
!,则一定存在满足①的符号将子式正确分成两份。
特殊中的特殊:出现多重括号。需要找到和最外层匹配的左括号。
对于第二种,暂时忽略掉感叹号,从第二个字符开始处理,则可以按①或②进行处理,最后生成子串的时候记得感叹号任然保留。
计算公式的真值
根据我们之前的设计,我们要从最底层的叶子节点开始,并且同层优先,自下而上进行计算。而我们求真值的时候真的需要这么做吗?
答案是:不需要
同样的,我们利用递归,从根节点开始计算,递归表达式如下:
f(father)=f(left_kid) [operator] f(right_kid)
值得注意的一点:
在C语言中,与运算和或运算,当能够确定真值时,将前者能够确定真值时将不再计算后者。即进行与运算的两个公式,如果前者为假,后者将不再计算。
所有我们在书写程序时,不能直接
return A&&B,而要先计算
C=A&&B,在返回
return C
当然,我们还需要按顺序对命题变元赋值,循环计算公式的真值,并进行存储。
本教程将他们放到了
打印真值表里进行,当然你也可以不这么做。
打印真值表
在这部分,我们需要实现大约3个模块赋值需要知道每个叶子节点在树中的位置
打印真值表,需要存储树的每一层含有的节点
一个循环计算真值表
叶子节点的位置,在建树的时候记忆
每一层含有的节点,单独写一个函数计算
给出伪代码:
print(树) //从下向上,按层级打印 for(i取遍所有赋值) //二进制转十进制 { for() //给命题变元赋值 for() //打印命题变元的值 cal(1) //计算总公式的值和子式的值 for() //打印子式和总公式的值 print(换行) }
注意:后面打印的真值要和第一行的命题公式一一对应
参考代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXN 100 #define Maxn 100 struct point { char str[MAXN]; char oper; int value; } exp[Maxn]; const char op[6] = {'=', '>', '|', '&'}; int kid_bj[MAXN * 2], kid[MAXN]; int kid_num = 0; int f_value[MAXN * 2]; int layer[10][10], lay; void strcopy(char temp1[], char str[], int start, int end) { int i, j = 0; for (i = start; i < end; i++) { temp1[j++] = str[i]; } temp1[j] = '\0'; } void kill_bracket(char s[]) { int i = 1; int left = 0; if (s[0] == '(') { left++; while (i < strlen(s) - 1) { if (s[i] == ')') { if (left == 1) return; left--; } if (s[i] == '(') left++; i++; } char temp[MAXN]; strcopy(temp, s, 1, strlen(s) - 1); memset(s, 0, sizeof(s)); strcpy(s, temp); } } void build_tree(char s[], int node) { int i, k, len, mid, find; char s1[MAXN], s2[MAXN]; len = strlen(s); kill_bracket(s); if (s[0] == '!' && s[1] == '(' && s[len - 1] == ')') { exp[node].oper = s[0]; strcpy(exp[node].str, s); strcopy(s1, s, 2, len - 1); build_tree(s1, node * 2); return; } if (len <= 2) { if (len == 1) { strcpy(exp[node].str, s); if (kid_bj[s[0]] == 0) { kid_bj[s[0]] = 1; kid[kid_num++] = s[0]; } return; } else { char temp[MAXN]; temp[0] = s[1]; temp[1] = '\0'; strcpy(exp[node].str, s); exp[node].oper = s[0]; strcpy(exp[node * 2].str, temp); if (kid_bj[temp[0]] == 0) { kid_bj[temp[0]] = 1; kid[kid_num++] = temp[0]; } return; } } find = 0; for (k = 0; k < 4; k++) { i = 0; if (find) break; while (i < len) { if (s[i] == '(') { i++; int num = 1; while (i < len) { if (num == 0) break; if (s[i] == '(') num++; if (s[i] == ')') num--; i++; } } if (s[i] == op[k]) { mid = i; find = 1; break; } i++; } } // printf("%c\n",s[mid]); printf("%d %s\n", strlen(s), s); strcpy(exp[node].str, s); exp[node].oper = s[mid]; // if (s[mid - 1] == ')' && s[0] == '(') // strcopy(s1, s, 1, mid - 1); // else strcopy(s1, s, 0, mid); // if (s[mid + 1] == '(') // strcopy(s2, s, mid + 2, len - 1); // else strcopy(s2, s, mid + 1, len); printf("%d %s\n", strlen(s1), s1); printf("%d %s\n", strlen(s2), s2); build_tree(s1, node * 2); build_tree(s2, node * 2 + 1); } int cal_val(int node) { if (strlen(exp[node].str) == 1) { // printf("****%c %d***",exp[node].str[0],f_value[exp[node].str[0]]); return f_value[exp[node].str[0]]; } int lkid, rkid; int a, b; lkid = node * 2; rkid = node * 2 + 1; switch (exp[node].oper) { case '=': if (cal_val(lkid) == cal_val(rkid)) { exp[node].value = 1; return exp[node].value; } else { exp[node].value = 0; return exp[node].value; } break; case '>': if (cal_val(lkid) == 1 && cal_val(rkid) == 0) { exp[node].value = 0; return exp[node].value; } else { exp[node].value = 1; return exp[node].value; } break; case '|': a = cal_val(lkid); b = cal_val(rkid); exp[node].value = a || b; return exp[node].value; break; case '&': a = cal_val(lkid); b = cal_val(rkid); exp[node].value = a && b; return exp[node].value; break; case '!': if (cal_val(lkid) == 1) { exp[node].value = 0; return exp[node].value; } else { exp[node].value = 1; return exp[node].value; } break; } } void getLayer(int node, int l) { int lens = strlen(exp[node].str); if (lens > 1) { if (l > lay) lay = l; layer[l][++layer[l][0]] = node; if (lens == 2 || (exp[node].str[0] == '!' && exp[node].str[lens - 1] == ')')) getLayer(node * 2, l + 1); else { getLayer(node * 2, l + 1); getLayer(node * 2 + 1, l + 1); } } return; } void print_table() { int i, count = 1, j, temp, k; printf("layer=%d\n", lay); for (i = kid_num - 1; i >= 0; i--) { count *= 2; printf("%10c ", kid[i]); } printf("kidnum=%d\n", kid_num); // 打印各层节点 // for (i = lay; i > 0; i--) // for (j = 1; j <= layer[i][0]; j++) // printf("%10s ", exp[layer[i][j]].str); printf("\n"); for (i = 0; i < count; i++) { temp = i; for (j = 0; j < kid_num; j++) { f_value[kid[j]] = temp % 2; temp /= 2; } for (j = kid_num - 1; j >= 0; j--) printf("%10d ", f_value[kid[j]]); cal_val(1); // 打印各层节点 // for (k = lay; k > 0; k--) // for (j = 1; j <= layer[k][0]; j++) // printf("%10d ", exp[layer[k][j]].value); //打印根节点 printf("%10d ", exp[layer[1][1]].value); printf("\n"); } } int main() { char s[MAXN]; scanf("%s", s); build_tree(s, 1); getLayer(1, 1); print_table(); system("pause"); return 0; }
相关文章推荐
- 离散数学 第一章 命题逻辑 1-4真值表与等价公式
- 编程优化数学组合排列公式取模实现
- C语言实现离散数学中的命题逻辑
- 编程输出真值表
- 3.9 利用下列公式编程计算π的值:
- 数理逻辑:命题逻辑(2)命题逻辑公式
- 离散数学 02.01 命题以及逻辑联结词
- 离散数学输入表达式打印真值表和主析/合取范式
- 数理逻辑:命题逻辑(3)永真公式与可满足公式
- 离散数学及其应用--第一章-命题逻辑的基本概念
- 用递推关系显性公式替代递归的编程方法
- 编程更改公式字段示例代码
- 离散数学2 ____ 命题公式的等值运算__常用的命题定律表
- [转载] Discrete Mathematics——01 命题与命题公式
- 编程-任意多边形的面积公式
- 编程更改公式字段示例代码
- 软件编程——逻辑运算公式
- 委托、事件编程“公式”
- 用递推关系显性公式替代递归的编程方法
- 数理逻辑:命题演算(2)真公式的定义 (正在编辑)