小談數組去重

數組去重是一個常見的問題,在用C語言刷算法題的時候屬於比較水的題目,很容易就AC。不過在JavaScript中,由於方法多樣,因此解題的方法也多種多樣,如下是本身的研究過程與結果。javascript

準備一:隨機數組生成

在研究以前,咱們先實現一個隨機生成數組的方法:java

/**
*函數名:createArr
*參數:
*   len 表示要生成的數組的長度
*   size 表示要生成的數組的範圍的最大值
*返回值:一個生成的數組
**/
function createArr(len, size){
    var randomArr = [];
    while(len--){
        randomArr[len] = Math.floor(Math.random() * size + 1);
    }
    return randomArr;
}

咱們來測試一下運行結果:es6

示例1:算法

var arr = createArr(10, 5);
console.log(arr);

示例1的運行結果:數組

[3, 5, 5, 1, 4, 5, 2, 3, 5, 3]

示例2:dom

var arr = createArr(100000, 1000);
console.log(arr);

示例2的運行結果:函數

[691, 299, 863, 41, 476, 258, 196, 145, 717, 129, 797, 919, 184, 4, 336, 783, 92, 159, 216, 882, 159, 805, 302, 569, 940, 130, 655, 992, 585, 313, 206, 962, 584, 987, 35, 473, 350, 45, 558, 885, 226, 255, 506, 198, 16, 263, 431, 604, 109, 726, 706, 837, 916, 418, 387, 216, 291, 260, 366, 653, 459, 413, 700, 672, 168, 853, 672, 559, 116, 56, 711, 631, 284, 918, 495, 962, 561, 302, 268, 878, 671, 350, 768, 868, 482, 692, 22, 839, 650, 16, 868, 104, 856, 188, 930, 917, 746, 599, 309, 966…]

咱們在測試不一樣方法性能的時候,用到的是示例2的數組長度,即十萬個元素,這樣能夠體現出不一樣方法的性能差別。性能


準備二:運行消耗時間平均值

/**
*函數名:calculateMean
*參數:
*   func 要計算運行時間的函數
*   time 運行次數
**/
function calculateMean(func, time){
    var startTime, endTime;
    var sumTime = 0, meanTime;
    var arr = createArr(100000, 1000);
    for(var i = 0; i < time; i++){
         startTime = window.performance.now();
         func(arr);
         endTime = window.performance.now();
         sumTime += Math.floor((endTime - startTime)*100)/100;
    }
    meanTime = Math.floor((sumTime / time)*100)/100;
    return meanTime;
}

咱們知道,能夠用console.time() +console.timeEnd() 來計算程序運行的消耗時間,不過咱們不能獲取這個時間,沒法把控制檯打印出來的這個結果賦值給變量,所以也就沒法求得平均值。因此,這裏用window.performance.now() 來計算,計算結果幾乎是相同的(會有0.1ms左右的差別,可忽略不計)。測試


方法一:雙重for循環數組去重

咱們用最笨的方法來解,思路以下:優化

  1. 新建一個數組newArr
  2. 對原數組進行兩層的for循環,若是發現重複的,那麼就break,不然就添加到newArr

代碼以下:

// 方法一:雙重for循環數組去重
function unique1(arr) {
    var newArr = [];
    var arrLength = arr.length;
    var isRepeat;
    for(var i=0; i<arrLength; i++) {
        isRepeat = false;
        for(var j=i+1; j<arrLength; j++) {
            if(arr[i] === arr[j]){
                isRepeat = true;
                break;
            }
        }
        if(!isRepeat){
            newArr.push(arr[i]);
        }
    }
    return newArr;
}

咱們取該方法運行20次所消耗的平均時間(後面的方法一樣),運行如下代碼:

console.log(calculateMean(unique1, 20).toString() + "ms");

取運行20次的平均耗時,其結果爲:

221.27ms

性能分析

時間複雜度 : \[O(n^2)\]

最好狀況下的時間複雜度: \[O(n)\]

最差狀況下的時間複雜度: \[O(n^2)\]

空間複雜度: \[O(1)\]

穩定性:穩定


方法二:優化的雙重for循環數組去重

改進的方法,其思路以下:

  1. 新建一個數組newArr
  2. 遍歷原數組,判斷元素是否存在於newArr中,若是不存在,那麼就爲newArr添加該元素

代碼以下:

//方法二:優化的雙重for循環數組去重
function unique2(arr){
    var arrLength = arr.length;
    var newArr = [];
    var isRepeat;
    for(var i = 0; i < arrLength; i++){
        var isRepeat = false;
        var newArrLength = newArr.length;
        for(var j = 0; j < newArrLength; j++){
            if(arr[i] === newArr[j]){
                isRepeat = true;
                break;
            }
        }
        if(!isRepeat){
            newArr.push(arr[i]);
        }
    }
    return newArr;
}

