洗牌算法一:
生成一個不重復的隨機序列,將隨機序列綁定到nums[],然后對隨機序列做一次排序。
洗牌算法二:(經典洗牌算法)
for(int i=nums.length-1; i>=1; i--) Swap(nums[i], nums[rand()%(i+1)]);
經典算法的證明:
對於nums[i],洗牌后在第n-1個位置的概率是1/n(第一次交換的隨機數為i)
在n-2個位置概率是[(n-1)/n] * [1/(n-1)] = 1/n,(第一次交換的隨機數不為i,第二次為nums[i]所在的位置(注意,若i=n-1,第一交換nums[n-1]會被換到一個隨機的位置))
在第n-k個位置的概率是[(n-1)/n] * [(n-2)/(n-1)] *...* [(n-k+1)/(n-k+2)] *[1/(n-k+1)] = 1/n
(第一個隨機數不要為i,第二次不為nums[i]所在的位置(隨着交換有可能會變)……第n-k次為nums[i]所在的位置)
洗牌算法三:(inside-out算法,可用於未知牌數)
類似於蓄水池抽樣算法。
int i=0; while(nums[i]存在) { int k = rand()%(i + 1); res[i] = res[k]; res[k] = nums[i++];
}
上面是偽代碼,如果知道nums的lenght的話,可以改為for循環,由於是從前往后遍歷,所以可以應對nums[]數目未知的情況,或者nums[]是一個動態增加的情況。
證明如下:
原數組的第 i 個元素在新數組的前 i 個位置的概率都是:(1/i) * [i/(i+1)] * [(i+1)/(i+2)] *...* [(n-1)/n] = 1/n,(即第i次剛好隨機放到了該位置,在后面的n-i 次選擇中該數字不被選中)
原數組的第 i 個元素在新數組的 i+1 (包括i + 1)以后的位置(假設是第k個位置)的概率是:(1/k) * [k/(k+1)] * [(k+1)/(k+2)] *...* [(n-1)/n] = 1/n(即第k次剛好隨機放到了該位置,在后面的n-k次選擇中該數字不被選中)
蓄水池抽樣:
問題:如何隨機從n個對象中選擇一個對象,這n個對象是按序排列的,但是在此之前你是不知道n的值的。
解法:我們總是選擇第一個對象,以1/2的概率選擇第二個,以1/3的概率選擇第三個,以此類推,以1/m的概率選擇第m個對象。當該過程結束時,每一個對象具有相同的選中概率,即1/n.