算法之洗牌算法(隨機置亂算法)


排序算法用於將一個序列變成有序的,而洗牌算法則用於將一個序列打“亂”,可以認為是排序算法相反操作。洗牌算法需要借助隨機數實現來打“亂”序列。

什么才是“真的亂”

洗牌算法正確性的判斷准則(“亂”的判斷依據):對於包含n個元素的序列,其全排列有n!種可能。故若序列打亂的結果有n!種且每種出現的概率一樣,則是正確的洗牌算法。因打亂結果的種數肯定不大於n!,故反例有兩種情況:

打亂結果的種數小於n!:顯然此時全排列中的某些結果無法由洗牌算法產生,故此時的洗牌算法不對;

打亂結果的種數等於n!但每種的出現的概率不一樣

 

怎樣做到“真的亂”

洗牌算法代碼通過隨機獲取元素並交換來產生隨機結果。

原理:將數組分為已打亂和未打亂的前后兩部分(初始時兩者分別由0、n個元素),每次隨機從未打亂部分中選擇一個元素加入到已打亂部分中。易得種數為 n! 。

代碼:四種寫法本質上一樣。

// 得到一個在閉區間 [min, max] 內的隨機整數
int randInt(int min, int max);

// 第一種寫法
void shuffle(int[] arr) {
    int n = arr.length();
    /******** 區別只有這兩行 ********/
    for (int i = 0 ; i < n; i++) {
        // 從 i 到最后隨機選一個元素
        int rand = randInt(i, n - 1);
        /*************************/
        swap(arr[i], arr[rand]);
    }
}

// 第二種寫法
    for (int i = 0 ; i < n - 1; i++)
        int rand = randInt(i, n - 1);

// 第三種寫法
    for (int i = n - 1 ; i >= 0; i--)
        int rand = randInt(0, i);

// 第四種寫法
    for (int i = n - 1 ; i > 0; i--)
        int rand = randInt(0, i);
View Code

反例:每次都從整個數組中隨機選一個元素交換到首位,進行n輪。雖然結果種數也為n!,但由於每輪都有n種選擇故總結果數為nn(包含重復的結果),由於nn>n!且前者不能被后者整除故有些排列出現的概率會比較大,從而不是正確的洗牌算法。

如何驗證“真的亂”

可以用蒙特卡洛方法來驗證各種結果出現的頻率是否大致一樣,若大致一樣則說明洗牌算法是正確的。

法1:給定一個數組如{1,2,3,4,5},進行N次(N最好大些)實驗:以該數組為參數調用洗牌算法,記錄每次洗牌后的結果並統計。若結果數為n!且各結果出現的頻數大致一樣則說明正確。

法2:與上面類似,只不過數組中除了一個元素為1外其他元素為0。每次實驗只需記錄洗牌后1的位置,最后看1出現的位置是否大致一樣。

 

參考資料

https://github.com/labuladong/fucking-algorithm/blob/master/算法思維系列/洗牌算法.md


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM