如何用JavaScript学习算法复杂度


Posted in Javascript onApril 30, 2021

概述

在本文中,我们将探讨 “二次方” 和 “n log(n)” 等术语在算法中的含义。

在后面的例子中,我将引用这两个数组,一个包含 5 个元素,另一个包含 50 个元素。我还会用到JavaScript中方便的performance API来衡量执行时间的差异。

const smArr = [5, 3, 2, 35, 2];

const bigArr = [5, 3, 2, 35, 2, 5, 3, 2, 35, 2, 5, 3, 2, 35, 2, 5, 3, 2, 35, 2, 5, 3, 2, 35, 2, 5, 3, 2, 35, 2, 5, 3, 2, 35, 2, 5, 3, 2, 35, 2, 5, 3, 2, 35, 2, 5, 3, 2, 35, 2];

什么是 Big O 符号?

Big O 表示法是用来表示随着数据集的增加,计算任务难度总体增长的一种方式。尽管还有其他表示法,但通常 big O 表示法是最常用的,因为它着眼于最坏的情况,更容易量化和考虑。最坏的情况意味着完成任务需要最多的操作次数;如果你在一秒钟内就能恢复打乱魔方,那么你只拧了一圈的话,不能说自己是做得最好的。

当你进一步了解算法时,就会发现这非常有用,因为在理解这种关系的同时去编写代码,就能知道时间都花在了什么地方。

当你了解更多有关 Big O 表示法的信息时,可能会看到下图中不同的变化。我们希望将复杂度保持在尽可能低的水平,最好避免超过 O(n)。

如何用JavaScript学习算法复杂度

O(1)

这是理想的情况,无论有多少个项目,不管是一个还是一百万个,完成的时间量都将保持不变。执行单个操作的大多数操作都是 O(1)。把数据写到数组、在特定索引处获取项目、添加子元素等都将会花费相同的时间量,这与数组的长度无关。

const a1 = performance.now();
smArr.push(27);
const a2 = performance.now();
console.log(`Time: ${a2 - a1}`); // Less than 1 Millisecond


const b1 = performance.now();
bigArr.push(27);
const b2 = performance.now();
console.log(`Time: ${b2 - b1}`); // Less than 1 Millisecond

O(n)

在默认情况下,所有的循环都是线性增长的,因为数据的大小和完成的时间之间存在一对一的关系。所以如果你有 1,000 个数组项,将会花费的 1,000 倍时间。

const a1 = performance.now();
smArr.forEach(item => console.log(item));
const a2 = performance.now();
console.log(`Time: ${a2 - a1}`); // 3 Milliseconds

const b1 = performance.now();
bigArr.forEach(item => console.log(item));
const b2 = performance.now();
console.log(`Time: ${b2 - b1}`); // 13 Milliseconds

O(n^2)

指数增长是一个陷阱,我们都掉进去过。你是否需要为数组中的每个项目找到匹配对?将循环放入循环中是一种很好的方式,可以把 1000 个项目的数组变成一百万个操作搜索,这将会使你的浏览器失去响应。与使用双重嵌套循环进行一百万次操作相比,最好在两个单独的循环中进行 2,000 次操作。

const a1 = performance.now();
smArr.forEach(() => {
    arr2.forEach(item => console.log(item));
});
const a2 = performance.now();
console.log(`Time: ${a2 - a1}`); // 8 Milliseconds


const b1 = performance.now();
bigArr.forEach(() => {
    arr2.forEach(item => console.log(item));
});
const b2 = performance.now();
console.log(`Time: ${b2 - b1}`); // 307 Milliseconds

O(log n)

我认为关于对数增长最好的比喻,是想象在字典中查找像 “notation” 之类的单词。你不会在一个词条一个词条的去进行搜索,而是先找到 “N” 这一部分,然后是 “OPQ” 这一页,然后按字母顺序搜索列表直到找到匹配项。

通过这种“分而治之”的方法,找到某些内容的时间仍然会因字典的大小而改变,但远不及 O(n) 。因为它会在不查看大部分数据的情况下逐步搜索更具体的部分,所以搜索一千个项目可能需要少于 10 个操作,而一百万个项目可能需要少于 20 个操作,这使你的效率最大化。

在这个例子中,我们可以做一个简单的快速排序。

const sort = arr => {
  if (arr.length < 2) return arr;

  let pivot = arr[0];
  let left = [];
  let right = [];

  for (let i = 1, total = arr.length; i < total; i++) {
    if (arr[i] < pivot) left.push(arr[i]);
    else right.push(arr[i]);
  };
  return [
    ...sort(left),
    pivot,
    ...sort(right)
  ];
};
sort(smArr); // 0 Milliseconds
sort(bigArr); // 1 Millisecond

O(n!)

最糟糕的一种可能性是析因增长。最经典的例子就是旅行的推销员问题。如果你要在很多距离不同的城市之间旅行,如何找到在所有城市之间返回起点的最短路线?暴力方法将是检查每个城市之间所有可能的路线距离,这是一个阶乘并且很快就会失控。

由于这个问题很快会变得非常复杂,因此我们将通过简短的递归函数演示这种复杂性。这个函数会将一个数字去乘以函数自己,然后将数字减去1。阶乘中的每个数字都会这样计算,直到为 0,并且每个递归层都会把其乘积添加到原始数字中。

