Skip to content

Commit 728ca68

Browse files
committed
fix typo
1 parent e7d2b43 commit 728ca68

File tree

4 files changed

+23
-27
lines changed

4 files changed

+23
-27
lines changed

2.算法分析/2.3.大O符号/README.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,20 @@
33

44
计算机科学家更喜欢将这种分析技术进一步扩展。事实证明,操作步骤数量不如确定 T(n) 最主要的部分来的重要。换句话说,当问题规模变大时,T(n) 函数某些部分的分量会超过其他部分。函数的数量级表示了随着 n 的值增加而增加最快的那些部分。数量级通常称为大O符号,写为 `O(f(n))`。它表示对计算中的实际步数的近似。函数 f(n) 提供了 T(n) 最主要部分的表示方法。
55

6-
<<<<<<< HEAD
7-
在上述示例中,`T(n)=1+n`。当 n 变大时,常熟 1 对于最终结果变得越来越不重要。如果我们找的是 \(T(n)\) 的近似值,我们可以删除 1, 运行时间是\(O(n)\)。要注意,1 对于 \(T(n)\) 肯定是重要的。但是当 n 变大时,如果没有它,我们的近似也是准确的。
8-
=======
9-
在上述示例中,\(T(n)=1+n\)。当 n 变大时,常数 1 对于最终结果变得越来越不重要。如果我们找的是 \(T(n)\) 的近似值,我们可以删除 1, 运行时间是\(O(n)\)。要注意,1 对于 \(T(n)\) 肯定是重要的。但是当 n 变大时,如果没有它,我们的近似也是准确的。
10-
>>>>>>> e3ac537a1fb4277537aa0925e0371ff7f6e35aa7
6+
在上述示例中,`T(n)=1+n`。当 n 变大时,常数 1 对于最终结果变得越来越不重要。如果我们找的是 T(n) 的近似值,我们可以删除 1, 运行时间是 O(n)。要注意,1 对于 T(n) 肯定是重要的。但是当 n 变大时,如果没有它,我们的近似也是准确的。
117

12-
另外一个示例,假设对于一些算法,确定的步数是 T(n)=5n^2 +27n+1005。当 n 很小时, 例如 1 或 2 ,常数 1005 似乎是函数的主要部分。然而,随着 n 变大,\(n^2\) 这项变得越来越重要。事实上,当 n 真的很大时,其他两项在它们确定最终结果中所起的作用变得不重要。当 n 变大时,为了近似\(T(n)\),我们可以忽略其他项,只关注 \(5n^2 \)。系数 5 也变得不重要。我们说,\(T(n)\) 具有的数量级为 f(n)=n^2。 或者 O( n^2 ) 。
8+
另外一个示例,假设对于一些算法,确定的步数是 `T(n)=5n^2 +27n+1005`。当 n 很小时, 例如 1 或 2 ,常数 1005 似乎是函数的主要部分。然而,随着 n 变大,n^2 这项变得越来越重要。事实上,当 n 真的很大时,其他两项在它们确定最终结果中所起的作用变得不重要。当 n 变大时,为了近似 T(n),我们可以忽略其他项,只关注 5n^2 。系数 5 也变得不重要。我们说,T(n) 具有的数量级为 f(n)=n^2。 或者 O( n^2 ) 。
139

1410

15-
虽然我们没有在求和示例中看到这一点,但有时算法的性能取决于数据的确切值,而不是问题规模的大小。对于这种类型的算法,我们需要根据最佳情况,最坏情况或平均情况来表征它们的性能。最坏情况是指算法性能特别差的特定数据集。而相同的算法不同数据集可能具有非常好的性能。大多数情况下,算法执行效率处在两个极端之间(平均情况)。对于计算机科学家而已,重要的是了解这些区别,使它们不被某一个特定的情况误导。
11+
虽然我们没有在求和示例中看到这一点,但有时算法的性能取决于数据的确切值,而不是问题规模的大小。对于这种类型的算法,我们需要根据最佳情况,最坏情况或平均情况来表征它们的性能。最坏情况是指算法性能特别差的特定数据集。而相同的算法不同数据集可能具有非常好的性能。大多数情况下,算法执行效率处在两个极端之间(平均情况)。对于计算机科学家而言,重要的是了解这些区别,使它们不被某一个特定的情况误导。
1612

