拖啊拖,终于记得把它上传了!

 

题目

写一个能自动生成小学四则运算题目的命令行"软件",分别满足下列各种需求。下面这些需求都可以用命令行参数的形式来指定:

a.)除了整数以外,还支持真分数的运算。(例如1/6+1/8=7/24)。

b.)让程序能接受用户输入的答案,并判断对错。最后给出总对/错的数量。

c.)逐步扩展功能和可以支持的表达式类型,最后希望能支持下面类型的题目(最多10个运算符,括号的数量不限制):

      25-3*4-2/2+89=?          1/2+1/3-1/4=?         (5-4)*(3+28)=?

d.)一次可以批量出100道以上的题目,保存在文本文件中,并且保证题目不能重复,(1+2)和(2+1)是重复的题目,怎么保证题目不能重复呢,请看详细题目要求。

 ~和同学们比较一下各自程序的功能、性能、实现方法的异同等等~      So,快来和我比较比较

 

解答

  看着题目,我的第一反应是想联系MFC的对话框,每当出一题,使用者在对话框内输入答案,及时判断答案是否正确,并统计总的对/错的数量。但是我并没有去尝试,有机会还是要试试!那接下来就说说我的解答过程吧:

题目分析

@先来最基本的四则运算吧,何为简单?就是表达式和答案都是正整数,括号也可以出现。定义好了表达式,怎样用程序产生呢?

(1)参与运算的都是正整数,这个好解决,用c++里面的随机函数rand()就可以产生,限定1~10吧!

(2)四则运算符的产生,我采用的是产生-5~0之间的随机整数分别代表算符)、(、*、/、—、+。

(3)用什么来装产生的算数和算符呢?我采用malloc函数开辟了一个(4*算符个数)字节大小的空间,把空间的头指针记下来以便使用。

(4)限定运算符的个数n_OP,以n_OP作为循环的次数执行循环体。循环体内的内容为:先产生算数,再产生算符。产生0/1随机数,判断是否产生正括号’(’:若随机数为1,产生正括号后并修改相应标记,使得在标记被再次修改前不再产生正括号。由于我考虑的是括号内部只有2位数的四则运算,所以若之前产生了正括号,那就要执行先产生算数,然后产生反括号’)’,最后产生一个算符。拓展一下,记下英语单词brace 大括号,parenthesis圆括号,bracket方括号,但是之前不知道,于是我在程序中用bracket表示圆括号。

(5)表达式产生完毕,就要进行运算求值了。由于之前数据结构课程中有涉及利用栈(Stack)来进行表达式求值,所以调用之前的代码加以适当改动便可求值了:)。

    前面5步看似可以去编码实现整数的四则运算了,其实还存在大问题!

  Q1:如何保证每次都能整除?

  A1:产生1个表达式后,程序先进行求值,若值不存在小数,就让这个表达式出现在屏幕上,让使用者计算求值;否则,舍弃当前表达式,随机产生新的表达式。理论上这个想法可行,但是这个时间复杂度有点大。

  A2:一旦产生算符’/’,然后直接产生的算数就为1,不要括号的出现。哈哈,看起来没错,但除法在这儿就是一个摆设了,没有多大意义。稍微改进一下,一旦产生算符’/’,然后产生的算数就为分子最小的非1的因子(比如分子为6,那接下来产生的数就为2;分子为9,那接下来产生的数就为3),虽然还是不太好,但是有改进嘛(自我安慰)!

 

@实践中遇到值得进一步考虑的问题

 1.采用do-while的循环结构,会出现算符个数为N或是N+1的情况,这是不足之处。比如:限定运算符为4个时,会出现5个情况:

                                                          

   其实也可以改动,但是在不严格的要求下,为了改这个瑕疵而增加了代码的复杂度不太想,所以打算是否能借鉴他人的循环控制条件。

 2.在初始代码中,存在大量表达式值为负数的情况。改进方法为控制表达式中减号的个数,值为负数的表达式的确少了,但是还是不能避免出现负数的情况:

                                                                                                           

 3.在一定程度上体现了括号存在的意义:即括号里不能出现乘法或是除法。但是还是会出现几个加法和减法在一起也会有括号出现的情况(如上图第3题所示情形)。这样的括号只会影响程序的运算量,并不影响表达式的值。

 4.表达式求值部分是调用之前的代码,未做过多深究。

 

@自认为程序中的亮点

  针对整除,我的算法为:当除号出现后,我考虑的仅仅是除号之前的算数,为避免出现连除而得到小数的情况,我将随机产生算符的函数设计为相邻2个算符必须不同。

 

@实践中积累的知识

