随机题目之洗牌算法

洗牌算法是我们罕见的随机题目,在玩游戏、随机排序时经常会遇到。它能够笼统成如许一个题目。

获得一个M之内的一切自然数的随机递次数组。

在百度搜“洗牌算法”,第一个效果是《百度文库 — 洗牌算法》。扫了一下内里的内容,许多内容都轻易误导他人走上邪路,包含末了用链表替代数组,也只是一个有限的优化(链表也引入了读取效力的丧失)。

抽牌思绪

基础算法

该文里的第一种方法,能够简朴形貌成:随机抽牌,放在另一组;再次抽取,抽到空牌则反复抽。
“抽到空牌则反复抽”这会致使背面抽到空牌的时机愈来愈大,显然是不合理的。能够优化一步成:牌抽走后,原牌变少(而不是留下空牌)。代码以下:

function shuffle_pick_1(m) {
    // 天生m张牌
    var arr = new Array(m);
    for (var i = 0; i < m; i++) {
        arr[i] = i;
    }
    // 每次抽出一张牌,放在另一堆。由于要在数组里抽出元素,把背面的一切元素向前拉一名,所以很耗时
    var arr2 = new Array();
    for (var i = m; i > 0; i--) {
        var rnd = Math.floor(Math.random() * i);
        arr2.push(arr[rnd]);
        arr.splice(rnd, 1);
    }
    return arr2;
}

这个也显著有题目,由于数组假如很大的话,删除中心的某个元素,会致使背面的列队向前走一步,这是一个很耗时的行动。

优化算法

追念一下“我们为何要删除谁人元素?”目标就是为了不发生空牌。除了删除谁人元素以外,我们是不是是另有别的体式格局来去除空牌?有的,我们把末了一张未抽的牌放在谁人抽走的位置上就能够了。所以,这个思绪我们能够优化成如许:

function shuffle_pick(m) {
    // 天生m张牌
    var arr = new Array(m);
    for (var i = 0; i < m; i++) {
        arr[i] = i;
    }

    // 每次抽出一张牌,放在另一堆。把末了一张未抽的牌放在空位子上。
    var arr2 = new Array();
    for (var i = m; i > 0;) {
        var rnd = Math.floor(Math.random() * i);
        arr2.push(arr[rnd]);
        arr[rnd] = arr[--i];
    }
    return arr2;
}

换牌思绪

除了抽牌思绪,我们还能够用换牌思绪。
《百度文库-洗牌算法》提到一种换牌思绪:“随机交流两个位置,共交流n次,n越大,越靠近随机”。
这个做法是不对的,就算n很大(比方10张牌,举行10次换取),也还存在很大能够“有的牌基础没换位置”。
顺着这个思绪,做一点小调解就能够了:第i张与恣意一张牌换位子,换完一轮即可。代码以下:

function shuffle_swap(m) {
    // 天生m张牌
    var arr = new Array(m);
    for (var i = 0; i < m; i++) {
        arr[i] = i;
    }

    // 第i张与恣意一张牌换位子,换完一轮即可
    for (var i = 0; i < m; i++) {
        var rnd = Math.floor(Math.random() * (i + 1)),
            temp = arr[rnd];
        arr[rnd] = arr[i];
        arr[i] = temp;
    }
    return arr;
}

插牌思绪

除了抽牌与换牌的思绪,我们还能够用插牌的思绪:先有一张牌,第二张牌有两个位置可随机插进去(第一张牌前,或后),第三张牌有三个位置可随机插进去(放在背面,或插在第一名,或插在第二位),依此类推
代码以下:

function shuffle_insert_1(m) //洗牌 //插牌法
{
    //每次天生一张最大的牌,插在随机的某张牌前。由于要在数组里插进去元素,把背面的一切元素向后挤一名,所以很耗时。
    var arr = [0];
    for (var i=1; i<m; i++) {
        arr.splice(Math.floor(Math.random()*(i+1)),0,i);
    }
    return arr;
}

以上的代码也会有一些题目:就是跟着牌数的增加,插牌变得愈来愈难题,由于插牌会致使背面的许多牌都今后推一步。

