雁过无痕

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::

N个数计算24

问题:

    N113之间的自然数,找出所有能通过加减乘除计算(每个数有且只能用一次)得到24的组合

 

计算24点常用的算法有:① 任取两个数,计算后,将结果放回去,再从剩下的数中任取两个,如此反复直到只剩下一个数;② 先构建前缀/后缀表达式,再计算该表达式;③ 用集合保存中间结果,集合间两两进行合并计算得到新集合(或者对给定的一个集合,对其所有的子集合进行合并计算)。

本文采用第一种方法。定义六种操作符:ADDSUBMULDIVRSUBRDIV,分别对应加、减、乘、除、反减和反除(反减/除:先交换两个数,再减/除)。

显然,取两个数计算时,六种计算结果可能有重复,可以对这6个结果进行去重(实际上,只要分别对加减(ADDSUBRSUB)和乘除(MULDIVRDIV)的3个计算结果进行去重判断就可以了,效率和对6个结果去重相差不大)。

另外一种剪枝方法:保存每个数上次计算时所用的操作符(初始值为空)。所取的两个数:

若某个数的上次操作符为减(SUBRSUB),那么不进行加减(ADDSUBRSUB)计算。

若某个数的上次操作符为除(DIVRDIV),那么不进行乘除(MULDIVRDIV)计算。

比如:取的两个数为 a-b c(c的上次操作符任意),如果进行加减计算的话,

a-b+c  c+a-b重复,

c-(a-b)c+b-a重复

a-b-c  c+b RSUB a重复

也就是说,上次操作符为减的,进行加减计算时,总可以转为某个上次操作符为加的表达式,因而可以不计算。同样,上次操作符为除的,不进行乘除计算。

当然,还可以考虑记录位置进行剪枝,这样避免a+b+ca+c+b都进行计算。但要注意的是:在给定的组合无解时,越多的剪枝方法,极有可能提高搜索效率,但在给定的组合有解时,很可能反而降低搜索效率

另外,对有解时输出的表达式的处理对程序的性能影响很大。如果每次计算都保存对应的表达式,会进行大量的字符串操作,严重影响性能。实际上,每次计算只要保存取出的两个数的位置和所进行计算的操作符就够了,最终需要输出表达式时,只要模拟一下递归函数调用过程,进行相应的字符串操作。

下面是程序(gcc 4.5,优化参数-O2)在T4200/2G单核下运行的结果,计算10个数,646646个组合只用了不到13分钟。


 
N113之间的数的所有组合,计算24

N

4

5

6

7

8

9

10

时间(ms)

78

610

4157

19593

160922

173766

764328

有解组合数

1362

6104

18554

50386

125969

293930

646646

总组合

1820

6188

18564

50388

125970

293930

646646

总表达式

1124776

15656645

105278906

492587616

3760744504

4535030813

19912345238

表达式A

457549

11864184

88679768

409129896

1173803224

4535030813

19912345238

表达式B

667227

3792461

16599138

83457720

2586941280

0

0

总函数调用

1482939

20950792

141892513

669790534

5258218577

6112529945

26948662625

函数调用A

603206

15849306

119160441

551913059

1576965280

6112529945

26948662625

函数调用B

879733

5101486

22732072

117877475

3681253297

0

0

其中:表达式A/B、函数调用A/B为:给定的n个数有/无解时,所处理的表达式总数和函数调用总次数。

 

    N=8N=9,两个所用时间只相差13秒,这是由于N8时,有一个组合(81)无解,判断这个组合无解需110多秒,而计算剩下的125969个组合还不要50秒。


程序代码:

24.cpp
posted on 2010-08-15 23:20 flyinghearts 阅读(2297) 评论(0)  编辑 收藏 引用 所属分类: 算法编程之美C++

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理