JavaScript随机打乱数组元素的位置(洗牌算法)

function mess(arr){
    var _floor = Math.floor, _random = Math.random,
        len = arr.length, i, j, arri,
        n = _floor(len/2)+1;
    while( n-- ){
        i = _floor(_random()*len);
        j = _floor(_random()*len);
        if( i!==j ){
            arri = arr[i];
            arr[i] = arr[j];
            arr[j] = arri;
        }
    }
    return arr;
}
var testa = [1, 2, 3, 4, 5, 6, 7];
mess(testa);
console.log(testa);

要注意一点的是,这是改变原数组的,不想改变原数组的话请先拷贝原数组:

var testa = [1, 2, 3, 4, 5, 6, 7];
var newarr = mess( testa.slice(0) ); //这里只是浅拷贝数组
console.log(testa);
console.log(newarr);
这个洗牌函数算好么?

呵呵,或许经过测试你会发现:有一些元素在洗完牌后位置依然是原来的位置,而且出现这种元素的几率很大。那怎么改进呢?

在现实当中,我们洗完牌后,通常还会把扑克一分为二(俗称“切牌”),然后交换这两部分的上下位置。嗯,对,这个洗牌函数里我们没有切牌!

所以我们可以做如下改进:

//洗牌算法
function mess(arr){
    var _floor = Math.floor, _random = Math.random,
        len = arr.length, i, j, arri,
        n = _floor(len/2)+1;
    while( n-- ){
        i = _floor(_random()*len);
        j = _floor(_random()*len);
        if( i!==j ){
            arri = arr[i];
            arr[i] = arr[j];
            arr[j] = arri;
        }
    }
    //增加切牌操作
    i = _floor(_random()*len);
    arr.push.apply(arr, arr.splice(0,i));
    //return arr; //要不要返回打乱后的数组呢?
}
var testa = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var newarr = testa.slice(0);
mess(newarr);
console.log(testa);
console.log(newarr);
好,现在看来,已经很少出现洗牌前后位置不变的元素了,可以说大功告成了。

巴特,稍等,我们已经发现,这个洗牌函数是会改变原始数组的,那么,接下来有一个问题:这个函数要不要返回值,返回打乱后的数组?因为我可能想这么来调用它:

var testa = [1, 2, 3, 4, 5, 6, 7];
var newarr = mess( testa.slice(0) ); //这里只是浅拷贝数组
这种调用方式好吗?如果加上这种调用方式,那么可以算一共有两种调用方式。

其实,对这么一个简单函数而言,调用方法应该越简单越好,简单易用,最好只提供一种调用方式。使用方法多了就需要人多记东西,容易让人迷糊。所以,我不想给这个洗牌函数返回值,不想提供这种调用方式。
而且还有一个原因:这有利于强调这是一个改变原数组的函数,而不是通过返回值来给出调用结果,这样可以让调用者明确知道调用函数的影响。

//-------------------------2012-11-12后记------------------

有一天,知道上面的洗牌算法,还是不太正确,正确的算法应该是保证每张牌出现在每个位置的概率是1/n,比如54张牌就是1/54.

于是又写了如下的算法:从后往前遍历,随机取小于i的一个正整数,与第i个位置交换,直到i=1停止。即从最后一个元素往前,随机取得的元素与之交换。

//再次改进的洗牌算法,听说C++的洗牌算法就是这样的。。。
function mess(arr){
    var _floor = Math.floor, _random = Math.random,
        i = arr.length, j, arri;
    while( i > 1 ){
        j = _floor( _random()*i );
        i--;
        //console.log(i);
        if( i!==j ){
            arri = arr[i];
            arr[i] = arr[j];
            arr[j] = arri;
        }
    }
}
var testa = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
var newarr = testa.concat();
mess(newarr);
console.log(testa);
console.log(newarr);
从代码上看,比前两种更简单了!但是思想变了,貌似更合理了。。。

真是大道至简啊!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值