导语
本文只是一个比较简陋的洗牌算法,并不是从概率论的角度出发,有建议的读者可以在评论区留言。
什么是洗牌算法
啥是洗牌算法啊?好像听着很高大上啊。但其实很简单,一些洗牌算法10行代码就可以写出来了。
比如有一个数组 [1 , 2 , 3] ,想要设计一个函数 , 可以打乱这个数组并返回。
怎么个打乱法呢? 我们知道 数组 [ 1, 2, 3] 有 6 种排列组合 : 分别是
[ 1, 2 ,3 ]
[ 1 ,3 ,2 ]
[ 2, 1, 3 ]
[2 , 3 ,1 ]
[3 , 1 , 2]
[3 , 2 , 1]
说白了就是随机返回这6中排列组合中的一种
1. 抽牌
现实世界:要洗牌,那么最随机的做法无疑是从牌堆里随便抽出一张出来,然后放到一边,之后从剩下的牌里重复之前的动作(抽出一张牌),直到所有牌被抽完到,另一边就是已经乱序的牌了。
计算机的世界:按相同的做法,就是随机从数组里取出一个元素,保存到另一个数组中,并且从原数组删除这个随机出来的元素,然后重复从原数组随机取元素,直到原数组的所有元素都处理掉
function shuffle (array) {
//用来存储打乱后的数组
let copy = [] ;
let len = array.length ;
while(len) {
//随机生成 0 到 n-1 的数字 , 作为数组下标
let index = Math.floor( Math.random() * len-- ) ;
//把随机到的数字放入新数组中,并从原数组中删除
copy.push( array.splice(index , 1)[0] ) ;
}
return copy ;
}
时间复杂度 O(n2) 空间复杂度O(n)
2. 换牌
仔细分析我们会发现,其实我们根本不需要多余的空间来存储我打乱后的数组,完全可以把空间复杂度降低至O(1),而且从一个数组中删除某个元素是一个相当耗时间的操作O(n),怎么做呢?
优化:这次咱们直接从计算机角度出发。随机从数组中抽出一个元素,然后把它与最后一个元素交换。相当于把这个随机出来的元素放到了数组最后面去,表示它已经是被随机过的了,同时被换到前面的那个元素被在后续的重复操作中被随机掉。一轮过后,下一轮我们只剩下前 n - 1 个元素需要被随机,直到进行到第一个。
function Shuffle (array) {
let len = array.length ;
//如果还有剩余元素
while( len ) {
// 随机出来一个下标
let index = Math.floor( Math.random() * len-- )
//随机出来的元素与最后一个元素交换
[array[index] , array[len] ] = [ array[len] , array[index] ]
}
return array ;
}
时间复杂度 O(n) 空间复杂度 O(1)
结束语
其实还有很多随机算法,比如一种基于插牌的方法,但作者也才刚刚入门,所以有点尴尬。有意见可以随便提