假设 n = 4
第一轮,我们随机获得2时,我们不将 2 从数组中移除,而是将数组的最后一个元素移动到2的位置
这时数组变成了
第二轮我们对 0-2 取随机数,这时数组可用的最后一个元素位置已经变成了2,而不是3。假设这时取到随机数为1
我们再把下标为2 的元素移动到下标1,这时数组变成了
以此类推,直到取出n个元素为止。
这个算法的优点是不需要用一个hashtable 来存储已获取的数字,不需要反复尝试,也不用像上一个算法那样删除数组元素,要做的只是每次把数组有效位置的最后一个元素移动到当前位置就可以了,这样算法的复杂度就降低为 O(n) ,速度大大提高。
经测试,在 n= 100000 时,这个算法的用时仅为7ms。
下面给出这个算法的实现代码
/// <summary>
/// Designed by eaglet
/// </summary>
/// <param name="total"></param>
/// <returns></returns>
public static int[] GetRandomSequence2(int total)
{
int[] sequence = new int[total];
int[] output = new int[total];
for (int i = 0; i < total; i++)
{
sequence[i] = i;
}
Random random = new Random();
int end = total - 1;
for (int i = 0; i < total; i++)
{
int num = random.Next(0, end + 1);
output[i] = sequence[num];
sequence[num] = sequence[end];
end--;
}
return output;
}
下面是n 等于1万,10万和100万时的测试数据,时间单位为毫秒。从测试数据看GetRandomSequence2的用时和n基本成正比,线性增长的,这个和理论上的算法复杂度O(n)也是一致的,另外两个算法则随着n的增大,用时超过了线性增长。在1百万时,我的算法比用hashtable的算法要快10倍以上。
10000 | 100000 | 1000000 | |
GetRandomSequence0 | 5 | 44 | 1075 |
GetRandomSequence1 | 11 | 1038 | 124205 |
GetRandomSequence2 | 1 | 7 | 82 |