zoukankan      html  css  js  c++  java
  • js面试题之数组去重对比

     最近看一些面试题,很多都提到了数组去重,用的最多的不外乎就是下面这个例子

    arr.filter(function(value,index,arr){
      return arr.indexOf(value,index+1) === -1
    })

    如果忽略其他因素,只从代码简洁度和易读性来说,这个代码确实是很好的,也确实体现了对js的掌握程度。

    但是,从其他方面来说,这个代码是否就真的是很好的呢?作为一个曾经的半吊子acmer,效率恐怕是我最在意的东西了。那我们就来看下效率吧。

    以下所有实验均基于nodejs环境。

    首先,我们需要随机生成一个足够大的数组。这里就来个10万吧。然后跨度是1-10000。(为什么要提跨度,这个后面再说),然后记录下开始时间,并使用上面的代码跑一下,最后记录结束时间,并相减就可以了。

    下面是代码

     1 var arr = [];
     2 //生成随机数组
     3 for (var i = 100000; i >= 0; i--) {
     4     arr.push(Math.ceil(Math.random()*10000));
     5 }
     6 
     7 //开始时间
     8 var now1 = new Date();
     9 var arr1 = arr.filter(function(value,index,arr){
    10     return arr.indexOf(value,index+1) === -1
    11 })
    12 // console.log(arr1.sort(function(a,b){
    13 //     return a-b;
    14 // }));
    15 //结束时间
    16 var now2 = new Date();
    17 console.log(now2-now1);

    那么10万个跨度是10000的数组,效率是多少呢?

    执行结果: 2000毫秒左右,3000毫秒以内(其他浏览器环境测试结果可能不尽相同)

    这个效率,恩。。很一般。

    我们看下代码也不难得出这个结论。filter效率为n,indexOf效率为n,又是嵌套的,那么效率就是n²了。(这里有小问题,下面会解答)

    接下来是我自己想的两个去重的方法,对应不同的场景吧。

    一:如果数组里面的值都是数字的话。那么,我们可以使用数字对应下标的方法来进行去重。比如说,我的数组是arr1=[1,3,3,3,5,6],那么我就新开一个数组arr2,arr2的值就是[1,,1,,1,1]

    也就是说,只要是arr1中出现过的值,我就在arr2中找到对应的下标,并且赋值为1。

     1 var now1 = new Date();
     2 
     3 //中间数组,用来记录arr出现过的值
     4 var tmp = [];
     5 arr.forEach(function(value){
     6     tmp[value] = 1;
     7 });
     8 
     9 var arr2 = [];
    10 tmp.filter(function(value,index){
    11     if(value === 1){
    12         arr2.push(index);
    13     }
    14 })
    15 
    16 var now2 = new Date();
    17 console.log(now2-now1);

    执行结果: 5毫秒左右,10毫秒以内

    那么这个结果就很明显,效率之间的差距不是一般的大。。。

    从代码也可以看出来,都只有一层循环,那么效率就是O(n)了。还是非常快的。

    二:使用sort进行排序。并使用filter过滤数组即可

    1 var now1 = new Date();
    2 //使用sort从小到大排序并过滤掉前后相同的元素
    3 var arr3 = arr.sort(function(a,b){
    4     return a-b;
    5 }).filter(function(value,index){
    6     return arr[index] !== arr[index-1];
    7 });
    8 var now2 = new Date();
    9 console.log(now2-now1);

    执行结果: 80毫秒左右,100毫秒以内

    可以看出来这个结果也还算可以(对比第一种)

    不难看出来,node中sort的效率应该在nlogn(-。-废话么这不是)

    并且2次遍历数组的消耗基本可以忽略不计(从第二种可以看出来)

    那么为什么既然比第二个的效率低,又没第一个简洁,为什么要用这种方法呢,第一是可以扩宽思路,第二是因为这种方法的适用范围比第二种要高。

    比如说,我要去重的数组不是数字,而是字符。那么第二种方法就无法使用(可以使用对象来实现,不过不知道对象遍历的效率高不高,留作以后测试)。并且, 第一种方法如果其中有一个数字为10E,那么空间消耗就很大(js中不确定,大部分语言是这样)。因为要开一个10E的数组。。。以前比赛的时候用c开个1000万的数组就超了。

    三个方法放一次看下:

     1 var arr = [];
     2 //生成随机数组
     3 for (var i = 100000; i >= 0; i--) {
     4     arr.push(Math.ceil(Math.random()*10000));
     5 }
     6 
     7 //开始时间
     8 var now1 = new Date();
     9 var arr1 = arr.filter(function(value,index,arr){
    10     return arr.indexOf(value,index+1) === -1
    11 })
    12 
    13 //结束时间
    14 var now2 = new Date();
    15 console.log(now2-now1);
    16 
    17 console.log("------------------------");
    18 
    19 now1 = new Date();
    20 
    21 //中间数组,用来记录arr出现过的值
    22 var tmp = [];
    23 arr.forEach(function(value){
    24     tmp[value] = 1;
    25 });
    26 
    27 var arr2 = [];
    28 tmp.filter(function(value,index){
    29     if(value === 1){
    30         arr2.push(index);
    31     }
    32 })
    33 
    34 now2 = new Date();
    35 console.log(now2-now1);
    36 
    37 console.log("------------------------");
    38 
    39 now1 = new Date();
    40 //使用sort从小到大排序并过滤掉前后相同的元素
    41 arr3 = arr.sort(function(a,b){
    42     return a-b;
    43 }).filter(function(value,index){
    44     return arr[index] !== arr[index-1];
    45 });
    46 //过滤数组
    47 now2 = new Date();
    48 console.log(now2-now1);

    效率对比就非常明显了。

    下面来说一下之前留下的坑:

    1、为什么要用跨度呢,因为实验中发现(一开始并没有细想),如果数组跨度越下,那么第一种方法的速度就越快,但是其他两种,尤其是第二种的变化幅度就没那么剧烈,这是为什么呢?

    其实是因为,indexOf的机制造成的。indexOf的实现是从你规定的位置开始往后找,找到第一个就停下来。所以很明显的,如果跨度越小,那么出现重复数字的几率就越高,那么就越有可能很快有返回结果,所以跨度越小就会越接近n的效率。

    反观其他两个方法,不管你跨度多少,影响的都是数组的长度,那么影响就只在遍历一遍数组的效率这方面,所以就很小。

    2、其实第一种方法的2000毫秒左右的效率是基于是随机数组的情况下,那么如果我们把数组改成顺序数组,也就是没有重复的数组呢?

    我们来试验一下

    var arr = [];
    //生成顺序数组
    for (var i = 100000; i >= 0; i--) {
        arr.push(i);
    }

    看到了吧,这才是真正的n²的效率,之前的2000毫秒和这个还是有常数级别的区别的。

    我们还发现,第三种方法的速度也变久了。将近翻了一倍,这个的话应该是sort内部实现的问题,我也不知道node内部sort使用的是什么排序,不过排序方法一般都是对顺序或者倒序和随机之间有一定的差距,这个很正常。

    实验做完了,我们来总结一下:

    第一种:

    优点:简洁,明了,一定程度上可以看出对js的掌握程度,适用范围很广,并且不会改变数组元素的相对位置(分为对后去重和对前去重两种)。

    缺点:效率非常慢,尤其是在重复数非常少的情况下。

    第二种:

    优点:速度非常快,受其他因素影响小(如果数量少,跨度大的话,也就是稀疏数组,反而会比其他的慢)。

    缺点:适用范围比较小,只能适合数字数组,并且数组不能过大。如果为其他数组,可以使用对象作为数组。遍历使用for in 即可,效率和第三种差距不大。代码如下,可自行实验。

     1 var now1 = new Date();
     2 
     3 //中间对象
     4 var tmp = {};
     5 arr.forEach(function(value){
     6     tmp[value+'we'] = 1;
     7 })
     8 
     9 var arr3 = [];
    10 for(index in tmp){
    11     if(tmp[index] === 1){
    12         arr3.push(index);
    13     }
    14 }
    15 
    16 var now2 = new Date();
    17 console.log(now2-now1);

    第三种:

    优点:速度相对较快,受其他因素影响不大,适用范围也相对较广。

    缺点:中轨终于,适用范围不如第一种大,速度不如第三种快。会受其他因素一定的影响。

  • 相关阅读:
    web测试方法总结
    APP测试点总结
    函数初识
    字符编码及文件操作
    简单购物车程序(Python)
    基本数据类型(列表,元祖,字典,集合)
    python基础
    基本数据类型(数字和字符串)
    Python入门
    操作系统
  • 原文地址:https://www.cnblogs.com/baqiphp/p/6122725.html
Copyright © 2011-2022 走看看