假设有 n n n张牌,每个牌编号 1 ∼ n 1\sim n 1∼n,要求设计一个随机算法,返回 1 ∼ n 1\sim n 1∼n组成的序列,但排列顺序要求是完全随机的。完全随机的意思是,首先显然有 n ! n! n!个排列可能性,完全随机就是指,返回的序列出现的概率是 1 n ! \frac{1}{n!} n!1。
我们直接给出算法的python代码:
import random
def shuffle(cards):
for k in range(len(cards)):
i = random.randint(k, len(cards) - 1)
cards[i], cards[k] = cards[k], cards[i]
算法的大致意思是,先从第 1 ∼ n 1\sim n 1∼n里随机选一个位置的数,将其与第一个数交换;接着从第 2 ∼ n 2\sim n 2∼n里随机选一个位置的数,将其与第二个数交换;如此操作下去,得到的序列就是完全随机排列的。该算法时间复杂度 O ( n ) O(n) O(n),空间 O ( 1 ) O(1) O(1),是非常优秀的算法了。
我们说明一下这个算法的性质。
首先,每个数出现在第
1
1
1位的概率显然是
1
n
\frac{1}{n}
n1。接着考虑每个数出现在第
2
2
2位的概率,根据条件概率公式,其概率为
1
n
0
+
n
−
1
n
1
n
−
1
=
1
n
\frac{1}{n}0+\frac{n-1}{n}\frac{1}{n-1}=\frac{1}{n}
n10+nn−1n−11=n1。以此类推可知,每个数出现在第
k
k
k位的概率都等于
1
n
\frac{1}{n}
n1。
其次,每个排列取到的概率都是 1 n ! \frac{1}{n!} n!1。给定一个排列 ( a 1 , a 2 , . . . , a n ) (a_1,a_2,...,a_n) (a1,a2,...,an),第一遍循环中 a 1 a_1 a1恰好换到第 1 1 1位的概率是 1 n \frac{1}{n} n1,第二遍循环中 a 2 a_2 a2恰好换到第 2 2 2位的概率是 1 n − 1 \frac{1}{n-1} n−11,以此类推。根据乘法原理,排列 ( a 1 , a 2 , . . . , a n ) (a_1,a_2,...,a_n) (a1,a2,...,an)出现的概率是 1 n 1 n − 1 1 n − 2 . . . 1 1 = 1 n ! \frac{1}{n}\frac{1}{n-1}\frac{1}{n-2}...\frac{1}{1}=\frac{1}{n!} n1n−11n−21...11=n!1。