如何用JavaScript学习算法复杂度

概述

在本文中,我们将探讨 “二次方” 和 “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)。

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算法复杂度的资料请关注我们其它相关文章!

(0)

相关推荐

  • 通过js示例讲解时间复杂度与空间复杂度

    1. 博客背景 今天有同事在检查代码的时候,由于函数写的性能不是很好,被打回去重构了,细思极恐,今天和大家分享一篇用js讲解的时间复杂度和空间复杂度的博客 2. 复杂度的表示方式 之前有看过的,你可能会看到这么一串东西 T(n) = O(f(n)) S(n) = O(f(n)) 这个叫做大O表示法,其中的T代表的是算法需要执行的总时间 S表示的算法需要的总空间 f(n)表示的是代码执行的总次数 举个例子 function go(n) { var item = 0; // 这里执行了一次 for

  • js实现无限层级树形数据结构(创新算法)

    由于做项目的需要,把一个线性数组转成树形数组,在网上查了很多文章,觉得他们写的太复杂了,于是自己写了一个,在折腾了一下午终于把它写出来啦(激动.gif),用两个filter过滤器就搞定了,代码简洁明了,数据结构小白都能看懂. js代码:把扁平数据转成树形数据 function setTreeData(source){ let cloneData = JSON.parse(JSON.stringify(source)) // 对源数据深度克隆 return cloneData.filter(fat

  • JS求解两数之和算法详解

    本文实例讲述了JS求解两数之和算法.分享给大家供大家参考,具体如下: 题目描述 给定一个整数数组和一个目标值,找出数组中和为目标值的两个数. 你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用. ::: tip 给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1] ::: 解法 利用 Map 记录数组元素值和对应的下标,对于一个数 nums[i],判断 target - num

  • 如何通过JS实现日历简单算法

    最近有用到日历可需要编辑文本的日历,为了绑定数据的方便,所以用js写了一套日历,其实思想也是很简单.实现步骤如下: 1.首先取得处理月的总天数 js不提供此参数,我们需要计算.考虑到闰年问题会影响二月份的天数,我们先编写一个判断闰年的自编函数: function is_leap(year) { return (year%100==0?res=(year%400==0?1:0):res=(year%4==0?1:0)); } 2.接着定义一个包含十二个月在内的月份总天数的数组: m_days=ne

  • Javascript校验密码复杂度的正则表达式

    目前使用的正则表达式如下: 复制代码 代码如下: (?=.*\d)(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30} 对应的验证规则是:密码中必须包含字母.数字.特称字符,至少8个字符,最多30个字符. 这个正则表达式在C#可以正常使用,但是在Javascript中却有问题. 请问是在js中如何写这样的正则表达式? 测试字符串:a123456- 解决方法如下所示: 把\d改为[0-9]问题就解决了,正则表达式如下: 复制代码 代码如下: var regex = new

  • 基于原生js实现九宫格算法代码实例

    九宫格算法核心: 利用控件索引index计算出控件所在的行数和列数: 利用控件计算出left距离: 利用控件计算出top距离: 写特效时需要用到定位 公式: 行 row=parseInt(i/cols); 列 col=parseInt(i%cols); i是当前的盒子,cols是总列数, 代码示例: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"&g

  • JS算法教程之字符串去重与字符串反转

    一.字符串去重 说起字符串去重,第一时间就会想起数组去重,所以我们可以把字符串转换为数组,然后对数组去重,去重完毕后在拼接成字符串,下面就简单罗列两种字符串去重的方法,接下来我们看代码. 第一种方法: 逻辑步骤: 1.使用split或者ES6的展开运算符...把字符串切割成数组 2.使用ES6的Set数据解构,它类似于数组,但是它成员的值都是唯一的,使用new创建 3.对去重后的数组使用join拼接 let str = '11223344aabbcc' function strSeparate(

  • 基于JS实现计算24点算法代码实例解析

    前言 休息的时候无意间看到群里有人发出了华为的校招题,一开始看题目的时候觉得很简单,于是晚上就试着写了一下,结果写的过程中打脸,不断的整理逻辑不断的重写,但我的性格又是不做出来晚上睡不好的那种,于是在做出来的时候就分享给大家(快凌晨三点了有木有,这校招题难度都达到这级别了?o(╥﹏╥)o) 题目描述 审题要注意:1+2+3*4是前面三个已经相加为6再乘4,没有括号!! 代码: <!DOCTYPE html> <html lang="en"> <head&g

  • JavaScript冒泡算法原理与实现方法深入理解

    本文实例讲述了JavaScript冒泡算法.分享给大家供大家参考,具体如下: 在面试中经常会遇到面试官问到冒泡算法.今天总结一下. ###概念 有一组数,依次比较两个相邻的数,如果他们的顺序(如从大到小或从小到大等)错误就把他们交换过来. 我们先假设这一组数是有顺序的,那么我们找出它的规则. 我们按照从小到大的顺序依次交换长方形,得到以下的结果. 第一轮交换结果:CBAD     交换次数:3次 第二轮交换结果:BACD     交换次数:3次 第三轮交换结果:ABCD     交换次数:3次

  • 如何用JavaScript学习算法复杂度

    概述 在本文中,我们将探讨 "二次方" 和 "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,

  • JavaScript学习笔记之数组随机排序

    推荐阅读:JavaScript学习笔记之数组求和方法 JavaScript学习笔记之数组的增.删.改.查 JavaScript中提供了sort()和reverse()方法对数组项重新排序.但很多时候这两个方法无法满足我们实际业务的需求,比如说扑克牌游戏中的随机洗牌. 在这篇文章一起来学习如何完成上面这个示例的效果,以及一些有关于数组随机排序的相关知识. 在网上查了一下有关于数组随机排序的相关资料,都看到了Math.random()的身影.打开浏览器控制器,输入: Math.random() 从图

  • JavaScript排序算法动画演示效果的实现方法

    之前在知乎看到有人在问 自己写了一个冒泡排序算法如何用HTML,CSS,JavaScript展现出来排序过程.   感觉这个问题还挺有意思 .前些时间就来写了一个.这里记录一下实现过程. 基本的思想是把排序每一步的时候每个数据的值用DOM结构表达出来. 问题一:如何将JavaScript排序的一步步进程展现出来? 我试过的几种思路: 1.让JavaScript暂停下来,慢下来. JavaScript排序是很快的,要我们肉眼能看到它的实现过程,我首先想到的是让排序慢下来. 排序的每一个循环都让它停

  • JavaScript学习笔记之DOM操作实例分析

    本文实例讲述了JavaScript学习笔记之DOM操作.分享给大家供大家参考,具体如下: 一.DOM概念 1. "D":Docment,指的是文档 2. "O":Object,指的是对象,在javascript有三种对象:用户定义对象.内建对象(JavaScript语言对象.如Math,Array).宿主对象(浏览器对象) 3. "M":Model,值得是Model,某种事物的表现形式 二.节点 1. 元素节点 :<body> <

  • JavaScript学习笔记之数组基本操作示例

    本文实例讲述了JavaScript学习笔记之数组基本操作.分享给大家供大家参考,具体如下: 一.数组定义 1.定义 vara=[1,2,3] vara=newArray(1,2,3); 2.长度 返回长度 <script> vara=[1,2,3,4,5,6]; alert(a.length); </script> 设置长度 <script> vara=[1,2,3,4,5,6]; a.length=2; alert(a); </script> 二.数组连接

  • JavaScript学习笔记之基于定时器实现图片无缝滚动功能详解

    本文实例讲述了JavaScript学习笔记之基于定时器实现图片无缝滚动功能.分享给大家供大家参考,具体如下: 一.无缝滚动理论基础 基础知识 1.setInterval(function,time).clearInterval(timer) setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式. setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭.由 setInterval() 返回的 ID 值可用作 clea

  • JavaScript实现文本相似度对比

    目录 一.发现问题 二.解决问题 1.编辑距离的概念 2.测试文本 3.代码实现 4.相似度对比结果 一.发现问题 在开发过程中,难免会使用到2个(多个)文本内容处理,一是便于宏观知道文本的重合度,而是更好的区分文本的创新度,也能更好的避免出现大篇幅复制. 为此,可以通过2个文本的相似度对比来实现业务需求. 二.解决问题 如果使用后端语言1来处理,就需要调取接口,对比少量的短文本可以实现,但是一旦遇到在界面实现多个文本对比,并且篇幅巨多,再通过接口可能就出现耗时特别长的情况.既然如此,但不如直接

  • 详解如何用JavaScript编写一个单元测试

    目录 为什么要进行单元测试? 范围界定和编写单元测试 保持单元测试简短而简单 考虑正面和负面的测试用例 分解长而复杂的函数 避免网络和数据库连接 如何编写单元测试 创建一个新项目 实现一个类 配置和添加我们的第一个单元测试 添加更多单元测试 修复错误 最后 测试代码是确保代码稳定的第一步.能做到这一点的最佳方法之一就是使用单元测试,确保应用程序中的每个较小的功能都按应有的方式运行——尤其是当应用程序接收到极端或无效输入,甚至可能有害的输入时. 为什么要进行单元测试? 进行单元测试有许多不同的方法

  • 每天一篇javascript学习小结(基础知识)

    1.字符转换 var s1 = "01"; var s2 = "1.1"; var s3 = "z";//字母'z'无法转换为数字,所以或返回NaN var b = false; var f = 1.1; var o = { valueOf: function() { return -1; } }; s1 = -s1; //value becomes numeric -1 s2 = -s2; //value becomes numeric -1.

随机推荐