目 录
- 实际的练习题目、系统的总功能和各子模块的功能………………………………………………………………………………1
1.1题目及问题描述………………………………………………………………1
1.2功能概述………………………………………………………………………1
1.3技术选型………………………………………………………………………1
1.4代码实现………………………………………………………………………2
- 主要算法简述…………………………………………………………9
- 程序流程图……………………………………………………………10
3.1功能模块设计…………………………………………………………………10
3.2关键方法流程图………………………………………………………………11
3.3界面设计………………………………………………………………………11
3.4系统测试………………………………………………………………………11
- 总结报告………………………………………………………………16
- 实际的练习题目、系统的总功能和各子模块的功能
- 题目及问题描述
(1)求任意一个命题公式的真值表。
(2)利用真值表求任意一个命题公式的主范式。
(3)判断两个命题公式是否等值。
-
- 功能概述
1. 求任意一个命题公式的真值表:首先通过调用init函数输入命题公式,然后根据命题公式中包含的所有变量,生成该命题公式的真值表,即计算所有可能情况下命题公式的结果,并输出其真值表。
2. 利用真值表求任意一个命题公式的主范式:根据真值表中为1或0的情况,计算得出该命题公式的主合取范式和主析取范式,并分别输出。
3. 判断两个命题公式是否等值:输入两个命题公式,分别计算它们的真值表,并比较它们的真值表是否相等,最后输出结果。
-
- 技术选型
操作系统 | Windows 11 |
编程语言(及版本) | C++ |
编辑软件/IDE | VS |
-
- 代码实现
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
#include<map>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;typedef struct optrstack
{char oper[30];int loc;
}OPStack;
void initop(OPStack& op)
{int i;op.loc = 0;for (i = 0; i < 30; i++)op.oper[i] = '\0';
}
void push(OPStack& op, char c)
{op.oper[op.loc++] = c;
}
char pop(OPStack& op)
{return(op.oper[--op.loc]);
}
typedef struct opndstack
{int oper[60];int loc;
}OPndStack;
void initopnd(OPndStack& op)
{int i;op.loc = 0;for (i = 0; i < 30; i++)op.oper[i] = '\0';
}
void pushopnd(OPndStack& op, int c)
{op.oper[op.loc++] = c;
}
int popopnd(OPndStack& op)
{return(op.oper[--op.loc]);
}
void init(char s[])
{int t;printf("\n请输入任意一个命题公式(命题变元为一个字符)\n");printf("非、析取、合取、条件、双条件词分别用符号!、|、&、-、+表示\n");cin>>s;t = strlen(s);s[t] = '@';s[t + 1] = '\0';
}
int is_optr(char c)
{char optr_list[] = "+-|&!()@";for (int i = 0; i < (int)strlen(optr_list); i++) if (c == optr_list[i])return 1;return 0;
}
char first(char op1, char op2)
{char tab[8][9] = {"><<<<<>>",">><<<<>>",">>><<<>>",">>>><<>>",">>>>><>>","<<<<<<=E",">>>>>E>>","<<<<<<E=",};char optr_list[] = "+-|&!()@";//双条件、条件、析取、合取、非int op1_loc, op2_loc;for (op1_loc = 0; op1_loc < (int)strlen(optr_list); op1_loc++)if (optr_list[op1_loc] == op1)break;for (op2_loc = 0; op2_loc < (int)strlen(optr_list); op2_loc++)if (optr_list[op2_loc] == op2)break;return tab[op1_loc][op2_loc];
}
int operate(int x, char op, int y)
{switch (op) {case '+': return (((!x) || y) && (x || (!y))); break;case '-': return ((!x) || y); break;case '|': return x || y; break;case '&': return x && y; break;}return -1;
}
void divi(char s[], char c[])
{int i, j = 0, t;for (i = 0; s[i] != '@'; i++) if (!is_optr(s[i])) { for (t = 0; t < j; t++) if (c[t] == s[i]) break; if (t == j)c[j++] = s[i]; }c[j] = '\0';char aa;for (i = 0; i < j - 1; i++)//按字典序排序for (t = i + 1; t < j; t++) if (c[i] > c[t]) { aa = c[i]; c[i] = c[t]; c[t] = aa; }
}
int locate(char s[], char c)
{int i;for (i = 0; i < (int)strlen(s); i++) if (s[i] == c)break;return i;
}
int calc(char s[100], int* p)
{char myopnd[10], c;int sloc = 0;OPStack optr;initop(optr);push(optr, '@');OPndStack opnd;initopnd(opnd);divi(s, myopnd);c = s[sloc++];while (c != '@' || optr.oper[optr.loc - 1] != '@') {if (!is_optr(c)) { int d1; d1 = p[locate(myopnd, c)]; pushopnd(opnd, d1); c = s[sloc++]; }else {switch (first(optr.oper[optr.loc - 1], c)) {case '<': push(optr, c); c = s[sloc++]; break;case '=': pop(optr); c = s[sloc++]; break;case '>': char op; op = pop(optr);if (op == '!') { int a; a = !popopnd(opnd); pushopnd(opnd, a); }else {int a, b; a = popopnd(opnd); b = popopnd(opnd);int res; res = operate(b, op, a); pushopnd(opnd, res);}break;}}}return opnd.oper[opnd.loc - 1];
}
void main()
{//(1)求任意一个命题公式的真值表:cout << "求任意一个命题公式的真值表:" << endl;char exp[100], myopnd[10];int i, j, n, m, A[1024][10], flag, k;int F[1024];init(exp);divi(exp, myopnd);n = (int)strlen(myopnd);m = (int)pow(2, n);for (j = 0; j < n; j++) {flag = 1;k = (int)pow(2, n - j - 1);for (i = 0; i < m; i++) {if (!(i % k))flag = !flag;if (flag)A[i][j] = 1;else A[i][j] = 0;}}char ss[100];int t;strcpy(ss, exp);t = (int)strlen(ss);ss[t - 1] = '\0';printf("命题公式%s的真值表如下:\n", ss);for (j = 0; j < n; j++)printf("%4c", myopnd[j]);printf(" %s\n", ss);for (i = 0; i < m; i++) {for (j = 0; j < n; j++)printf("%4d", A[i][j]);F[i] = calc(exp, A[i]);printf("%6d", F[i]);printf("\n");}// (2)利用真值表求任意一个命题公式的主范式。// 计算主合取范式printf("\n命题公式%s的主合取范式为:\n", ss);for (i = 0; i < m; i++) {if (F[i] == 0) continue;printf("(");for (j = 0; j < n; j++) {if (A[i][j] == 0) printf("%c", myopnd[j]);else printf("!%c", myopnd[j]);if (j != n - 1 && A[i + 1][j + 1] == 1) printf("&");}printf(")");if (i != m - 1 && F[i + 1]) printf("|");}// 计算主析取范式printf("\n命题公式%s的主析取范式为:\n", ss);for (i = 0; i < m; i++) {if (F[i] == 1) continue;printf("(");for (j = 0; j < n; j++) {if (A[i][j] == 1) printf("%c", myopnd[j]);else printf("!%c", myopnd[j]);if (j != n - 1 && A[i + 1][j + 1] == 1) printf("|");}printf(")");if (i != m - 1 && F[i + 1] == 0) printf("&");}cout << endl;//(3)判断两个命题公式是否等值。// 计算第一个命题公式的真值表//前面已经输入过第一个命题公式,其数据直接用就行cout << "请再输入一个命题公式,判断两个命题公式是否等值:" << endl;int F1[1024], F2[1024];divi(exp, myopnd);n = (int)strlen(myopnd);m = (int)pow(2, n);for (j = 0; j < n; j++)
for (i = 0; i < m; i++) {for (j = 0; j < n; j++)F1[i] = calc(exp, A[i]);}// 计算第二个命题公式的真值表char exp2[100], myopnd2[10];init(exp2);//插入新的命题公式,并计算其真值表divi(exp2, myopnd2);printf("第二个命题公式%s的真值表如下:\n", exp2);for (j = 0; j < n; j++)printf("%4c", myopnd2[j]);printf(" %s\n", exp2);for (i = 0; i < m; i++) {for (j = 0; j < n; j++)printf("%4d", A[i][j]);F2[i] = calc(exp2, A[i]);printf("%6d", F2[i]);printf("\n");}printf("\n");// 判断两个命题公式是否等值int equal = 1;for (i = 0; i < m; i++) {if (F1[i] != F2[i]) {equal = 0;break;}}if (equal) printf("****两个命题公式相等\n");else printf("****两个命题公式不相等\n");}
- 主要算法简述
这份代码实现的是命题逻辑中的真值表,主合取范式、主析取范式和判断两个命题公式是否等值的功能。
在计算命题公式的真值表时,程序先输入命题公式,然后将每一个命题变元的可能取值全部遍历一遍,最后通过逐行计算得到整个真值表。在计算主合取范式和主析取范式时,程序会扫描整个真值表,并将所有取值为1或0的行(具体要看是求主合取范式还是主析取范式)转换为对应的逻辑表达式。
至于判断两个命题公式是否等值,则是先计算出两个真值表,再逐行进行比较。
整个程序用到的数据结构主要是栈,用于控制运算符优先级以及存储操作数。由于命题逻辑的运算符并不多,所以代码里使用了一个二维数组来存储两个运算符之间的优先级关系,从而避免了繁琐的if-else语句。
需要注意的是,这份代码实现的是命题逻辑,而不是一阶逻辑。如果要实现更高阶别的逻辑,需要修改部分代码和数据结构。
- 程序流程图
-
- 功能模块设计
- 基础功能
- 读取命题公式:从用户处获取输入的命题公式。
- 解析命题公式:将中缀表达式转换为后缀表达式,并建立一个命题变元编号与变量名的对应关系,可以利用栈来实现。
- 生成真值表:根据命题变元的个数生成真值表的表头,并依次遍历每一行,在当前行计算出命题公式的取值结果,并将结果填充至真值表中。
- 计算主合取范式和主析取范式:扫描整张真值表,找出所有取值为1或0的行(具体要看是求主合取范式还是主析取范式),并将它们转换为对应的逻辑表达式。这部分可以使用字符串拼接来实现。
- 判断两个命题公式是否等价:分别计算出两个命题公式的真值表,然后逐行比较它们的取值是否相同。
- 输出结果:将命题公式的真值表、主合取范式、主析取范式和是否等价的结果输出给用户。
- 关键方法流程图
- 生成真值表
- 计算主合取范式和主析取范式
- 判断两个命题公式是否等价
- 总结报告
- 大作业完成中遇到的主要问题和解决方法
在完成命题逻辑代码的过程中,可能会遇到以下一些问题:
1- 在解析命题公式时,可能会遇到表达式中带有多个括号的情况。这时可以使用栈来处理,每当遇到左括号时就将其入栈,在遇到右括号时就将栈顶的左括号出栈,并将中间的表达式加入后缀表达式中,即将不同运算符设定不同的优先级,从而确定括号的结合顺序。为了简化这个问题,我们可以采用逆波兰式表示法,将操作数放在前面,运算符放在后面的形式来表示算式。这样就不需要考虑括号在算式中的位置了。
2- 在生成真值表时,可能会遇到变量名长度不同、包含空格或其他特殊字符等问题。为了解决这些问题,可以使用哈希表来存储变量名和变元编号之间的对应关系。
3- 在计算主合取范式和主析取范式时,可以采用字符串拼接的方式来组合起真值表中取值为1或0的行所对应的逻辑表达式。
4-处理非操作符(~)和双重否定:在处理命题公式的过程中,我遇到非操作符和双重否定的情况。为了对这些情况进行处理,可以使用递归的方式来实现。具体来说,在从左到右解析命题公式的过程中,如果遇到非操作符(~),则将后面的命题公式作为参数递归调用自身,再添加一个“~”操作符,作为新的一项存入栈中;如果遇到双重否定,则直接跳过两个“~”操作符并继续解析后面的命题公式。
5-处理表达式中的变量名:有时在命题公式中会包含变量名,需要对这些变量名进行处理才能计算出表达式的值。一个简单的方法是将变量名转换为布尔型变量,例如,将“P”和“Q”转换为True和False。但是,如果变量名比较多或者变量名的长度不同,这种方法就会变得复杂和低效。为了解决这个问题,我们可以使用哈希表(Hash Table)来存储变量名和对应的布尔型变量之间的映射关系。具体来说,我们可以将变量名所对应的布尔值存储在哈希表中,然后在解析命题公式时,可以直接查找哈希表中的值并进行替换。
6-处理表达式中的逻辑运算符:在逻辑运算的时候,我不知如何明确表示逻辑运算符。命题公式中常见的逻辑运算符包括“与(&)”、“或(|)”、“非(~)”、“蕴含(->)”和“等价(<->)”。为了正确处理这些运算符,我们需要考虑它们的优先级和结合性。一般来说,非运算符的优先级最高,其次是与运算和或运算,蕴含和等价运算的优先级相同,但比与运算和或运算低。而这些运算符的结合性都是从左到右。因此,在解析命题公式时,我们需要将运算符按照优先级和结合性进行合理地组合。
(2)创新和得意之处
1- 采用后缀表达式的形式来处理命题公式,减少了符号优先级和括号的复杂性。
2- 使用哈希表来存储变量名和变元编号的对应关系,可以快速地查找变元。
3- 通过字符串拼接的方式生成主合取范式和主析取范式,减少了对逻辑运算符的依赖。
我为这些创新感到得意,并对它们的实现感到满意。
(3)大作业完成中存在的不足,需进一步改进的设想
在命题逻辑代码的编写过程中,可能存在以下一些不足之处:
1- 对于比较复杂的命题公式,程序可能会出现栈溢出等问题。此时可以考虑使用递归来解决这个问题。
2- 当命题公式中出现重复的变量名时,程序可能会出现错误。这时可以考虑在哈希表中加入变量名的出现次数来进行判断。
3-在计算主合取范式和主析取范式时,目前的代码使用了字符串拼接的方式。但是,在处理比较复杂的公式时,这种方式的效率可能会很低。这时候我们可以考虑使用二叉树的方式存储逻辑表达式,并利用树的遍历方式,将其转换成主合取范式和主析取范式。同时,如果我们想要求其他更加高级的逻辑运算,比如蕴含、等价等,也可以采用类似的方式来处理和计算。
(4)大作业完成中的感想和心得体会
在完成命题逻辑代码的过程中,我深深体会到了编程的乐趣和挑战。通过不断地思考、调试和优化,我不仅学会了如何设计和实现一个小型的逻辑计算器,还提升了对算法和数据结构的理解和应用能力。同时,我也清楚地意识到自己的不足之处,并希望在今后的学习和工作中能够不断地改进和优化自己的编程技能,为社会、为人类创造更多的价值。