从满足特定条件的列表中选择随机元素的最快方法

时间:2009-11-18 19:08:47

标签: java algorithm random

我需要从列表中选择一个满足特定条件的随机元素。我一直在使用的方法有效,但我确信并非一切都有效。最有效的方法是什么?

以下代码位于while(true)循环内,因此在每次迭代时对列表进行洗牌显然效率不高。

Foo randomPick = null;
Collections.shuffle(myList);
for (Foo f : myList) {
    if (f.property) {
        randomPick = f;
        break;
    }
}

提前致谢!

11 个答案:

答案 0 :(得分:7)

最有效的解决方案部分取决于您选择随机元素的频率,是否要选择不同的随机元素,以及哪些元素符合标准

一些选择:

  • 创建仅包含符合条件的元素的副本。然后,您可以将其移动并对其进行迭代以获得连续的不同随机元素,或者通过选择随机索引来选择任意随机元素。这显然是时间和空间上的O(n)设置,但此后效率很高。
  • 将集合洗牌一次,然后按照您的方式迭代 - 但保留迭代器。或者,使用索引手动执行迭代。这将允许您获得不同的随机元素。这是O(n)再次设置。
  • 继续从原始列表中挑选随机元素,直到找到符合条件的元素。如果你有一个只有少数“有效”项目的大型列表,这可能需要很长时间。这不需要设置,但你可能会通过反复测试相同的元素来“浪费”工作。
  • 混合方法:继续从原始列表中挑选随机元素,但如果它们不符合标准,则删除。不幸的是,对于ArrayList的常见情况,从列表中间删除也是O(n)操作:(

(请注意,这主要是假设ArrayList的复杂性。例如,LinkedList中的特定索引是O(n),但是删除很便宜。)

答案 1 :(得分:3)

使用lazy shuffle:它只在需要时计算并产生混洗元素。

C#实现,但很容易转换为Java:Is using Random and OrderBy a good shuffle algorithm?

答案 2 :(得分:2)

为什么不选择一个随机索引整数,然后测试它的属性呢?

而不是改组

  public Foo picker() {
    while (true) { // TODO: realistically, you need to deal with exit conditions
      int randIdx = myRand.nextInt();
      Foo randomPick = myList.get(randIdx % randIdx);
      if (randomPick.property)
        return randomPick;
    }
  }

注意这确实假设列表的非平凡数字的属性为true,并且这些属性会发生变化。

如果两个假设被伪造,你需要选择一个子集,然后随机选择其中一个。

答案 3 :(得分:1)

由于你正在进行洗牌,你基本上只触及每个元素一次,所以你已经有一个至少为N的大O.如果你选择一个随机索引,然后测试该位置的元素,那么你是在你触及N个元素之前得到你想要的变量,保证,从而保证改进。如果你有20%的元素分布,那么你会期望每5个随机索引给你一个符合你标准的元素。虽然这不是保证,但这是一个可能性。在绝对最坏的情况下,你会选择所有80%不符合你标准的元素,然后下一个将是你的随机元素。你的最大执行将被限制为.8N + 1,仍然优于N.平均而言,你的大O成本将是5-10的常数。随着N的增加,WAAAAY在执行方面更好。

答案 4 :(得分:1)

只需选择一个随机索引;

Random r = new Random();
while (true)
{
    ...
    Foo element = null;
    while (element == null)
    {
        Foo f = myList.get(r.nextInt(myList.size());
        if (f.property) element = f;
    }
    ...
}

答案 5 :(得分:0)

我建议从myList中制作一个临时过滤列表,其中每个项目都符合您的条件。然后在每次迭代时,您可以随机播放并选择顶部项目,或使用随机索引选择随机项目。

答案 6 :(得分:0)

    List<Foo> matchingItems = new ArrayList<Foo>();
    for(Foo f : myList) {
      if (f.property) matchingItems.add(f);
    }

   Random randomGenerator = new Random();
   int index = randomGenerator.nextInt(matchingItems.size());
   Foo randomFoo = matchingItems.get(index);

答案 7 :(得分:0)

您可以使用linear congruential random number generator循环遍历列表的索引,再加上一些可以选择合适的参数,然后检查此序列索引的值,同时丢弃索引大,挑选你找到的第一个元素。如果你发现什么都没有,你可以在随机序列开始重复时停止。

为此, m 需要至少与列表一样大。为了能够选择 a ,模数 m 必须包含因子8或某个素数的平方&gt; = 3.c应该随机选择,以便它是相对的素数到m。也可以随机选择初始值。

答案 8 :(得分:0)

不止一次随机地改组阵列是愚蠢的。

洗牌一次。然后,对于随机值的每个请求,按顺序返回下一个值,直到您的列表用完为止。

答案 9 :(得分:0)

O(N):

Random r = new Random();
int seen = 0;
Foo randomPick = null;
for (Foo foo : myList) {
  if (foo.property && r.nextInt(++seen) == 0) {
    randomPick = foo;
  }
}

答案 10 :(得分:0)

请查看此链接

http://www.javamex.com/tutorials/random_numbers/random_sample.shtml

public static <T> T[] pickSample(T[] population, int nSamplesNeeded, Random r) {
  T[] ret = (T[]) Array.newInstance(population.getClass().getComponentType(),
                                nSamplesNeeded);
  int nPicked = 0, i = 0, nLeft = population.length;
  while (nSamplesNeeded > 0) {
    int rand = r.nextInt(nLeft);
   if (rand < nSamplesNeeded) {
      ret[nPicked++] = population[i];
      nSamplesNeeded--;
   }
   nLeft--;
   i++;
}
return ret;
}