为什么这种随机生成图形的方式不公平?

时间:2017-11-14 02:06:07

标签: algorithm graph kotlin

我的目标是生成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}

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

2 个答案:

答案 0 :(得分:5)

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

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

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

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

一般问题是顶点与自身匹配的情况并不独立于所有其他选择。所以只是在某些比赛之后重新洗牌并在其他比赛之后拒绝而导致偏见。

任何匹配后拒绝并重新启动可能不是最有效的解决方案,但它会起作用。使算法更有效的一种方法是逐步进行混洗,而不是进行整个混洗然后验证它。另一种可能性在this question on Mathematics Stack Exchange

引用的论文中描述

答案 1 :(得分:2)

  

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

你缺少的是你的algrothim 是不公平的。

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

然后,请考虑以下

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条件会阻止position = value。可能的结果是

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

请注意,结果的分布均为 NOT 。请考虑以下事项:

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条件 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
    }
}

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