如何用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 相关文章推荐
jQuery(1.3.2) 7行代码搞定跟随屏幕滚动的层
May 21 Javascript
javascript 全选与全取消功能的实现代码
Dec 23 Javascript
jquery中获取id值方法小结
Sep 22 Javascript
jquery.post用法关于type设置问题补充
Jan 03 Javascript
JQuery删除DOM节点的方法
Jun 11 Javascript
JavaScript之promise_动力节点Java学院整理
Jul 03 Javascript
jQuery完成表单验证的实例代码(纯代码)
Sep 30 jQuery
Vuejs实现购物车功能
Nov 05 Javascript
Angular2管道Pipe及自定义管道格式数据用法实例分析
Nov 29 Javascript
基于Axios 常用的请求方法别名(详解)
Mar 13 Javascript
JavaScript制作3D旋转相册
Aug 02 Javascript
如何解决vue在ios微信&quot;复制链接&quot;功能问题
Mar 26 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
微信自定义菜单的处理开发示例
2015/04/16 PHP
Yii框架小部件(Widgets)用法实例详解
2020/05/15 PHP
Json和Jsonp理论实例代码详解
2013/11/15 Javascript
判断客户浏览器是否支持cookie的示例代码
2013/12/23 Javascript
一个简单的jQuery插件ajaxfileupload.js实现ajax上传文件例子
2014/06/26 Javascript
jQuery判断元素上是否绑定了指定事件的方法
2015/03/17 Javascript
JavaScript中exec函数用法实例分析
2015/06/08 Javascript
浅谈jQuery中setInterval()方法
2015/07/07 Javascript
理解javascript异步编程
2016/01/27 Javascript
jQuery图片轮播(二)利用构造函数和原型创建对象以实现继承
2016/12/06 Javascript
微信小程序 label 组件详解及简单实例
2017/01/10 Javascript
JS实现最简单的冒泡排序算法
2017/02/15 Javascript
微信网页授权并获取用户信息的方法
2018/07/30 Javascript
原生js+css调节音量滑块
2020/01/15 Javascript
Javascript如何实现扩充基本类型
2020/08/26 Javascript
js实现前端界面导航栏下拉列表
2020/08/27 Javascript
Javascript Symbol原理及使用方法解析
2020/10/22 Javascript
Python XML RPC服务器端和客户端实例
2014/11/22 Python
Python读取网页内容的方法
2015/07/30 Python
菜鸟使用python实现正则检测密码合法性
2016/01/05 Python
django在接受post请求时显示403forbidden实例解析
2018/01/25 Python
python dict 相同key 合并value的实例
2019/01/21 Python
python中实现控制小数点位数的方法
2019/01/24 Python
python 利用文件锁单例执行脚本的方法
2019/02/19 Python
Python3.7基于hashlib和Crypto实现加签验签功能(实例代码)
2019/12/04 Python
python3连接kafka模块pykafka生产者简单封装代码
2019/12/23 Python
Matplotlib绘制雷达图和三维图的示例代码
2020/01/07 Python
Python list和str互转的实现示例
2020/11/16 Python
Python爬虫爬取有道实现翻译功能
2020/11/27 Python
Origins悦木之源英国官网:雅诗兰黛集团高端植物护肤品牌
2017/11/06 全球购物
Qoo10台湾站:亚洲领先的在线市场
2018/05/15 全球购物
新年主持词
2014/03/27 职场文书
艺术学院毕业生求职信
2014/07/09 职场文书
如何利用Python实现一个论文降重工具
2021/07/09 Python
VUE中的v-if与v-show区别介绍
2022/03/13 Vue.js
python数字图像处理:图像简单滤波
2022/06/28 Python