1. 程式人生 > >Shuffle&Sample

Shuffle&Sample

近來打算通過自己實現幾個常見模型來複習一下之前看過的,順便練習一下Python語法的熟練度。首先準備搭好除模型外的整個程式的其他部分,比如資料的匯入、劃分。
在寫劃分資料集部分的函式時,查了一下random模組中有sample(data, k)和shuffle(data)兩個函式,分別實現從一個序列中取樣k個數和將資料集內資料打亂的,其中sample函式將取樣後資料作為返回值,shuffle函式直接對data操作,無返回值。在這裡對兩個函式的實現演算法探究一下。
######shuffle
shuffle的意思是讓序列亂序。在random模組的shuffle函式中,用的是經典的Fisher-Yates shuffle演算法。其虛擬碼如下

for i = 1:N
    temp = random(0, N-i)
    交換 a[temp] 和 a[N-i]
end

易證,對於每個元素,其shuffle後在每個位置的概率都為 1 N \frac{1}{N} 。同理,也可從前往後實現。
######sample
sample意思是從原序列中隨機選擇k個元素,不改變原序列。
最簡單的自然是每次隨機從[0, N-1]中選一個,若該元素已經選出,則重新選擇,虛擬碼為

while length(已抽)<k
    temp = random(0, N-1)
    if temp in 已抽
        continue
    已抽.append(a[temp])
end

該演算法需要一塊額外的空間,進行儲存原序列元素是否已經被抽取的標記。但是,當k較大時,每次random到已經抽到的概率越來越大,此時時間複雜度升高,接近於 O ( n

l o g n ) O(nlogn)
也可以採用shuffle的思路,到第k個時停止,此時時間複雜度為 O ( k ) O(k) 。但是,此時原序列已經被改變,或者需要另外複製一個原序列,在此序列上進行處理。
水塘抽樣演算法(Reservoir sampling)可以適用於N為一很大或者未知數量的情況,尤其是不能將所有N個專案存入記憶體中時。其虛擬碼如下

for i in range(1, k)
    reservoir[i] = stream[i]
for i in range(k, N+1)
    p = random(0, i)
    if p < k
        reservoir[p] = stream[i]

時間複雜度為 O ( N ) O(N)