1713
当你学习算法时,一些常见的数量级函数将会反复出现。见 Table 1。为了确定这些函数中哪个是最主要的部分,我们需要看到当 n 变大的时候它们如何相互比较。
1814

1915
![数量级函数](assets/%E6%95%B0%E9%87%8F%E7%BA%A7%E5%87%BD%E6%95%B0.png)
2016

2117
*Table 1*
2218

23-
Figure 1 表示了 Table 1 中的函数图。注意,当 n 很小时,函数彼此间不是很好的定义。很难判断哪个是主导的。随着 n 的增长,就有一个很明确的关系,很容易看出它们之间的大小关系。
19+
Figure 1 表示了 Table 1 中的函数图。注意,当 n 很小时,函数彼此间不能很好的定义。很难判断哪个是主导的。随着 n 的增长,就有一个很明确的关系,很容易看出它们之间的大小关系。
2420

2521
![newplot](assets/newplot.png)
2622
*Figure 1*
@@ -43,11 +39,11 @@ d = 33
4339
````
4440
*Listing 2*
4541

46-
分配操作数分为四个项的总和。第一个项是常数 3, 表示片段开始的三个赋值语句。第二项是 3n^2, 因为由于嵌套迭代,有三个语句执行 n^2 次。第三项是 2n, 两个语句迭代 n 次。最后,第四项是常数 1,表示最终赋值语句。最后得出 T(n)=3+3n^(2)+2n+1=3n^2 + 2n+4,通过查看指数,我们可以看到 n^2 项是显性的,因此这个代码段是 O(n^ 2 )。当 n 增大时,所有其他项以及主项上的系数都可以忽略。
42+
分配操作数分为四个项的总和。第一个项是常数 3, 表示片段开始的三个赋值语句。第二项是 3n^2, 因为由于嵌套迭代,有三个语句执行 n^2 次。第三项是 2n, 两个语句迭代 n 次。最后,第四项是常数 1,表示最终赋值语句。最后得出 T(n)=3+3n^2 +2n+1=3n^2 + 2n+4,通过查看指数,我们可以看到 n^2 项是显性的,因此这个代码段是 O(n^ 2 )。当 n 增大时,所有其他项以及主项上的系数都可以忽略。
4743
![newplot2](assets/newplot2.png)
4844
*Figure 2*
4945

50-
Figure 2 展示了一些常用的 大O 函数,跟上面讨论的 T(n) 函数比较,一开始的时候,T(n) 大于 三次函数,后来随着 n 的增长,三次函数超过了 T(n)。T(n) 随着二次函数继续增长。
46+
Figure 2 展示了一些常用的 大O 函数,跟上面讨论的 T(n) 函数比较,一开始的时候,T(n) 大于三次函数,后来随着 n 的增长,三次函数超过了 T(n)。T(n) 随着二次函数继续增长。
5147

5248

5349

2.算法分析/2.4.一个回文字符串检查的例子/README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
显示不同量级的算法的一个很好的例子是字符串的回文检查。一个字符串是另一个字符串的回文。如果第二个字符串只是第一个的重新排列,例如,'heart' 和 'earth' 就是回文字符串。'python' 和 'typhon' 也是。为了简单起见,我们假设所讨论的两个字符串具有相等的长度,并且他们由 26 个小写字母集合组成。我们的目标是写一个布尔函数,它将两个字符串做参数并返回它们是不是回文。
44

55
### 2.4.1.解法1:检查
6-
我们对回文问题的第一个解法是检查第一个字符串是不是出现在第二个字符串中。如果可以检验到每一个字符,那两个字符串一定是回文。可以通过用 None 替换字符来完成检查。但是,由于 Python 字符串是不可变的,所以第一步是将第二个字符串转换为列表。第一个字符串中的每个字符可以通过检查在第二个列表中检查元素是否存在,如果存在,替换成None。见 ActiveCode1
6+
我们对回文问题的第一个解法是检查第一个字符串是不是出现在第二个字符串中。如果可以检验到每一个字符,那两个字符串一定是回文。可以通过用 None 替换字符来完成检查。但是,由于 Python 字符串是不可变的,所以第一步是将第二个字符串转换为列表。第一个字符串中的每个字符可以通过检查在第二个列表中检查元素是否存在,如果存在,替换成 None。见 ActiveCode1
77

