如何从K个对象中随机选择少于N个对象?

时间:2018-09-15 13:55:40

标签: arrays algorithm random data-structures

从4个对象中,1,2,3,4。想随机选择2个对象,但也可以不选择任何对象,也可以只选择1个对象。 (仅考虑组合。无顺序。)

因此,可能的状态为以下11种状态:

[(empty)],[1],[2],[3],[4],[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]

如何以11次出现一次的方式生成上述状态之一?

需要编写此的通用版本。从K个对象中随机选择少于N个对象。

1 个答案:

答案 0 :(得分:3)

您首先需要确定要拾取的对象数。在您的示例中,您有11个可能的子集,大小为1的1个,大小1的4个,大小2的6个。因此,应根据加权分布1:4:6选择大小0、1或2。可视化的一种方法是想象11个大小相等,间距相等的插槽:1用0标记,4用1标记,6用2标记。现在,将一个球随机放到其中一个插槽中,并注意标签。每个插槽都有一个接收球的机会相同,但是获得带有标签0、1或2的插槽的概率为1:4:6。

通常,k给出一组n大小的n!/(k!*(n-k)!)个对象的组合数。我们可以使用此公式来确定我们的加权分布。请注意,我遵循使用k表示从n可能性中拾取的对象数量的常规约定-您以相反的方式使用它们,这有点令人困惑。

确定了p的选择数量后,您就可以使用Fisher-Yates随机播放的Durstenfeld变体从输入中随机选择p个元素。

下面是一些Java代码来说明:

static <E> List<E> randomPick(List<E> in, int k)
{   
    int n = in.size();

    // determine number of elements to pick using a random selection
    // weighted by the number of subsets of each size, 0..k
    Random r = new Random();
    NavigableMap<Integer, Integer> map = new TreeMap<Integer, Integer>();
    int total = 0;
    for(int i=0; i<=k; i++)
    {
        total += fact(n)/(fact(i)*fact(n-i));
        map.put(total, i);
    }       
    int p = map.higherEntry(r.nextInt(total)).getValue();

    // Use Durstenfeld shuffle to pick p random elements from list
    List<E> out = new ArrayList<>(in);
    for(int i=n-1; i>=n-p; i--)
    {
        Collections.swap(out, i , r.nextInt(i + 1));
    }       
    return out.subList(n-p, n);
}

static int fact(int n)
{
    int f = 1;
    while(n > 0) f *= n--;
    return f;
}

测试:

public static void main(String[] args)
{
    List<Integer> in = Arrays.asList(1, 2, 3, 4);       
    for(int i=0; i<10; i++)
        System.out.println(randomPick(in, 2));
}

输出:

[]
[2, 1]
[4]
[3, 2]
[1]
[1, 4]
[2, 1]
[2, 3]
[4]
[1, 4]