我有一组在一个矢量对象从中我想选择的随机子集(例如100个项目回来;挑5随机地)。 在我的第一(很草率)通过我做了一个非常简单的,也许过于聪明的解决方案:
Vector itemsVector = getItems();
Collections.shuffle(itemsVector);
itemsVector.setSize(5);
虽然这具有很好的和简单的优势,我怀疑它不会规模非常好,即Collections.shuffle()必须是(3)至少(N)。 我不太聪明的选择是
Vector itemsVector = getItems();
Random rand = new Random(System.currentTimeMillis()); // would make this static to the class
List subsetList = new ArrayList(5);
for (int i = 0; i < 5; i++) {
// be sure to use Vector.remove() or you may get the same item twice
subsetList.add(itemsVector.remove(rand.nextInt(itemsVector.size())));
}
更好的办法任何建议,以绘制出从集合随机子集?
Answer 1:
乔恩·本特利讨论这两种“编程珠玑”或“更多的编程珍珠”。 你需要注意你的m个选择的过程中N,但我认为所示的代码工作正常。 而不是随意打乱所有的项目,你可以做随机洗牌洗牌只有第N个位置 - 这是一个有用的节省当N << M。
克努特还讨论了这些算法的 - 我相信这将是第3卷“排序和搜索”,但我的集合包装未决房屋的举动,所以我不能正式确认。
Answer 2:
@Jonathan,
我相信这是你在谈论的解决方案:
void genknuth(int m, int n)
{ for (int i = 0; i < n; i++)
/* select m of remaining n-i */
if ((bigrand() % (n-i)) < m) {
cout << i << "\n";
m--;
}
}
这是由乔恩·本特利编程珠玑的127页上,并基于关闭Knuth的实现。
编辑:我刚才看到129页上的进一步修改:
void genshuf(int m, int n)
{ int i,j;
int *x = new int[n];
for (i = 0; i < n; i++)
x[i] = i;
for (i = 0; i < m; i++) {
j = randint(i, n-1);
int t = x[i]; x[i] = x[j]; x[j] = t;
}
sort(x, x+m);
for (i = 0; i< m; i++)
cout << x[i] << "\n";
}
这是基于这样的思想:“......我们需要洗牌只有数组的第m个元素......”
Answer 3:
如果你想从n的列表中选择k个不同的元素,你上面给的方法将是O(N)或O(KN),因为从Vector移除元素将导致arraycopy所有单元上下移动。
既然你问的最佳方式,它取决于你允许你输入列表做什么。
如果这是可以接受修改输入列表,如你的例子,那么你可以简单地交换ķ随机元素的列表的开头和O返回他们像这样(k)的时间:
public static <T> List<T> getRandomSubList(List<T> input, int subsetSize)
{
Random r = new Random();
int inputSize = input.size();
for (int i = 0; i < subsetSize; i++)
{
int indexToSwap = i + r.nextInt(inputSize - i);
T temp = input.get(i);
input.set(i, input.get(indexToSwap));
input.set(indexToSwap, temp);
}
return input.subList(0, subsetSize);
}
如果该列表必须就开始相同的状态结束了,你可以跟踪你交换了位置,然后复制所选子列表后,列表恢复到原来的状态。 这仍然是一个O(k)的溶液中。
但是,如果您不能修改输入列表中的所有,k是大于n(如5 100)少得多,这将是更好不要每次删除选定的元素,但只需选择每一个元素,如果你有机会重复,折腾出来,并重新选择。 这会给你O(KN /(NK)),它仍然是接近O(K)当n主导ķ。 (例如,如果k小于N / 2,然后将其降低到O(k))的。
如果K不被n为主,而不能修改的列表,你还不如复制原始的列表,并使用你的第一个解决方案,因为为O(n)将是多么的O(k)的一样好。
正如其他人指出,如果根据随机性强,每一个子表是可能的(偏),你一定会需要比更强烈的东西java.util.Random
。 见java.security.SecureRandom
。
Answer 4:
我写了一个高效的实现这个几个星期前。 这是在C#中,但转换到Java是微不足道的(本质上是相同的代码)。 有利的一面是,它也完全公正的(其中一些现有的答案都不是) - 一种方式来测试就在这里 。
它是基于一个Durstenfeld执行费雪耶茨洗牌。
Answer 5:
您使用随机挑选元素的第二个解决方案似乎是合理,但是:
Answer 6:
多少成本去掉? 因为如果需要的阵列重写一个新的内存块,然后你做O(5N)的第二个版本,而不是为O(n),你之前想要的操作。
你可以创建设置为false,然后布尔值的数组:
for (int i = 0; i < 5; i++){
int r = rand.nextInt(itemsVector.size());
while (boolArray[r]){
r = rand.nextInt(itemsVector.size());
}
subsetList.add(itemsVector[r]);
boolArray[r] = true;
}
如果你的子集是由显著利润率比你的总大小,这个方法工作。 由于这些尺寸接近彼此(即1/4的大小或某事),你会得到在该随机数生成更多的碰撞。 在这种情况下,我会想办法让你的整数较大的阵列的大小的列表,然后洗牌整数该名单,并拉出从第一要素,让您的(非碰撞)的indeces。 这样的话,你必须为O(n)的建设整数数组的成本,另一个为O(n)在洗牌,但没有来自内部的冲突,而检查不到的潜在O(5N)是去除可能成本。
Answer 7:
我最好的个人选择您最初的实施:非常简洁。 性能测试将显示它扩展有多好。 我实现了一个非常类似的代码块在一个体面被虐方法,它足够的推广。 的特定代码依赖于含有> 10,000项目以及阵列。
Answer 8:
Set<Integer> s = new HashSet<Integer>()
// add random indexes to s
while(s.size() < 5)
{
s.add(rand.nextInt(itemsVector.size()))
}
// iterate over s and put the items in the list
for(Integer i : s)
{
out.add(itemsVector.get(i));
}
Answer 9:
这是一个计算器非常类似的问题。
总结从该页面(从用户凯尔弗斯特一个)我最喜欢的答案:
- 为O(n)的解决方案 :通过迭代您的列表,并复制出的元素的概率(#needed / #remaining)(或参考于此)。 例如:如果k = 5和n = 100时,则采取与概率5/100的第一个元素。 如果复制了一个,就选择下一个与概率4/99; 但如果你没有走第一个,概率是5/99。
- O(ķ日志K)或O(K 2):构建ķ指数的排序列表(在数字{0,1,...,N-1})由随机选择的数<n,则随机选择一个数<N-1等,在每个步骤中,你需要recallibrate你的选择,以避免冲突,保持概率均匀。 作为一个例子,如果k = 5和n = 100,和你的第一选择是43,您的下一个选择是在范围[0,98],以及如果它是> = 43,那么你把它加1。 所以,如果你的第二个选择是50,那么你把它加1,你有{43} 51。 如果你的下一个选择是51加2,把它拿到{43,51,53}。
下面是一些pseudopython -
# Returns a container s with k distinct random numbers from {0, 1, ..., n-1}
def ChooseRandomSubset(n, k):
for i in range(k):
r = UniformRandom(0, n-i) # May be 0, must be < n-i
q = s.FirstIndexSuchThat( s[q] - q > r ) # This is the search.
s.InsertInOrder(q ? r + q : r + len(s)) # Inserts right before q.
return s
我是说,时间复杂度为O(K 2) 或 O-(K数K),因为这取决于你如何快速搜索并插入到你的的集装箱。 如果s是一个正常的列表,这些操作的一个是线性的,你会得到K ^ 2。 但是,如果你愿意建立S作为平衡二叉树,你可以走出Ô(K数k)的时间。
Answer 10:
两种解决方案,我不认为出现在这里 - 的对应是相当长的,并包含一些链接,但是,我不认为所有的职位都涉及选择K的SUBST的问题elemetns出一组N个元素的。 [通过“设置”,我指的是数学术语,即所有的元素出现一次,顺序并不重要。
索尔1:
//Assume the set is given as an array:
Object[] set ....;
for(int i=0;i<K; i++){
randomNumber = random() % N;
print set[randomNumber];
//swap the chosen element with the last place
temp = set[randomName];
set[randomName] = set[N-1];
set[N-1] = temp;
//decrease N
N--;
}
这类似于丹尼尔给出的答案,但它实际上是非常不同的。 这是O(k)的运行时间。
另一个解决方案是使用一些数学:考虑数组索引作为Z_n所以可随机选择2号中,X是互质到n,即chhose GCD(X,N)= 1,和另一个,一个,这是“起点” - 然后串联:一个%N,A + X%N,A + 2 * x%的N,...一个+(K-1)×x%的n是不同的号码(一个序列,只要ķ<= N)。
文章来源: best way to pick a random subset from a collection?