88
````
99
def anagramSolution1(s1,s2):
@@ -34,14 +34,14 @@ print(anagramSolution1('abcd','dcba'))
3434
````
3535
*ActiveCode1*
3636

37-
为了分析这个算法,我们注意到 s1 的每个字符都会在 s2 进行最多 n 个字符的迭代。列表中的 n 个位置将被访问一次来匹配来自 s1 的字符。访问次数可以写成 1 到 n 整数的和,可以写成
37+
为了分析这个算法,我们注意到 s1 的每个字符都会在 s2 中进行最多 n 个字符的迭代。s2 列表中的 n 个位置将被访问一次来匹配来自 s1 的字符。访问次数可以写成 1 到 n 整数的和,可以写成
3838
![2.4.1 求和](assets/2.4.1%20%E6%B1%82%E5%92%8C.png)
3939

4040
当 n 变大,n^2 这项占据主导,1/2 可以忽略。所以这个算法复杂度为 O(n^2 )。
4141

4242
### 2.4.2.解法2:排序和比较
4343

44-
另一个解决方案是利用这么一个事实,即使 s1,s2 不同,它们只有由完全相同的字符组成,它们才是回文。所以,如果我们按照字母顺序排列每个字符串,从 a 到 z, 如果两个字符串相同,则这两个字符串为回文。见 ActiveCode2。
44+
另一个解决方案是利用这么一个事实,即使 s1,s2 不同,它们只有由完全相同的字符组成,它们才是回文。所以,如果我们按照字母顺序排列每个字符串,从 a 到 z如果两个字符串相同,则这两个字符串为回文。见 ActiveCode2。
4545

4646
````
4747
def anagramSolution2(s1,s2):
@@ -66,17 +66,17 @@ print(anagramSolution2('abcde','edcba'))
6666
````
6767
*ActiveCode2*
6868

69-
首先你可能认为这个算法是 O(n), 因为只有一个简单的迭代来比较排序后的n个字符。但是,调用 Python 排序不是没有成本。正如我们将在后面的章节中看到的,排序通常是 O(n^2)或 O(nlogn)。所以排序操作比迭代花费更多。最后该算法跟排序过程有同样的量级。
69+
首先你可能认为这个算法是 O(n),因为只有一个简单的迭代来比较排序后的 n 个字符。但是,调用 Python 排序不是没有成本。正如我们将在后面的章节中看到的,排序通常是 O(n^2 ) 或 O(nlogn)。所以排序操作比迭代花费更多。最后该算法跟排序过程有同样的量级。
7070

7171
### 2.4.3.解法3: 穷举法
7272

73-
解决这类问题的强力方法是穷举所有可能性。对于回文检测,我们可以生成所有 s1 的所有回文字符串列表,然后查看是不是有 s2。这种方法有一点困难。当 s1 生成所有可能的字符串时,第一个位置有 n 种可能,第二个位置有 n-1 种,第三个位置有 n-3 种,等等。总数为 n∗(n−1)∗(n−2)∗...∗3∗2∗1n∗(n−1)∗(n−2)∗...∗3∗2∗1, 即 n!。虽然一些字符串可能是重复的,程序也不可能提前知道这样,所以他仍然会生成 n! 个字符串。
73+
解决这类问题的强力方法是穷举所有可能性。对于回文检测,我们可以生成 s1 的所有回文字符串列表,然后查看是不是有 s2。这种方法有一点困难。当 s1 生成所有可能的字符串时,第一个位置有 n 种可能,第二个位置有 n-1 种,第三个位置有 n-3 种,等等。总数为 n∗(n−1)∗(n−2)∗...∗3∗2∗1n∗(n−1)∗(n−2)∗...∗3∗2∗1, 即 n!。虽然一些字符串可能是重复的,程序也不可能提前知道这样,所以他仍然会生成 n! 个字符串。
7474

