前端面试题:高效地随机选取数组中的元素

有前端题目大概是这样的:考虑到性能问题,如何快速从一个巨大的数组中随机获取部分元素。

比如有个数组有100K个元素,从中不重复随机选取10K个元素。

为了演示方便我们将数据简化,先给出方案最后再用大点的数据来测试性能的对比。

常规解法

常规做法倒也不难,生成一个0到数组长度减1的随机数,这个数也就是被选中元素在原数组中的下标,获得该元素后将值保存到另一个数组同时通过数组的splice方法将该元素从原数组中删除,以保证下次不会重复取到。

按以上思路,代码大概就是这样的:

//元素总数,为了方便演示这里取个小一点的数目比如5,表示总共5个元素
var TOTAL_NUM = 5,
    //要取得的个数,表示我们要从原数组中随机取3个元素
    COUNT = 3,
    //用随机字符串初始化原数组
    arr = new Array(TOTAL_NUM + 1).join(‘0‘).split(‘‘).map(function() {
        return Math.random().toString(36).substr(2);
    }),
    //保存结果的数组
    result = [];

console.log(‘原数组:‘, arr);

//开始我们的选取过程
for (var i = COUNT - 1; i >= 0; i--) {
    //从原数组中随机取一个元素出来
    var index = Math.floor(Math.random() * arr.length);
    //压入结果数组
    result.push(arr[index]);
    //将该元素从原数组中删除
    arr.splice(index, 1);
};
console.log(‘结果数组:‘, result);

运行结果如下图:

当然上面例子中为了便于演示,将题目要求的100 000 大数目简化为总数为5,同时只取3个。

由测试结果看这种做法是完全可行的。

但存在一个问题:为了下次随机时不重复选取已经选择过的元素,我们将选择过的元素从原数组中通过splice方法进行删除,但这个splice方法操作的过程本身就是数组重新维护其元素索引的过程,这意味着被选择的元素之后的所有元素需要前移一个位置来重新生成一个紧凑的数组,可以想象如果我们取走了原数组中的第1个元素,那么之后的99 999个元素都需要发生变动来完成重组数组的操作,无疑有点耗时。

利用洗牌算法

另一个思路可以是这样的,既然要随机选取,那我可以先把数组的元素打乱先,然后要多少就从开始取多少就行了。一提到随机,自然想到洗牌算法,而关于洗牌算法已经有一个非常经典且高效的Fisher-Yates算法了,这个算法我之前有写过一篇博客介绍过。

这个想法较之前的方法有点逆行的感觉,前面着重点是随机,所以每次都产生一个随机下标到原数组去取,现在是先将数组元素随机打乱,再去正常取。由于洗牌算法非常高效且省去了数组的重组,较之前性能应该有所提升。

照这个思路最后实现的代码大概就是这个样子的:

//元素总数,为了方便演示这里取个小一点的数目比如5,表示总共5个元素
var TOTAL_NUM = 5,
    //要取得的个数,表示我们要从原数组中随机取3个元素
    COUNT = 3,
    //用随机字符串初始化原数组
    arr = new Array(TOTAL_NUM + 1).join(‘0‘).split(‘‘).map(function() {
        return Math.random().toString(36).substr(2);
    }),
    //保存结果的数组
    result = [];

console.log(‘原数组:‘, arr);
//随机化原数组
arr = shuffle(arr);
//选取元素
result = arr.slice(0, COUNT);
console.log(‘结果数组:‘, result);

function shuffle(array) {
    var m = array.length,
        t, i;
    // 如果还剩有元素…
    while (m) {
        // 随机选取一个元素…
        i = Math.floor(Math.random() * m--);
        // 与当前元素进行交换
        t = array[m];
        array[m] = array[i];
        array[i] = t;
    }
    return array;
}

上面代码中包含了经典的洗牌算法Fisher-Yates Shuffle算法,即shuffle函数。具体可参见我的另一篇博客

运行结果:

从结果来看,此种方法也是可行的。

细想还是存在问题,对于一个比较大的数组来说,不管你的洗牌算法多么高效(即使上面Fisher-Yates算法时间复杂度为O(n)),要随机整个数组也还是很庞大的工程的吧。

所以对于这个题目的探索还没有完。当我在stackoverflow上面发问后,虽然没得到什么惊人的回答,但有个回答却提醒我可以将上面的方法再次改进。

只取所需

那就是我们没有必要随机掉整个数组,在我们取完需要数量的元素后,可以将Fisher-Yates乱序方法中止掉!

思路是非常明显的了, 这样可以省下不少无意义的操作。

所以最后的实现大概成了这样子:

//元素总数,为了方便演示这里取个小一点的数目比如5,表示总共5个元素
var TOTAL_NUM = 5,
    //要取得的个数,表示我们要从原数组中随机取3个元素
    COUNT = 3,
    //用随机字符串初始化原数组
    arr = new Array(TOTAL_NUM + 1).join(‘0‘).split(‘‘).map(function() {
        return Math.random().toString(36).substr(2);
    }),
    //保存结果的数组
    result = [];

console.log(‘原数组:‘, arr);

//此段代码由Fisher-Yates shuflle算法更改而来
var m = arr.length,
    t, i;
while (m && result.length < COUNT) {
    // 随机选取一个元素…
    i = Math.floor(Math.random() * m--);
    t = arr[m];
    arr[m] = arr[i];
    arr[i] = t;
    result.push(arr[m]);
}

console.log(‘结果数组:‘, result);

上面代码将Fisher-Yates算法略做修改,在取得满足要求的元素之后便停止了,所以较前面的做法更加科学。

