數組去重是一個常見的問題,在用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循環數組去重 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循環數組去重 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方法去重 function unique3(arr){ var newArr = []; arr.forEach(function(v){ if(newArr.indexOf(v) < 0){ newArr.push(v); } }); return newArr; }
取運行20次的平均耗時,其結果爲:
56.08ms
思路以下:
//方法四:利用對象鍵去重 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
由於須要作數據轉化,所以須要消耗額外的時間,其耗時明顯增長。所以,是否須要作此優化,視具體狀況而定。
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
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方法,代碼簡潔,高效。