1. 程式人生 > >【每日演算法】洗牌演算法

【每日演算法】洗牌演算法

洗牌演算法

給定一個n個數的序列,設計一個演算法將其隨機打亂,保證每個數出現在任意一個位置的概率相同(也就是說在n!個的排列中,每一個排列出現的概率相同)。

樸素的做法:

假設輸入為陣列num[length]。

隨機選一個數,放到num[0]中,再隨機選數,如果該數已經選過,重新選,直到該數未選過時放入num[1]中,以此類推,直到所有的數都選出來,很明顯,這種選法一共有n!中可能,每種可能出現的概率都相同。

但是該做法效率不高,因為選過的數再選將耗費大量時間。

改進的洗牌演算法:

基於以上演算法的缺陷,我們做出改進:選過的數將不再考慮。比如num[0…k]為已選的數,那麼我們的隨機只在k+1到length-1間進行。

void MySwap(int &a, int &b)
{
    int tmp = x;
    x = y;
    y = tmp;
}

void Shuffle(int num[], int length)
{
    if (NULL == num || length <= 0) return;
    for (int index = length-1; index >= 1; --index)
    {
        MySwap(num[index], num[rand()%(index+1)]);
    }
}

解釋一下:

index為本次選的牌的存放位置,rand()%(index+1)產生0到index之間的隨機數,而已選的數的下標為index+1到length-1,所以可以保證已選的數不會再重複選到。

一些小細節:

習慣使用–index而不是index–的原因是:index–需要一個臨時變數來儲存自減前的index值,而–index,直接先自減,之後返回自身,因此效率更高一些。(當然,實際上編譯器可能會做一些優化,使得兩者的區別不大,這僅僅是一個良好的程式設計習慣)。

使用逆序:rand()%(index+1)比較方便地產生0到index之間的隨機數,如果是正序,則需要寫成:

    for (int index = 0; index < length; ++index)
    {
        MySwap(num[index], num[index+rand()%(length
-index)]); }

運算多一些,而且不夠簡潔。

最後提一個點:以上程式碼每次運算的結果都會是一樣的,如果想要每次都不一樣,需要新增種子(使用srand((int)time(0));根據時間來選擇種子)。

每天進步一點點,Come on!
(●’◡’●)

本人水平有限,如文章內容有錯漏之處,敬請各位讀者指出,謝謝!