运行结果:

性能比较

最后给出上面三个方法耗时的比较,这里将需要操作的数组元素个数回归到题目中要求的100 000来。

下图是jsperf上运行测试的结果,详情可点测试页面重新运行。数值越大越好。由上到下依次是本文中介绍的三种方法。

总结

目前PO主只能想到这些,更优的做法还有待进一步探究。

REFERNCE

由乱序播放说开了去-数组的打乱算法Fisher–Yates Shuffle http://www.cnblogs.com/Wayou/p/fisher_yates_shuffle.html

时间: 2024-10-07 15:17:05

前端面试题:高效地随机选取数组中的元素的相关文章

前端面试题总结——Html5(持续更新中)

前端面试题总结--H5(持续更新中) 1.HTML5 为什么只需要写 <!DOCTYPE HTML>? HTML5 需要doctype来规范浏览器的行为,让浏览器按照它们应该的方式来运行:HTML4.01基于SGML,所以需要对DTD进行引用,才能告知浏览器文档所使用的文档类型. 2.HTML5的form如何关闭自动完成功能? 给不想要提示的 form 或某个 input 设置为 autocomplete=off. 3.HTML5 中如何嵌入音频? 当前,audio 元素支持三种音频格式:&l

javascript如何随机输出数组中的内容

javascript如何随机输出数组中的内容: 有时候我们可能需要从数组中随机抽出一项内容,下面就通过一段代码实例介绍一下如何实现此效果. 代码如下: <script type="text/JavaScript"> var theArray=new Array(); theArray[0]="蚂蚁部落"; theArray[1]="蚂蚁部落一"; theArray[2]="蚂蚁部落二"; theArray[3]=&

各大互联网公司前端面试题(js)

对于巩固复习js更是大有裨益.    初级Javascript: 1.JavaScript是一门什么样的语言,它有哪些特点? 没有标准答案. 2.JavaScript的数据类型都有什么? 基本数据类型:String,Boolean,Number,Undefined, Null 引用数据类型:Object(Array,Date,RegExp,Function) 那么问题来了,如何判断某变量是否为数组数据类型? 方法一.判断其是否具有“数组性质”,如slice()方法.可自己给该变量定义slice方

Web前端面试题集锦

Web前端面试题集锦 前端开发面试知识点大纲: 注意 转载须保留原文链接(http://www.cnblogs.com/wzhiq896/p/5927180.html )作者:wangwen896 HTML&CSS: 对Web标准的理解.浏览器内核差异.兼容性.hack.CSS基本功:布局.盒子模型.选择器优先级及使用.HTML5.CSS3.移动端适应. JavaScript: 数据类型.面向对象.继承.闭包.插件.作用域.跨域.原型链.模块化.自定义事件.内存泄漏.事件机制.异步装载回调.模板

前端面试题 -- JS篇

前端面试题 -- JS篇 类型 1.js中有哪些数据类型,并解释清楚原始数据类型和引用数据类型 js中共有null,undefined, string,number,boolean,object六种数据类型. 原始数据类型: null,undefined, string,number,boolean 引用数据类型:object 两者的区别:1)值存储方式不同: 原始数据类型:将变量名和值都存储在栈内存中 引用数据类型:将变量名存储在栈内存中,将值存储在堆内存中,并在栈内存中存储值的地址,该地址指

Web前端面试题-1

Web前端面试题 Web前端面试题 1 HTML/CSS部分 4 1.什么是盒子模型?有几种? 4 2.行内元素有哪些?块级元素有哪些? 空(void)元素有那些? 4 3.CSS实现垂直水平居中 4 4.简述一下src与href的区别 5 5.什么是CSS Hack? 5 6.简述同步和异步的区别 6 7.px和em的区别 6 8. 什么叫优雅降级和渐进增强? 6 9.浏览器的内核分别是什么? 7 10.XHTML和HTML有什么区别 7 12.前端页面有哪三层构成,分别是什么?作用是什么?

一份来自于全球的前端面试题清单,看看老外喜欢考哪些题(部分有答案)

方括号中的蓝色标题是题目的出处,有些题目在原址内包含答案.搜集的大部分外国前端面试题没有做翻译,单词并不难,大家应该看得懂.题目旁边的方括号内, 简单记录了与此题相关的知识点.总共大概一千多道,包含国内的题目,如有错误,欢迎指正.有些原链可能已无法打开,有些可能需要代理才能查看. 一.HTML [HTML related interview questions] 1.What is doctype? Why do u need it? 2.What is the use of data-* at

前端面试题关于JavaScript 这些你都会吗?

昨天我们一起分享了关于html和css的面试题<前端面试题之Html和CSS>,今天我们来分享关于javascript有关的面试题.我面试的时候最害怕面试官问我js了,因为我真心不擅长这个.不过我在努力的学习中. 本宝宝第一次面试的时候比这个还紧张呢!!! 1.介绍js的基本数据类型 Undefined.Null.Boolean.Number.String 2.js有哪些内置对象? 数据封装类对象:Object.Array.Boolean.Number 和 String 其他对象:Functi

前端面试题 之 JavaScript

原文:前端面试题 之 JavaScript 昨天我们一起分享了关于html和css的面试题<前端面试题之Html和CSS>,今天我们来分享关于javascript有关的面试题.我面试的时候最害怕面试官问我js了,因为我真心不擅长这个.不过我在努力的学习中. 本宝宝第一次面试的时候比这个还紧张呢!!! 1.介绍js的基本数据类型 Undefined.Null.Boolean.Number.String 2.js有哪些内置对象? 数据封装类对象:Object.Array.Boolean.Numbe