【问题标题】:Fisher yates algorithm is not yielding unbiased resultsFisher yates 算法没有产生无偏的结果
【发布时间】:2021-05-15 11:09:19
【问题描述】:

维基百科上描述的Fisher yates算法是

该算法产生一个无偏的排列:每个排列都是等可能的。

我浏览了一些文章,这些文章解释了朴素和费希尔耶茨算法如何产生集合中项目的有偏和无偏组合。

文章链接

Fisher-Yates Shuffle – An Algorithm Every Developer Should Know

Randomness is hard: learning about the Fisher-Yates shuffle algorithm & random number generation

文章继续展示了这两种算法的几乎无偏见和非常有偏见的结果图表。我试图重现概率,但我似乎无法产生差异。

这是我的代码

import java.util.*

class Problem {
    private val arr = intArrayOf(1, 2, 3)
    private val occurrences = mutableMapOf<String, Int>()
    private val rand = Random()

    fun biased() {
        for (i in 1..100000) {
            for (i in arr.indices) {
                val k = rand.nextInt(arr.size)
                val temp = arr[k]
                arr[k] = arr[i]
                arr[i] = temp
            }


            val combination = arr.toList().joinToString("")

            if (occurrences.containsKey(combination)) {
                occurrences[combination] = occurrences[combination]!! + 1
            } else {
                occurrences[combination] = 1
            }
        }

        print("Naive:\n")
        occurrences.forEach { (t, u) ->
            print("$t: $u\n")
        }
    }

    /**
    * Fisher yates algorithm - unbiased
    */
    fun unbiased() {
        for (i in 1..100000) {
            for (i in arr.size-1 downTo 0) {
                val j = rand.nextInt(i + 1)
                val temp = arr[i]
                arr[i] = arr[j]
                arr[j] = temp
            }

            val combination = arr.toList().joinToString("")

            if (occurrences.containsKey(combination)) {
                occurrences[combination] = occurrences[combination]!! + 1
            } else {
                occurrences[combination] = 1
            }
        }

        print("Fisher Yates:\n")
        occurrences.forEach { (t, u) ->
            print("$t: $u\n")
        }
    }
}

fun main() {
    Problem().biased()
    Problem().unbiased()
}

这会产生以下结果

Naive:
312: 16719
213: 16654
231: 16807
123: 16474
132: 16636
321: 16710
Fisher Yates:
123: 16695
312: 16568
213: 16923
321: 16627
132: 16766
231: 16421

我的结果在这两种情况下并没有太大的不同。我的问题是,我的实现错了吗?还是我的理解有误?

【问题讨论】:

    标签: algorithm kotlin fisher-yates-shuffle


    【解决方案1】:

    您对这两种算法的实现都有一个错误,它消除了由天真的洗牌引入的偏差。您不会从每次 shuffle 的相同排列开始,而是从最后一次 shuffle 产生的排列开始。一个简单的解决方法是每次将数组重置为[1, 2, 3]

    import java.util.*
    
    class Problem {
        private var arr = intArrayOf(1, 2, 3)
        private val occurrences = mutableMapOf<String, Int>()
        private val rand = Random()
    
        fun biased() {
            for (i in 1..100000) {
                arr = intArrayOf(1, 2, 3)  // reset arr before each shuffle
                for (i in arr.indices) {
                    val k = rand.nextInt(arr.size)
                    val temp = arr[k]
                    arr[k] = arr[i]
                    arr[i] = temp
                }
    
    
                val combination = arr.toList().joinToString("")
    
                if (occurrences.containsKey(combination)) {
                    occurrences[combination] = occurrences[combination]!! + 1
                } else {
                    occurrences[combination] = 1
                }
            }
    
            print("Naive:\n")
            occurrences.forEach { (t, u) ->
                print("$t: $u\n")
            }
        }
    
        /**
        * Fisher yates algorithm - unbiased
        */
        fun unbiased() {
            for (i in 1..100000) {
                arr = intArrayOf(1, 2, 3)  // reset arr before each shuffle
                for (i in arr.size-1 downTo 0) {
                    val j = rand.nextInt(i + 1)
                    val temp = arr[i]
                    arr[i] = arr[j]
                    arr[j] = temp
                }
    
                val combination = arr.toList().joinToString("")
    
                if (occurrences.containsKey(combination)) {
                    occurrences[combination] = occurrences[combination]!! + 1
                } else {
                    occurrences[combination] = 1
                }
            }
    
            print("Fisher Yates:\n")
            occurrences.forEach { (t, u) ->
                print("$t: $u\n")
            }
        }
    }
    
    fun main() {
        Problem().biased()
        Problem().unbiased()
    }
    

    输出:

    Naive:
    213: 18516
    132: 18736
    312: 14772
    321: 14587
    123: 14807
    231: 18582
    Fisher Yates:
    321: 16593
    213: 16552
    231: 16674
    132: 16486
    123: 16802
    312: 16893
    

    不是 Kotlin 程序员,所以可能有更优雅的方式来做到这一点,但我想这已经足够了。

    【讨论】:

    • 谢谢!是的,它做到了。虽然我仍然想知道为什么数组的顺序会有所不同?假设在现实生活中,我们每次洗牌时都不会对一组项目进行排序。
    • @f_i 如果您总是从以[1, 2, 3] 排序的元素开始,则输出表示元素被打乱的方式。如果您从预洗牌数组开始,则必须“减去”预洗牌以获得实际排列。所以输出不再是执行排列的表示。所以基本上区别不在于偏差不再存在,而是它在输出中不可见。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-07-08
    • 2019-12-11
    • 2011-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多