从大小N列表中进行2N随机选择,同时避免顺序重复

时间:2016-02-17 03:20:52

标签: algorithm random

给定N个对象的列表,如何以随机顺序进行2N选择,以便连续两次不选择任何对象? (所有N个对象必须选择两次。)

编辑开始:我的目标是提供一个单词列表,一个在左侧,一个在右侧。没有两个连续的试验是同一个词。

我真的在问是否有一种已知的技术可以做到这一点,或者有人可以想办法在没有小提琴的情况下做到这一点。 编辑结束

我的尝试如下所示,可能有助于作为测试模板。

前提是生成随机指数,然后检查重复。找到重复时,将第一次重复与之前的值交换。最后,如果较低的两个元素相同,则交换2 nd 和3 rd

<script type="text/javascript">
    function init() {
        for (rep = 0; rep < 100000; rep++) {
            var indices = [0,0, 1,1, 2,2];
            shuffle(indices);

            for (var i = indices.length - 1; i > 1; i--)
                if (indices[i - 1] == indices[i]) {
                    var tmp = indices[i - 1];
                    indices[i - 1] = indices[i - 2];
                    indices[i - 2] = tmp;
                }

            if (indices[0] == indices[1]) {
                var tmp = indices[1];
                indices[1] = indices[2];
                indices[2] = tmp;
            }

            // test
            for (i = indices.length - 1; i > 1; i--)
                if (indices[i - 1] == indices[i])
                    rep = 1E8;  // fail
        }

        // **EDIT2:** BAD BAD BAD mistake in the check code!!!  Sorry!
        dbg.innerHTML = (rep >= 1E8) ? "Oh bother." : "OK";
    }

    function randomInt(max) { return Math.floor(Math.random() * max); }
    function shuffle(o) { for (var j, x, i = o.length; i; j = randomInt(i), x = o[--i], o[i] = o[j], o[j] = x); return o; }
</script>

<div>
    <span id="dbg" />
</div>

处理最低两个元素的方法失败对于从缩减列表中选择的替代方法是常见的。一个人可能最终会留下两个相同的元素。