75-
事实证明,n! 比 n^2 增长还快,事实上,如果 s1 有 20个字符长,则将有 20! = 2,432,902,008,176,640,000 个字符串产生。如果我们每秒处理一种可能字符串,那么需要 77,146,816,596 才能过完整个列表。所以这不是很好的解决方案。
75+
事实证明,n! 比 n^2 增长还快,事实上,如果 s1 有 20个字符长,则将有 20! = 2,432,902,008,176,640,000 个字符串产生。如果我们每秒处理一种可能字符串,那么需要 77,146,816,596 年才能过完整个列表。所以这不是很好的解决方案。
7676

7777
### 2.4.4.解法4: 计数和比较
7878

79-
我们最终解决回文的方法是利用两个回文字符串具有相同的 a, b, c等等的事实。我们首先计算的是每个字母出现的次数。由于有 26 个可能的字符,我们就用 26 个列表,每个可能的字符一个。每次看到一个特定的字符,就增加该位置的计数器。最后如果两个列表的计数器一样,则字符串为回文字符串。见 ActiveCode 3
79+
我们最终解决回文的方法是利用两个回文字符串具有相同的 a, b, c 等等的事实。我们首先计算的是每个字母出现的次数。由于有 26 个可能的字符,我们就用 26 个列表,每个可能的字符一个。每次看到一个特定的字符,就增加该位置的计数器。最后如果两个列表的计数器一样,则字符串为回文字符串。见 ActiveCode 3
8080

8181
```` python
8282
def anagramSolution4(s1,s2):
@@ -106,7 +106,7 @@ print(anagramSolution4('apple','pleap'))
106106
````
107107
*ActiveCode 3*
108108

109-
同样,这个方案有多个迭代,但是和第一个解法不一样,它不是嵌套的。两个迭代都是 n, 第三个迭代,比较两个计数列表,需要 26个步,因为有 26个字母。一共 T(n)=2n+26T(n)=2n+26,即 O(n),我们找到了一个线性量级的算法解决这个问题。
109+
同样,这个方案有多个迭代,但是和第一个解法不一样,它不是嵌套的。两个迭代都是 n, 第三个迭代,比较两个计数列表,需要 26 步,因为有 26个字母。一共 T(n)=2n+26T(n)=2n+26,即 O(n),我们找到了一个线性量级的算法解决这个问题。
110110

111111
在结束这个例子之前,我们来讨论下空间花费,虽然最后一个方案在线性时间执行,但它需要额外的存储来保存两个字符计数列表。换句话说,该算法牺牲了空间以获得时间。
112112

2.算法分析/2.6.列表/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def test4():
2828

2929
要捕获我们的每个函数执行所需的时间,我们将使用 Python 的 timeit 模块。timeit 模块旨在允许 Python 开发人员通过在一致的环境中运行函数并使用尽可能相似的操作系统的时序机制来进行跨平台时序测量。
3030

31-
要使用 timeit,您需要创建一个 Timer 对象,其参数是两个 Python 语句。第一个参数是一个你想要执行时间的 Python 语句; 第二个参数是一个将运行一次以设置测试的语句。然后 timeit 模块将计算执行语句所需的时间。默认情况下,timeit 将尝试运行语句一百万次。 当它完成时,它返回时间作为表示总秒数的浮点值。由于它执行语句一百万次,可以读取结果作为执行测试一次的微秒数。你还可以传递 timeit 一个参数名字为 number,允许您指定执行测试语句的次数。以下显示了运行我们的每个测试功能 1000 次需要多长时间。
31+
要使用 timeit,你需要创建一个 Timer 对象,其参数是两个 Python 语句。第一个参数是一个你想要执行时间的 Python 语句; 第二个参数是一个将运行一次以设置测试的语句。然后 timeit 模块将计算执行语句所需的时间。默认情况下,timeit 将尝试运行语句一百万次。 当它完成时,它返回时间作为表示总秒数的浮点值。由于它执行语句一百万次,可以读取结果作为执行测试一次的微秒数。你还可以传递 timeit 一个参数名字为 number,允许你指定执行测试语句的次数。以下显示了运行我们的每个测试功能 1000 次需要多长时间。
3232

3333
````
3434
t1 = Timer("test1()", "from __main__ import test1")
@@ -52,15 +52,15 @@ list range 0.0655000209808 milliseconds
5252

