我的问题如下:我需要对数组进行混洗,然后只得到前N个元素。
我目前正在改组整个阵列,它有50多个元素,但这给我带来性能问题,因为洗牌程序被称为1e + 9次。
我目前正在实施Fisher-Yates算法进行随机播放:
public static void shuffle(int[] array) {
Random gen = new Random();
for (int i = array.length - 1; i > 0; i--) {
int index = gen.nextInt(i + 1);
int a = array[index];
array[index] = array[i];
array[i] = a;
}
}
然后我只选择前N个元素。
我也尝试过使用Reservoir sampling,但它只让我节省了1秒钟。这还不够,因为我的程序运行了30秒。另外,我可能错误地实现了它,因为与Fisher-Yates算法相比,我没有得到相同的结果。这是我的实施:
public static int[] shuffle(int[] array, int N) {
int[] ret = new int[N];
for (int i = 0; i < N; i++) {
ret[i] = array[i];
}
Random gen = new Random();
int j;
for (int i = N; i < array.length; i++) {
j = gen.nextInt(i+1);
if (j <= N - 1)
ret[j] = array[i];
}
return ret;
}
总而言之,我需要的是一种改组算法,它使用长度为N的搜索来选择N个随机元素,而不是50+。如果不可能的话,比Fisher-Yates和Reservoir采样更好。
注1:改变原来的“int []数组”不是问题。
注意2:N通常在10左右。
答案 0 :(得分:3)
从数组中获取N
混洗元素的简单方法如下:
r
。r
添加到输出中。r
的位置,并将数组大小缩小1. N
次。
在代码中:
public static int[] shuffle(int[] array, int N) {
int[] result = new int[N];
int length = array.length;
Random gen = new Random();
for (int i = 0; i < N; i++) {
int r = gen.nextInt(length);
result[i] = array[r];
array[r] = array[length-1];
length--;
}
return result;
}
这个算法优于FY,它只计算混洗数组的第一个N
元素,而不是改组整个数组。
您的优化算法并非最佳,原因有两个:
N
元素永远不会被洗牌。例如,元素0永远不会出现在混洗数组中的第1位。N=10
且总数组长度为1000000
,则您仍在计算1000000
个随机值,而您只需要10
。答案 1 :(得分:1)
您可以通过仅进行N次迭代来修改FY,然后获取数组的最后N个元素。或者,在数组的开头而不是结尾处构建混洗区域,并获取前N个元素。但是,这只会保存实际shuffle的迭代次数,并且那里的增益将是N / 50的因子。这可能还不够。
根据具体情况,您可以生成例如一百万个洗牌的甲板,然后为每个1e9外部迭代随机选择其中一个。这将为每个外部迭代生成一个随机数生成,加上当前shuffle调用的千分之一。
答案 2 :(得分:0)
当Collection.shuffle存在时,请尽量不重新发明轮子:
public static int[] shuffle(int[] array, int N) {
List<Integer> list = new ArrayList<Integer>();
for (int r : array) {
list.add(r);
}
Collections.shuffle(list);
Integer[] ret = list.toArray(); // Integer is int, except autoboxed
return ret;
}