最近讀了專案中的工具指令碼,發現乙個隨機取數的函式,功能大概是從m個數中不重複的隨機取出n個數,算是陣列隨機排序然後取前n個值的變種。
指令碼實現採取原始的方法,每隨機取乙個數就放到乙個陣列中,下次取數時遍歷結果陣列判斷是否已經取出,平均時間複雜度為o(mlogm),空間複雜度o(n),效率不高。
想了一下解決方案,能優化的地方應該就是將遍歷陣列判斷是否取出使用雜湊或者紅黑樹實現,以空間換時間,雖然可以降低時間複雜度,但原演算法仍存在問題:當m接近於n的時候,效率會急劇下降,十分恐怖。
如果借助洗牌演算法,獲取乙個隨機排列的子集,便能實現等概率隨機取數的功能。
最早於2023年由ronald fisher和frank yates所著《statistical tables for biological,agricultural and medical research》提出,演算法描述為:
將1到n數字存到陣列中
從陣列中取乙個1到剩下數字個數的隨機數k
從低位開始,將陣列第k個數字取出,並儲存到結果陣列末尾
重複第2步,直到所有數字都被取出
第3步得到的結果陣列就是所求的隨機序列
該演算法存在對陣列隨機元素的刪除操作,時間複雜度o(n^2),空間複雜度o(m),效率仍然比較低。
knuth和durstenfeld在fisher等人的基礎上進行了改進。每次隨機取出數字後不是從陣列中刪除,而是與陣列尾部進行交換,時間複雜度提公升到o(n)。
-- knuth-durstenfeld shuffle演算法,時間複雜度o(n),不需要額外空間
function shuffle(array)
local len = #array -- 取array的長度
while len > 0 do
local r = math.random(1,len)
-- 交換
t[r],t[len] = t[len],t[r]
-- 待排列陣列長度減小
len = len - 1
end
return array
end-- 等概率隨機取數演算法
function random_n_not_repeated(array,n)
local len = #array
if n > len then return array end
local result = {}
for v = 1 , n do
local r = math.random(1,len)
table.insert(result,array[r])
-- 交換
array[r] = array[len]
-- 待取陣列長度減小
len = len - 1
endreturn result
end
knuth-durstenfeld shuffle是乙個in-place演算法,會改變原始資料的順序,而有些場景中我們需要保留原始資料,因此需要額外的空間儲存打亂的序列。
inside-out演算法思想是設定乙個游標i從前向後掃瞄原始資料的拷貝,在[0,i]之間隨機乙個下標j,然後用位置j的元素替換掉位置i的數字,再用原始資料位置i的元素替換掉拷貝資料位置j的元素。其作用相當於在拷貝資料中交換i與j位置處的值。與直接從複製陣列進行交換相比少一次交換操作,時間複雜度o(n)。
-- inside-out 演算法,時間複雜度o(n)
function shuffle(array)
local len = #array
local result = deep_copy(array)
for i = 1 , len do
r = math.random(1,i)
result[i] = result[j]
result[j] = array[i]
endreturn result
end-- 等概率隨機取數演算法
function random_n_not_repeated(array,n)
local m = #array -- 取array的長度
-- 集合數目不夠抽取數目
if m <= n then
return t
endlocal result = array
for i = 1 , n do
j = math.random(1,i)
result[i] = result[j]
result[j] = array[i]
endreturn result
end
上述演算法均需要乙個額外的輔助空間,但實際環境中可能會遇到無法完全儲存的海量資料流,從這個海量資料流中等概率取出n個數的思路略有不同,當我們無法確定資料流的個數,即m的大小時,無法使用上述方法進行隨機取數,這就是資料科學中常見的蓄水池抽樣的問題,演算法實現描述為:
將資料流前n個數字存到陣列中
從資料流的第n+1個數開始,取出資料流中的乙個數。
假設這個數索引為k,則以n/k的概率選中該數。
如果該數選中,則隨機替換陣列中的乙個記錄。
重複第2步直到資料流結束。
1.fisher-yates shuffle 洗牌演算法
2.洗牌演算法shuffle
3.蓄水池抽樣-《程式設計珠璣讀書筆記》
等概率隨機函式的實現
題目 已知隨機函式rand 以p的概率產生0,以1 p的概率產生1,現在要求設計乙個新的隨機函式newrand 使其以1 n的等概率產生1 n之間的任意乙個數。解決思路 可以通過已知隨機函式rand 產生等概率產生0和1的新隨機函式rand 然後呼叫k k為整數n的二進位制表示的位數 次rand 函...
等概率隨機函式的實現
題目 已知隨機函式rand 以p的概率產生0,以1 p的概率產生1,現在要求設計乙個新的隨機函式newrand 使其以1 n的等概率產生1 n之間的任意乙個數。解決思路 可以通過已知隨機函式rand 產生等概率產生0和1的新隨機函式rand 然後呼叫k k為整數n的二進位制表示的位數 次rand 函...
等概率隨機函式的實現
解決思路 可以通過已知隨機函式rand 產生等概率產生0和1的新隨機函式rand 然後呼叫k k為整數n的二進位制表示的位數 次rand 函式,得到乙個長度為k的0和1序列,以此序列所形成的整數即為1 n之間的數字。注意 從產生序列得到的整數有可能大於n,如果大於n的話,則重新產生直至得到的整數不大...