@@ -51,9 +51,9 @@ def algorithm(n):
5151 return fact
5252```
5353
54- 把上述算法中所有语句的执行次数加起来 $1 + n + n + 1 = 2n + 2$,可以用一个函数 $f(n)$ 来表达语句的执行次数:$f(n) = 2n + 2$。
54+ 把上述算法中所有语句的执行次数加起来 $1 + n + n + 1 = 2 \times n + 2$,可以用一个函数 $f(n)$ 来表达语句的执行次数:$f(n) = 2 \times n + 2$。
5555
56- 则时间复杂度的函数可以表示为:$T(n) = O(f(n))$。它表示的是随着问题规模 n 的增大,算法执行时间的增长趋势跟 $f(n)$ 相同。$O$ 是一种渐进符号,$T(n)$ 称作算法的 ** 渐进时间复杂度(Asymptotic Time Complexity)** ,简称为 ** 时间复杂度** 。
56+ 则时间复杂度的函数可以表示为:$T(n) = O(f(n))$。它表示的是随着问题规模 $n$ 的增大,算法执行时间的增长趋势跟 $f(n)$ 相同。$O$ 是一种渐进符号,$T(n)$ 称作算法的 ** 渐进时间复杂度(Asymptotic Time Complexity)** ,简称为 ** 时间复杂度** 。
5757
5858所谓「算法执行时间的增长趋势」是一个模糊的概念,通常我们要借助像上边公式中 $O$ 这样的「渐进符号」来表示时间复杂度。
5959
@@ -69,7 +69,7 @@ def algorithm(n):
6969
7070也就是说,如果函数 $f(n) = \Theta(g(n))$,那么我们能找到两个正数 $c_1$、$c_2$,使得 $f(n)$ 被 $c_1 \cdot g(n)$ 和 $c_2 \cdot g(n)$ 夹在中间。
7171
72- 例如:$T(n) = 3n ^2 + 4n + 5 = \Theta(n^2)$,可以找到 $c_1 = 1$,$c_2 = 12$,$n_0 = 1$,使得对于所有 $n \ge 1$,都有 $n^2 \le 3n ^2 + 4n + 5 \le 12n ^2$。
72+ 例如:$T(n) = 3 \times n ^2 + 4 \times n + 5 = \Theta(n^2)$,可以找到 $c_1 = 1$,$c_2 = 12$,$n_0 = 1$,使得对于所有 $n \ge 1$,都有 $n^2 \le 3 \times n ^2 + 4 \times n + 5 \le 12 \times n ^2$。
7373
7474#### 2.2.2 $O$ 渐进上界符号
7575
@@ -173,7 +173,7 @@ def permutations(arr, start, end):
173173 arr[i], arr[start] = arr[start], arr[i]
174174```
175175
176- 上述代码中实现「全排列」使用了递归的方法。假设数组 $arr$ 长度为 $n$,第一层 ` for ` 循环执行了 $n$ 次,第二层 ` for ` 循环执行了 $n - 1$ 次。以此类推,最后一层 ` for ` 循环执行了 $1$ 次,将所有层 ` for ` 循环的执行次数累乘起来为 $n \times (n - 1) \times (n - 2) \times … \times 2 \times 1 = n!$ 次。则整个算法的 ` for ` 循环中基本语句的执行次数为 $n!$ 次,所以对应时间复杂度为 $O(n!)$。
176+ 上述代码中实现「全排列」使用了递归的方法。假设数组 $arr$ 长度为 $n$,第一层 for 循环执行了 $n$ 次,第二层 for 循环执行了 $n - 1$ 次。以此类推,最后一层 for 循环执行了 $1$ 次,将所有层 for 循环的执行次数累乘起来为 $n \times (n - 1) \times (n - 2) \times … \times 2 \times 1 = n!$ 次。则整个算法的 for 循环中基本语句的执行次数为 $n!$ 次,所以对应时间复杂度为 $O(n!)$。
177177
178178#### 2.3.5 对数 $O(\log n)$
179179
@@ -187,7 +187,7 @@ def algorithm(n):
187187 return cnt
188188```
189189
190- 上述代码中 ` cnt = 1 ` 的时间复杂度为 $O(1)$ 可以忽略不算。` while ` 循环体中 $cnt$ 从 $1$ 开始,每循环一次都乘以 $2$。当大于等于 $n$ 时循环结束。变量 $cnt$ 的取值是一个等比数列:$2^0, 2^1, 2^2,…, 2^x$,根据 $2^x = n$,可以得出这段循环体的执行次数为 $\log_2n$,所以这段代码的时间复杂度为 $O(\log_2n)$。
190+ 上述代码中 ` cnt = 1 ` 的时间复杂度为 $O(1)$ 可以忽略不算。while 循环体中 $cnt$ 从 $1$ 开始,每循环一次都乘以 $2$。当大于等于 $n$ 时循环结束。变量 $cnt$ 的取值是一个等比数列:$2^0, 2^1, 2^2, …, 2^x$,根据 $2^x = n$,可以得出这段循环体的执行次数为 $\log_2n$,所以这段代码的时间复杂度为 $O(\log_2n)$。
191191
192192因为 $\log n = k \times \log_2 n$,这里 $k = 3.322$,所以,$\log n$ 与 $\log_2 n$ 的差别比较小。为了方便书写,通常我们将对数时间复杂度写作是 $O(\log n)$。
193193
@@ -232,15 +232,15 @@ def find(nums, val):
232232 return pos
233233```
234234
235- 这段代码要实现的功能是:从一个整数数组 $nums$ 中查找值为 $val$ 的变量出现的位置。如果不考虑 ` break ` 语句,根据「2.3 时间复杂度计算」中讲的分析步骤,这个算法的时间复杂度是 $O(n)$,其中 $n$ 代表数组的长度。
235+ 这段代码要实现的功能是:从一个整数数组 $nums$ 中查找值为 $val$ 的变量出现的位置。如果不考虑 break 语句,根据「2.3 时间复杂度计算」中讲的分析步骤,这个算法的时间复杂度是 $O(n)$,其中 $n$ 代表数组的长度。
236236
237- 但是如果考虑 ` break ` 语句,那么就需要考虑输入的内容了。如果数组中第 $1$ 个元素值就是 $val$,那么剩下 $n - 1$ 个数据都不要遍历了,那么时间复杂度就是 $O(1)$,即最佳时间复杂度为 $O(1)$。如果数组中不存在值为 $val$ 的变量,那么就需要把整个数组遍历一遍,时间复杂度就变成了 $O(n)$,即最差时间复杂度为 $O(n)$。
237+ 但是如果考虑 break 语句,那么就需要考虑输入的内容了。如果数组中第 $1$ 个元素值就是 $val$,那么剩下 $n - 1$ 个数据都不要遍历了,那么时间复杂度就是 $O(1)$,即最佳时间复杂度为 $O(1)$。如果数组中不存在值为 $val$ 的变量,那么就需要把整个数组遍历一遍,时间复杂度就变成了 $O(n)$,即最差时间复杂度为 $O(n)$。
238238
239239这样下来,时间复杂度就不唯一了。怎么办?
240240
241241我们都知道,最佳时间复杂度和最坏时间复杂度都是极端条件下的时间复杂度,发生的概率其实很小。为了能更好的表示正常情况下的复杂度,所以我们一般采用平均时间复杂度作为时间复杂度的计算方式。
242242
243- 还是刚才的例子,在数组 $nums$ 中查找变量值为 $val$ 的位置,总共有 $n + 1$ 种情况:「在数组的的 $0 \sim n - 1$ 个位置上」和「不在数组中」。我们将所有情况下,需要执行的语句次数累加起来,再除以 $n + 1$,就可以得到平均需要执行的语句次数,即:$\frac{1 + 2 + 3 + ... + n + n}{n + 1} = \frac{n(n + 3)}{2(n + 1)}$。将公式简化后,得到的平均时间复杂度就是 $O(n)$。
243+ 还是刚才的例子,在数组 $nums$ 中查找变量值为 $val$ 的位置,总共有 $n + 1$ 种情况:「在数组的的 $0 \sim n - 1$ 个位置上」和「不在数组中」。我们将所有情况下,需要执行的语句次数累加起来,再除以 $n + 1$,就可以得到平均需要执行的语句次数,即:$\frac{1 + 2 + 3 + ... + n + n}{n + 1} = \frac{n \times (n + 3)}{2 \times (n + 1)}$。将公式简化后,得到的平均时间复杂度就是 $O(n)$。
244244
245245通常只有同一个算法在输入内容不同,不同时间复杂度有量级的差距时,我们才会通过三种时间复杂度表示法来区分。一般情况下,使用其中一种就可以满足需求了。
246246
0 commit comments