固然,我们也能够恰当的优化一下:先有n-1张牌,第n张牌放在末了,然后与恣意一张牌交换位置。
代码以下:

function shuffle_insert(m) //洗牌 //插牌法优化版,能够用数学归纳法证实,这类洗牌是匀称的。
{
    //每次天生一张最大的牌,与随机的某张牌换位子
    var arr = new Array(m);
    arr[0] = 0;
    for (var i=1; i<m; i++) {
        var rnd = Math.floor(Math.random()*(i+1));
        arr[i] = arr[rnd];
        arr[rnd] = i;
    }
    return arr;
}

好的,悉数的代码以下,有兴致的同砚能够在本身的机械上试下,看下他们各自的实行效力、以及末了的效果是不是是理论随机。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>JK:javascript 洗牌算法 </title>

</head>
<body>
<script type="text/javascript">

function shuffle_pick_1(m) //洗牌 //抽牌法
{
    //天生m张牌
    var arr = new Array(m);
    for (var i=0; i<m; i++) {
        arr[i] = i;
    }

    //每次抽出一张牌,放在另一堆。由于要在数组里抽出元素,把背面的一切元素向前拉一名,所以很耗时。
    var arr2 = new Array();
    for (var i=m; i>0; i--) {
        var rnd = Math.floor(Math.random()*i);
        arr2.push(arr[rnd]);
        arr.splice(rnd,1);
    }
    return arr2;
}


function shuffle_pick(m) //洗牌 //抽牌法优化牌
{
    //天生m张牌
    var arr = new Array(m);
    for (var i=0; i<m; i++) {
        arr[i] = i;
    }

    //每次抽出一张牌,放在另一堆。把末了一张未抽的牌放在空位子上。
    var arr2 = new Array();
    for (var i=m; i>0;) {
        var rnd = Math.floor(Math.random()*i);
        arr2.push(arr[rnd]);
        arr[rnd] = arr[--i];
    }
    return arr2;
}


function shuffle_swap(m) //洗牌 //换牌法
{
    //天生m张牌
    var arr = new Array(m);
    for (var i=0; i<m; i++) {
        arr[i] = i;
    }

    //第i张与恣意一张牌换位子,换完一轮即可
    for (var i=0; i<m; i++) {
        var rnd = Math.floor(Math.random()*(i+1)),
            temp = arr[rnd];
        arr[rnd] = arr[i];
        arr[i]=temp;
    }
    return arr;
}

function shuffle_insert_1(m) //洗牌 //插牌法
{
    //每次天生一张最大的牌,插在随机的某张牌前。由于要在数组里插进去元素,把背面的一切元素向后挤一名,所以很耗时。
    var arr = [0];
    for (var i=1; i<m; i++) {
        arr.splice(Math.floor(Math.random()*(i+1)),0,i);
    }
    return arr;
}

function shuffle_insert(m) //洗牌 //插牌法优化版,能够用数学归纳法证实,这类洗牌是匀称的。
{
    //每次天生一张最大的牌,与随机的某张牌换位子
    var arr = new Array(m);
    arr[0] = 0;
    for (var i=1; i<m; i++) {
        var rnd = Math.floor(Math.random()*(i+1));
        arr[i] = arr[rnd];
        arr[rnd] = i;
    }
    return arr;
}


//alert(shuffle_pick(10))


var funcs = [shuffle_pick_1, shuffle_pick, shuffle_swap, shuffle_insert_1, shuffle_insert],
    funcNames = ["抽牌", "抽牌优化", "换牌", "插牌", "插牌优化"]
    m = 10000,
    times=[];
for(var i = 0; i < funcs.length; i++){
    var d0= new Date();
    funcs[i](m);
    funcNames[i] = (new Date() - d0) + '\t' + funcNames[i];
}

alert(funcNames.join('\n'));

</script>


</body>

</html>

文章转载自《随机题目之–洗牌算法》

    原文作者:ChanceWong
    原文地址: https://segmentfault.com/a/1190000004154562
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