日前,中科大軟件學院二年級研究生 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 你真是人如其名——渣男,人品渣,技術也渣。
就這樣一直忍到次日中午,實在憋不住了,沒有手紙找不到廁所也顧不得了,只好~~~~噴吧!
好臭!
終於完全地應驗了魯迅老先生的那句話:「始於做僞,終於無恥!」。