js打亂數組的實戰應用


文章首發於: https://www.xiabingbao.com/post/javascript/js-random-array.html

在js中,能把數組隨機打亂的方法有很多,每個方法都有自己的特點。

1. 打亂數組的方法

這里主要講解3個打亂數組的方法。

1.1 隨機從數組中取出數據

這個方法的詳細操作步驟是:隨機從數組中取出一個數組放入到新數組中,然后將該數據從原數組中刪除,然后再隨機取出下一個數,直到原數據的長度為0。

function randomArrByOut(arr) {
    let result = [];
    let arrTemp = [...arr]; // splice會影響原數組,復制一個新的數組,防止影響原數組
    while(arrTemp.length) {
        let index = Math.floor(Math.random() * arrTemp.length);
        result.push(arrTemp[index]);
        arrTemp.splice(index, 1);
    }
    return result;
}
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
randomArrByOut(arr); // [7, 1, 3, 8, 2, 4, 6, 5, 9]
randomArrByOut(arr); // [8, 4, 3, 7, 9, 2, 1, 5, 6]

這個算法看似是O(n)的算法,但實際上arr.splice內部是一個O(n^2)的算法Array.prototype.splice的內部實現:外部循環用來刪除元素,內部的循環用來填充新添加的元素,或后面的元素向前移動,填充剛才被刪除的元素的坑。總的算下來,這個算法的時間復雜度就是O(n^3)了。

1.2 sort方法打亂

還有一種常見的方法就是使用數組自帶的sort方法來打算數組,sort方法是直接修改當前的數組:

function randomSortBySort(arr) {
    arr.sort(() => Math.random() - 0.5);
}

當前環節里所有的測試均在Chrome中。當我們使用9個數據,經過多次的測試發現,打亂的數據排布並不均勻:

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var n = 10000;
var count = {};
while(n--)  {
    randomSortBySort(arr);
    var index = arr.indexOf(1);
    
    count[index] ? count[index]++ : (count[index] = 1);
}
console.log(count);
/*
數據1經過10000次打亂后的分布規律,主要集中在前2個
0: 2047
1: 1403
2: 947
3: 822
4: 777
5: 822
6: 992
7: 1008
8: 1182
*/

我們再把arr的數組擴展為15,再進行測試:

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
var n = 10000;
var count = {};
while(n--)  {
    randomSortBySort(arr);
    var index = arr.indexOf(1);
    
    count[index] ? count[index]++ : (count[index] = 1);
}
console.log(count);
// {0: 668, 1: 647, 2: 652, 3: 665, 4: 692, 5: 652, 6: 679, 7: 657, 8: 665, 9: 683, 10: 685, 11: 690, 12: 662, 13: 663, 14: 640}

可以發現每次打亂后的分布比較均勻,每個數字出現在每個位置的機會都是均等的!

V8的源碼中L710行中可以看到:

function InnerArraySort( array, length, comparefn ) {
    // In-place QuickSort algorithm.
    // For short (length <= 22) arrays, insertion sort is used for efficiency.
    // 雖然注釋是length<=22,但代碼里是<=10

    // 插入排序
    var InsertionSort = function InsertionSort( a, from, to ) {

    };

    var QuickSort = function QuickSort( a, from, to ) {
        var third_index = 0;
        while ( true ) {
            // Insertion sort is faster for short arrays.
            if ( to - from <= 10 ) {
                InsertionSort( a, from, to );
                return;
            }
            // 快排其他的內容
        }
    }
    QuickSort(array, 0, num_non_undefined);
}

sort的內部使用快速排序,當快排拆分后的分區里的數據個數小於等於10個時,則采用插入排序!因此,當數據量比較小的時候,使用sort打亂排序時,會造成不均等的分布!

1.3 洗牌算法

最后一個經典的數組打亂算法就是洗牌算法:從最后一個數據開始往前,每次從前面隨機一個位置,將兩者交換,直到數組交換完畢:

function shuffleSort(arr) {
    var n = arr.length;
    
    while(n--) {
        var index = Math.floor(Math.random() * n);
        var temp = arr[index];
        arr[index] = arr[n];
        arr[n] = temp;
        // ES6的解耦交換方式: [arr[index], arr[n]] = [arr[n], arr[index]];
    }
}

這種方式是O(n)的時間復雜度,而且還能保證一個比較均勻的分布!高效了很多

2. 從數組中隨機取出多個元素

這是從數組中隨機取出幾個元素,上面的一節是將整個數組進行排序,而這里只是需要幾個元素而已!

2.1 打亂整個數組取出數據

當然,先把整個數組打亂了,然后再取出前n個數據也是其中的一種方法,比如我們這里就使用洗牌算法打亂數組,然后取出數據:

function getRandomArr(arr, num) {
    var _arr = arr.concat();
    var n = _arr.length;
    
    // 先打亂數組
    while(n--) {
        var index = Math.floor(Math.random() * n);
        [_arr[index], _arr[n]] = [_arr[n], _arr[index]];
    }
    return _arr.slice(0, num);
}

不過實際上我們只是需要其中的幾個元素而已,如果把整個數組都打亂排序,就顯得很浪費。因此這里我們使用洗牌算法的思路,稍微改進一下。

2.2 改進型

從最后一個數據開始往前,每次從前面隨機一個位置,將兩者交換,拿到最后的那個數據,直到達到要獲取的個數:

function getRandomArr(arr, num) {
    var _arr = arr.concat();
    var n = _arr.length;
    var result = [];
    
    // 先打亂數組
    while(n-- && num--) {
        var index = Math.floor(Math.random() * n); // 隨機位置
        [_arr[index], _arr[n]] = [_arr[n], _arr[index]]; // 交換數據
        result.push(_arr[n]); // 取出當前最后的值,即剛才交換過來的值
    }
    return result;
}

3. 總結

數組中還是有很多的學問的,看看其中的源碼,也會發現更多的奧妙!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM