一次遍歷,等概率隨機排列陣列與帶權隨機選取問題

2022-01-28 17:55:14 字數 3021 閱讀 6877

由於背單詞軟體中需實現測試單詞與答案選項的隨機排列和帶權值的概率抽取,程式中實現了以下三個演算法:

1.等概率隨機排列陣列(洗牌演算法)

假設有乙個陣列,包含n個元素。現在要重新排列這些元素,要求每個元素被放到任何乙個位置的概率都相等(即1/n),並且直接在陣列上重排(in place),不要生成新的陣列。用 o(n) 時間、o(1) 輔助空間。

演算法是非常簡單了,當然在給出演算法的同時,我們也要證明概率滿足題目要求。

先想想如果可以開闢另外一塊長度為n的輔助空間時該怎麼處理,顯然只要對n個元素做n次(不放回的)隨機抽取就可以了。先從n個元素中任選乙個,放入新空間的第乙個位置,然後再從剩下的n-1個元素中任選乙個,放入第二個位置,依此類推。

按照同樣的方法,但這次不開闢新的儲存空間。第一次被選中的元素就要放入這個陣列的第乙個位置,但這個位置原來已經有別的(也可能就是這個)元素了,這時候只要把原來的元素跟被選中的元素互換一下就可以了。很容易就避免了輔助空間。

來計算一下概率。如果某個元素被放入第i(1≤i

≤n)個位置,就必須是在前 i - 1 次選取中都沒有選到它,並且第 i 次選取是恰好選中它。其概率為:

可見任何元素出現在任何位置的概率都是相等的。

實現**

public

static t shuffle(ilistlist)

return

array;

}

2.單次遍歷,等概率隨機選取一組元素

假設我們有一堆資料(可能在乙個煉表裡,也可能在檔案裡),數量未知。要求只遍歷一次這些資料,隨機選取其中的乙個元素,任何乙個元素被選到的概率相等。o(n)時間,o(1)輔助空間(n是資料總數,但事先不知道)。

如果元素總數為n,那麼每個元素被選到的概率應該是1/n。然而n只有在遍歷結束的時候才能知道,在遍歷的過程中,n的值還不知道,可以利用乘法規則來逐漸湊出這個概率值。在《利用等概率rand5產生等概率rand3》中提到過,如果要通過有限步概率的加法和乘法運算,最終得到分子為1、分母為n的概率,那必須在某一次運算中引入乙個n在分母上,而分母和分子上其他的因數則通過加法、乘法、約分等規則去除。

ok,問題解決了。結束之前再做個簡單的擴充套件,改成等概率隨機選取m個元素(可知每個元素被選中的概率都是m/n)。

實現**

public

static t selectitems(ienumerablesource, int

count)

x++;

}return

list.toarray();

}

3.單次遍歷,帶權隨機選取

還是同樣的問題:有一組數量未知的資料,每個元素有非負權重。要求只遍歷一次,隨機選取其中的乙個元素,任何乙個元素被選到的概率與其權重成正比。

演算法很簡單:對於任意的i(1 <= i <= n),按照如下方法給第i個元素分配乙個鍵值key(其中ri是乙個0到1之間等概率分布的隨機數):

之後,如果要隨機選取乙個元素,就去key最大的那個;如果要選取m個元素,就取key最大的m個。

真不知道是怎麼想出來的這樣的方法,不過還是先來關注一下證明的過程。

對於m=1的證明過程會介紹得詳細些,主要是怕我自己過幾天就忘記了。概率達人可以直接秒殺之。

m=1時,第i個元素被選取到的概率,就等於它所對應的鍵值key(i)是最大值的概率,即:

把key(i)的計算公式代入,但要注意公式中的ri並不是乙個固定的數值,而是隨機變數。不考慮計算機數值表示的精度,可以假設ri是乙個在0到1之間的連續均勻概率分布,因此如果要計算key(i)是最大的概率,必須要對ri所有的可能值進行概率累加,也就是積分。於是上面的概率表示式就被寫成:

再看式子中的∀

,它表示每乙個j都要滿足後面的條件,而各個j之間相互獨立,因此可以寫成概率乘積,於是得到:

對於給定的j,

,另外rj也是個均勻概率分布,將概率密度函式代入可以得到:

因此,上面的概率算式就變成(其中w就是之前提到的所有元素的權重之和):

當m取任意值時,概率公式變得非常複雜,在前一篇文章中使用了第i個元素不被選到的概率來簡化表示式。現在的證明也從同樣的角度進行。

第i個元素不被選到的概率,顯然等於這n個元素中,至少存在m個元素的鍵值大於key(i),與之前的討論一樣,不妨設這m個元素的下標(按鍵值從大到小)依次為j1, j2, ..., jm,

,滿足。注意jk和tk的取值範圍,為了簡單起見,下面的式子中就不再重複了。

為了能夠進一步求解,必須把這個連等式拆開。這裡要非常小心,各個jk並不是相互獨立的,比如當j1改變的時候,j2的取值範圍也會隨之變化,依此類推。拆開之後的式子如下:

看起來還是相當恐怖的,一層套一層。注意等式右邊已經沒有顯式地關於i的資訊了,這些資訊被隱含在jk和tk的取值範圍中,切記。對每個jk,把key(jk)的式子代進去,轉換成積分;同時把∀tk

轉換為∏tk

。這些在m=1的證明中都提到過了。新出現的是∃jk

,這個顯然適用概率加法,因為jk取不同的值對應於不同的互斥方案。經過這些變換得到:

其中的積分式在之前已經見過了,其運算過程如下(注意tk的取值範圍):

最終,概率計算式子變成:

與之前的理論值完全一樣。

呼,可怕的推導過程。

實現**

public

static t selectweighteditems(ienumerableie, int count, funcdouble>weightfunc)

}return

sd.values.toarray();}}

等概率隨機排列陣列(洗牌演算法)

又是一道跟概率相關的簡單問題。話說我的概率學的太差了,趁這個機會也從頭開始補習一下。問題描述 假設有乙個陣列,包含n個元素。現在要重新排列這些元素,要求每個元素被放到任何乙個位置的概率都相等 即1 n 並且直接在陣列上重排 in place 不要生成新的陣列。用o n 時間 o 1 輔助空間。演算法...

查詢鍊錶的中間節點,只能遍歷一次陣列(C )詳細

方法及思想 因為要求只能遍歷一次鍊錶,那麼我們就可以邊遍歷邊查詢中間的節點。每遍歷一步,就找一次中間節點,然後移動只想中間節點的指標。此處借助鍊錶的長度來實現這個解法,我假設在鍊錶長度為偶數時,中間節點為length 2取下取整 參考二分法 include using namespace std s...

1 N個數隨機去掉兩個數,遍歷一次把這兩個數找出來

學習演算法,把一些簡單的問題寫一寫,做一做 題目 1 n這n個數隨機減少兩個數後,放在a中 打亂順序 以時間複雜度為o n 的演算法找到減少的這2個數 思路 如果是減少1個數,那麼大家都知道方法 迴圈一次,求a的和sum以及 1 2 n sum,然後用sum sum就能得到結果。可是,現在出現2個數...