5353
最后一点,你上面看到的时间都是包括实际调用函数的一些开销,但我们可以假设函数调用开销在四种情况下是相同的,所以我们仍然得到的是有意义的比较。因此,拼接字符串操作需要 6.54 毫秒并不准确,而是拼接字符串这个函数需要 6.54 毫秒。你可以测试调用空函数所需要的时间,并从上面的数字中减去它。
5454

55-
现在我们已经看到了如何具体测试性能,见 Table2, 你可能想知道 pop 两个不同的时间。当列表末尾调用 pop 时,它需要 O(1), 但是当在列表中第一个元素或者中间任何地方调用 pop, 它是 O(n)。原因在于 Python 实现列表的方式,当一个项从列表前面取出,列表中的其他元素靠近开始移动一个位置。你会看到索引操作为 O(1)。python的实现者会权衡选择一个好的方案。
55+
现在我们已经看到了如何具体测试性能,见 Table2, 你可能想知道 pop 两个不同的时间。当列表末尾调用 pop 时,它需要 O(1), 但是当在列表中第一个元素或者中间任何地方调用 pop, 它是 O(n)。原因在于 Python 实现列表的方式,当一个项从列表前面取出,列表中的其他元素靠近起始位置移动一个位置。你会看到索引操作为 O(1)。python的实现者会权衡选择一个好的方案。
5656

5757
![2.6.列表 Table2](assets/2.6.%E5%88%97%E8%A1%A8%20Table2.png)
5858

59-
作为一种演示性能差异的方法,我们用 timeit 来做一个实验。我们的目标是验证从列表从末尾 pop 元素和从开始 pop 元素的性能。同样,我们也想测量不同列表大小对这个时间的影响。我们期望看到的是,从列表末尾处弹出所需时间将保持不变,即使列表不断增长。而从列表开始出弹出元素时间将随列表增长而增加
59+
作为一种演示性能差异的方法,我们用 timeit 来做一个实验。我们的目标是验证从列表从末尾 pop 元素和从开始 pop 元素的性能。同样,我们也想测量不同列表大小对这个时间的影响。我们期望看到的是,从列表末尾处弹出所需时间将保持不变,即使列表不断增长。而从列表开始处弹出元素时间将随列表增长而增加
6060

61-
Listing 4 展示了两种 pop 方式的比较。从第一个示例看书,从末尾弹出需要 0.0003 毫秒。从开始弹出要花费 4.82毫秒。对于一个 200万的元素列表,相差 16000 倍。
61+
Listing 4 展示了两种 pop 方式的比较。从第一个示例看出,从末尾弹出需要 0.0003 毫秒。从开始弹出要花费 4.82 毫秒。对于一个 200 万的元素列表,相差 16000 倍。
6262

63-
Listing 4 需要注意的几点,第一, `from __main__ import x` , 虽然我们没有定义一个函数,我们确实希望能够在我们的测试中使用列表对象 x, 这种方法允许我们只计算单个弹出语句,获得该操作最精确的测量时间。因为 timer 重复了 1000 次,所以该列表每次循环大小都减1。但是由于初始列表大小为 200万,我们只减少总体大小的 0.05%。
63+
Listing 4 需要注意的几点,第一, `from __main__ import x` , 虽然我们没有定义一个函数,我们确实希望能够在我们的测试中使用列表对象 x, 这种方法允许我们只计算单个弹出语句,获得该操作最精确的测量时间。因为 timer 重复了 1000 次,该列表每次循环大小都减 1。但是由于初始列表大小为 200万,我们只减少总体大小的 0.05%。
6464

6565
````
6666
popzero = timeit.Timer("x.pop(0)",
@@ -95,7 +95,7 @@ for i in range(1000000,100000001,1000000):
9595
````
9696
*Listing 5*
9797

98-
Figure 3 展示了我们实验的结果,你可以看到,随着列表变长,pop(0) 时间也增加,而pop() 时间保持非常平坦。这正是我们期望看到的 O(n)和 O(1)
98+
Figure 3 展示了我们实验的结果,你可以看到,随着列表变长,pop(0) 时间也增加,而 pop() 时间保持非常平坦。这正是我们期望看到的 O(n)和 O(1)
9999

100100
![2.6.列表.poptime](assets/2.6.%E5%88%97%E8%A1%A8.poptime.png)
101101

0 commit comments

Comments
 (0)