取運行20次的平均耗時,其結果爲:

87.84ms

性能分析

時間複雜度 : \[O(n^2)\]
最好狀況下的時間複雜度: \[O(n)\]
最差狀況下的時間複雜度: \[O(n^2)\]
空間複雜度: \[O(1)\]
穩定性:穩定


優化分析

優化後用於校驗的內容爲新數組的內容,若是原數組中重複的元素越多,那麼新數組中的元素就越少,從而匹配原數組所用的循環數就越少。而方法一中,匹配的數目都是原數組的長度,於是比較耗時。


方法三:數組indexOf方法去重

思路以下:

  1. 新建一個新數組newArr
  2. 經過數組indexOf判斷是否存在,不存在,則添加

代碼以下:

//方法三:數組indexOf方法去重
function unique3(arr){
    var newArr = [];
    arr.forEach(function(v){
        if(newArr.indexOf(v) < 0){
            newArr.push(v);
        }
    });
    return newArr;
}

取運行20次的平均耗時,其結果爲:

56.08ms

方法四:利用對象鍵去重

思路以下:

  1. 新建一個數組newArr、一個空對象temp
  2. 將原數組的值加入到對象中,經過判斷對象的鍵來控制新數組的數據寫入
//方法四:利用對象鍵去重
function unique4(arr){
    var temp = {};
    var newArr = [];
    var arrLength = arr.length;
    for(var i = 0; i < arrLength; i++){
        if(!temp[arr[i]]){
            temp[arr[i]] = true;
            newArr.push(arr[i]);    
        }
    }
    return newArr;
}

取運行20次的平均耗時,其結果爲:

1.56ms

性能分析

由於判斷鍵是否存在於對象中能夠一步到位,不須要進行遍歷循環,所以其時間複雜度爲:\[O(n)\] ,因此耗時很是短,這應該是最佳的方法了。

固然,這裏要考慮一下能夠做爲鍵的值了,在JavaScript中,對象不能做爲鍵,也不能判斷數字和字符串的區別(好比鍵值中數字0和字符串「0」是相同的)。因此,若是在去重的數組中,包含了對象,或者須要區分數字和字符串,那麼咱們能夠把它們的特徵和值進行轉化,轉化爲字符串,再做爲鍵值。

改進的方法以下:

//改進的方法
function unique4(arr){
    var newArr = [];
    var arrLength = arr.length;
    var temp = {};
    var tempKey;
    for(var i=0; i<arrLength; i++){
        tempKey = typeof arr[i] + JSON.stringify(arr[i]);
        if(!temp[tempKey]){
            temp[tempKey] = true;
            newArr.push(arr[i]);
        }
    }
    return newArr;      
}

取運行20次的平均耗時,其結果爲:

47.33ms

由於須要作數據轉化,所以須要消耗額外的時間,其耗時明顯增長。所以,是否須要作此優化,視具體狀況而定。


方法五:ES6方法之Map方法去重

Map是ES6新增的有序鍵值對集合。它的key和value能夠是任何值。對比方法四中的侷限,那麼Map方法就沒有任何限制了,所以不須要作任何數據轉化。使用方法和方法四基本一致。代碼以下:

//方法五:ES6方法之Map方法去重
function unique5(arr){
    var newArr = [];
    var temp = new Map();
    var arrLength = arr.length;
    for(var i = 0; i < arrLength; i++){
        if(!temp.get(arr[i])){
            temp.set(arr[i],true);
            newArr.push(arr[i]);
        }
    }
    return newArr;
}

取運行20次的平均耗時,其結果爲:

19.75ms

方法六:ES6方法之Set方法去重

Set 是 ES6 新增的有序列表集合,它不會包含重複項,所以直接傳入數組,便可實現去重。

// 方法六:ES6方法之Set方法去重
function unique6(arr){
    var newArr = [...new Set(arr)];
    return newArr;
}

取運行20次的平均耗時,其結果爲:

7.18ms

總結

方法 數組長度爲100000,元素值爲1000之內的平均耗時
雙重for循環數組去重 221.27ms
優化的雙重for循環數組去重 87.84ms
數組indexOf方法去重 56.08ms
利用對象鍵去重 1.56ms
改進的利用對象鍵去重 47.33ms
ES6方法之Map方法去重 19.75ms
ES6方法之Set方法去重 7.18ms

若是去重的數組爲純number或者純字符串,那麼能夠用利用對象鍵去重的方法,由於它的效率是最高的。若是要兼容更多的類型,那麼就用改進的利用對象鍵去重的方法。

固然,若是支持ES6,那麼建議使用Set方法,代碼簡潔,高效。

相關文章
相關標籤/搜索