(注意,不建议使用所提出的方法,因为可能没有统一的随机分布。

编辑开始: 该实验应该在随机的&#34;中提供单词。订购。我的方法有一个小提琴&#39;最后两个数字似乎是错误的 - 至少是讨厌的。

(我同意整个序列不是真正随机的。我不是数学家,只是试图编写实验代码。) 编辑结束

(随机代码取自Jeff's answer。)

3 个答案:

答案 0 :(得分:1)

制作2n元素列表。走过去。如果您在i-1, i位置发现重复,则将i元素与ii-1i-2以外的随机位置交换。< / p>

(叹气,检查证明发现了一个错误。有些互换可以在没有创建早期配对的情况下完成。你可以通过交换道路来解决这个问题。但现在有1 / n你就是&#39 ;最后一对无法修复的东西结束。但它仍然接近随机。)

如果您的随机数生成器是真正随机的,这将给出完全随机的分布。

以下是i上的归纳证明的草图,在i步骤之后,所有没有重复的位置对(i-1, i)的分布同样可能。使用i = 2n-1的结果将显示算法有效。

p(i)成为i步之后可能出现分布的概率。如果结果适用于i,则i将明确定义。

接下来,对于i = 0来说,这是微不足道的,因为我们从一个随机的shuffle开始。所以p(0)定义明确。

在第一步之后,每个可能的分布都可能是由于概率为p(0)的混乱的结果,或者是在(0, 1)中得到的重复而得到的。交换到这个发行版。第二种可能性以概率p(0)/(n-2)发生。因此,结果适用于1p(1) = p(0) + p(0)/(n-2)

对于i中的2, ..., 2n-1,您应用与i=1相同的参数,但p(i) = p(i-1) + p(i-1)/(n-3)除外。

答案 1 :(得分:1)

方式花费太多时间思考这个问题[注1]之后,我无法提出除标准拒绝算法之外的无偏随机生成算法。 (生成{0, 0, 1, 1,..., N-1, N-1}的shuffle序列的无偏样本,如果序列包含两个连续元素,则拒绝该序列。)

幸运的是,在这种情况下,拒绝算法并不可怕,因为超过三分之一的序列符合条件,并且您通常可以在完全生成之前拒绝它们。因此,您可能希望在找到一个有效的序列之前平均生成少于两个被拒绝的序列,并且提前拒绝的可能性意味着生成每个长度为2N的序列的预期时间可能小于4N,这意味着它可能会更快而不是生成一个序列并再次通过它来修复&#34;修复&#34;重复。

超过三分之一序列符合条件的断言草图:

  1. 2 N个序列(包括重复)的数量是(2 N i)!/ 2 N < / sup>,基于标准组合计数公式。我称之为 A n )。我们可以很容易地看到 A n n (2 n -1) A < / em>的(名词 -1)。 (第一项是 n 而不是2 n 因为 A n )中的分母还有一个2 。)

  2. 没有重复的2 N 序列的数量( a n ),同样任意的名称)由递归 a n )= n (2 -1) a n -1)+ n (2 -1) a n -2)[注2]。这种递归非常相似,但它的第二项相对较小。

  3. 它比 a n )&lt; A n ),因为它正在计算相同序列的子集。此外, A n )/ A n -1)= n (2 n -1)而 a n )/ a n -1)&gt; 名词的(2 名词的-1)。所以 a n )的增长速度略快于 A n ),但它永远无法赶上。由于 A (2)是90而 a (2)是30,我们可以断言1&lt;所有 n A n )/ a n )≤3 2.(数字很快非常。根据OEIS a (16)是1453437879238150456164433920000,所以 A (16 )/ a (16)是2.762455825573288。我的猜测是这个比率会收敛到一个不太远的值,但是我没有做任何证实这个猜测。如果我要走出去我可能会推测这个比例的限制将是 e ,但相似性可能完全是巧合。)

  4. 这里有一些未经过严格测试的javascript代码,它随机地将一个向量混合以产生无重复的结果。 (你需要初始化一次向量,但是你将从之前的shuffle开始随机重新初始化,所以我强烈建议只初始化一次。)

    function reshuffle(indices) {
      for (var i = 0, len = indices.length; i < len;) {
        var j = i + Math.floor(Math.random() * (len - i));
        var tmp = indices[j]; indices[j] = indices[i]; indices[i] = tmp;
        if (i > 0 && indices[i - 1] == indices[i]) i = 0;
        else ++i;
      }
      return indices;
    }
    

    在快速测试中,通过添加循环计数器,循环执行平均22.9次以查找随机12元素向量,并执行346次以查找随机200元素向量。

    注释

    1. 好吧,几个小时。但我还有其他事情要做。

    2. 我最终设法证明这一点,但由于缺乏LaTeX,我不打算在这里写证明;序列是A114938 in the OEIS,你会在那里找到相同的递归。

答案 2 :(得分:0)

一种方法是执行N个项目的Fischer-Yates shuffle,然后按顺序返回它们。然后再次洗涤数组并按顺序返回它们。连续两次返回相同项目的唯一可能性是,如果第一个shuffle中的最后一个项目是第二个shuffle中的第一个项目。这很容易克服:

array = shuffle(array);
for each item in array
    return item;

lastItem = array[last];
array = shuffle(array);
if (array[0] == lastItem)
    swap(array[0], array[1]);
for each item in array
    return item;

如果你无法改组数组,你可以轻松地构建索引数组并将其改组。

我不清楚这是否会以您想要的方式解决问题。根据您的描述,只要重复项不是连续的,看起来好像有一个包含重复项的列表。我的解决方案在生成所有其他项目之前不会产生重复。