最近遇到需要生成随机数组的问题:即生成一个大小为N,不重复的数组,参考了一下网络上的:
http://hi.baidu.com/%D6%D0%CC%EC%B4%F3%C3%D7/blog/item/572df00a550e0339b0351dee.html
这位同学提出了原始方法,并提出了改进:
首先一开始按人的思维想到的方法是不停取随机数,如果已经有了就不要,否则就添加到我们的结果数组中:
ArrayList list = new ArrayList();
int k = 0;
do
...{
k =random .Next (N);
if (!list.Contains(k))
list.Add(k);
}
while (list.Count < N);
这个方法简明,符合人的思维,但是计算机在检查一个集合是否包含某个元素的时候,是要一个个比较的。所以光是那个list.Contains(k) 本身就带着N的复杂度。
这还不是最致命的,关键是这个算法理论上有可能永远不会结束,最坏的情况下,可能会有N-1到1个数永远也取不到导致死循环——尽管概率很小,而且随着结果数组的增加,可取数的减少,取不中的几率增加,这个算法外循环看起来是N,但实际上复杂度却是N2
所以原始算法的实际复杂度是 N3 而且理论上存在永远不结束的可能。后文中还提到用哈希桶、用递归检测重复,实际上都是一个原理。
接下来这位同学给出了自己改进的算法:
public int[] GetRandomUnrepeatArray(int minValue, int maxValue, int count)
...{
Random rnd = new Random();
int length = maxValue - minValue + 1;
byte[] keys = new byte[length];
rnd.NextBytes(keys);
int[] items = new int[length];
for (int i = 0; i < length; i++)
...{
items[i] = i + minValue;
}
Array.Sort(keys, items);
int[] result = new int[count];
Array.Copy(items, result, count);
return result;
}
即先产生一个顺序数组,然后用C#.NET提供的数组排序进行随机快速排序,再按结果数组大小拷贝出结果,这个算法有一个复杂度为 N 的赋初值过程,关键在于Array.Sort这个函数,排序无论如何都需要一定时间,按经典快速排序理论,其复杂度一般为N*log N 最后还有一个拷贝数组的算法,实际复杂度也是N,因此该算法最终复杂度为 (2+log N)*N,如果初始数组和输出数组一样大则复杂度为(1+log N)*N
另外这个算法致命的缺陷在于使用的是byte型数排序,限制了随机数生成的范围不能超过256;另外即便是比如十几个数,也要先产生256大小的数组。
还有一个方法看来比较可爱:
int[] index = new int[15];
for (int i = 0; i < 15; i++)
index = i;
Random r = new Random();
//用来保存随机生成的不重复的10个数
int[] result = new int[10];
int site = 15;//设置下限
int id;
for (int j = 0; j < 10; j++)
{
id = r.Next(1, site - 1);
//在随机位置取出一个数,保存到结果数组
result[j] = index[id];
//最后一个数复制到当前位置
index[id] = index[site - 1];
//位置的下限减少一
site--;
}
先用N的复杂度生成初始数组,然后再用N的复杂度复制,每次复制完成后,都少了一个,这样实际效果是 2N 或者更小,空间复杂度也是2N 而且算法完全收敛,应该是一个不错的算法。
另外今天还想到一个与这个算法同数量级的算法:
void ExchgNum(ref int src, ref int dst)
{
int tmp = src; src = dst; dst = tmp;
}
int[] GetRandomArray(int count, int maxValue, int minValue)
{
if (minValue > maxValue) ExchgNum(ref minValue, ref maxValue);
int maxCount = maxValue - minValue + 1;
if (count > maxValue - minValue) count = maxCount;
int[] srcArray = new int[maxCount];
for (int i = 0; i < maxCount; i++)
{
srcArray[i] = i + minValue;
}
Random rd = new Random(DateTime.Now.Millisecond);
for (int i = 0; i < maxCount / 2 + 2; i++)
{
int src = rd.Next(maxCount); int dst = rd.Next(maxCount);
if(src != dst)ExchgNum(ref srcArray[src], ref srcArray[dst]);
}
int[] rst = new int[count];
Array.Copy(srcArray, rst, count);
return rst;
}
关键在于用N/2的时间随机交换初始数组,然后再拷贝,如果初始数组就是输出数组,复杂度可以减少到 1.5 N ,同时空间复杂度也降低到N