日前,中科大软件学院二年级研究生 HCOONa 发表奇文 驳 GarbageMan 的《一个超复杂的简介递归》——对延迟计算的实验和思考。据他自称发此“文章”是为了“打”我“脸”,下面就来看看他是如何“打脸”的。html
在C语言初学者代码中的常见错误与瑕疵(5)中,我剖析了一位初学者针对以下问题所写的代码,c++
在世博园某信息通讯馆中,游客可利用手机等终端参与互动小游戏,与虚拟人物Kr. Kong 进行猜数比赛。程序员
当屏幕出现一个整数X时,若你能比Kr. Kong更快的发出最接近它的素数答案,你将会得到一个意想不到的礼物。算法
例如:当屏幕出现22时,你的回答应是23;当屏幕出现8时,你的回答应是7;编程
若X自己是素数,则回答X;若最接近X的素数有两个时,则回答大于它的素数。数组
指出其中的一个主要问题是从X分别向先后两个方向查找素数的解决方案。由于其中的一个素数可能离X很近,另外一个则离X很远。在这种状况下,去查找较远的素数是在作无用功。函数
我提出的改进办法是在X先后依照下面次序post
X X+1 X-1 X+2 X-2 X+3……性能
交替寻找素数。这种方案在最坏状况下(X两边素数到X距离相等),与原方案计算量相同,但对于距离不等的状况则不会作无用功。测试
判断素数的方法为试除法,即用[2~X1/2]之间中全部的整数试除X,如余数都不为0则X为素数。
当X较大时,因为区间内素数个数远低于整数个数,因此,更优的解决方案是用[2~X1/2]之间中全部的素数试除。在一个超复杂的间接递归——C语言初学者代码中的常见错误与瑕疵(6)中我实现了这种方案。
因为这种方案过于精打细算,因此代码比较复杂。
对此,HCOONa发表评论:
这道题不是应该预先计算素数表,而后用二分查找吗?怎么会搞得这么复杂?
我考虑过你说的这种方案但我认为计算出直到大于等于X的素数表得不偿失举例来讲 X=100要计算出直到101的素数表计算过程当中大约100个数都须要试除2 3 5 7而文中的方案只须要对100 101这两个数分别用2 3 5 7 和 2 3 5 7 11试除一下就能够了这个差异太大了若是X=1000000000这个量级二者之间确实是天壤之别计算素数表还有一个问题就是事先不清楚到底有多少素数
所以很难估计数组大小
另外二分查找应该是用不上的
这就看你这个问题须要回答多少次了,
我只考虑回答一次的情形
通常说来,10^9 之内的素数并很少,用筛法的话很快就能获得一个素数表,接下来使用二分查找一下就能找到给定的数是否是素数,以及距它最近的两个素数是什么。
而你的方案则有可能须要更多的计算,好比说最近的素数差的比较多的时候,尤为是在 10^9 这个数量级,素数会变得比较稀疏,重复的计算可能会花费更多的时间。
若是你以为维持一个素数表所须要的空间太大了,难以接受,那么我建议使用 Rabin-Miller 素数测试。
据我所知
Rabin-Miller 素数测试只是一个可靠性很高的近似测试
不能保证必定测试出素数
就本问题的要求来讲
我认为不适合应用这种方法
或者
建议你抽空也写一个
或者写两个
比较一下
如何?
以前我假设这个问题是须要回答屡次的,即给定若干个数字,分别回答这个问题,也是ACM中常见的情形。
原问题确实是须要回答屡次
但我没按照那个写
由于我以为那有些过于偷奸耍滑了
因此只按照回答一次写的
但即便是回答一次
也须要测试这个数周围几个数是不是素数
因此才有了这种方案
素数的个数和分布状况见,其中指出,10^9 之内的质数有 5761455 个,所占比率是 5.761455%
另一个已知的结论是,素数会随着数的增大而变得愈来愈稀疏。
但个人方案只须要3000个左右的素数表
3000/5761455 = 0.052%
你的方法只是惰性计算质数表,本质上并无什么提升,并且是用的比较原始的质数表生成方式,生成的效率比筛法要慢,粗劣的分析见这里。
你把用筛法生成质数表的成本给忽略了那可不是能够忽略不计的一项并且这不干稀疏什么事情理由我前面已经说了
除此以外,你是对给定数值附近的每个数“由近及远”的分别进行判断,所以在质数已经很是稀疏的状况下,不免会计算不少数据。而若是已经有了质数表,则能够直接找到相邻的两个质数,不管它们距离给定的数值有多远。
你把用筛法生成质数表的成本给忽略了那可不是能够忽略不计的一项并且这不干稀疏什么事情理由我前面已经说了
毫无疑问,在生成一样大小的素数表的时候,筛法比这种直接的素数判断并生成的算法要快。我给的连接不知道为何被吞掉了,这里 http://plussai.iteye.com/blog/1070387 有一个粗略的分析。即使是你的方法,把惰性生成素数表改为提早生成素数表,也是一个可以简化问题,提升效率的方法。
下面再说只问一次的状况,若是只问一次的妥妥的用 Rabin-Miller 素数测试。Rabin-Miller 素数测试能够以任意给定的精确度来判断一个数是否是质数,虽然本质上是非肯定性的算法,可是若是你要求的精确度很是高,就能够近似认为给出的结果是正确的,好比说这个精确度比 10^-11 还高。
“就能够近似认为”你弄错了场合连问题的基本需求都无论不顾了这个问题根本没有说要求一个“近似”素数
具体要比较性能的话比较麻烦,个人想法是在给定的区间上均匀的生成 10000 个随机数,而后分别问你和个人两个程序,而后判断总的用时,我相信个人程序会比你的快 :P可是鉴于你说你考虑的状况是只问一次的状况,我就没作这个不公平的测试。若是使用 Rabin-Miller 素数测试的话,省却了打表的时间,对于 10^9 这个数量级,理论上会比你的算法快出好几条街。
因此其实我以为你这个方案很差,增长了没必要要的复杂性,尚未更好的解决问题。思路毫无疑问是正确的,可是延迟计算而不是预先计算这个策略选择的不太好,由此带来的问题就是代码很是复杂。再加上你标题写的是给初学者,我以为就更应该注意一下,有点把问题搞复杂了。
Talk is cheap , show me your code.
Rabin-Miller 素数测试能够给出一个很是精确地答案,该答案的精度高于浮点型数据的精度时我看不出来什么理由不接受这个“近似”答案。更况且,整形计算也是有很是微小的概率发生错误的,这是因为CPU制程越来先进,而由量子力学理论所预言的。
愈来愈扯了
直接回答"很是精确地答案"是否是"精确"
连浮点数都上来
这个问题和浮点数半毛钱关系都没有
“整形计算也是有很是微小的概率发生错误的”
天哪!
大家科大就学这个?
老师怎么教的?
回去跟大家老师讲讲
看大家老师脸红不脸红?
那你是对我说的第一段话不信呢仍是第二段话不信呢,仍是都不信?我要是写了程序可就要发文章打你脸了。
若是楼主的数学和物理没有学好,不如再去读读书补习一下,没有必要泼脏水。
然而事实说了什么?
首先是自说自话,伪造前提条件:
该题有两种模式,一种是只考虑一次问答的状况,另外一种是考虑连续问答。
但又不得不用虚伪的态度认可:(MD,彷佛有点露怯啊。露怯还怎么将不要脸进行到底)
首先确定一下,GarbageMan 的思路是正确的:在给定的 n 附近由近及远的进行素数断定,直到找到一个素数为止。
尼玛!昨天不是说什么“其实我以为你这个方案很差,增长了没必要要的复杂性,尚未更好的解决问题”吗?不是大言不惭什么“可是延迟计算而不是预先计算这个策略选择的不太好,由此带来的问题就是代码很是复杂。”吗 ?怎么开始本身打本身脸啦?
而后在伪造的前提下开始圆谎:
若是考虑屡次问答的状况,考虑平均状况,延迟计算最终也会计算出大部分的素数表。与其动态的不断补充素数表,还不如用更有效率的方法直接计算出整张素数表,因此对于这种状况,GarbageMan 的优化是毫无心义的。
个人算法原本就是为一次问答设计的,对此他早就一清二楚。因此所谓“若是考虑屡次问答的状况,考虑平均状况”“因此对于这种状况,GarbageMan 的优化是毫无心义的”难道不是屁话吗?
Rabin-Miller 素数测试,是一种素数断定法则,利用随机化算法判断一个数是合数仍是可能是素数。
听起来像是一个不靠谱的算法,可是该算法能够以任意给定的准确率给出可能正 确的答案。当这个准确率足够大时,咱们能够近似的认为这个算法给出的答案正确。(这一点遭到了 GarbageMan 的疯狂嘲讽,我猜他不知道为何无穷大的倒数等于 0)
弱智想赢得辩论,就只能把对手说成弱智。
有关 Rabin-Miller 素数测试是否真的比经过试除法检验素数快,咱们暂且将这一问题留待实验结果说明
嗯,分歧在于Rabin-Miller近似法是否适用于精确问题,但却用Rabin-Miller比试除法检验素数快来论证。尼玛!你这什么逻辑啊?猪都不会这么想!
GarbageMan 自鸣得意的一个优化就是延迟计算素数表。在我看来,这是一个彻底没有必要的,而且极大地增长了代码复杂度的优化。在屡次问答的状况下
看到了吗。偷来的锣鼓敲不得。偷换的前提,只好鬼鬼祟祟地猥琐地藏在后面扭扭捏捏小声地说。
个人建议是,使用初始化方法预先计算从 [2, log MAX_N] 之间的素数,而后再用 GarbageMan 的
get_nearest
方法进行计算。在问答量比较大时,这种方法甚至会比 GarbageMan 优化过的算法还要快。
这不可是卑鄙的掩饰,同时也是自供。等到您后面知道了“在问答量比较大时”里的“比较大”究竟是多大时,您就明白了。
素数筛法已经十分先进了,甚至有亚线性时间复杂度的算法,所以,在实现生成素数表的状况下,没有理由不选择素数筛法。
用好听的“亚线性时间复杂度的算法”来蒙骗读者,由于通常人都会以为线性时间复杂度很好。但对个人算法只有二分之一次方时间复杂度绝口不提。什么叫欺骗,这就是欺骗。选择性地告诉你一部分事实,同时选择性地故意隐瞒一部分事实。
我建议在计算素数表的时候,直接计算到比 n 的上限还要大的一个素数以后再中止生成素数表,而后经过二分查找,直接肯定给定的 n 在素数表中的位置,从而找到距离 n 最近的素数。
不解释。这个二分法的愚蠢程度和陈良乔对链表使用二分法查找有得一拼。
算法的思路至此介绍完毕,下面将设计实验来验证个人想法是否正确。
嗯。伪造前提的铺垫已经完成,下面能够进入角色了。我不知道他在写这段话的时候有没有脸红。
实验分别比较在单次问答模式下,GarbageMan 所用算法和 Rabin-Miller 算法的用时;在屡次问答模式下,GarbageMan 所用算法和其余算法的用时。其中,在屡次问答模式下,须要对 GarbageMan 所用的算法进行一些微调,以保证测试的公平性和正确性:
尼玛。把我专为单次问答设计的算法进行肢解篡改,以便绑架到他本身亲口认可的“不公平的测试”中去,但还要伪装“公平”。这等的虚伪和伪善须要几吨包天狗胆和寡廉鲜耻啊?!
编译选项
/STACK:10485760,1048576 /O2
轻描淡写地提了一下编译选项。这个编译选项不少小朋友不知道,由于几乎没人用过。这个选项是由于他本身的筛法计算109内的素数表须要巨大的内存开销。素数表对他本身的方案是有利因素,但对于个人方案来讲,因为根本不须要这么大的内存,因此不但没好处,反而有坏处。由于访问大块内存是有时间开销的。我相信,微软若是知道这个编译选项会被无耻之徒这样利用,必定会对设立了这个选项然后悔不迭。微软,你欠我一个道歉。
给出的测试代码依赖于 C++11 标准中提供的随机数生成函数,所以只能在 Visual Studio 2013 和较新版本的 g++,clang++ 上不须要修改的经过编译。使用较低版本的编译器编译时,能够结合 boost 库提供的支持,进行有限的修改后经过编译。 若是使用 g++ 或者 clang++ 进行编译的话,请使用参数
-O2 -std=c++11
。
你们看到了吗?结果依赖于随机数生成函数,不少编译器的随机数生成函数都有毛病,这个毛病就是不那么“随机”。这里他留了一个伏笔,之后有人发现做弊,他能够赖到C++11 标准中提供的随机数生成函数上去。另外一个做用就是是暗补他前面说过的“均匀的生成”“随机数”的谎言。除此以外,把个人C代码看成C++代码编译,“结合 boost 库”,这都是在不动声色地做弊。这就是他口口声声的“公平”。
实验结果
当设定 n 的范围在 [1, 10^6 - 10],且生成 50,000 个随机数时,屡次问答模式下的测试结果以下:
Elapsed : 3900005ms // GarbageMan 的方法 Elapsed : 500001ms // 积极计算的方法 Elapsed : 2890109ms // Rabin-Miller 算法 Elapsed : 270015ms // 素数筛法和二分查找再测一次:
Elapsed : 3920017ms // GarbageMan 的方法 Elapsed : 500001ms // 积极计算的方法 Elapsed : 2800004ms // Rabin-Miller 算法 Elapsed : 300001ms // 素数筛法和二分查找由大数定律可知,GarbageMan 的算法在平均状况下运行效率较低,而且低于积极计算的方法,可见不只白优化了,还起到了负面效果。
因为实验结果显示,在屡次问答模式下 GarbageMan 的算法运行效率仍然低于为单次问答模式所设计的 Rabin-Miller 算法,所以没有必要再进行单次问答模式的实验。
明眼人一下就能看出这段的毛病,但许多见识很少的小朋友可能看不出来。注意到“在屡次问答模式下 GarbageMan 的算法运行效率仍然低于为单次问答模式所设计的 Rabin-Miller 算法”这句话的玄机了吗?在“屡次问答模式下”,不管如何个人算法都是吃亏的,由于个人算法原本就是为单次问答设计的。“屡次问答模式下”会把个人算法的优点完全铲除。可是Rabin-Miller 算法不受单次模式或屡次模式的影响。他刻意造了这个生涩难懂的句子,以回避公布单次问答模式下个人算和Rabin-Miller 算法的结果。而后用刻意营造出的结果,以及八竿子打不着的不相关的用来蒙人的“大数定律”,以便装腔做势地宣布他精心炮制的谎话。
前面他曾信誓旦旦煞有介事地表示,他要“分别比较在单次问答模式下”,但要宣布结果时,却硬生生地把这话本身吃了下去。无耻至斯,叹为观止!
注意到这段开头的那个“ 50,000”了吗?还记得前面提到过的他在回复中说的那个“ 10000 个随机数,而后分别问你和个人两个程序,而后判断总的用时,我相信个人程序会比你的快 ”“会比你的算法快出好几条街”吗?为何他要把“10000”个随机数改为“50,000”呢?缘由很简单,生成一个109以内的素数表须要不少时间成本,为了“证实”他的方法快而又想用“事实”来编造谎话,这个巨大的时间成本就须要被分摊。这就是缘由,没有之一。
后面的结论就不引述了,谁愿意相信谁相信!这个世界上一贯不乏乐于相信谎话的傻B!
回过头来看这篇奇文标题——驳 GarbageMan 的《一个超复杂的简介递归》——对延迟计算的实验和思考。咱们不由要问,他到底驳了什么了?
我认为Rabin-Miller 算法不适合精确计算,他驳了吗?他吭哧瘪肚地用移花接木的手法刻意地营造了一个实验,我就算是退一万步,抛开他的种种龌蹉的做弊做假不提,就算假定他的结论是对的,那也只说明了Rabin-Miller 算法比个人快而已,那是对“Rabin-Miller 算法不适合精确计算”这个观点的驳斥吗?
从这里不难发现,中科大的这个HCOONa的科学素质极低,基本上是一个逻辑文盲。他的奇文中这种低素质不自觉的流露处处都是,好比标题中居然明晃晃地出现了两个错别大字“简介”,经验告诉我,优秀的程序员对错别字很是敏感,通常不会写错别字。再好比素数的定义:
素数,又称质数,指除了 1 和该整数自身外,没法被其余正整数整除的正整数。
尽管他装模做样的在参考资料部分引用了
但维基百科中对素数的定义全然不是这么回事。按他的定义,1也是素数,这和谭浩强是同一个水平。一个认真的小学生都能发现他的这个错误。因此科学素质其实跟学历没有必然关系,有不少高学历者的科学素质并不高,有的甚至是自觉或不自觉的骗子。好比,清华大学的几朵奇葩教授,……之后有机会再跟你们聊。
我认为计算109量级的素数表,哪怕是用简单的筛法,也必然要付出高昂的时间成本和内存成本做为代价。他驳了吗?他只是用精心构建的实验去掩盖而已,并且掩盖的手法实在不咋地,以致于到处露马脚。即使抛开这我的极其龌蹉低下的技术品德不谈,做为一个骗子,他也是一点技术含量都没有。被扒光内裤以后,你看到的无非就是一个无知无耻之徒在裸奔而已。
曾经有人说过,世界上有三种谎话:谎话,弥天大谎,统计局公布的数字。如今咱们有幸见识了第四种,就是HCOONa创造的这种用似是而非的理论,精心炮制的实验,无耻的伪公平架势,选择性的事实构筑的谎话。
为何他要炮制这些谎话欺骗你们,缘由只可能有一个,前一天晚上在个人博文评论中放了半宿不通的狗屁,致使肚子里憋了两大泡稀屎:
在此也劝告 GarbageMan,没什么本事就别在那叫嚣了,还写什么《C语言初学者代码中的常见错误与瑕疵》,误人子弟。
GarbageMan 屡次对我进行人身攻击,而且侮辱个人母校。在此我要说一句,GarbageMan 你真是人如其名——渣男,人品渣,技术也渣。
就这样一直忍到次日中午,实在憋不住了,没有手纸找不到厕所也顾不得了,只好~~~~喷吧!
好臭!
终于完全地应验了鲁迅老先生的那句话:“始于做伪,终于无耻!”。