Roshin's blog Roshin's blog
首页
  • 《数据结构与算法》笔记
  • 手写代码
  • 实用方法
  • 文档教程
  • 面试集锦
  • 分类
  • 标签
  • 归档
  • 个人介绍
  • 关于博客
  • 友情链接
GitHub (opens new window)

Roshin

如果你只做自己能力范围之内的事情,就永远没法进步。
首页
  • 《数据结构与算法》笔记
  • 手写代码
  • 实用方法
  • 文档教程
  • 面试集锦
  • 分类
  • 标签
  • 归档
  • 个人介绍
  • 关于博客
  • 友情链接
GitHub (opens new window)
  • 如何衡量程序运行的效率?
    • 复杂度是什么
    • 时间复杂度
      • 常数阶O(1)
      • 线性阶O(n)
      • 对数阶O(logn)
      • 线性对数阶O(nlogn)
      • 平方阶O(n²)
      • 立方阶O(n³)
      • K 次方阶O(nᵏ)
      • 指数阶O(2ⁿ)
      • 阶乘阶O(n!)
    • 空间复杂度
      • 常数阶O(1)
      • 线性阶O(n)
      • 平方阶O(n²)
    • 结论
    • 总结
    • 练习
  • 将“昂贵”的时间复杂度转换成“廉价”的空间复杂度
  • 《数据结构与算法》笔记
roshin
2021-03-17

如何衡量程序运行的效率?

复杂度是什么 #

复杂度是衡量代码运行效率的重要度量因素。

对于同一个计算任务,不同的计算方法得到结果的过程复杂度是不一样的。根据资源消耗情况,我们可以从两个维度衡量复杂度。

1. 首先,这段代码消耗的资源是什么

一般而言,代码执行过程中会消耗 计算时间 和 计算空间,那么需要衡量的就是时间复杂度和空间复杂度。

2. 其次,这段代码对于资源的消耗是多少

我们通常不会关注这段代码对于资源消耗的绝对量,因为不管是时间还是空间,它们的消耗程度都和输入的数据量高度相关。为了更客观地衡量复杂度,我们通常会关注时间或者空间消耗量与输入数据量之间的关系。

复杂度是一个关于输入数据量n的函数。假设你的代码复杂度是f(n),那么就用个大写字母O把f(n)括起来就可以,即O(f(n))。通常,复杂度的计算方法遵循以下几个原则:

1. 复杂度与具体的常系数无关

O(n)和O(2n)表示的是同样的复杂度。即 O(2n) = O(n + n) = O(n) + O(n)

2. 多项式级的复杂度相加的时候,选择高者作为结果

O(n²) + O(n)和O(n²)表示的是同样的复杂度。即 O(n²) = O(n²) + O(n) = O(n² + n)

注意

O(1)也是表示一个特殊复杂度。含义为某个任务通过有限可数的资源即可完成。此处有限可数的是指,与输入数据量n无关。

时间复杂度 #

注意

时间复杂度与代码的结构有着非常紧密的关系

时间复杂度是程序执行所耗费的时间(执行次数)。

常见的时间复杂度衡量级别有:

常数阶O(1) #

无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)。如:

let i = 1;
let j = 2;
++i;
j++;
let m = i + j;
1
2
3
4

线性阶O(n) #

代码共执行n次,则f(n) = n。时间复杂度为O(n)。

for (let i = 1; i < n; i++) {
  // 语句执行 n 次
}
1
2

对数阶O(logn) #

通过不断的缩小查找范围,每次都将i * 2,假设循环x次之后i > 2,也就是说2ˣ = n,那么 x = log2ⁿ,则f(n) = log2ⁿ。时间复杂度为O(logn)。

let i = 1;
while (i < n) {
  i = i * 2;
}
1
2
3

线性对数阶O(nlogn) #

将时间复杂度为O(logn)的代码循环n遍的话,那么它的时间复杂度就是n * O(logn),则f(n) = nlogn。时间复杂度为O(nlogn)。

for (let i = 1; i < n; i++) {
  let j = 1;
  while (j < n) {
    j = j * 2;
  }
}
1
2
3
4
5

平方阶O(n²) #

代码共执行n² + n次,则f(n) = n²。时间复杂度为O(n²)。

for (let i = 0; i < n; i++) {
  // 语句执行 n 次
  for (let j = 0; j < n; j++) {
    // 语句执行 n² 次
  }
}
1
2
3
4
5

立方阶O(n³) #

