java random.nextInt的坑及解決
java random.nextInt的坑
下面的代碼
Random random = new Random(); Integer code = random.nextInt(len);
很簡單的兩句代碼,需要註意兩點
第一:nextInt的取值是[0,n) ,不包括n。如果是隨機list,直接傳list的size,不用擔心下標越界。
api說明:
Returns a pseudorandom, uniformly distributed int value between 0 (inclusive) and the specified value (exclusive)
第二個:nextInt在數據量小的時候,重復概率比較高。比如現在有一個大小為6的list,我希望隨機顯示4條且不重復。正確的做法是每次得到隨機數後,移除下標對於的對象。這樣即使random重復瞭也沒關系,因為下標對應數據移除後,同樣的下標對應的對象是不一樣的。
千萬別像我之前的做法,遍歷list,然後隨機取到下標後,再去重。這樣有時能得到4個,有時得不到。比如下標會出現 5,1,1,1,2,1.這樣的話,最終list隻會有三個。
之前一直沒有懷疑是這段代碼的問題,懷疑接口不穩定或者是數據不完整之類的。查日志還一直在看接口傳遞參數和返回參數,結果是因為對nextInt理解不深刻,在我印象中感覺randomInt是隨機數且不重復的,不過事實證明我想多瞭。
java random.nextInt()不隨機性
最近在研究算法,也寫一些小程序,其中有一個是《算法導論》中的習題:描述RANDOM(a, b)過程的一種實現,它隻調用RANDOM(0, 1),作為a和b的函數,你的程序的期望時間運行時間是多少?
這個題在網上已經有很多人給出瞭答案
我也自己寫瞭一個算法,不過本文的主題不是針對這個問題,而是RANDOM(0, 1)的實現方法。我剛開始使用的是random.nextInt(0, 1)來取隨機的0和1,也測試瞭其“隨機性”,代碼如下:
//用Random.nextInt(2)獲取0,1隨機數 //獲取概率均為0.5,但不隨機 public int randomBase0() { Random r = new Random(); return r.nextInt(2); }
用for循環10000萬次,得到的0和1大致相當,可以得出獲得0和1的概率為0.5。但之後我就遇到瞭麻煩,我寫瞭一個方法去實現RANDOM(a, b),例如RANDOM(0, 3),得到的結果是:
0有8335個
1有825個
2有42個
3有798個
我的算法是
將b-a+1擴展到2的最小冪級數,如果是個數為5,則取8(2^3),如果為16,則取16(2^4),然後用分治算法獲取a與b之間的數,其中需要將大於b的數去除掉,重新獲取。
我原本以為我寫RANDOM(a, b)的算法錯瞭。後來又寫瞭一個算法,是網上很多人使用的算法,是將隨機產生的0和1拼成2進制數,然後轉換為十進制數,在一個區間內在有效,超過這個區間則排除重取。
用這個算法得到的結果與第一種算法結果相同。我隻好把第二種算法的二進制數打印出來查找原因,發現一個問題,就是0或1連續出現的概率要比0和1交叉出現的概率大,我想既然是隨機產生0和1,那這兩個生成的概率應該相同才對。
因此我得出結論,使用Random.nextInt(0, 1)獲取隨機數的概率並不隨機,原因是其生成連續相同0或1的概率與生成交叉0和1的概率不等,並且前者大於後者,盡管單獨獲取0和1的個數比相當,換句話說,用這種方法獲取0和1的事件不獨立。
驗證如下
//檢測Random.nextInt(2),連續獲取兩位0和1隨機數 public int randomBaseS() { String s = new String(new StringBuffer().append(getBoolean()).append(getBoolean())); if("00".equals(s)){ return 0; }else if("01".equals(s)){ return 1; }else if("10".equals(s)){ return 2; }else{ return 3; } } //獲取隨機數二進制字符串 public String getBoolean(){ return new String(new Integer(randomBase0()).toString()); }
使用for循環10000次,得到的計數結果如下:
“00”有4145個
“01”有928個
“10”有905個
“11”有4022個
那哪種算法能滿足要求呢?如下:
//用Math.random()獲取0,1隨機數 //獲取概率均為0.5,且隨機 public int randomBase() { return Math.random()>0.5?1:0; }
以上面的方法為基礎,用for循環獲取[0, 3]之間的整數,得到的結果如下:
0有2525個
1有2551個
2有2433個
3有2491個
滿足要求,哈哈!
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Java使用Servlet生成驗證碼圖片
- java 將 list 字符串用逗號隔開拼接字符串的多種方法
- java後臺驗證碼生成的實現方法
- Java8 將List轉換為用逗號隔開的字符串的多種方法
- java使用IO流對數組排序實例講解