文章目录
- 一、算法的时间复杂度
- 1.1 度量算法执行时间的两种方法
- 1.1.1 事后统计
- 1.1.2 事前估算
- 1.2 时间频度
- 1.2.1 基本介绍
- 1.2.2 举例说明:基本案例
- 1.2.3 举例说明:忽略常数项
- 1.2.4 举例说明:忽略低次项
- 1.2.5 举例说明:忽略系数
- 1.3 时间复杂度
- 1.4 常见的时间复杂度
- 1.4.1 常数阶 O(1)
- 1.4.2 对数阶
- 1.4.3 线性阶 O(n)
- 1.4.4 线性对数阶
- 1.4.5 平方阶
- 1.4.6 立方阶
- 1.4.7
- 1.5 平均时间复杂度和最坏时间复杂度
- 二、算法的空间复杂度
- 2.1 基本介绍
一、算法的时间复杂度
1.1 度量算法执行时间的两种方法
1.1.1 事后统计
直接运行一遍程序,记录程序运行的用时
这种方法可行, 但是有两个问题:
- 一是要想对设计的算法的运行性能进行评测, 需要实际运行该程序;
- 二是所得时间的统计量依赖于计算机的硬件、软件等环境因素, 这种方式, 要在同一台计算机的相同状态下运行, 才能比较哪个算法速度更快。
1.1.2 事前估算
通过分析某个算法的时间复杂度来判断哪个算法更优
1.2 时间频度
1.2.1 基本介绍
时间频度: 一个算法花费的时间与算法中语句的执行次数成正比例, 哪个算法中语句执行次数多, 它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为
1.2.2 举例说明:基本案例
比如计算1-100所有数字之和,我们设计两种算法
第一种算法:T(n) = n + 1
int total = 0;
int end = 100;
for (int i = 1; i <= end; i++) {
total += i;
}
第二种算法:T(n) = 1
int end = 100;
// 等差数列求和公式
int total = (1 + end) * end / 2;
1.2.3 举例说明:忽略常数项
结论
- 和 随着
- 和 随着
1.2.4 举例说明:忽略低次项
结论
- 和 随着 变大, 执行曲线无限接近, 可以忽略
- 和 随着 变大,执行曲线无限接近, 可以忽略
1.2.5 举例说明:忽略系数
结论
- 随着 值变大, 和 , 执行曲线重合, 说明这种情况下, 5 和 3 可以忽略。
- 而 和 , 执行曲线分离, 说明多少次方是关键
1.3 时间复杂度
1. 一般情况下, 算法中的基本操作语句的重复执行次数是问题规模 的某个函数, 用 , 若有某个辅 助函数 , 使得当 趋近于无穷大时, 的极限值为不等于零的常数, 则称 是 的同数量级函数。 记作 , 称
2. 。如: 与 它们的 不同, 但时间复杂度相同, 都为
3. 计算时间复杂度的方法:
- 用常数 1 代替运行时间中的所有加法常数
- 修改后的运行次数函数中, 只保留最高阶项
- 去除最高阶项的系数
1.4 常见的时间复杂度
- 常数阶
- 对数阶
- 线性阶
- 线性对数阶
- 平方阶
- 立方阶
- 矢阶
- 指数阶
常见的时间复杂度对应的图
说明
- 常见的算法时间复杂度由小到大依次为: , 随着问题规模
- 从图中可见, 我们应该尽可能避免使用指数阶的算法
1.4.1 常数阶
无论代码执行了多少行,只要没有循环等复杂结构,那这个代码的时间复杂度就是
int i = 1;
int j = 2;
int k = j - i;
++k;
j++;
i += 1;
上述代码在执行的时侯, 它消耗的时候并不随着某个变量的增长而增长, 那么无论这类代码有多长, 即使有几万、几十万行, 都可以用 O(1) 来表示它的时间复杂度。
1.4.2 对数阶
在while循环里面, 每次都将 乘以 2 , 乘完之后, 距离 就越来越近了。
假设循环 次之后, 就大于 2 了, 此时这个循环就退出了, 也就是说 2 的 次方等于 , 那么 也就是说当循环 次以后, 这个代码就结束了。
因此这个代码的时间复杂度为: 。
的这个 2 时间上是根据代码变化的, 假设是 , 则是 .
int i = 1;
while (i < n) {
i = i * 2;
}
1.4.3 线性阶
说明: 这段代码, for循环里面的代码会执行 遍, 因此它消耗的时间是随着 的变化而变 化的, 因此这类代码都可以用
for (int i = 0; i < n; i++) {
// some O(1) code ...
}
1.4.4 线性对数阶
线性对数阶就很好理解了,就是将时间复杂度为 的代码循环执行 n 次
for (int i = 0; i < n; i++) {
int k = 1;
while (k < n) {
k = k * 2;
}
}
1.4.5 平方阶
2层for循环,最内层是O(1)的代码
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// some O(1) code ...
}
}
1.4.6 立方阶
3层for循环,最内层是O(1)的代码
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
// some O(1) code ...
}
}
}
1.4.7 次方阶
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
for (int l = 0; l < n; l++) {
for (int m = 0; m < n; m++) {
......
}
}
}
}
}
1.5 平均时间复杂度和最坏时间复杂度
- 平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下, 该算法的运行时间。
- 最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。这样做的 原因是: 最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限, 这就保证了算法的运行时间不会 比最坏情况更长。
- 平均时间复杂度和最坏时间复杂度是否一致, 和算法有关(如下图所示)。
二、算法的空间复杂度
2.1 基本介绍
- 类似于时间复杂度的讨论, 一个算法的空间复杂度(Space Complexity)定义为该算法所耗费的存储空间, 它也是 问题规模
- 空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。有的算法需要占用的 临时工作单元数与解决问题的规模 有关, 它随着 的增大而增大, 当
- 在做算法分析时, 主要讨论的是时间复杂度。从用户使用体验上看, 更看重的程序执行的速度。一些缓存产品 (redis, memcache)和算法(基数排序)本质就是用空间换时间.