一起手写吧!数组去重、数组乱序!
数组去重
双循环去重
双重for(或while)循环是比较笨拙的方法,它实现的原理很简单:
先定义一个包含原始数组第一个元素的数组,然后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对。
如果不重复则添加到新数组中,最后返回新数组;因为它的时间复杂度是O(n^2),如果数组长度很大,那么将会非常耗费内存。
function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } let res = [arr[0]] for (let i = 1; i < arr.length; i++) { let flag = true for (let j = 0; j < res.length; j++) { if (arr[i] === res[j]) { flag = false; break } } if (flag) { res.push(arr[i]) } } return res }
set与解构赋值去重
ES6中新增了数据类型set,set的一个最大的特点就是数据不重复。
Set函数可以接受一个数组(或类数组对象)作为参数来初始化,利用该特性也能做到给数组去重。
function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } return [...new Set(arr)] }
Array.from与set去重
Array.from方法可以将Set结构转换为数组结果,而我们知道set结果是不重复的数据集,因此能够达到去重的目的
function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } return Array.from(new Set(arr)) }
数组乱序
8在处理sort方法时,使用了插入排序和快排两种方案。 当目标数组长度小于10时,使用插入排序;反之,使用快速排序。
其实不管用什么排序方法,大多数排序算法的时间复杂度介于O(n)到O(n²)之间, 元素之间的比较次数通常情况下要远小于n(n-1)/2。
也就意味着有一些元素之间根本就没机会相比较(也就没有了随机交换的可能), 这些 sort 随机排序的算法自然也不能真正随机。
其实我们想使用array.sort进行乱序,理想的方案或者说纯乱序的方案是数组中每两个元素都要进行比较, 这个比较有50%的交换位置概率。
这样一来,总共比较次数一定为n(n-1)。 而在sort排序算法中,大多数情况都不会满足这样的条件。因而当然不是完全随机的结果了。
Fisher–Yates
因为这个算法是由 Ronald Fisher 和 Frank Yates 首次提出的。
这个算法其实非常的简单,就是将数组从后向前遍历,然后将当前元素与随机位置的元素进行交换。结合图片来解释一下:
首先我们有一个已经排好序的数组
Step1: 第一步需要做的就是,从数组末尾开始,选取最后一个元素。
在数组一共9个位置中,随机产生一个位置,该位置元素与最后一个元素进行交换。
Step2: 上一步中,我们已经把数组末尾元素进行随机置换。 接下来,对数组倒数第二个元素动手。
在除去已经排好的最后一个元素位置以外的8个位置中, 随机产生一个位置,该位置元素与倒数第二个元素进行交换。
Step3: 理解了前两步,接下来就是依次进行。
实现代码如下
function shuffle(arr) { let m = arr.length; while (m > 1){ let index = Math.floor(Math.random() * m--); [arr[m] , arr[index]] = [arr[index] , arr[m]] } return arr; }
从表格中我们可以看出,每个元素在每个位置出现的次数相差不大,说明这种方式满足了随机性的要求。
而且 Fisher–Yates 算法只需要通过一次遍历即可将数组随机打乱顺序,性能极为优异~~
至此,我们找到了将数组乱序操作的最优办法:Fisher–Yates~