zoukankan      html  css  js  c++  java
  • 小谈数组去重

    数组去重是一个常见的问题,在用C语言刷算法题的时候属于比较水的题目,很容易就AC。不过在JavaScript中,因为方法多样,所以解题的方法也多种多样,以下是自己的研究过程与结果。

    准备一:随机数组生成

    在研究之前,我们先实现一个随机生成数组的方法:

    /**
    *函数名:createArr
    *参数:
    *	len 表示要生成的数组的长度
    *	size 表示要生成的数组的范围的最大值
    *返回值:一个生成的数组
    **/
    function createArr(len, size){
    	var randomArr = [];
    	while(len--){
    		randomArr[len] = Math.floor(Math.random() * size + 1);
    	}
    	return randomArr;
    }
    

    我们来测试一下运行结果:

    示例1:

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

    示例1的运行结果:

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

    示例2:

    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方法,代码简洁,高效。

  • 相关阅读:
    Windows平台分布式架构实践
    压力测试的轻量级具体做法
    vs.net git版本仓库使用 之解决冲突方法 原创
    VS2015 Git 插件使用教程
    HTML页面禁止选择、页面禁止复制、页面禁止右键
    python标准库介绍——3 stat 模块详解
    python标准库介绍——2 os.path模块详解
    python标准库介绍——1 os详解
    Python 多进程 一分钟下载二百张图片 是什么样子的体验
    封ip对爬虫的影响
  • 原文地址:https://www.cnblogs.com/hlwyfeng/p/6754842.html
Copyright © 2011-2022 走看看