文章

洗牌算法及其测试

前言

之前面试的时候,被问到过洗牌算法,当时没有了解过洗牌算法,所以是按自己的理解去回答的。今天复盘的时候,发现当时面试说的洗牌算法是错误的。

下意识的实现

for循环一次,然后随机交换两个数。

func shuffleArrayGeneral(nums []byte) []byte {
	n := len(nums)
	rand.NewSource(time.Now().UnixNano())
	for i := 0; i < n; i++ {
		a := rand.Intn(n)
		b := rand.Intn(n)
		nums[a], nums[b] = nums[b], nums[a]
	}
	return nums
}

简单跑几次感觉没问题

image-vpoe.png

测试

现在,我们知道洗牌算法应该是应该随机函数,也就是说,我每张牌出现在各个位置上的概率应该是 1 / 牌数。
概率问题,我们只能采用统计的方法来做测试。也就是说,你调用rand()函数100次,其中,每个数出现的次数大约都在 [5,15] 次。

那么接下来,对我前面的洗牌函数进行测试。

在1000000次测试中

image-pcoz.png

注意看对角线的数据和误差数据,我们会发现之前的算法不能达到我们预期的效果。

Fisher-Yates 洗牌算法

使用一个倒序的循环来进行洗牌操作。循环变量i从n-1开始,递减到0。在每次迭代中,我们将当前位置i的元素与一个随机位置j的元素进行交换

func shuffleArrayFisherYates(nums []byte) []byte {
	n := len(nums)
	rand.NewSource(time.Now().UnixNano())
	for i := n - 1; i >= 0; i-- {
		j := rand.Intn(i + 1)
		nums[i], nums[j] = nums[j], nums[i]
	}
	return nums
}

在1000000次测试中

image-pnsu.png

我们能够明显得看到每张牌在各个位置上出现的概率都较为接近。

License:  CC BY 4.0