雁过无痕

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

《编程之美》读书笔记22:    1.16  24点游戏(补充)

 

给定n个数,能否只通过加减乘除计算得到24

书上给出的最后一种解法,通过使用集合记录中间结果来减少冗余计算。本以为,程序会占用大量的内存,用一个极端的例子(13, 773, 28, 98, 731, 1357,973572467个数)测试了一下实现的程序,发现程序竟然占用了1G以上的内存(无论集合的实现采用STL中的set还是unordered_set),但后来,取7个均在113之间的数,再重新测试了下发现,程序所占用的内存比想像的小的多,也就几兆。

对数值都在113之间的n个数的所有组合进行判断。在n等于4时,实现的程序约过1秒就给出了结果,而n等于5时,程序要运行58秒,效率实在差,可以通过这几方面优化:

① 保存每个集合的子集合的编号:

对给定的n,共有12^n – 1个集合,每个集合的子集合编号是固定的,但原程序每计算一个n个数的组合,都要对这些子集合编号计算一遍,可以保存每个集合的子集合编号,减少大量的重复计算。

② 改进计算子集合编号的算法:

原程序的算法的时间复杂度是O(4^n),在n较大时,相当慢。

③ 对最后一个集合是否含有24的判断:

原程序要遍历该集合所有的元素,效率比较差,可以考虑,在将两个集合合并到该集合时,只对取出的两个元素的计算结果是否为24进行判断,这样不仅省去最后的遍历,而且不用将结果插入到最后的那个集合中,省去了大量操作。

 

采用①和③两种改进方法后,程序运行时间由原来的58秒缩短到了14秒,但这还不能让人满意。对2,3,5,6,85个数,如果用书上的第一种方法,可以只调用4次函数就可以得到结果:2+3+5+6+8=24,但用集合的方法来处理,却要对所有的集合进行子集合合并后才能给出结果,这造成了效率极差,可以这样改进该算法:

初始有n个集合:每一次任意取出2个集合,合并后,放回去,再取出任意2个集合,重复前面的操作,直到只剩下一个集合为止。

例如:初始有5个数,把这5个数分别放到5个集合,并分别编号为:124816。任意取出2个集合,假设为14,将14合并,得到编号为5(=1+4)的集合,剩下的集合为:52168,再取出2个,假设为58,合并后,得到13216,再取2个,假设为1316,合并后得到292,当剩下2个集合时,可以直接对这两个集合间的的计算结果是否为24进行判断,直接得出结果,省去不必要的合并(合并后再判断是否有元素近似等于24,程序运行时间8s多,而直接对计算结果判断,程序只要运行1s多)。

优化后的程序,只要运行1s多。但其效率还是不如书上的第一种方法的改进版,仔细想想,n越大,集合的元素也就越多,两个集合之间的合并,就越耗时间。而且采用集合保存中间结果,表面上减少了重复状态,会提高效率,但实际上,由于采用了集合,就多了很多不必要的计算,(比如,对2+3+5+6+8=24,最少只要4次计算就能得出结果,采用集合合并后,则要计算几百次(约为6^4)),再加上实现集合所采用的数据结构的开销,效率高不了。

24_set_1



24_set_2.cpp


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

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