数组去重是一个常见的问题,在用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方法,代码简洁,高效。