mirror of
https://github.com/krahets/hello-algo.git
synced 2025-07-05 05:01:43 +08:00
Modify 。**
to **。
for better visualization.
This commit is contained in:
@ -153,7 +153,7 @@ $$
|
||||
}
|
||||
```
|
||||
|
||||
但实际上, **统计算法的运行时间既不合理也不现实。** 首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。
|
||||
但实际上, **统计算法的运行时间既不合理也不现实**。首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。
|
||||
|
||||
## 统计时间增长趋势
|
||||
|
||||
@ -363,11 +363,11 @@ $$
|
||||
|
||||
相比直接统计算法运行时间,时间复杂度分析的做法有什么好处呢?以及有什么不足?
|
||||
|
||||
**时间复杂度可以有效评估算法效率。** 算法 `B` 运行时间的增长是线性的,在 $n > 1$ 时慢于算法 `A` ,在 $n > 1000000$ 时慢于算法 `C` 。实质上,只要输入数据大小 $n$ 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。
|
||||
**时间复杂度可以有效评估算法效率**。算法 `B` 运行时间的增长是线性的,在 $n > 1$ 时慢于算法 `A` ,在 $n > 1000000$ 时慢于算法 `C` 。实质上,只要输入数据大小 $n$ 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。
|
||||
|
||||
**时间复杂度的推算方法更加简便。** 在时间复杂度分析中,我们可以将统计「计算操作的运行时间」简化为统计「计算操作的数量」,这是因为,无论是运行平台还是计算操作类型,都与算法运行时间的增长趋势无关。因而,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”,这样的简化做法大大降低了估算难度。
|
||||
**时间复杂度的推算方法更加简便**。在时间复杂度分析中,我们可以将统计「计算操作的运行时间」简化为统计「计算操作的数量」,这是因为,无论是运行平台还是计算操作类型,都与算法运行时间的增长趋势无关。因而,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”,这样的简化做法大大降低了估算难度。
|
||||
|
||||
**时间复杂度也存在一定的局限性。** 比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。对于以上情况,我们很难仅凭时间复杂度来判定算法效率高低。然而,即使存在这些问题,计算复杂度仍然是评判算法效率的最有效且常用的方法。
|
||||
**时间复杂度也存在一定的局限性**。比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。对于以上情况,我们很难仅凭时间复杂度来判定算法效率高低。然而,即使存在这些问题,计算复杂度仍然是评判算法效率的最有效且常用的方法。
|
||||
|
||||
## 函数渐近上界
|
||||
|
||||
@ -538,9 +538,9 @@ $T(n)$ 是个一次函数,说明时间增长趋势是线性的,因此易得
|
||||
|
||||
对着代码,从上到下一行一行地计数即可。然而,**由于上述 $c \cdot f(n)$ 中的常数项 $c$ 可以取任意大小,因此操作数量 $T(n)$ 中的各种系数、常数项都可以被忽略**。根据此原则,可以总结出以下计数偷懒技巧:
|
||||
|
||||
1. **跳过数量与 $n$ 无关的操作。** 因为他们都是 $T(n)$ 中的常数项,对时间复杂度不产生影响。
|
||||
2. **省略所有系数。** 例如,循环 $2n$ 次、$5n + 1$ 次、……,都可以化简记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度也不产生影响。
|
||||
3. **循环嵌套时使用乘法。** 总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 `1.` 和 `2.` 技巧。
|
||||
1. **跳过数量与 $n$ 无关的操作**。因为他们都是 $T(n)$ 中的常数项,对时间复杂度不产生影响。
|
||||
2. **省略所有系数**。例如,循环 $2n$ 次、$5n + 1$ 次、……,都可以化简记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度也不产生影响。
|
||||
3. **循环嵌套时使用乘法**。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 `1.` 和 `2.` 技巧。
|
||||
|
||||
根据以下示例,使用上述技巧前、后的统计结果分别为
|
||||
|
||||
@ -1004,7 +1004,7 @@ $$
|
||||
|
||||
!!! tip
|
||||
|
||||
**数据大小 $n$ 是根据输入数据的类型来确定的。** 比如,在上述示例中,我们直接将 $n$ 看作输入数据大小;以下遍历数组示例中,数据大小 $n$ 为数组的长度。
|
||||
**数据大小 $n$ 是根据输入数据的类型来确定的**。比如,在上述示例中,我们直接将 $n$ 看作输入数据大小;以下遍历数组示例中,数据大小 $n$ 为数组的长度。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@ -2308,7 +2308,7 @@ $$
|
||||
|
||||
## 最差、最佳、平均时间复杂度
|
||||
|
||||
**某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关。** 举一个例子,输入一个长度为 $n$ 数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 $1$ 的索引。我们可以得出以下结论:
|
||||
**某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关**。举一个例子,输入一个长度为 $n$ 数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 $1$ 的索引。我们可以得出以下结论:
|
||||
|
||||
- 当 `nums = [?, ?, ..., 1]`,即当末尾元素是 $1$ 时,则需完整遍历数组,此时达到 **最差时间复杂度 $O(n)$** ;
|
||||
- 当 `nums = [1, ?, ?, ...]` ,即当首个数字为 $1$ 时,无论数组多长都不需要继续遍历,此时达到 **最佳时间复杂度 $\Omega(1)$** ;
|
||||
|
Reference in New Issue
Block a user