【问题标题】:Why is this way of randomly generating a graph unfair?为什么这种随机生成图表的方式不公平?
【发布时间】:2017-11-14 02:06:06
【问题描述】:

我的目标是生成一个有 n 个顶点的有向图,这样每个顶点都有一条出边和一条进边。我认为这样做的一种方法是将所有顶点放在一个罐子中,然后为顶点轮流洗牌并拉出条目 - 例如,如果顶点 1 拉出顶点 3,那么这意味着将有一条从 1 到 3 的边。如果一个顶点将自己拉出底池,它只会把它放回去并改组。如果最后,最后一个顶点发现锅只包含自己,那么我们需要重新开始。这是我的 Kotlin 代码:

fun generateGraph(n: Int): Map<Int, Int> {
    val vertices : List<Int> = (1..n).toList()
    while (true) {
        val pot = vertices.toMutableList()
        val result = mutableMapOf<Int, Int>()
        for (vertex in 1 until n) {
            do {
                java.util.Collections.shuffle(pot)
            } while (pot[0] == vertex)
            result.put(vertex, pot.removeAt(0))
        }
        if (pot[0] != n) {
            result.put(n, pot.removeAt(0))
            return result
        }
        else {
            // The last vertex left in the pot is also the last one unassigned.  Try again...
        }
    }
}

它似乎工作。但是,在测试时,我发现它的一些图表比其他图表更多。当 n 为 3 时,唯一有效的图是循环

{1=3, 2=1, 3=2}
{1=2, 2=3, 3=1}

但我发现第一个出现的次数是第二个的两倍:

fun main(args: Array<String>) {    
    val n = 3
    val patternCounts = mutableMapOf<Map<Int, Int>, Int>()
    val trials = 10000
    (1..trials).forEach({
        val graph = generateGraph(n)
        patternCounts[graph] = patternCounts.getOrDefault(graph, 0) + 1
    })
    println(patternCounts)
}

刚刚打印了这个运行

{{1=3, 2=1, 3=2}=6669, {1=2, 2=3, 3=1}=3331}

我错过了什么?而且,有没有办法让这个公平?

【问题讨论】:

    标签: algorithm graph kotlin


    【解决方案1】:

    不难看出为什么会出现这种结果。顶点 1 与顶点 3 有一半时间匹配。如果发生这种情况,则不能拒绝该图,因为仅当最后一个剩余顶点为 n(在本例中为 3)并且该顶点已被使用时才会发生拒绝。所以有一半的时间你会得到 {(1,3), (2,1), (3,2)}。

    另一半时间,顶点 1 将与顶点 2 匹配,但在顶点 2 与顶点 1 匹配后,这些情况的一半(即总数的 ¼)将被拒绝。所以 {(1,2 ), (2,3), (3,1)} 将被选择四分之一的时间。

    在剩下的季度,整个过程将重复,这意味着 {(1,3), (2,1), (3,2)} 将继续被选择两次。

    一种解决方案是在将顶点与其自身匹配后立即拒绝整个图。这种情况下,选择前无需重新洗牌;如果图表被拒绝,您只会重新洗牌。

    一般的问题是,将顶点与其自身匹配的情况并不独立于所有其他选择。因此,仅在某些匹配后重新洗牌并在其他匹配后拒绝会导致偏见。

    在任何比赛后拒绝并重新开始可能不是最有效的解决方案,但它会起作用。使算法更有效的一种方法是增量洗牌,而不是进行整个洗牌然后验证它。另一种可能性在引用自this question on Mathematics Stack Exchange

    的论文中进行了描述

    【讨论】:

    • 这真的很酷。这意味着如果您对自己的操作方式天真,那么许多“从帽子中挑选名字”类型的游戏是不公平的。想象一下在一个聚会上,如果有人拔了自己的名字,谁会想重新设置整个帽子?
    • @reveloper:您正在寻找的是所谓的“错位”——没有元素匹配自身的排列——众所周知,所有排列中大约有 1/e 是错位。我添加了一个可能有用的链接。
    【解决方案2】:

    我错过了什么?而且,有没有办法让这个公平?

    您缺少的是您的算法是不公平的。

    首先,您需要知道软件随机数生成器不是真正的随机数。它总是让它看起来很公平,不像真正的随机。

    然后,考虑以下内容

    java.util.Collections.shuffle(pot)
    

    给你 3 个结果。

    1, 2, 3
    1, 3, 2
    2, 1, 3
    2, 3, 1
    3, 1, 2
    3, 2, 1
    

    如果您删除了 do-while 条件和 if 条件,则所有结果都具有相似的计数。

    但是,do-while 条件会阻止 position = value。可能的结果是

    2, 1, 3
    2, 3, 1
    3, 1, 2
    

    请注意,结果的分布是均匀的。考虑以下几点:

    When vertex == 1:
        case pot[0] == 1:
            reroll
        case pot[0] == 2:
            continue // 50%
        case pot[0] == 3:
            continue // 50%
    
    If the result[0] == 2:
        When vertex == 2:
            case pot[0] == 1:
                continue // 25%
            case pot[0] == 3:
                continue // 25%
    
    If the result[0] == 3:
        When vertex == 2:
            case pot[0] == 1:
                continue // 50%
            case pot[0] == 2:
                reroll
    
    Result:
        2, 1, 3 (25%)
        2, 3, 1 (25%)
        3, 1, 2 (50%)
    

    然后,if-condition DISCARD 2, 1, 3(与while循环不同的是不重新滚动。它又从头开始。剩下的结果是

        2, 3, 1 (25%)
        3, 1, 2 (50%)
    

    (3, 1, 2):(2, 3, 1) 大约是 2:1,与您的结果相符。

    解决方案

    fun generateGraph(n: Int): Map<Int, Int> {
        val vertices : List<Int> = (1..n).toList()
        loop@ while (true) {
            val pot = vertices.toMutableList()
            val result = mutableMapOf<Int, Int>()
            // No need to shuffle evey position
            java.util.Collections.shuffle(pot)
            for (vertex in 1..n) {
                val value = pot[vertex-1]
                // if position == value, always start from scratch
                if (value == vertex)
                    continue@loop
                result.put(vertex, value)
            }
            return result
        }
    }
    

    此外,在怀疑随机数生成器的数字分布之前,您应该提高概率和统计的数学能力。

    【讨论】:

      猜你喜欢
      • 2011-05-21
      • 2014-03-21
      • 1970-01-01
      • 2013-02-17
      • 2021-12-10
      • 2014-02-28
      • 2016-09-29
      • 2013-05-13
      • 2012-06-26
      相关资源
      最近更新 更多