代码共执行n³ + n² + n,则f(n) = n³。时间复杂度为O(n³)。

for (let i = 1; i < n; i++) {
  // 语句执行 n 次
  for (let j = 1; j < n; j++) {
    // 语句执行 n² 次
    for (let k = 1; k < n; k++) {
      // 语句执行 n³ 次
    }
  }
}
1
2
3
4
5
6
7
8

K 次方阶O(nᵏ) #

代码共执行nᵏ + ... + n³ + n² + n次,则f(n) = nᵏ。时间复杂度为O(nᵏ)。

for (let i = 1; i < n; i++) {
  // 语句执行 n 次
  for (let j = 1; j < n; j++) {
    // 语句执行 n² 次
    for (let k = 1; k < n; k++) {
      // 语句执行 n³ 次
      // ...
    }
  }
}
1
2
3
4
5
6
7
8
9

指数阶O(2ⁿ) #

代码共执行2ⁿ⁺¹ - 1次,则f(n) = 2ⁿ。时间复杂度为O(2ⁿ)。

function fn(n) {
  if (n === 0) return 1;
  return fn(n - 1) + fn(n - 1);
}
1
2
3

阶乘阶O(n!) #

待举例。这类算法的时间复杂度为O(n!)。

空间复杂度 #

注意

空间复杂度与数据结构的设计有关

空间复杂度是程序运行所耗费的空间。

空间复杂度比较常用的有:

常数阶O(1) #

如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为O(1)

let i = 1;
let j = 2;
++i;
j++;
let m = i + j;
1
2
3
4

线性阶O(n) #

这里声明了一个大小为n的数组,所以复杂度应该是O(n)

const arr = [];
for (let i = 1; i < n; i++) {
  arr[i] = i;
}
1
2
3

平方阶O(n²) #

这里声明了一个大小为n的数组,但是里面又开辟了一个新的大小为n的数组,即f(n) = n²,所以复杂度应该是O(n²)

const arr = [];
for (let i = 1; i < n; i++) {
  arr[i] = [];
  for (let j = 1; j < n; j++) {
    arr[i][j] = i + j;
  }
}
1
2
3
4
5
6

结论 #

  • 一个顺序结构的代码,时间复杂度是O(1)
  • 二分查找,或者更通用地说是采用分而治之的二分策略,时间复杂度都是O(logn)
  • 一个简单的 for 循环,时间复杂度是O(n)
  • 两个顺序执行的 for 循环,时间复杂度是O(n) + O(n) = O(2n),其实也是O(n)
  • 两个嵌套的 for 循环,时间复杂度是O(n²)

总结 #

复杂度通常包括时间复杂度和空间复杂度,其中时间复杂度与代码的结构设计高度相关,空间复杂度与代码中数据结构的选择高度相关。在具体计算复杂度时需要注意以下几点:

  • 复杂度与具体的常系数无关,O(n)和O(2n)表示的是同样的复杂度。
  • 复杂度相加的时候,选择高者作为结果,也就是说O(n²) + O(n)和O(n²)表示的是同样的复杂度。
  • O(1)也是表示一个特殊复杂度,即任务与算例个数n无关。

练习 #

评估以下代码片段的时间复杂度是多少

for (let i = 0; i < n; i++) {
  for (let j = 0; j < n; j++) {
    for (let k = 0; k < n; k++) {
      // ...
    }
    for (let m = 0; m < n; m++) {
      // ...
    }
  }
}
1
2
3
4
5
6
7
8
9
答案解析

代码共执行n + n² + n³ + n³次,则f(n) = n³。时间复杂度为O(n³)

for (let i = 0; i < n; i++) {
  // 语句执行 n 次
  for (let j = 0; j < n; j++) {
    // 语句执行 n² 次
    for (let k = 0; k < n; k++) {
      // 语句执行 n³ 次
      // ...
    }
    for (let m = 0; m < n; m++) {
      // 语句执行 n³ 次
      // ...
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
编辑 (opens new window)
#笔记#算法
上次更新: 2021-06-23 08:43:55
将“昂贵”的时间复杂度转换成“廉价”的空间复杂度

将“昂贵”的时间复杂度转换成“廉价”的空间复杂度→

最近更新
01
函数柯里化
04-15
02
Array.prototype.flat()
04-14
03
Array.prototype.reduce()
04-14
更多文章>
Theme by Vdoing Copyright © 2021-present Roshin | MIT License | 粤ICP备18116277号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式