随机且独特子集生成

4
假设我们有从1到25的数字,我们需要选择15个数字的集合。
可能的集合有3268760种。
在这3268760个选项中,你需要生成100000个。
如何以最佳方式生成这100000个唯一且随机的子集?
是否有一种算法可以实现这一点?
如果没有,检测重复项的最佳选项是什么?
我计划在PHP上完成这个任务,但一般解决方案就足够了,任何不太“学术”的参考资料都会对我有很大帮助。

只是为了明确 - 您是否关心集合成员的顺序? - timdev
显然不是。术语“集合”本身就暗示了一种无序结构,更明显的是,3268760计数(即C(15,25))也表明了这一点。 - mjv
错了——忽略了3268760的重要性。 - timdev
4个回答

4

有一种方法可以生成一个样本子集,该方法是随机的,保证不会重复,使用O(1)存储,并且可以在任何时候重新生成。首先,编写一个函数来根据其词汇索引生成组合。其次,使用第一个Combin(n, m)个整数的伪随机排列以随机顺序遍历这些组合。只需将数字0...100000输入置换中,使用置换的输出作为组合生成器的输入,并处理结果组合即可。


是的,非常有用的参考资料。谢谢Theran。字典索引肯定是一种方便的技术。我还没有完全理解伪随机置换部分,但第二遍阅读时我确定会明白的。 - mjv

2
这是基于mjv答案的PHP解决方案,与我想的一样。如果你运行100k组合,你确实会看到很多碰撞。然而,我很难设计一个避免它们的系统。相反,我们只需快速检查它们即可。
我将思考更好的解决方案……在这台笔记本电脑上,我可以在5秒内完成10k组合,在20秒以内完成20k组合。100k需要几分钟。
这些集合表示为(32位)整数。
<?PHP
    /* (c) 2009 tim - anyone who finds a use for this is very welcome to use it with no restrictions unless they're making a weapon */

    //how many sets shall we generate?
    $gNumSets = 1000;

    //keep track of collisions, just for fun.
    $gCollisions = 0;

    $starttime = time();

    /**
     * Generate and return an integer with exactly 15 of the lower 25 bits set (1) and the other 10 unset (0)
     */ 
    function genSetHash(){
      $hash = pow(2,25)-1;

      $used = array();

      for($i=0;$i<10;){

        //pick a bit to turn off
        $bit = rand(0,24);

        if (! in_array($bit,$used)){
          $hash =  ( $hash & ~pow(2,$bit) );
          $i++;  
          $used[] = $bit;  
        }
      }
      return  $hash;
    }

    //we store our solution hashes in here.  
    $solutions = array();

    //generate a bunch of solutions.
    for($i=0;$i<$gNumSets;){
      $hash = genSetHash(); 

      //ensure no collisions
      if (! in_array($hash,$solutions)){
        $solutions[] = $hash;
        //brag a little.
        echo("Generated $i random sets in " . (time()-$starttime) . " seconds.\n");
        $i++;
      }else { 
        //there was a collision. There will generally be more the longer the process runs.
        echo "thud.\n"; 
        $gCollisions++;
      }
    }

    // okay, we're done with the hard work.  $solutions contains a bunch of
    // unique, random, ints in the right range.  Everything from here on out
    // is just output.

    //takes an integer with 25 significant digits, and returns an array of 15 numbers between 1 and 25
    function hash2set($hash){
      $set = array();
      for($i=0;$i<24;$i++){  
        if ($hash & pow(2,$i)){
          $set[] = $i+1;
        }
      }
      return $set;
    }

    //pretty-print our sets.
    function formatSet($set){
      return "[ " . implode(',',$set) . ']';
    }

    //if we wanted to print them, 
    foreach($solutions as $hash){
      echo formatSet(hash2set($hash)) . "\n";
    }

    echo("Generated $gNumSets unique random sets in " . (time()-$starttime) . " seconds.\n");

    echo "\n\nDone.  $gCollisions collisions.\n";

我认为这些都是正确的,但现在已经很晚了,我已经喝了几瓶很好的啤酒。

(注意:保留HTML标签,不进行解释或修改)


对于这个功能性的PHP脚本,点赞!虽然算法不像Theran建议的那样数学优雅,但它仍然很实用,也可以通过ID /哈希重新生成子集。我认为碰撞相关的开销不是一个因素(正如onebyone指出的那样,总体上不到2%)。对于一个CWI(边编码边...)的努力来说,还不错。 - mjv
所有的答案都非常出色!将采纳代码的机会给了Tim。可惜不能接受多个答案。 - Cesar

2
他们必须是真正的随机吗?还是看起来像是随机的?
选择:生成一个包含所有25个元素的集合 - 使用Fisher-Yates / Knuth shuffle随机排列前15个元素,然后检查是否之前已经看到过这个排列。如果是,则忽略并重试。
重复项:您有25个存在或不存在的值 - 这可以轻松地哈希为整数值(如果第一个元素存在,则添加2 ^ 0,如果第二个存在,则添加2 ^ 1等 - 它可以直接表示为25位数字),因此您可以轻松地检查是否已经看到它。
您会得到相当多的冲突,但如果这不是关键性能代码段,那么可能是可行的。

即使选择最后的(100K)子集,也只有1/32的机会选到之前已经出现过的。因此,与某个假设算法相比,丢弃相等的开销不到3%,该算法需要花费相同的时间来生成候选项,并且不需要丢弃任何内容。如果您需要1.6M个子集,那就是另一回事了。 - Steve Jessop

2
你的环境中的随机数生成器(RNG)会提供在特定范围内均匀分布的随机数。这种类型的分布通常是需要的,比如如果你的子集模拟彩票抽奖,但重要的是要提到这一点,以防你正在建模例如在中学校园发现的人的年龄...
给定这个RNG,你可以在1到25之间“抽取”10(或15,见下文)个数字。这可能需要将生成器产生的随机数乘以(并四舍五入),并忽略大于25的数字(即重新抽取),具体取决于与RNG相关的确切API,但是在给定范围内进行抽奖很容易。当一个数字再次出现时,你也需要重新抽取。
我建议你只取10个数字,因为这些数字可以从1-25完整序列中删除,以产生一个15个数字的集合。换句话说,抽取15个数字放入和抽取10个数字拿出是一样的...
接下来,你需要断言集合的唯一性。你可以使用哈希来唯一地标识每个集合,而不是存储整个集合。这应该只需要少于25位,因此可以存储在32位整数上。然后你需要对这些值的高达100,000个进行有效的存储;除非你想将其存储在数据库中。
关于从所有可能的集合中取出的100,000个集合的唯一性问题,碰撞的概率似乎相对较低。编辑:哎呀...我太乐观了...这个概率并不那么低,在抽取第50,000个之后开始有大约1.5%的碰撞概率,会有相当多的碰撞,足以需要一个排除它们的系统...

通过存储要排除的元素的哈希值,可以节省一些哈希时间! - timdev
没错,这不会影响哈希的大小,但如果我们基于排除的数字进行计算,它的计算速度会更快。 - mjv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接