不重复随机数列生成算法
首先我们来看命题:
给定一个正整数n,需要输出一个长度为n的数组,数组元素是随机数,范围为0 n-1,且元素不能重复。比如 n = 3 时,需要获取一个长度为3的数组,元素范围为0-2,
比如 0,2,1。
这个问题的通常解决方案就是设计一个 hashtable ,然后循环获取随机数,再到 hashtable 中找,如果hashtable 中没有这个数,则输出。下面给出这种算法的代码
如上图所示,我们设计一个顺序的数组,假设n = 4
第一轮,我们取 0 3 之间的随机数,假设为2,这时,我们把数组位置为2的数取出来输出,并把这个数从数组中删除,这时这个数组变成了
第二轮,我们再取 0-2 之间的随机数,假设为1,并把这个位置的数输出,同时把这个数从数组删除,以此类推,直到这个数组的长度为0。这时我们就可以得到一个随机的不重复的序列。
这个算法的好处是不需要用一个hashtable 来存储已获取的数字,不需要反复尝试。算法代码如下:
?
第一轮,我们随机获得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;
}.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas,"Courier New",courier,monospace; background-color: rgb(255, 255, 255); }.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }
?
下面是n 等于1万,10万和100万时的测试数据,时间单位为毫秒。从测试数据看GetRandomSequence2的用时和n基本成正比,线性增长的,这个和理论上的算法复杂度O(n)也是一致的,另外两个算法则随着n的增大,用时超过了线性增长。在1百万时,我的算法比用hashtable的算法要快10倍以上。
?
?100001000001000000GetRandomSequence05441075GetRandomSequence1111038124205GetRandomSequence21782