Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

42.洗牌算法(shuffle)的js实现 #44

Open
ccforward opened this issue Oct 11, 2016 · 6 comments
Open

42.洗牌算法(shuffle)的js实现 #44

ccforward opened this issue Oct 11, 2016 · 6 comments
Assignees
Labels

Comments

@ccforward
Copy link
Owner

洗牌算法(shuffle)的js实现

Fisher-Yates

先看最经典的 Fisher-Yates 的洗牌算法

这里有一个该算法的可视化实现

其算法思想就是 从原始数组中随机抽取一个新的元素到新数组中

  1. 从还没处理的数组(假如还剩n个)中,产生一个[0, n]之间的随机数 random
  2. 从剩下的n个元素中把第 random 个元素取出到新数组中
  3. 删除原数组第random个元素
  4. 重复第 2 3 步直到所有元素取完
  5. 最终返回一个新的打乱的数组

按步骤一步一步来就很简单的实现

function shuffle(arr){
    var result = [],
        random;
    while(arr.length>0){
        random = Math.floor(Math.random() * arr.length);
        result.push(arr[random])
        arr.splice(random, 1)
    }
    return result;
}

这种算法要去除原数组 arr 中的元素,所以时间复杂度为 O(n2)

Knuth-Durstenfeld Shuffle

Fisher-Yates 洗牌算法的一个变种是 Knuth Shuffle

每次从未处理的数组中随机取一个元素,然后把该元素放到数组的尾部,即数组的尾部放的就是已经处理过的元素,这是一种原地打乱的算法,每个元素随机概率也相等,时间复杂度从 Fisher 算法的 O(n2)提升到了 O(n)

  1. 选取数组(长度n)中最后一个元素(arr[length-1]),将其与n个元素中的任意一个交换,此时最后一个元素已经确定
  2. 选取倒数第二个元素(arr[length-2]),将其与n-1个元素中的任意一个交换
  3. 重复第 1 2 步,直到剩下1个元素为止
function shuffle(arr){
    var length = arr.length,
        temp,
        random;
    while(0 != length){
        random = Math.floor(Math.random() * length)
        length--;
        // swap
        temp = arr[length];
        arr[length] = arr[random];
        arr[random] = temp;
    }
    return arr;
}

Durstenfeld Shuffle的算法是从数组第一个开始,和Knuth的区别是遍历的方向不同

Other

Array.sort()

利用Array的sort方法可以更简洁的实现打乱,对于数量小的数组来说足够。因为随着数组元素增加,随机性会变差。

[1,2,3,4,5,6].sort(function(){
    return .5 - Math.random();
})

ES6

Knuth-Durstenfeld shuffle 的 ES6 实现,代码更简洁

function shuffle(arr){
    let n = arr.length, random;
    while(0!=n){
        random =  (Math.random() * n--) >>> 0; // 无符号右移位运算符向下取整
        [arr[n], arr[random]] = [arr[random], arr[n]] // ES6的结构赋值实现变量互换
    }
    return arr;
}
@ccforward ccforward self-assigned this Oct 11, 2016
@fuchao2012
Copy link

arr 要容错,给 arr.length >>>0

@huyansheng3
Copy link

第一个时间复杂度为什么是 O(n^2),应该就是 O(n),代码中也只做了一次遍历吧?

@huyansheng3
Copy link

明白了 splice 的复杂度是 O(n),所以综上复杂度是 O(n^2)

@huyansheng3
Copy link

第二种方式快要结束时的数字相对固定了,这样的洗牌感觉随机性不够了。

@743v45
Copy link

743v45 commented Mar 9, 2018

@huyansheng3 数字固定对随机性并没有什么影响呀。比如剩下 1,4,3,每个概率就是 1 / 3。肯定是等概率的呀,怎么会有随机性不够的说法。

@huyansheng3
Copy link

huyansheng3 commented Mar 12, 2018

@zyvas 是我理解有误。遍历数组,每个元素和其他 n-1中的任意交换位置,即可满足每个元素都有 1/n 的概率出现在任一位置,即是洗牌。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants