【正解】高纳德置乱算法(Knuth-Fisher-Yates algorithm)
准确性:这个算法是无偏的,即每个排列都是等可能的
效率:时间复杂度Θ(n),无需额外空间(基于交换元素)
伪代码:
int a[n]
for i from n-1 to 1
get a random_integer j (0≤j≤i)
swap a[i] with a[j]
end
以下是C++实现方法:
①使用STL内置的random_shuffle函数实现;
②手动实现;
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <ctime>
using namespace std;
int main()
{
int a[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; //原始数列
//random_shuffle(a, a+10); //random_shuffle:STL洗牌函数
srand(time( NULL )); //随机数种子
for(int i = 9; i > 0; --i)
swap(a[i], a[rand() % (i + 1)]); //swap:STL交换函数
for(int i = 0; i < 10; ++i)
cout << a[i] << ' '; //输出乱序后数列
return 0;
}
【错解一】
伪代码:
int a[n]
for i from n-1 to 1
get a random_integer j (0≤j≤i-1)
swap a[i] with a[j]
end
分析:将产生一个随机的错位排列,即没有一个元素在原来的位置上
【错解二】Naive algorithm
伪代码:
int a[n]
for i from n-1 to 1
get a random_integer j (0≤j≤n-1)
swap a[i] with a[j]
end
这种方法在不仔细思考地情况下极其容易被认为是正确的,但其实是错误的。
分析:此方法将产生n^n中不同的交换方式,每种交换方式是等可能的,而所有排列方式有 n! 种。
显然n^n mod n! ≠ 0(因为 n^n 没有 (n-1) 这个因子而 n! 有),这意味着 n! 种排列方式不使等可能的。
事实上:
①三元素数组乱序时
②四元素乱序时
与正解算法比较,可以看出其正确性有较大问题。
【参考资料】
【C++ STL应用与实现】64: 如何使用shuffle和random_shuffle : 洗牌 (since C++11)