static静态变量只会被初始化1次,但它可以被不断赋值:

 

@题目升级!除了整数,程序还支持真分数的四则运算。同样需要对将要编写的代码进行详细的定义,并考虑可能出现的问题。

   如果把整数和真分数混在一起出题,出题的话应该没有问题,同整数一样限制除号’/’出现的,分母确定的情况下,将分子限制在1到(分母-1)之间的整数,但是计算时就会出问题:目前我的表达式求值的算法会把分数当成除法,并求出相应的带小数的值。因此,我决定先来探究仅有真分数的运算:加法、减法、乘法,且不包括括号。

我给出定义:

(1)何为真分数?分子和分母都是正整数,且分子小于分母。由于不是最简真分数,所以分子与分母存在公约数,比如2/5,4/8都是我的操作对象。

@算法讨论:

1.只是真分数的加/减法

(1)随机产生1~10之间的整数,依次为分子和分母;为调用之前求值的代码,随机产生-4、-3、-2分别代表算符+、—、*,(代码为-rand()%3-2。开辟2个空间C和M(为什么是C和M呢?因为C是Child,M是Mother,哈哈!),用C依次装分子和算符,M装分母。

(2)把分母进行通分,寻求M中所以数的最小公倍数,记作B。重点为写算法寻求几个数的最小公倍数,中心思想是一直寻求这几个数的公约数,直到没有公约数为止,函数GBS()。

(3)将C中下标为偶数的整数Ci都乘以最小公倍数B再除以其对应的分母得到值Di,将所得的值Di替换原来的Ci,这样就产生了一个新的且只有整数加减法的表达式,调用表达式求值函数就得到了值K。

(4)表达式最终的值就为K/B的值,为保证答案是最简分数,所以需写能求取两个数最大公约数的功能。中心思想为构造双重循环,内循环都为不断从2到分母之间找寻能使分子和分母都能被整除的数,一直循环,外循环为while循环,控制条件为分子和分母都大于1,函数GYS()。

2.涉及真分数的加/减/乘法

!重点是怎样计算真分数的乘法!我会限制乘号的数量,并保证不会出现乘号连续出现的情况。

(1)在C中找寻’*’的存在(值为-2),一旦找到,便将其前一项Ci和后一项Cj记录下来,进行分子相乘,记作Ci,把后面的有效数据依次往前移;在M中找到对应的Mi,Mj,同样进行乘法运算,记作Mi,把后面的有效数据依次往前移。于是就变得与只有真分数的加/减法一样了。

 

@实践中的问题

1.本来打算写一个能求取一个数组内所有元素的公倍数的函数,但是最后没有实现,所以求取公倍数的函数GBS()的是针对两个数的,多次调用,进而求得多个数的公倍      数。就这点来说,公倍数是一个随着参与元素的增多而不断往外扩展的数,而公约数是一个随着参与元素的增多而不断往内缩小的数,因而若想求取多个数的公约数就    不能通过反复调用参数为2个整数的函数得到。因为此次算法中只涉及求取2个数的最大公约数,所以不做进一步探求(有机会试试)。其实最大公约数和最小公倍数还是有很多相似之处的,区别就是最大公约数只需记录下能使被除数被整除的数,而最小公倍数在前者的基础上还要记录被除数被整除后的商值。

2.在实际中遇到这种情况:分母为2时,分子要满足小于2的话就只能为1,而之前定义的分子出现1的概率是极小的,时间复杂度增大。改进:先限定*M分母属于 2~10,分子的范围为:rand()%(*M-1)+1,于是分子就被限制在了1~*M-1。Perfect!

 

@展示我的运行情况(*8个算符*) [><]:

                                                                        

  其实对于分数,运算量还是挺大的。

@文件保存情况:

 

@不足之处:

  对于不重复的那个要求,我没怎么考虑,因为题数在一定数量内重复的情况还是很少出现的,比如30道题,只有1个算符(1个算符时发生重复的可能性最大,但是它都有11*4*11种组合类型)的整数四则运算时:

  没有重复的耶!再来看看真分数,20道。

  哇!清一色的加法,其实这是算法控制的:为避免出现负数,当算符太少时,算法就会禁止减号的出现。20道题,不幸的是有同样的题:上图红线圈出的题经化简后是一模一样的。但是就不化简来看是不同的题,这种题还可以考察做题者的仔细程度呢(再次自我安慰)!

 

  鉴于编程能力有限,还不能面面俱到,希望看见本文的你能指出我的不足及建议,先谢谢啦~~

 

posted on 2016-03-24 18:49  QFighting  阅读(300)  评论(3编辑  收藏  举报