嗯,自從前幾天,我寫了一篇博文《我以前用過的一個洗牌算法》http://tonyxiaohome.blog.51cto.com/925273/302220)之後,有很多網友和我討論,並提出了自己的算法。
這裡我聲明一件事啊,我非常歡迎大家就純技術問題和我討論,惡意PK,我自然沒有好臉色,不過,技術討論,還是歡迎的。
尺有所短,寸有所長,我從來沒有說過我的東西對完了,也沒說過我的東西都是聖旨,一點不能改的,無論是過去、現在還是以後,我都歡迎大家就技術問題和我展開討論,批評也可以啊,只要說得有理,我就改。
我幾乎每篇博文不是都說過嘛,“一家之言,歡迎拍磚”哈。
嗯,這位朋友呢,說實話哈,算法可以,不過,這個代碼呢,我看著有點暈:
Code:
- #include <stdio.h>
- #include <stdlib.h>
- #include <time.h>
- int d[6];
- int i,n,a,b,t;
- int c,j;
- void Wash()
- {
- srand((unsigned int)time(NULL));
- printf("shuffle 0..n-1 demo\n");
- for (n=1;n<=5;n++)
- {/* 測試1~5個元素 */
- printf("_____n=%d_____\n",n);
- j=1;
- for (c=1;c<=n;c++)
- j=j*c;/* j為n! */
- j*=n*2;
- for (c=1;c<=j;c++)
- {/* 測試n*2*n!次 */
- for (i=0;i<n;i++)
- d[i]=i;/* 填寫0~n-1 */
- for (i=n;i>0;i--)
- {/* 打亂0~n-1 */
- a=i-1;
- b=rand()%i;
- if (a!=b)
- {
- t=d[a];
- d[a]=d[b];
- d[b]=t;
- }
- }
- printf("%04d:",c);
- for (i=0;i<n;i++)
- printf("%d",d[i]);
- printf("\n");
- }
- }
- printf("shuffle 1..n demo\n");
- for (n=1;n<=5;n++)
- {/* 測試1~5個元素 */
- printf("_____n=%d_____\n",n);
- j=1;
- for (c=1;c<=n;c++)
- j=j*c;/* j為n! */
- j*=n*2;
- for (c=1;c<=j;c++)
- {/* 測試n*2*n!次 */
- for (i=1;i<=n;i++)
- d[i]=i;/* 填寫1~n */
- for (i=n;i>1;i--)
- {/* 打亂1~n */
- a=i;
- b=rand()%i+1;
- if (a!=b)
- {
- t=d[a];
- d[a]=d[b];
- d[b]=t;
- }
- }
- printf("%04d:",c);
- for (i=1;i<=n;i++) printf("%d",d[i]);
- printf("\n");
- }
- }
- }
說實話,這還是我重新排版過的,原來的版本還要暈一點。呵呵。
我當時看了15分鐘,最終確認,我暈了。只有給他寫了一封回信:
看了,能work,能看出來,在1~5之間亂序,程序本身是成功的。
不過,說實話我沒看懂原理,主要是程序太難讀了。
根據我寫程序的經驗,我給點建議吧,僅供參考:
1、我團隊的規矩,測試代碼和正式代碼的質量要求相同,不能因為測試代碼就放寬要求,均要求嚴格按照《0bug-C/C++商用工程之道》第三章的無錯化程序設計方法執行。
2、不建議使用全局變量,所有變量聲明時建議同時賦初值,避免“野變量”,因此,有個推論,每一行僅僅處理一個變量的聲明,這樣看起來非常清晰。
3、數組定義為6,比5多,d[6],我一直沒看懂這多出來的一個單元格是做什麼的,如果僅僅是為了防止溢出錯誤,則應該是修訂自己的程序,避免溢出,而不能采用這個打補丁的辦法。
4、我的C/C++無錯化程序設計方法,規定for只有一種寫法,是有道理的,for(i=0;i<n;i++),這裡面暗示了所有n次的循環,i均是從0到n-1,恰好符合d[5]這類數組單元的0~4的有效index 的訪問需求,建議以後采用這類統一格式處理for。像目前的這個for (n=1;n<=5;n++),說實話,我看著就頭大,因為我腦子裡面一直想,那個d[0]在做什麼?
5、我的C/C++無錯化程序設計方法,強調簡單化代碼,每個函數內部只寫一重循環,目的就是簡化代碼,讓人好讀懂,函數不怕多,怕的是函數太長,建議以後可以把這個函數分拆來處理。
6、我的C/C++無錯化程序設計方法,強調函數名和變量名嚴格定名,就是為了把每句話都寫得像個英文短句一樣,大家看起來一目了然,避免讀者誤讀。目前的寫法,我發呆了15分鐘,說實話,最後暈了。
7、這算是復雜邏輯,建議在前面專門注釋一段,說明你采用的原理,並且舉例子說明,這樣我看起來,起碼有個脈絡,現在的寫法,我理解起來很費解。
8、兩段不同代碼的差異在哪裡我沒能看出來,能不能說明一下?
9、程序中間的注釋,建議采用C++模式,即“//”,這樣,我調試,需要暫時隱掉大段代碼時,可以使用“/*...*/”,這是我的一個習慣,也是團隊的習慣,大家覺得很好用,像現在這樣“/*...*/”已經被大量采用的時候,我作為看代碼的人,再想大段隱掉就很困難。
10、“for (c=1;c<=n;c++) j=j*c;/* j為n! */ j*=n*2;”這段一直沒看懂,為什麼階乘後,還要再乘個n的兩倍,有什麼數學道理?
11、“t=d[a];d[a]=d[b];d[b]=t;”,這明顯是交換算法,並且在程序中出現兩次,我團隊的規定,每一種邏輯,只允許出現一次,一次寫對,以後只調用,不重寫,避免無謂的筆誤bug,建議單獨提出來,做個函數,哪怕是inline,也好過現在的寫法。
12、“for (i=n;i>0;i--){/* 打亂0~n-1 */a=i-1;b=rand()%i;”這段看起來非常暈,因為我知道前面你賦值的時候,只給了1~5這5個單元賦值,而這個時候,顯然a的取值范圍是n-1~0,就是4~0,我感覺這裡算法上就是有bug,不對。
先這麼多吧,我呢,水平有限,可能暫時還沒有理解到這個算法的深意,因此,暫時也提不出太好的算法建議。
歡迎討論。
嗯,你這段代碼我覺得很有代表意義,如果你不反對的話,我想發篇博文,列出這個代碼,給大家學習一下。
你意下如何?
不過,這位朋友顯然是一位非常認真的朋友,他隨即給我了回信:
原理打個比方:
若干張撲克牌,最上面放一張紙條。
①把紙條挪到它下面那張牌的下面,即往下一張牌。如果此時紙條已經到達所有牌的下面,結束。
②在緊帖紙條上面那一張和紙條下面所有牌中隨機選一張和緊帖紙條上面那一張交換位置。當然如果本來選的就是緊帖紙條上面那一張,就不用交換了)
重復上面步驟①和②直到結束。
數組多定義一個的原因是因為下面要演示對0~n-1和1~n兩種下標范圍的代碼。因為參考這個代碼的人可能兩種情況只能選用其一或兩種都要在不同場合使用。
兩端代碼算法上沒有差異,只是表明當下標范圍不同時寫法不同。
注釋我原來都是采用//風格的,在Win-TC裡面編譯時提示不支持此風格所以讓其自動替換為/**/風格了。
在回帖時被連成長句也正好說明在這種特殊情況下/**/風格比//風格好。當然我還是支持//風格的。
測試次數選用n*2*n!只是我開始想用n*n!得到所有排列,結果在n很小時失敗,所以隨手改成n*2*n!基本達到目的而已,沒什麼數學道理。
正如你的很多建議,我這段代碼如果直接拿去作為一個工程代碼的一部分,是遠遠不夠的。
但用來演示對0~n-1和1~n兩種下標范圍的數組進行洗牌,應該除了沒加上原理說明的注釋外,也夠用了。
很高興和你交流、討論。
也很樂意看到在你的新博文中引用我這段代碼。
這時候,我才真正看明白,嗯,我的回復如下:
這樣我就明白了。
你是以一根指針,順序遍歷數組每個元素,確保每個元素至少有一次交換機會,這樣牌會洗得更爛。
我的方法是隨機選擇兩個,由於隨機數的不確定性,因此,我並不保證每個元素都確定擁有至少一次被洗牌的機會。看起來,從單純洗爛的角度上講呢,我的方法沒你洗得爛。
嗯,我當初設定這個雙隨機交換時,有個考量,是因為我們平時洗盤是,牌分兩半,大拇指隨機蹦出,其實是把牌隨機對插,這時候,有時候我們大拇指松的快點呢,其實可能會有幾張牌被連著彈出去,就是說其順序沒有被打亂,這在洗一些很髒的舊牌的時候,由於牌中間有粘連,很容易出現。
我當時想雙隨機,沒有以一個順序遍歷隨機,就是想真實模擬這種情況,我想的是:“某一張或幾張牌不被洗到,其實也是生活中隨機洗牌的合理情況”,所以,我沒有刻意去遍歷,保證每張牌都有被洗到的機會,其根源來自於此。
嗯,我這麼說,你能理解我的意思嗎?不被洗,保留原位置信息,其實也是隨機性的一種體現。既然是隨機,就是說,選不到,洗不到,也是隨機的一種。
因此,我選擇了我前文的洗牌法。
嗯,歡迎探討哈。
這是這次關於洗牌算法到目前為止我們的討論結果,我想了一下,覺得有必要把這些信息的詳細情況,寫篇博文來給大家看,幫助大家理解一下隨機數的認識。
這裡我說一點自己的看法:
隨機數求解時,最關鍵的是不能預設立場,即我們既不能斷言,某個數一定存在,也不能斷言,某個數一定不存在,這些都不是真正的隨機,是被其他參量程序員的思想)影響過的偽隨機數。
好比我們甩骰子,第一把我們甩了個6點,第二把不一定是6點,這大家都知道,否則就不是隨機數,但是,我發現很少有程序員朋友關注一個細節,我們同時也不能預先把6點排除,即影響第二把的結果一定“不是”6點,這也不是隨機。
我的隨機洗牌算法,使用雙隨機位置交換,主要就是不想預設立場,我既不想某張牌一定不被交換,但是,我同時也不想某張牌一定被交換,我認為這都不是真正的隨機,所以我選擇了現在的算法。
這位朋友的算法,有一定成分的預設立場在裡面,我認為其隨機度不夠。
嗯,這算我一家之言吧,歡迎大家繼續討論哈。
=======================================================
在線底價購買《0bug-C/C++商用工程之道》
直接點擊下面鏈接或拷貝到浏覽器地址欄)
http://s.click.taobao.com/t_3?&p=mm_13866629_0_0&n=23&l=http%3A%2F%2Fsearch8.taobao.com%2Fbrowse%2F0%2Fn-g%2Corvv64tborsvwmjvgawdkmbqgboq---g%2Cgaqge5lhebbs6qzlfmqmttgtyo42jm6m22xllqa-------------1%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16%2C17%2C18%2C19%2C20---40--coefp-0-all-0.htm%3Fpid%3Dmm_13866629_0_0
肖舸
本文出自 “肖舸的blog” 博客,請務必保留此出處http://tonyxiaohome.blog.51cto.com/925273/311861