很多人都有耳聞過洗牌演算法,時常會在面試中碰到,我們下面來定義一下這個問題。
所謂洗牌演算法,就是給你一個1到n的序列,讓你隨機打亂,保證每個數出現在任意一個位置的機率相同,也就是說在n!個的排列中,每一個排列出現的機率相同。
最樸素的做法
對於這個問題我們從最樸素的解法談起。每次隨機選出一個沒有被選過的數放到一個隊列中,如果隨機出來的數已經被選過,那麼繼續隨機直到遇到一個沒有被選過的數放入到隊列中;重複這樣子操作直到所有的數都被選擇出來。
我們看看這樣子作為什麼是對的。首先選第一個數的時候有n個數可選,第2個數的時候有(n-1)個數可選,-----這樣子看來我們的排列有n*(n-1)*(n-1)*-----*1種,也就是我們最後選出來的排列是n!的排列中的任意一個,這樣子顯然是符合隨機洗牌的要求的。
但是我們反觀該方法的時間複雜度,由於我們每次隨機選出一個數都有可能是之前選過的數,需要進行再次隨機,因此對選出一個數的隨機平均情況下需要隨機O(n)次,因此該方法的時間複雜度是O(n^2)的。而且另外還要記一個隊列,甚至需要記一個數是否被選出來過,因此該最樸素的做法的時間和空間複雜度都不是最好的,我們需要考慮更好的辦法。
經典的洗牌演算法
很多面試官需要你會的洗牌演算法當然是經典的洗牌演算法,也就是我們考慮如何對上面的洗牌演算法進行最佳化。
我們考慮,當一個數被選之後,我們是沒有必要在下一次隨機的時候再考慮它的,因此,我們每次只從可選的數的集合中進行隨機,也就不用考慮是否會碰到已經選過的數了,這樣子直接將演算法的複雜度降了一個數量級。
void MySwap(int &x, int &y){ int temp = x; x = y; y = temp;}void Shuffle(int n){ for(int i=n-1; i>=1; i--) { MySwap(num[i], num[rand()%(i+1)]); }}
參照上面的代碼,我們可以看到,我們每次都是隨機的從沒有選的數中選擇一個,該洗牌演算法的時間複雜度為O(n),空間複雜度為O(0)。正確性如上面一樣證明。