目 录
- 实际的练习题目、系统的总功能和各子模块的功能………………………………………………………………………………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. 处理重复变量名的问题:如果命题公式中出现重复的变量名,目前的代码可能会出现错误。我们可以考虑在哈希表中加入变量名的出现次数来进行判断,并采取相应的处理措施。例如,在构建变量表时,可以检查变量名是否已经存在,并对重复的变量名进行编号。
当遇到重复的变量名时,我们可以为它们添加不同的后缀或编号以区分它们。例如,在哈希表中存储变量名和变元编号的对应关系时,我们可以为重复变量名添加"_1"、"_2"等后缀,确保每个变量名都是唯一的。
3. 提高计算效率:在计算主合取范式和主析取范式时,目前的代码使用了字符串拼接的方式。但是,在处理复杂的公式时,这种方式的效率可能会较低。我们可以考虑使用二叉树的方式存储逻辑表达式,并利用树的遍历方式将其转换成主合取范式和主析取范式。这样,可以通过遍历二叉树的方式来生成相应的范式,减少了对字符串操作的依赖。
我们可以定义一个二叉树的节点类,包括节点值和左右子节点。通过构建逻辑表达式的二叉树,我们可以利用不同的遍历方式(如前序、中序、后序遍历)来生成主合取范式和主析取范式。遍历过程中,可以根据节点值的类型进行相应的处理,并将结果拼接成范式形式。
这些改进设想可以提高代码的可扩展性、可读性和效率,并使其能够更好地处理复杂的命题公式。
(4)大作业完成中的感想和心得体会
完成这个大作业的过程让我深刻体会到了编程中的创新和挑战。通过思考、调试和优化,我不仅学会了如何设计和实现一个小型的逻辑计算器,还提升了对算法和数据结构的理解和应用能力。同时,也意识到自己在实现过程中存在的不足,并希望在今后的学习和工作中能够不断改进和优化自己的编程技能,为社会、为人类创造更多的价值。文章来源:https://www.toymoban.com/news/detail-499363.html
通过这个项目,我深入了解了命题逻辑的背后原理,并将其转化为实际的代码实现,这为我打下了坚实的计算机科学基础。我相信这个项目经历将对我的未来学习和职业发展产生积极的影响,并且培养了我解决问题、思考抽象概念和设计复杂系统的能力。我将继续努力学习,提升我的编程能力!文章来源地址https://www.toymoban.com/news/detail-499363.html
到了这里,关于离散数学大作业任务书的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!