不重複隨機數列生成演算法
轉載自:http://www.cnblogs.com/eaglet/archive/2011/01/17/1937083.html
本文將講述一個高效的不重複隨機數列的生成演算法,其效率比通常用hashtable 消重的方法要快很多。
首先我們來看命題:
給定一個正整數n,需要輸出一個長度為n的陣列,陣列元素是隨機數,範圍為0 – n-1,且元素不能重複。比如 n = 3 時,需要獲取一個長度為3的陣列,元素範圍為0-2,
比如 0,2,1。
這個問題的通常解決方案就是設計一個 hashtable ,然後迴圈獲取隨機數,再到 hashtable 中找,如果hashtable 中沒有這個數,則輸出。下面給出這種演算法的程式碼
public static int[] GetRandomSequence0(int total)
{
int[] hashtable = new int[total];
int[] output = new int[total];
Random random = new Random();
for (int i = 0; i < total; i++)
{
int num = random.Next(0, total);
while (hashtable[num] > 0)
{
num = random.Next(0, total);
}
output[i] = num;
hashtable[num] = 1;
}
return output;
}
程式碼很簡單,從 0 到 total - 1 迴圈獲取隨機數,再去hashtable 中嘗試匹配,如果這個數在hashtable中不存在,則輸出,並把這個數在hashtable 中置1,否則迴圈嘗試獲取隨機數,直到找到一個不在hashtable 中的數為止。這個演算法的問題在於需要不斷嘗試獲取隨機數,在hashtable 接近滿時,這個嘗試失敗的概率會越來越高。
那麼有沒有什麼演算法,不需要這樣反覆嘗試嗎?答案是肯定的。
如上圖所示,我們設計一個順序的陣列,假設n = 4
第一輪,我們取 0 – 3 之間的隨機數,假設為2,這時,我們把陣列位置為2的數取出來輸出,並把這個數從陣列中刪除,這時這個陣列變成了
第二輪,我們再取 0-2 之間的隨機數,假設為1,並把這個位置的數輸出,同時把這個數從陣列刪除,以此類推,直到這個陣列的長度為0。這時我們就可以得到一個隨機的不重複的序列。
這個演算法的好處是不需要用一個hashtable 來儲存已獲取的數字,不需要反覆嘗試。演算法程式碼如下:
public static int[] GetRandomSequence1(int total)
{
List<int> input = new List<int>();
for (int i = 0; i < total; i++)
{
input.Add(i);
}
List<int> output = new List<int>();
Random random = new Random();
int end = total;
for (int i = 0; i < total; i++)
{
int num = random.Next(0, end);
output.Add(input[num]);
input.RemoveAt(num);
end--;
}
return output.ToArray();
}
這個演算法把兩個迴圈改成了一個迴圈,演算法複雜度大大降低了,按說速度應該比第一個演算法要快才對,然而現實往往超出我們的想象,當total = 100000 時,測試下來,第一個演算法用時 44ms, 第二個用時 1038 ms ,慢了很多!這是為什麼呢?問題的關鍵就在這個 input.RemoveAt 上了,我們知道如果要刪除一個數組元素,我們需要把這個陣列元素後面的所有元素都向前移動1,這個移動操作是非常耗時的,這個演算法慢就慢在這裡。到這裡,可能有人要說了,那我們不用陣列,用連結串列,那刪除不就很快了嗎?沒錯,連結串列是能解決刪除元素的效率問題,但查詢的速度又大大降低了,無法像陣列那樣根據陣列元素下標直接定位到元素。所以用連結串列也是不行的。到這裡似乎我們已經走到了死衚衕,難道我們只能用hashtable 反覆嘗試來做嗎?在看下面內容之前,請各位讀者先思考5分鐘。
…… 思考5分鐘
演算法就像一層窗戶紙,隔著窗戶紙,你永遠無法知道里面是什麼,一旦捅穿,又覺得非常簡單。這個演算法對於我,只用了2分鐘時間想出來,因為我經常實現演算法,腦子裡有一些模式,如果你的大腦還沒有完成這種經驗的積累,也許你要花比我長很多的時間來考慮這個問題,也許永遠也找不到捅穿它的方法。不過不要緊,我把這個方法公佈出來,有了這個方法,你只需輕輕一動,一個完全不同的世界便出現在你的眼前。原來就這麼簡單……。
還是上面那個例子,假設 n = 4
第一輪,我們隨機獲得2時,我們不將 2 從陣列中移除,而是將陣列的最後一個元素移動到2的位置
這時陣列變成了
第二輪我們對 0-2 取隨機數,這時陣列可用的最後一個元素位置已經變成了2,而不是3。假設這時取到隨機數為1
我們再把下標為2 的元素移動到下標1,這時陣列變成了
以此類推,直到取出n個元素為止。
這個演算法的優點是不需要用一個hashtable 來儲存已獲取的數字,不需要反覆嘗試,也不用像上一個演算法那樣刪除陣列元素,要做的只是每次把陣列有效位置的最後一個元素移動到當前位置就可以了,這樣演算法的複雜度就降低為 O(n) ,速度大大提高。
經測試,在 n= 100000 時,這個演算法的用時僅為7ms。
下面給出這個演算法的實現程式碼
/// <summary>
/// Designed by eaglet
/// </summary>
/// <param name="total"></param>
/// <returns></returns>
public static int[] GetRandomSequence2(int total)
{
int[] sequence = new int[total];
int[] output = new int[total];
for (int i = 0; i < total; i++)
{
sequence[i] = i;
}
Random random = new Random();
int end = total - 1;
for (int i = 0; i < total; i++)
{
int num = random.Next(0, end + 1);
output[i] = sequence[num];
sequence[num] = sequence[end];
end--;
}
return output;
}
下面是n 等於1萬,10萬和100萬時的測試資料,時間單位為毫秒。從測試資料看GetRandomSequence2的用時和n基本成正比,線性增長的,這個和理論上的演算法複雜度O(n)也是一致的,另外兩個演算法則隨著n的增大,用時超過了線性增長。在1百萬時,我的演算法比用hashtable的演算法要快10倍以上。
10000 | 100000 | 1000000 | |
GetRandomSequence0 | 5 | 44 | 1075 |
GetRandomSequence1 | 11 | 1038 | 124205 |
GetRandomSequence2 | 1 | 7 | 82 |
相關推薦
不重複隨機數列生成演算法
轉載自:http://www.cnblogs.com/eaglet/archive/2011/01/17/1937083.html 本文將講述一個高效的不重複隨機數列的生成演算法,其效率比通常用hashtable 消重的方法要快很多。 首先我們來看命題: 給定一個正整數n,
【Python】Python生成一個不重複隨機list
在一個範圍內,生成一個固定元素個數的,不重複的隨機list. 錯誤方法 使用for迴圈逐一對生成每個值進行判斷,在有重複值的時候,不會被被新增.在需要生成多個隨機值的時候,容易出現輸出的結果比較少的情況.比如下面需要生成20個隨機值,可是一般輸出的都不到20. import ra
java中生成不重複隨機的數字
Java中產生隨機數1 . 呼叫java.lang下面Math類中的random()方法產生隨機數新建一個檔案字尾名為java的檔案,檔名取為MyRandom,該類中編寫如下的程式碼:public class MyRandom {public static void main(String[] args) {
java生成不重複隨機賬號
/** * 生成隨機賬號 * @return */ public static String uuid() {
sql 生成8位字母數字組合不重複隨機碼
先設定code不可重複,自動忽略 如果生成的串包含0,O,跳過 SET NOCOUNT ON; declare @s varchar(8) declare @i int set @i=0 while(@i<1000000) begin set @
【記錄】2種隨機迷宮生成演算法的cpp實現
1.DFS dfs(x,y) 標記(x,y 若(x,y)存在未標記的相鄰位置 從中隨機選擇一個(nx,ny) 聯通(x,y)和這個位置 dfs(nx,ny)
隨機迷宮生成演算法——遞迴分割演算法
迷宮生成三大演算法,Prime演算法、深度優先演算法、遞迴分割演算法,其中遞迴分割演算法最簡單,效率也最高,不過生成的迷宮也最簡單,看圖: 原理很簡單,首先假設迷宮全是路,在裡面畫四面牆,把迷宮分割成四個新區域,如下: 隨機選擇三面牆打通,這時原本隔開的四個區域又
海量不重複資料的生成
前幾天看到了一個專案需求,自己嘗試寫了下3億行資料大概需要20分鐘的時間,普通硬碟應該要慢些,記憶體對映或者記憶體盤應該會更快一點,懶得驗證了,放出程式碼供學習下,演算法是以前偶然看到得
已知一個數組int[98],該數組裡面儲存了0~99共100個數字中的98個,數字不重複,請用演算法算出0~99中缺少的2個數字是哪兩個?
public class Test24 {public static void main(String[] args) {int[] num=in();//生成陣列noNumber(num);//判斷不同的數} public static int[] in() { int[] array = new
[PHP] 高併發 php uniqid 不重複唯一識別符號生成方案
PHP uniqid()函式可用於生成不重複的唯一識別符號,該函式基於微秒級當前時間戳。在高併發或者間隔時長極短(如迴圈程式碼)的情況下,會出現大量重複資料。即使使用了第二個引數,也會重複,最好的方案是結合md5函式來生成唯一ID。PHP uniqid() 生成不重複唯一標識方法一 這種方法會產生大量的重複
產生一組不重複隨機數的高效演算法
需要從 0 到 n 之間選 k 個不重複的陣列成一個序列。 最早我想的是用一個輔助陣列記錄之前已經產生的隨機數,如果當前產生的隨機數已經出現過就再重新隨機。 顯然這樣的實現效率是很低的,設想從10000個數中隨機產生10000個數的序列,當前面9999個數已
遊戲中的隨機地形生成演算法(三)
在上篇教程中,我們已經把Map中的每個點都實實在在的畫了出來,四個點形成一個正方形。其中,正方形的每條邊都有一箇中點。 那麼,這節課我們來把該連在一起的點連起來,繪製我們的mesh。 public class Square { publ
snowflake 不重複id程式碼生成類
不重複id程式碼生成類 package com.hp.snowflake; /** * An object that generates IDs. * This is broken into a separate class in case * we
寫一個函式,隨機生成N條不重複的手機號
方法一:import random def phone(count): results = [] while len(results)!=count: starts = [138,156,130,170,188,189] start = random.ch
Python:生成隨機不重複的數
想要使用 Python 生成隨機不重複的數,我們可以使用 random 模組來實現: >>> import random ## 先創個 list >>> list = [1.0 ,1.2 ,1.4, 1.3, 1.65] >>>
生成隨機的字串而且不重複
隨機結果1:C88B3 package bb; import java.util.UUID; public class uuuid { public static void main(Stri
從給定數字集合中隨機取不重複的數字演算法
遊戲開發過程中,會遇到比如:從給定的10個道具中隨機生成三個給使用者,要求生成的道具相互之間不重複。一般情況下,我們會將道具儲存在一個集合中,然後生成一個隨機數,使用生成的隨機數作為集合的索引,從集合中取出物件,傳遞給例項化函式。這樣做有一個缺點,生成的隨機數是
python 生成隨機不重複的使用者id
資料庫裡面有時候需要不重複的id 來表示使用者id,就像QQ號碼一樣。 如果簡單用uuid來生成的話,生成64位,太長。 生成6到8位gid def generate_gid(): gids
js生成隨機不重複ID
function createRandomId() { return (Math.random()*10000000).toString(16).substr(0,4)+'-'+(new Date()).getTime()+'-'+Math.rand
獲取多個隨機生成的不重複的6位數,不重複的6位數或8位數串
獲取多個隨機生成的6位數,每次獲取都不會與前一次重複 思路1:建立一張表table,每次生成一條之後,查詢一下表中是否已存在這樣的話,資料越來越多,後面就挺麻 煩的,判斷多次才能取到一個表中不存在的 思路2:建議一次性在資料庫表中插入多條資料(如10000條), 表名t