前端必刷手写题系列 [15]

506 阅读5分钟

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战

这个系列也没啥花头,就是来整平时面试的一些手写函数,考这些简单实现的好处是能看出基本编码水平,且占用时间不长,更全面地看出你的代码实力如何。一般不会出有很多边界条件的问题,那样面试时间不够用,考察不全面。

平时被考到的 api 如果不知道或不清楚,直接问面试官就行, api 怎么用这些 Google 下谁都能马上了解的知识也看不出水平。关键是在实现过程,和你的编码状态习惯思路清晰程度等。

注意是简单实现,不是完整实现,重要的是概念清晰实现思路清晰,建议先解释清除概念 => 写用例 => 写伪代码 => 再实现具体功能,再优化,一步步来。

25. 随机打乱数组

问题是什么

问题很清晰,就是给你个数组,让你随机打乱顺序。

分析

有一个常用写法是这个, 利用 Math.randomArray.prototype.sort()

let arr = [1,2,3,4,5,6,7,8,9,10]

arr.sort(() => Math.random() - 0.5);

console.log(arr)

主要是这两个 api 的使用

Math.random

Math.random() 函数返回一个浮点数, 伪随机数在范围从0到小于1,也就是说,从0(包括0)往上,但是不包括1(排除1),然后您可以缩放到所需的范围。实现将初始种子选择到随机数生成算法; 它不能被用户选择或重置。

Math.random() 不能提供像密码一样安全的随机数字。不要使用它们来处理有关安全的事情。使用Web Crypto API 来代替, 和更精确的 window.crypto.getRandomValues() 方法.

Array.prototype.sort()

sort() 方法用原地算法对数组的元素进行排序,并返回数组。(原数组会改变)默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的

  • 语法
arr.sort([compareFunction])
  • 参数

compareFunction 可选 - 用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。 - firstEl - 第一个用于比较的元素。 - secondEl - 第二个用于比较的元素。

  • 返回值

排序后的数组。请注意,数组已原地排序,并且不进行复制。

  • 描述

如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:

  • 如果 compareFunction(a, b) 小于 0那么 a 会被排列到 b 之前
  • 如果 compareFunction(a, b) 等于 0a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);
  • 如果 compareFunction(a, b) 大于 0b 会被排列到 a 之前
  • compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。

所以我们用Math.random() - 0.5 返回一个正负未知的数,从而实现乱序。

此方法的局限性

且经过实验验证,用此方法打乱并不是概率相同的随机,会发现每个元素仍然有很大机率在它原来的位置附近出现。这样在做某些抽奖等现实需求会出问题。

而且由于 v8 出于对性能的考虑 在处理 sort 方法时,使用了插入排序和快排两种方案。当目标数组长度小于10时,使用插入排序;否则,使用快排。

这样会造成 10 这个分界线,两边概率分布又有所不同。

那么下面来介绍更好的方式

Fisher–Yates shuffle (洗牌算法)

这个算法是由 Ronald Fisher 和 Frank Yates 首次提出的。

总的来说思路是这样:

指针 i 从最后位置开始,从头开始到当前指针 i 位置中随机选出一个位置 j,i 和 j 位置替换,然后 i 指针前移直到头。如图

  • 指针从尾部开始,因为index 从 0 开始,所以 i 先 -1, j 从 [0-4] 5 个数的位置中随机生成,跟 i 指针 (index=4)交换
[1, 2, 3, 4, 5]
             |
             i
  • 比如随机生成的 j = 1
[1, 2, 3, 4, 5]
    |        |
    j        i
  • 交换 可以用解构赋值, 注意是值交换指针不动
[1, 5, 3, 4, 2]
    |        |
    j        i
  • 下一轮循环 i--
[1, 5, 3, 4, 2]
    |     |  
    j     i
  • j 从剩下的 [0-3]中随机 ,比如 j = 2
[1, 5, 3, 4, 2]
       |  |  
       j  i
  • 第二轮交换
[1, 5, 4, 3, 2]
       |  |  
       j  i
  • i 再前移

  • ......

  • 直到 i === 0

手写实现

function shuffle(arr) {
    let i = arr.length;
    while (i) {
        let j = Math.floor(Math.random() * i--);
        [arr[j], arr[i]] = [arr[i], arr[j]];
    }
}

let arr = [1,2,3,4,5,6,7,8,9,10]
shuffle(arr)
console.log(arr)

现实使用

当然这种算法我们开发当然也可以直接使用各种库, 当然引入方式可以更优雅和更节省体积

推荐 babel-plugin-lodash 这个工具

import { shuffle } from 'lodash'

shuffle([1, 2, 3, 4])
// => [4, 1, 3, 2]

另外向大家着重推荐下另一个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列 记得点赞哈

今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友 Or 搜索我的微信号infinity_9368,可以聊天说地 加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我 presious tower shock the rever monster,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考