计算命题演算公式的真值数据结构课程设计.docx
- 文档编号:24402760
- 上传时间:2023-05-27
- 格式:DOCX
- 页数:23
- 大小:116.56KB
计算命题演算公式的真值数据结构课程设计.docx
《计算命题演算公式的真值数据结构课程设计.docx》由会员分享,可在线阅读,更多相关《计算命题演算公式的真值数据结构课程设计.docx(23页珍藏版)》请在冰豆网上搜索。
计算命题演算公式的真值数据结构课程设计
第一章计算命题演算公式的真值
1.1问题引出与需求分析···············1
1.2程序设计·························1
1.2.1设计思想···················1
1.2.2设计表示···················2
1.2.3详细设计···················3
1.3调试分析·························5
1.4用户手册·························6
1.5测试数据及其结果·················6
第二章修道士与野人问题
2.1问题引出与需求分析···············2
2.2程序设计·························8
2.2.1设计思想···················8
2.2.2设计表示···················9
2.2.3详细设计··················10
2.3调试分析························13
2.4用户手册························13
2.5测试数据及其结果················13
附录1致谢·························16
参考文献····················16
1.1问题引出与需求分析
问题引出:
所谓命题演算公式是指由逻辑变量(其值为TRUE或FALSE)和逻辑运算符∧(AND)、∨(OR)和┐(NOT)按一定规则所组成的公式(蕴含之类的运算可以用∧、∨和┐来表示)。
公式运算的先后顺序为┐、∧、∨,而括号()可以改变优先次序。
已知一个命题演算公式及各变量的值,要求设计一个程序来计算公式的真值。
要求:
(1)利用二叉树来计算公式的真值。
首先利用堆栈将中缀形式的公式变为后缀形式;然后根据后缀形式,从叶结点开始构造相应的二叉树;最后按后序遍历该树,求各子树之值,即每到达一个结点,其子树之值已经计算出来,当到达根结点时,求得的值就是公式之真值。
(2)逻辑变元的标识符不限于单字母,而可以是任意长的字母数字串。
(3)根据用户的要求显示表达式的真值表。
需求分析:
由题目可知,所设计程序需满足以下需求:
输入方面:
接受输入的命题公式,逻辑变元可为任意长字母数字串。
功能方面:
能判断命题是否正确,若正确则将命题转化为后缀形式。
可以由用户输入各逻辑变元的真值求得命题的真值大小。
输出方面:
输出用户给定的逻辑变元真值下计算出的命题的真值。
输出该项命题的真值表。
(可选择)
1.2程序设计
1.2.1设计思想
(1)数据与操作的特性
逻辑变元应设计为字符串,逻辑符号对应分别为:
与(&),或(|),非(~),其中括号采用英文输入的‘(’和‘)’,对应ASCII码分别为40与41。
操作过程为用户先输入所需计算真值的命题,然后还可选择是否查看真值表,用户选择‘是’则显示命题的真值表,否则退出程序。
(2)数据结构的设计:
线性堆栈1:
用来存储输入的字符,将中缀表达式变成后缀表达式。
线性堆栈2:
这个堆栈和上述堆栈的不同在于存储的数据的类型不同,此堆栈存储的是树结点。
将后缀表达式构成一棵二叉树。
(3)算法设计:
首先实现将中缀表达式变成后缀表达式:
整个过程分为两部:
写入,先存储在一个一维的字符数组中;转化,将其打入栈中进行转化。
写入:
先用一个字符数组存放输入的中缀表达式,然后将一维的中缀表达式中的字符串逻辑变元用一个字符进行标识,这样我们就可以将原来复杂的中缀表达式变成熟悉而又简单的中缀表达式,同时用二维数组存放那些字符串逻辑变元。
实现的过程就是首先扫描一维中缀表达式,如果遇到逻辑符号,那么记住这个逻辑符号在数组中的相对位置用一个变量存放,然后继续扫描中缀表达式直到再次遇到逻辑符号,再一次记住它在中缀表达式中的相对位置,这两个逻辑符号之间的部分就是一个完整的逻辑变元,将这个字符串逻辑变元用一个字符代替并将这个字符串逻辑变元保存在二维数组中。
转化:
在实现该功能时,首先需要定义各符号的优先级,即:
'('和')'的优先级最高;‘~’(逻辑非号)的优先级次之;‘&’(逻辑与号)的优先级又低一级‘|’(逻辑或号)的优先级跟低;‘#’(他不是逻辑符号,只是为了方便使用堆栈而设置)的优先级最低,接着将'#'压入堆栈。
转化的其过程为:
当读到的是逻辑变元时直接输出,并保存到保存后缀表达式的数组中,当读到的单词为运算符时,令x1为当前栈顶运算符的变量,x2为当前扫描到的简单中缀表达式的运算符的变量,把当前读入的单词赋予变量x2,然后比较x1和x2的优先级。
若x1的优先级高于x2的优先级,将x1退栈作为后缀表达式的一个单词输出,然后接着比较新的栈顶运算符x1的优先级与x2的优先级;若x1的优先级低于x2的优先级,将x2的值进栈,然后接着读下一个单词;若x1的优先级等于x2的优先级且x1为“(”,x2为“)”,将x1退栈,然后接着读下一个单词;若x1的优先级等于x2的优先级且x1为“#”,x2为“#”,算法结束。
接着用后缀表达式构造出二叉树:
此过程中,用到所定义的存放树的堆栈。
具体实现为:
扫描后缀表达式,如果遇到逻辑变元然后将这个变元变成一个树节点,它的实现就是将该逻辑变元赋给树的data域,然后将它的左右子树赋为NULL,然后将这个树节点压入相应的堆栈;接着继续扫描,如果遇到的是单目运算符(非号‘~’)也将它构造成一个树节点然后从堆栈里面弹出一个树形节点,将弹出的元素的作为它的左子树,右子树设置为NULL,然后将这个树节点压入相应的堆栈;如果扫描到的是双目运算符(与号“&”或者或号“|”)将它也构造成一棵树,然后将这个树节点压入相应的堆栈,然后从栈中弹出两个元素,一个作为它的左子树,一个作为它的右子树,如此重复n(n为后缀表达式的长度)次。
最后打印真值表:
用整型数组d[]作为存储各变量的真值,k用以计量变量的数目。
其中,d[]中各变量产生方式采用2进制加法产生,如取3个变量,从000,加1为001,再加1进位010...以此类推。
然后各变量依次取其中的值,进行计算。
最后依照控制的格式打印出真值表。
1.2.2设计表示
(1)数据类型定义:
线性堆栈1的数据结构定义:
typedefstruct
{
DataTypestack[MaxStackSize];
inttop;
}SeqStack;
线性堆栈2的数据结构定义:
typedefstructNode
{
DataTypedata[1000];
structNode*leftChild;
structNode*rightChild;
structNode*parent;
}BiTreeNode;
typedefstruct
{
BiTreeNode*address[1000];
inttop;
}SeqStack2;
(2)函数关系调用图
(3)函数接口规格声明:
intInputPpt(void)
voidCheckBrackets(charBra[])
BiTreeNode*Tree(charb[][100],intn)
intChange(chara[],charb[][100],SeqStack1*S,intn)
intPostOrder(BiTreeNode*tre,intc[],charb[][100],intn)
voidAddCheck()
voidprint_table(intchoice,BiTreeNode*P,intn)
1.2.3详细设计
(1)定义所需要的结构体,结构体设计中已经做出陈述。
(2)中缀表达式变成后缀表达式:
写入与转化
其中写入部分用伪代码描述为:
定义字符数组,大小为500
for(所有字符数组)
{
用0赋值
}
字符串方式输入命题
转化部分用伪代码描述为:
循环扫描中缀表达式
{
if(扫描到逻辑变元)
保存到后缀表达式中;
Else
{
StackTop(myStack,&x);
res=Precede(x,扫描到的运算符);
if(res=='>')x退栈;
if(res=='<')扫描到的运算符进栈;
if(res=='='&&x=='(')退栈
}
}
(3)构造二叉树,其思想就是将逻辑变元作为叶子节点,运算符作为根节点,用堆栈实现,用伪代码简单描述为:
循环扫描后缀表达式
{
if(扫描到逻辑变元')
讲逻辑变元进栈;
else
{
if('扫描到双目运算符){
StackPop1(myTree,&x1);
StackPop1(myTree,&x2);
将x1,x2作为它的左右子树;
StackPush1(myTree,p);}
else
{
StackPop1(myTree,&x1);
x1对应左子树,又子树为空;
StackPush1(myTree,p);
}
}
StackPop1(myTree,&x1);
root->leftChild=x1;}
(4)打印真值表
计算真值表的行数
输出真值表的第一行(由各逻辑变元名称构成)
for(所有行依次循环)
{
for(所有逻辑变元)
{
将二进制产生的,与逻辑变元数相同的位数的二进制数,依次赋值给c[]
}
计算并输出真值
二进制数+1
进位判断检测
}
1.3.调试分析
需要解决的几个难点:
(1)中缀表达式中的逻辑变元非单个字符而是字符串。
经过与老师的讨论后,采用二维数组的方式进行存储。
结果证明实现起来确实相对简单些。
经过一步步调试,怎样去找到一个完整的字符串逻辑变元,找到之后又怎样存放等等一系列问题也跟着迎刃而解。
(2)根据后缀表达式构造二叉树并计算真值。
在构造树时要用到堆栈,但是前面用到的堆栈的数据类型和此时用到的又有很大的差别,此时想到做一个树节点的堆栈,设计出在构造树的时候合适的算法。
(3)真值表的打印。
对这一模块的实现最容易想到的就是有几个逻辑变元就进行几次循环,每一重循环对应着一个变量的取值。
但是经过分析显然是不可行的。
开始并不知道会有多少个变元。
然后想到的方法是用一重循环去实现,每重循环都会有一个值,将这个值反复进行对2取余和对2进行整除,将取余后的值赋给相应的变元。
这样总共循环2的变元素的n次方次即可。
但是这种方法的效率比较低,反复进行乘除运算。
最后采用了二进制数的产生方式,如三个逻辑变量时起始时为000,加1为001,加1进位产生010,...,加1为111。
经过实际验证这种方法效率是比较高的,所需时间要短很多,不足的是当位数非常多的时候因为要对每一位判断进位,循环次数略多,但整体仍优于第一种方案。
算法的时空复杂度分析:
本次程序的完成除书上的那些头文件外,其它的算法均由自己编写。
对于所编写的函数,比如change()函数,Tree()函数,PrintTable()函数…他们的时间复杂度均为o(n)。
而对于change()函数其空间复杂度为o
(1)。
1.4用户手册
运行环境:
VS2010
运行过程:
程序经编译、连接、运行后只需要用户输入一个中缀表达式,即用户想要进行的逻辑运算的表达式。
这里有几点需要要提醒用户:
1.在输入表达式时‘&’表示逻辑与、‘|’号表示逻辑或、‘~’表示逻辑非。
在输入中缀表达式后程序将自动执行并输出结构,用户不需其他操作;
2.在本程序的书写中命题最大的长度为500,用户在输入表达式时需要注意,不可超过上限。
3.在计算完用户输入的命题的真值后,用户可根据需要通过1/0选择(1-YES,0-NO)是否查看命题的真值表,若不需查看,程序将自动关闭。
1.5测试数据及测试结果
(1)测试数据:
ab|~b&(def|~g),)dfa|d
(2)测试目的:
数据1测试输入正确表达式时是否恩能够得出正确的真值并输出真值表。
数据2测试表达式中输入错误(括号不匹配时是否能正确识别)。
测试结果如图:
(正确输入时)
(3)正确输出:
数据1对应逻辑变元输入真值为1、0、1、0时,得出结果为1。
并输出如下的真值表:
ab
b
def
g
结果真值
0
0
0
0
1
0
0
0
1
1
0
0
1
0
1
0
0
1
1
1
0
1
0
0
0
0
1
0
1
0
0
1
1
0
0
0
1
1
1
0
1
0
0
0
1
1
0
0
1
1
1
0
1
0
1
1
0
1
1
1
1
1
0
0
1
1
1
0
1
1
1
1
1
0
1
1
1
1
1
1
数据2应显示:
输入有误,并提示重新输入。
(4)实际输出:
数据1输入后:
数据2输入后:
(5)当前状态
预期结果与实际结果一致,程序正确运行。
2.1问题引出与需求分析
问题引出:
这是一个古典问题。
假设有n个修道士和n个野人准备渡河,但只有一条能容纳c人的小船,为了防止野人侵犯修道士,要求无论在何处,修道士的个数不得少于野人的人数(除非修道士个数为0)。
如果两种人都会划船,试设计一个算法,确定他们能否渡过河去,若能,则给出一个小船来回次数最少的最佳方案。
要求:
(1)用一个三元组(x1,x2,x3)表示渡河过程中各个状态。
其中,x1表示起始岸上修道士个数,x2表示起始岸上野人个数,x3表示小船位置(0——在目的岸,1——在起始岸)。
例如(2,1,1)表示起始岸上有两个修道士,一个野人,小船在起始岸一边。
(2)采用邻接表做为存储结构,将各种状态之间的迁移图保存下来。
(3)采用广度搜索法,得到首先搜索到的边数最少的一条通路。
(4)输出数据
若问题有解(能渡过河去),则输出一个最佳方案。
用三元组表示渡河过程中的状态,并用箭头指出这些状态之间的迁移:
目的状态←…中间状态←…初始状态。
输出时,在状态迁移旁,说明渡河的动作和当前两岸的状态。
若问题无解,则给出“渡河失败”的信息。
(5)求出所有的解。
需求分析:
由题目可知,所设计程序需满足以下需求:
输入方面:
接受用户输入修道士与野人的人数以及小船最多可容纳人数。
功能方面:
计算出所有的渡河方案以及最佳方案(来回次数最少)。
输出方面:
按照给定格式输出所有的渡河方案以及至少一种最佳方案。
2.2程序设计
2.2.1设计思想
(1)数据与操作的特性
由题目可知数据的结构应设计为图结构。
在图结构的基础函数上,添加安全性检查模块与输出显示模块即可。
(2)数据结构的设计:
图节点:
用以存储过去与当前时刻的状态与生成未来的状态。
邻接表:
用以存储一个完整的渡河过程。
即一张完整的图。
(4)算法设计:
首先用一个三元组(x1,x2,x3)表示渡河过程中各个状态。
x1表示起始岸上修道士的个数,x2为起始岸上野人个数,x3表示小船位置(1--在起始岸上,0--在目的岸上)。
然后根据给出的小船上的位置数量,生成小船上的安全状态,即在船上的时候修道士的人数也要比野人的数量要多(除非修道士人数为0)。
渡船优先规则:
起始岸一次运走的人越多越好(即起始岸运多人优先),同时野人优先运走;目的岸一次运走的人越少越好(即目的岸运少人优先),同时修道士优先运走;
接着对安全性进行检查。
若两岸的修道士安全,则将此结点存储到邻接表中。
最后通过广度搜索,得到首先搜索到的边数最少的一条通路。
并输出最佳方案与所有方案。
2.2.2设计表示
(1)数据类型定义:
typedefstruct
{
intx1;
intx2;
intx3;
}DataType;//三元组存储状态
typedefstructNode//以下部分为邻接表结构的定义
{
intdest;
structNode*next;
}Edge;
typedefstruct
{
DataTypedata;
intsource;
intpre;
Edge*adj;
}AdjLHeight;
typedefstruct
{
AdjLHeighta[MaxVertices];
intnumOfVerts;
intnumOfEdges;
}AdjLGraph;
(2)关系调用图
(3)函数接口规格声明:
intcheck(DataTypex)
intboat(DataTypex)
intprint(AdjLGraph*p,intg)
voidwork(AdjLGraph*p)
2.2.3详细设计
(1)定义所需要的结构体,结构体设计中已经做出陈述。
(2)生成小船上载人情况
inti=0,a,b,t=0;
if(x.x3)//从初始岸到目的岸
{
a=0;
b=c-a;
while(a+b>=1)
{
t++;
while(b>=0)
{
a1[i].x1=a;
a1[i].x2=b;
i++;
a++;
b--;
}
a=0;
b=c-a-t;
}
}
else//目的岸驶回初始岸
{
a=1;b=0;t=0;
while(a+b<=c)
{
t++;
while(a>=0)
{
a1[i].x1=a*(-1);
a1[i].x2=b*(-1);
i++;
a--;
b++;
}
a=a1[0].x1*(-1)+t;
b=0;
}
}
returni;
}
(3)安全性检查
intcheck(DataTypex)
{
If(两岸修道士人数>=野人人数)返回1;
else返回0;
}
(4)生成渡河情况
DataTypetem;
inti,flag1,g=0,j,count=0,k=0,t;
while(p->a[k].data.x3!
=-1)
{
j=boat(p->a[k].data);//生成在小船上的具体方案
for(i=0;i { 将值赋给tem各参数; if(安全性检查) { 标志位置1; t=k; while(t! =-1) { if(tem.x1各参数等于a[t].data.x3) { 标志位置0; 跳出循环; } t--; } If(标志位为1时) { g++; p->a[g].pre=k; 插入结点(p,g,tem); 插入边(p,k,g); if(tem各参数为0.即当前状态成功渡河时) { count++; print(p,g);//输出方案 } } } } k++; } if(count==0) 渡河失败 else 输出几种渡河方式 (5)打印输出所有情况与最佳方案 DataTypeb[1000]; inti=0; while(g! =-1) { b[i++]=p->a[g].data; g=p->a[g].pre; } while((--i)>-1) { printf("(%d%d%d)",b[i].x1,b[i].x2,b[i].x3); if(! (b[i].x1==0&&b[i].x2==0&&b[i].x3==0)) { if(b[i].x3==1)//以一定格式输出过去的状态 printf("→ú(%d%d)→ú(%d%d0)\n",b[i].x1-b[i-1].x1,b[i].x2-b[i-1].x2,b[i-1].x1,b[i-1].x2); else//以一定格式输出回来的状态 printf("←? (%d%d)←? (%d%d1)\n",(b[i].x1-b[i-1].x1)*(-1),(b[i].x2-b[i-1].x2)*(-1),b[i-1].x1,b[i-1].x2); } else printf("\n"); } printf("成功渡河! \n"); return1; 2.3.调试分析 需要解决的几个难点: (1)采用何种数据结构。 开始的时候想采用树结构进行设计,但是发现树结构并不适合,可能会有环出现,这时便不是树结构了,最终确定采用图结构实现,并调试成功。 (2)生成小船上的情况。 刚刚设计出来的时候,发现程序一定会进入一个死循环,初始状态下运送过去的人,在1次或者n次运送的过程中产生了循环。 例如坐船过去2个修道士,回来的时候仍然是两个修道士坐船回来。 后采用的是设立一个标志位flag进行判断,避免出现“白跑一趟”的情况。 (3)如何找到最短路径。 通过设立一个变量计量最终生成的方案中往返的次数。 再输出所有的方案后,取得最小的i值并将次数符合i值的情况输出。 2.4用户手册 运行环境: VS2010 运行过程: 程序经编译、连接、运行后只需要用户输入修道士与野人的人数,小船最多可容纳人数后,即可运行,之后无需任何操作。 但需要注意的是当n与c的值比较大的时候,生成的情况可能会非常多,并不是程序问题,用户只需要耐心等待结果全部输出完毕。 2.5测试数据及测试结果 (1)测试数据: n=3,c=2;n=5,c=3;n=4,c=2。 (2)测试目的: 数据1测试是否能输出最佳与所有方案;数据2测试是否可以输出所有方案;数据3测试对于无解的数据是否能正确显示。 (3)正确输出: 数据1应显示4种方案,数据2应显示361种方案,数据3应显示无渡河方案。 (4)实际输出: 数据1输入后: 数据2输入后: (因方法太多截图不便故不一一截图) 数据3输入后: (5)当前状态 预期结果与实际结果一致,程序正确运行。 附录1 致谢 从起初的完全不明白到最后程序的设计与正确运行,离不开郭燕老师与王鑫学姐的耐心指导。 郭燕老师的细心回答,以及王鑫学姐在每次实习中所提供的诸多意见,极大的帮助完成了本次实习。 同时还要感谢班级的同学,彼此之间的讨论大大开阔了我的思维,也因此得到了非常棒的解决方案,在此表示由衷感谢! 参考文献: 《数据结构》,电子工业出版社,朱战立编著 《编写可读代码的艺术》,机械工业出版社,Tr
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 计算 命题演算 公式 真值 数据结构 课程设计