Java - 随机播放数组中特定数量的元素

时间:2014-01-30 15:03:01

标签: java arrays algorithm shuffle

我的问题如下:我需要对数组进行混洗,然后只得到前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左右。

3 个答案:

答案 0 :(得分:3)

从数组中获取N混洗元素的简单方法如下:

  1. 选择一个随机元素r
  2. 将元素r添加到输出中。
  3. 将数组的最后一个元素移动到r的位置,并将数组大小缩小1.
  4. 重复N次。
  5. 在代码中:

    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元素,而不是改组整个数组。

    您的优化算法并非最佳,原因有两个:

    1. 第一个N元素永远不会被洗牌。例如,元素0永远不会出现在混洗数组中的第1位。
    2. 你还在做很多工作。如果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;
}