阶乘只是从 1 开始直至该数字的乘积。那么6!是1x2x3x4x5x6 = 720。

const factorial = n => {
  let num = n;

  if (n === 0) return 1
  for (let i = 0; i < n; i++) {
    num = n * factorial(n - 1);
  };

  return num;
};
factorial(1); // 2 Milliseconds
factorial(5); // 3 Milliseconds
factorial(10); // 85 Milliseconds
factorial(12); //  11,942 Milliseconds

我原本打算显示factorial(15),但是 12 以上的值都太多,并且使页面崩溃了,这也证明了为什么需要避免这种情况。

结束语

我们需要编写高性能的代码似乎是一个不争得事实,但是我敢肯定,几乎每个开发人员都创建过至少两重甚至三重嵌套循环,因为“它确实有效”。Big O 表示法在表达和考虑复杂性方面是非常必要的,这是我们从未有过的方式。

以上就是如何用JavaScript学习算法复杂度的详细内容,更多关于JS算法复杂度的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
JavaScript 编程引入命名空间的方法
Jun 29 Javascript
jquery实现的超出屏幕时把固定层变为定位层的代码
Feb 23 Javascript
指定区域的图片自动按比例缩小的js代码(防止页面被图片撑破)
Feb 21 Javascript
通过JQuery将DIV的滚动条滚动到指定的位置方便自动定位
May 05 Javascript
JavaScript使用Prototype实现面向对象的方法
Apr 14 Javascript
jquery判断密码强度的验证代码
Apr 22 Javascript
学习JavaScript设计模式之迭代器模式
Jan 19 Javascript
JS脚本实现动态给标签控件添加事件的方法
Jun 02 Javascript
vue2笔记 — vue-router路由懒加载的实现
Mar 03 Javascript
微信小程序文字显示换行问题
Jul 28 Javascript
JQuery实现折叠式菜单的详细代码
Jun 03 jQuery
swiperjs实现导航与tab页的联动
Dec 13 Javascript
JS不要再到处使用绝对等于运算符了
Apr 30 #Javascript
如何用Node.js编写内存效率高的应用程序
用几道面试题来看JavaScript执行机制
Apr 30 #Javascript
详解前端任务构建利器Gulp.js使用指南
Apr 30 #Javascript
浅谈node.js中间件有哪些类型
Apr 29 #Javascript
JavaScript实现简单图片切换
何时使用Map来代替普通的JS对象
You might like
php读取数据库信息的几种方法
2008/05/24 PHP
php示例详解Constructor Prototype Pattern 原型模式
2015/10/15 PHP
PHP字符串逆序排列实现方法小结【strrev函数,二分法,循环法,递归法】
2017/01/13 PHP
PHP array_shift()用法实例分析
2019/01/07 PHP
安装docker和docker-compose实例详解
2019/07/30 PHP
收藏一些不常用,但是有用的代码
2007/03/12 Javascript
解决用jquery load加载页面到div时,不执行页面js的问题
2014/02/22 Javascript
Java中Timer的用法详解
2015/10/21 Javascript
jQuery EasyUI编辑DataGrid用combobox实现多级联动
2016/08/29 Javascript
Angular中使用ui router实现系统权限控制及开发遇到问题
2016/09/23 Javascript
JavaScript DOM节点操作实例小结(新建,删除HTML元素)
2017/01/19 Javascript
谈谈JavaScript数组常用方法总结
2017/01/24 Javascript
JS实现留言板功能[楼层效果展示]
2017/12/27 Javascript
对vuejs的v-for遍历、v-bind动态改变值、v-if进行判断的实例讲解
2018/08/27 Javascript
vue this.reload 方法 配置
2018/09/12 Javascript
es6中比较有用的7个技巧小结
2019/07/12 Javascript
Vue+Element UI+vue-quill-editor富文本编辑器及插入图片自定义
2019/08/20 Javascript
基于vue+echarts数据可视化大屏展示的实现
2020/12/25 Vue.js
python操作数据库之sqlite3打开数据库、删除、修改示例
2014/03/13 Python
Python中使用PIL库实现图片高斯模糊实例
2015/02/08 Python
python之Socket网络编程详解
2016/09/29 Python
独特的python循环语句
2016/11/20 Python
Python3.6正式版新特性预览
2016/12/15 Python
浅谈Python 列表字典赋值的陷阱
2019/01/20 Python
Python实现爬取马云的微博功能示例
2019/02/16 Python
python opencv 读取图片 返回图片某像素点的b,g,r值的实现方法
2019/07/03 Python
Pytorch之Tensor和Numpy之间的转换的实现方法
2020/09/03 Python
h5页面背景图很长要有滚动条滑动效果的实现
2021/01/27 HTML / CSS
澳大利亚巧克力花束和礼品网站:Tastebuds
2019/03/15 全球购物
文员自我评价怎么写
2013/09/19 职场文书
军训自我鉴定怎么写
2014/02/13 职场文书
2014年圣诞节促销方案
2014/03/14 职场文书
秋天的怀念教学反思
2014/04/28 职场文书
党支部三会一课计划
2014/09/24 职场文书
2015年元旦标语大全
2014/12/09 职场文书
react中props 的使用及进行限制的方法
2021/04/28 Javascript