js打亂數組的實戰應用

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

在js中,能把數組隨機打亂的方法有不少,每一個方法都有本身的特色。html

1. 打亂數組的方法

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

1.1 隨機從數組中取出數據

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

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)了。github

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,再進行測試:dom

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}

能夠發現每次打亂後的分佈比較均勻,每一個數字出如今每一個位置的機會都是均等的!post

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. 總結

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

相關文章
相關標籤/搜索