diff --git "a/1.\344\273\213\347\273\215/1.1.\347\233\256\346\240\207/README.md" "b/1.\344\273\213\347\273\215/1.1.\347\233\256\346\240\207/README.md" index e180dae..5297212 100644 --- "a/1.\344\273\213\347\273\215/1.1.\347\233\256\346\240\207/README.md" +++ "b/1.\344\273\213\347\273\215/1.1.\347\233\256\346\240\207/README.md" @@ -1,7 +1,7 @@ ## 1.1.目标 -* 回顾计算机科学的思想, 提高编程和解决问题的 能力。 +* 回顾计算机科学的思想, 提高编程和解决问题的能力。 * 理解抽象化以及它在解决问题过程中发挥的作用 * 理解和实现抽象数据类型的概念 * 回顾 Python 编程语言 diff --git "a/1.\344\273\213\347\273\215/1.3.\344\273\200\344\271\210\346\230\257\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/README.md" "b/1.\344\273\213\347\273\215/1.3.\344\273\200\344\271\210\346\230\257\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/README.md" index 72aece4..92fa8c3 100644 --- "a/1.\344\273\213\347\273\215/1.3.\344\273\200\344\271\210\346\230\257\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/README.md" +++ "b/1.\344\273\213\347\273\215/1.3.\344\273\200\344\271\210\346\230\257\350\256\241\347\256\227\346\234\272\347\247\221\345\255\246/README.md" @@ -1,5 +1,5 @@ ## 1.3.什么是计算机科学 -计算机科学往往难以定义。这可能是由于在名称中不幸使用了“电脑”一词。正如你可能知道的,计算机科学不仅仅是计算机的研究。虽然计算机作为一个工具在学科中发挥重要的支持作用,但它们只是工具。 +计算机科学往往难以定义。这可能是由于在名称中不幸使用了“计算机”一词。正如你可能知道的,计算机科学不仅仅是计算机的研究。虽然计算机作为一个工具在学科中发挥重要的支持作用,但它们只是工具。 计算机科学是对问题,解决问题以及解决问题过程中产生的解决方案的研究。给定一个问题,计算机科学家的目标是开发一个算法,一系列的指令列表,用于解决可能出现的问题的任何实例。算法遵循它有限的过程就可以解决问题。 diff --git "a/1.\344\273\213\347\273\215/1.5.\344\270\272\344\273\200\344\271\210\350\246\201\345\255\246\344\271\240\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" "b/1.\344\273\213\347\273\215/1.5.\344\270\272\344\273\200\344\271\210\350\246\201\345\255\246\344\271\240\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" index 3f2dd4e..eca0fff 100644 --- "a/1.\344\273\213\347\273\215/1.5.\344\270\272\344\273\200\344\271\210\350\246\201\345\255\246\344\271\240\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" +++ "b/1.\344\273\213\347\273\215/1.5.\344\270\272\344\273\200\344\271\210\350\246\201\345\255\246\344\271\240\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" @@ -2,10 +2,12 @@ 为了管理问题的复杂性和解决问题的过程,计算机科学家使用抽象使他们能够专注于 “大局” 而不会迷失在细节中。通过创建问题域的模型,我们能够利用更好和更有效的问题解决过程。这些模型允许我们以更加一致的方式描述我们的算法将要处理的数据。 -之前,我们将过程抽象称为隐藏特定函数的细节的过程,以允许用户或客户端在高层查看它。我们现在将注意力转向类似的思想,即数据抽象的思想。`抽象数据类型`(有时缩写为 ADT )是对我们如何查看数据和允许的操作的逻辑描述,而不y用考虑如何实现它们。这意味着我们只关心数据表示什么,而不关心它最终将如何构造。通过提供这种级别的抽象,我们围绕数据创建一个封装。通过封装实现细节,我们将它们从用户的视图中隐藏。这称为信息隐藏。 +之前,我们将过程抽象称为隐藏特定函数的细节的过程,以允许用户或客户端在高层查看它。我们现在将注意力转向类似的思想,即数据抽象的思想。`抽象数据类型`(有时缩写为 ADT )是对我们如何查看数据和允许的操作的逻辑描述,而不用考虑如何实现它们。这意味着我们只关心数据表示什么,而不关心它最终将如何构造。通过提供这种级别的抽象,我们围绕数据创建一个封装。通过封装实现细节,我们将它们从用户的视图中隐藏。这称为信息隐藏。 -Figure 2 展示了抽象数据类型是什么以及如何操作。用户与接口交互,使用抽象数据类型指定的操作。抽象数据类型是用户与之交互的 shell。实现是隐藏在更深的底层。用户不关心实现的细节。 +Figure 2 展示了抽象数据类型是什么以及如何操作。用户与接口交互,使用抽象数据类型指定的操作。抽象数据类型是用户与之交互的 shell。实现隐藏在更深的底层。用户不关心实现的细节。 ![1.5.为什么要学习数据结构和抽象数据类型.figure2](assets/1.5.%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%AD%A6%E4%B9%A0%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E6%8A%BD%E8%B1%A1%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B.figure2.png) + + *Figure 2* 抽象数据类型(通常称为数据结构)的实现将要求我们使用一些程序构建和原始数据类型的集合来提供数据的物理视图。 正如我们前面讨论的,这两个视角的分离将允许我们将问题定义复杂的数据模型,而不给出关于模型如何实际构建的细节。 这提供了独立于实现的数据视图。由于通常有许多不同的方法来实现抽象数据类型,所以这种实现独立性允许程序员在不改变数据的用户与其交互的方式的情况下切换实现的细节。 用户可以继续专注于解决问题的过程。 diff --git "a/1.\344\273\213\347\273\215/1.6.\344\270\272\344\273\200\344\271\210\350\246\201\345\255\246\344\271\240\347\256\227\346\263\225/README.md" "b/1.\344\273\213\347\273\215/1.6.\344\270\272\344\273\200\344\271\210\350\246\201\345\255\246\344\271\240\347\256\227\346\263\225/README.md" index 8fac8b7..f3e2380 100644 --- "a/1.\344\273\213\347\273\215/1.6.\344\270\272\344\273\200\344\271\210\350\246\201\345\255\246\344\271\240\347\256\227\346\263\225/README.md" +++ "b/1.\344\273\213\347\273\215/1.6.\344\270\272\344\273\200\344\271\210\350\246\201\345\255\246\344\271\240\347\256\227\346\263\225/README.md" @@ -1,6 +1,6 @@ ## 1.6.为什么要学习算法 -计算机科学家通过经验学习。我们通过看别人解决问题和自己解决问题来学习。接触不同的问题解决技术,看不同的算法设计有助于我们承担下一个具有挑战性的问题。通过思考许多不同的算法,我们可以开始开发模式识别,以便下一次出现类似的问题时,我们能够更好地解决它。 +计算机科学家经常通过经验学习。我们通过看别人解决问题和自己解决问题来学习。接触不同的问题解决技术,看不同的算法设计有助于我们承担下一个具有挑战性的问题。通过思考许多不同的算法,我们可以开始开发模式识别,以便下一次出现类似的问题时,我们能够更好地解决它。 算法通常彼此完全不同。考虑前面看到的 `sqrt` 的例子。完全可能的是,存在许多不同的方式来实现细节以计算平方根函数。一种算法可以使用比另一种更少的资源。一个算法可能需要 10 倍的时间来返回结果。我们想要一些方法来比较这两个解决方案。即使他们都工作,一个可能比另一个“更好”。我们建议使用一个更高效,或者一个只是工作更快或使用更少的内存的算法。当我们研究算法时,我们可以学习分析技术,允许我们仅仅根据自己的特征而不是用于实现它们的程序或计算机的特征来比较和对比解决方案。 diff --git "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.1.\347\233\256\346\240\207/README.md" "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.1.\347\233\256\346\240\207/README.md" index eb981f6..f2b01b4 100644 --- "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.1.\347\233\256\346\240\207/README.md" +++ "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.1.\347\233\256\346\240\207/README.md" @@ -1,10 +1,10 @@ ## 2.1.目标 -* 理解算法的重要性 -* 使用 大*O* 符号表示执行时间 -* 理解 Python 列表和字典的大*O*执行时间 +* 理解算法分析的重要性 +* 能够使用 大*O* 符号描述算法执行时间 +* 理解 Python 列表和字典的常见操作的 大*O* 执行时间 * 理解 Python 数据的实现是如何影响算法分析的。 -* 了解如何对 Python 程序做简单的基准测试。 +* 了解如何对简单的 Python 程序做基准测试(`benchmark`)。 diff --git "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.2.\344\273\200\344\271\210\346\230\257\347\256\227\346\263\225\345\210\206\346\236\220/README.md" "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.2.\344\273\200\344\271\210\346\230\257\347\256\227\346\263\225\345\210\206\346\236\220/README.md" index 385b891..ab78d86 100644 --- "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.2.\344\273\200\344\271\210\346\230\257\347\256\227\346\263\225\345\210\206\346\236\220/README.md" +++ "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.2.\344\273\200\344\271\210\346\230\257\347\256\227\346\263\225\345\210\206\346\236\220/README.md" @@ -1,61 +1,71 @@ ## 2.2.什么是算法分析 -一些普遍的现象是,刚接触计算机科学的同学会将自己的程序和其他的相比较。你可能还注意到,计算机程序看起来很相似,尤其是简单的程序。经常出现一个有趣的问题。当两个程序解决同样的问题,但看起来不同,哪一个更好呢?为了回答这个问题,我们需要记住,程序和程序代表的底层算法之间有一个重要的区别。正如我们在第 1 章中所说,一个算法是一个通用的,解决问题的指令列表。它是用于解决问题的任何实例的方法,给定特定输入,产生期望的结果。另一方面,程序是已经被编码成某种编程语言的算法。根据所使用的编程器和编程语言,可能存在用于相同算法的许多程序。要进一步探讨这种差异,请参考 ActiveCode 1 中显示的函数。这个函数解决了一个熟悉的问题,计算前 n 个整数的和。该算法使用初始化为 0 的累加器变量。然后迭代 n 个整数,将每个添加到累加器。 +一些普遍的现象是,刚接触计算机科学的学生会将自己的程序和其他人的相比较。你可能还注意到,这些计算机程序看起来很相似,尤其是简单的程序。经常出现一个有趣的问题。当两个程序解决同样的问题,但看起来不同,哪一个更好呢? -```` python +为了回答这个问题,我们需要记住,程序和程序代表的底层算法之间有一个重要的区别。正如我们在第 1 章中所说,一种算法是一个通用的,一步一步解决某种问题的指令列表。它是用于解决一种问题的任何实例的方法,给定特定输入,产生期望的结果。另一方面,程序是使用某种编程语言编码的算法。根据程序员和他们所使用的编程语言的不同,可能存在描述相同算法的许多不同的程序。 + +要进一步探讨这种差异,请参考 _ActiveCode 1_ 中显示的函数。这个函数解决了一个我们熟悉的问题,计算前 n 个整数的和。该算法使用初始化值为 0 的累加器(`accumulator `)变量。然后迭代 n 个整数,将每个依次添加到累加器。 + +######ActiveCode 1 + +```python def sumOfN(n): - theSum = 0 - for i in range(1,n+1): - theSum = theSum + i + theSum = 0 + for i in range(1,n+1): + theSum = theSum + i - return theSum + return theSum print(sumOfN(10)) -```` -*ActiveCode 1* +``` -现在看看 ActiveCode 2 中的函数。乍一看,它可能很奇怪,但进一步的观察,你可以看到,这个功能本质上和前面的程序做同样的事情。不直观的原因在于编码习惯不好。我们没有使用良好的标识符名称来提升可读性,我们在迭代步骤中使用了一个额外的赋值语句,这并不是真正必要的。 +现在看看 _ActiveCode 2_ 中的函数。乍一看,它可能很奇怪,但进一步的观察,你可以看到这个函数本质上和前一个函数在做同样的事情。不直观的原因在于编码习惯不好。我们没有使用良好的标识符(`identifier`)名称来提升可读性,我们在迭代步骤中使用了一个额外的赋值语句,这并不是真正必要的。 -```` python +######ActiveCode 2 + +```python def foo(tom): fred = 0 for bill in range(1,tom+1): - barney = bill - fred = fred + barney + barney = bill + fred = fred + barney return fred print(foo(10)) -```` -*ActiveCode 2* +``` + +先前我们提出一个问题是哪个函数更好,答案取决于你的标准。如果你关注可读性,函数 *sumOfN* 肯定比 *foo* 好。事实上,你可能已经在介绍编程的课程中看到过很多例子,他们的目标之一就是帮助你编写易于阅读和理解的程序。然而,在本课程中,我们对算法本身的表示更感兴趣(当然我们希望你继续努力编写可读的,易于理解的代码)。 -先前我们提出一个问题是哪个函数更好,答案取决于你的标准。如果你关注可读性,函数 sumOfN 肯定比 foo 好。事实上,你可能已经在你介绍编程的课程中看到过很多例子,他们的目标之一就是帮助你编写易于阅读和理解的程序。在本课程中,我们对如何表示算法感兴趣(当然我们希望你继续努力编写可读的,易于理解的代码)。 +算法分析是基于每种算法使用的计算资源量来比较算法。我们比较两个算法,说一个比另一个算法好的原因在于它在使用资源方面更有效率,或者仅仅使用的资源更少。从这个角度来看,上面两个函数看起来很相似。它们都使用基本相同的算法来解决求和问题。 -算法分析涉及基于每个算法使用的计算资源量来比较算法。我们比较两个算法,说一个比另一个算法好的原因在于它在使用资源方面更有效率,或者仅仅使用的资源更少。从这个角度来看,上面两个函数看起来很相似。它们都使用基本相同的算法来求解问题。在这点上,更重要的是我们如何考虑真正意义上的计算资源。有两个方法,一种是考虑算法所需的空间或者内存。所需的空间通常由问题本身决定。但是,算法会有一些特殊的空间需求,我们可以细细观察解释这些变动。 +在这点上,重要的是要更多地考虑我们真正意义上的计算资源。有两种方法,一种是考虑算法解决问题所需的空间或者内存。解决方案所需的空间通常由问题本身决定。但是,有时候有的算法会有一些特殊的空间需求,这种情况下我们需要非常仔细地解释这些变动。 -作为空间需求的一种替代方法,我们可以基于时间来分析算法。这种度量有时被称为算法的‘执行时间’或’运行时间‘。我们可以通过基准分析来测量函数 SumOfN 的执行时间。这意味着我们将记录程序计算所需的实际时间。在 Python 中,我们可以通过记录相对于系统的开始时间和结束时间来对函数进行基准测试。在时间模块中有一个时间函数,它将返回系统时钟时间(以秒为单位)。通过在开始和结束的时候调用时间函数,然后计算差异,就可以得到一个精确地秒数(大多数情况下)。 +作为空间需求的一种替代方法,我们可以基于算法执行所需的时间来分析和比较算法。这种测量方式有时被称为算法的“执行时间”或“运行时间”。我们可以通过基准分析(`benchmark analysis`)来测量函数 *SumOfN* 的执行时间。这意味着我们将记录程序计算出结果所需的实际时间。在 Python 中,我们可以通过记录相对于系统的开始时间和结束时间来对函数进行基准测试。在 *time* 模块中有一个 *time* 函数,它可以在任意被调用的地方返回系统时钟的当前时间(以秒为单位)。通过在开始和结束的时候分别调用两次 *time* 函数,然后计算差异,就可以得到一个函数执行花费的精确秒数(大多数情况下是这样)。 -#### Listing 1 +######Listing 1 -```` python +```python import time def sumOfN2(n): - start = time.time() + start = time.time() - theSum = 0 - for i in range(1,n+1): - theSum = theSum + i + theSum = 0 + for i in range(1,n+1): + theSum = theSum + i - end = time.time() + end = time.time() - return theSum,end-start -```` + return theSum, end-start +``` -Listing 1 嵌入了时间函数,函数返回一个包含结果和消耗时间的数组。如果我们 执行这个函数 5 次,每次计算前 10,000 个整数的和,将得到一下结果: -```` + +Listing 1 嵌入了时间函数,函数返回一个包含了执行结果和执行消耗时间的元组(`tuple`)。如果我们执行这个函数 5 次,每次计算前 10,000 个整数的和,将得到如下结果: + +``` >>>for i in range(5): print("Sum is %d required %10.7f seconds"%sumOfN(10000)) Sum is 50005000 required 0.0018950 seconds @@ -63,11 +73,11 @@ Sum is 50005000 required 0.0018620 seconds Sum is 50005000 required 0.0019171 seconds Sum is 50005000 required 0.0019162 seconds Sum is 50005000 required 0.0019360 seconds -```` - -我们发现时间是相当一致的,它执行该代码平均需要0.0019秒。如果我们运行计算前 100,000个 整数的函数呢? - -```` +``` + +我们发现时间是相当一致的,执行这段代码平均需要0.0019秒。如果我们运行计算前 100,000 个整数的和的函数呢? + +``` >>>for i in range(5): print("Sum is %d required %10.7f seconds"%sumOfN(100000)) Sum is 5000050000 required 0.0199420 seconds @@ -76,11 +86,11 @@ Sum is 5000050000 required 0.0194821 seconds Sum is 5000050000 required 0.0178988 seconds Sum is 5000050000 required 0.0188949 seconds >>> -```` +``` -再次的,尽管时间更长,每次运行所需的时间是非常一致的,平均大约多10倍。 对于 n 等于 1,000,000,我们得到: +再次的,尽管时间更长,但每次运行所需的时间也是非常一致的,平均大约多10倍。 对于 n 等于 1,000,000,我们得到: -```` +``` >>>for i in range(5): print("Sum is %d required %10.7f seconds"%sumOfN(1000000)) Sum is 500000500000 required 0.1948988 seconds @@ -89,35 +99,34 @@ Sum is 500000500000 required 0.1809771 seconds Sum is 500000500000 required 0.1729250 seconds Sum is 500000500000 required 0.1646299 seconds >>> -```` -在这种情况下,平均值也大约是前一次的10倍。现在考虑ActiveCode 3,它显示了求解求和问题的不同方法 +``` +在这种情况下,平均值也大约是前一次的10倍。现在考虑 *ActiveCode 3*,它显示了求解求和问题的不同方法。函数 *sumOfN3* 利用[封闭方程](https://en.wikipedia.org/wiki/1_%2B_2_%2B_3_%2B_4_%2B_%E2%8B%AF)而不是迭代来计算前n个整数的和。 ![求和](assets/%E6%B1%82%E5%92%8C.png) +######ActiveCode 3 -```` python +``` python def sumOfN3(n): return (n*(n+1))/2 print(sumOfN3(10)) -```` -*ActiveCode 3* +``` -如果我们对 sumOfN3 做同样的基准测试,使用 5 个不同的 n(10,000, 100,000, 1,000,000, 100,000,000), 我们得到如下结果 +如果我们对 *sumOfN3* 做同样的基准测试,使用 5 个不同的 n `(10,000, 100,000, 1,000,000, 10,000,000 和 100,000,000)`, 我们得到如下结果 -```` python +``` Sum is 50005000 required 0.00000095 seconds Sum is 5000050000 required 0.00000191 seconds Sum is 500000500000 required 0.00000095 seconds Sum is 50000005000000 required 0.00000095 seconds Sum is 5000000050000000 required 0.00000119 seconds -```` - -有两点关注下,首先上面记录的时间比上面任何例子都短, 另外他们的时间和 n 无关, 看来 sumOfN3 几乎不受 n 的影响。 +``` -但是这个基准测试告诉我们什么?我们可以直观的看到用迭代的方案在做更多的工作,因为一些步骤重复。这可能是它需要更长时间的原因。此外,迭代所需时间随着 n 递增。还有个问题,如果我们在不同计算机上或者使用不用的编程语言运行这个函数,我们也可能得到不同的结果。如果用老计算机,可能需要更长时间才能执行完 sumOfN3。 +在这个输出中有两件事需要重点关注,首先上面记录的执行时间比之前任何例子都短,另外他们的执行时间和 n 无关,看起来 *sumOfN3* 几乎不受 n 的影响。 +但是这个基准测试能告诉我们什么?我们可以很直观地看到使用了迭代的解决方案需要做更多的工作,因为一些程序步骤被重复执行。这可能是它需要更长时间的原因。此外,迭代方案执行所需时间随着 *n* 递增。另外还有个问题,如果我们在不同计算机上或者使用不用的编程语言运行这个函数,我们也可能得到不同的结果。如果使用老旧的计算机,可能需要更长时间才能执行完 *sumOfN3*。 -我们需要一个更好的方法来表征这些算法的执行时间。基准测试计算的是执行的实际时间。它并不真正提供给我们一个有用的测量,因为它取决于特定的机器,程序,时间,编译器和编程语言。 相反,我们希望具有独立于所使用的程序或计算机的特性。不过基准度量将有助于单独判断算法,并且可以用于在方案之间比较算法。 +我们需要一个更好的方法来描述这些算法的执行时间。基准测试计算的是程序执行的实际时间。它并不真正地提供给我们一个有用的度量(`measurement`),因为它取决于特定的机器,程序,时间,编译器和编程语言。 相反,我们希望有一个独立于所使用的程序或计算机的度量。这个度量将有助于独立地判断算法,并且可以用于比较不同实现方法的算法的效率。 diff --git "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.3.\345\244\247O\347\254\246\345\217\267/README.md" "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.3.\345\244\247O\347\254\246\345\217\267/README.md" index 00dbf4f..f8a7bb5 100644 --- "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.3.\345\244\247O\347\254\246\345\217\267/README.md" +++ "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.3.\345\244\247O\347\254\246\345\217\267/README.md" @@ -1,14 +1,14 @@ ## 2.3.大O符号 -当我们试图通过执行时间来表征算法的效率时,并且独立于任何特定程序或计算机,重要的是量化算法需要的操作或者步骤的数量。选择适当的基本计算单位是个复杂的问题,并且将取决于如何实现算法。对于先前的求和算法,一个比较好的基本计算单位是对执行语句进行计数。在 sumOfN 中,赋值语句的计数为 1 (\(theSum = 0\)) 加上 n 的值(我们执行 \(theSum=theSum+i\) 的次数)。我们通过函数 T 表示 \(T(n)=1 + n\)。参数 n 通常称为‘问题的规模’,我们称作 ‘T(n) 是解决问题大小为 n 所花费的时间,即 1+n 步长’。在上面的求和函数中,使用 n 来表示问题大小是有意义的。我们可以说,100,000 个整数和比 1000 个问题规模大。因此,所需时间也更长。我们的目标是表示出算法的执行时间是如何相对问题规模大小而改变的。 +当我们试图通过执行时间来表征算法的效率时,并且独立于任何特定程序或计算机,重要的是量化算法需要的操作或者步骤的数量。选择适当的基本计算单位是个复杂的问题,并且将取决于如何实现算法。对于先前的求和算法,一个比较好的基本计算单位是对执行语句进行计数。在 sumOfN 中,赋值语句的计数为 1 `(theSum = 0)` 加上 n 的值(我们执行 `theSum=theSum+i` 的次数)。我们通过函数 T 表示 `T(n)=1 + n`。参数 n 通常称为‘问题的规模’,我们称作 ‘T(n) 是解决问题大小为 n 所花费的时间,即 1+n 步长’。在上面的求和函数中,使用 n 来表示问题大小是有意义的。我们可以说,100,000 个整数和比 1000 个问题规模大。因此,所需时间也更长。我们的目标是表示出算法的执行时间是如何相对问题规模大小而改变的。 -计算机科学家更喜欢将这种分析技术进一步。事实证明,操作步骤数量不如确定 \(T(n)\) 最主要的部分来的重要。换句话说,当问题规模变大时, \(T(n)\) 函数某些部分的分量会超过其他部分。函数的数量级表示了随着 n 的值增加而增加最快的那些部分。数量级通常称为大O符号,并写为 \(O(f(n))\)。它表示对计算中的实际步数的近似。函数 \(f(n)\) 提供了 \(T(n)\) 最主要部分的表示方法。 +计算机科学家更喜欢将这种分析技术进一步扩展。事实证明,操作步骤数量不如确定 T(n) 最主要的部分来的重要。换句话说,当问题规模变大时,T(n) 函数某些部分的分量会超过其他部分。函数的数量级表示了随着 n 的值增加而增加最快的那些部分。数量级通常称为大O符号,写为 `O(f(n))`。它表示对计算中的实际步数的近似。函数 f(n) 提供了 T(n) 最主要部分的表示方法。 -在上述示例中,\(T(n)=1+n\)。当 n 变大时,常熟 1 对于最终结果变得越来越不重要。如果我们找的是 \(T(n)\) 的近似值,我们可以删除 1, 运行时间是\(O(n)\)。要注意,1 对于 \(T(n)\) 肯定是重要的。但是当 n 变大时,如果没有它,我们的近似也是准确的。 +在上述示例中,`T(n)=1+n`。当 n 变大时,常数 1 对于最终结果变得越来越不重要。如果我们找的是 T(n) 的近似值,我们可以删除 1, 运行时间是 O(n)。要注意,1 对于 T(n) 肯定是重要的。但是当 n 变大时,如果没有它,我们的近似也是准确的。 -另外一个示例,假设对于一些算法,确定的步数是 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 ) 。 +另外一个示例,假设对于一些算法,确定的步数是 `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 ) 。 -虽然我们没有在求和示例中看到这一点,但有时算法的性能取决于数据的确切值,而不是问题规模的大小。对于这种类型的算法,我们需要根据最佳情况,最坏情况或平均情况来表征它们的性能。最坏情况是指算法性能特别差的特定数据集。而相同的算法不同数据集可能具有非常好的性能。大多数情况下,算法执行效率处在两个极端之间(平均情况)。对于计算机科学家而已,重要的是了解这些区别,使它们不被某一个特定的情况误导。 +虽然我们没有在求和示例中看到这一点,但有时算法的性能取决于数据的确切值,而不是问题规模的大小。对于这种类型的算法,我们需要根据最佳情况,最坏情况或平均情况来表征它们的性能。最坏情况是指算法性能特别差的特定数据集。而相同的算法不同数据集可能具有非常好的性能。大多数情况下,算法执行效率处在两个极端之间(平均情况)。对于计算机科学家而言,重要的是了解这些区别,使它们不被某一个特定的情况误导。 当你学习算法时,一些常见的数量级函数将会反复出现。见 Table 1。为了确定这些函数中哪个是最主要的部分,我们需要看到当 n 变大的时候它们如何相互比较。 @@ -16,7 +16,7 @@ *Table 1* -Figure 1 表示了 Table 1 中的函数图。注意,当 n 很小时,函数彼此间不是很好的定义。很难判断哪个是主导的。随着 n 的增长,就有一个很明确的关系,很容易看出它们之间的大小关系。 +Figure 1 表示了 Table 1 中的函数图。注意,当 n 很小时,函数彼此间不能很好的定义。很难判断哪个是主导的。随着 n 的增长,就有一个很明确的关系,很容易看出它们之间的大小关系。 ![newplot](assets/newplot.png) *Figure 1* @@ -39,11 +39,11 @@ d = 33 ```` *Listing 2* -分配操作数分为四个项的总和。第一个项是常熟 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 增大时,所有其他项以及主项上的系数都可以忽略。 +分配操作数分为四个项的总和。第一个项是常数 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 增大时,所有其他项以及主项上的系数都可以忽略。 ![newplot2](assets/newplot2.png) *Figure 2* -Figure 2 展示了一些常用的 大O 函数,跟上面讨论的 T(n) 函数比较,一开始的时候,T(n) 大于 三次函数,后来随着 n 的增长,三次函数超过了 T(n)。T(n) 随着二次函数继续增长。 +Figure 2 展示了一些常用的 大O 函数,跟上面讨论的 T(n) 函数比较,一开始的时候,T(n) 大于三次函数,后来随着 n 的增长,三次函数超过了 T(n)。T(n) 随着二次函数继续增长。 diff --git "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.4.\344\270\200\344\270\252\344\271\261\345\272\217\345\255\227\347\254\246\344\270\262\346\243\200\346\237\245\347\232\204\344\276\213\345\255\220/README.md" "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.4.\344\270\200\344\270\252\344\271\261\345\272\217\345\255\227\347\254\246\344\270\262\346\243\200\346\237\245\347\232\204\344\276\213\345\255\220/README.md" new file mode 100644 index 0000000..1e07e25 --- /dev/null +++ "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.4.\344\270\200\344\270\252\344\271\261\345\272\217\345\255\227\347\254\246\344\270\262\346\243\200\346\237\245\347\232\204\344\276\213\345\255\220/README.md" @@ -0,0 +1,116 @@ +## 2.4.一个乱序字符串检查的例子 + +显示不同量级的算法的一个很好的例子是字符串的乱序检查。乱序字符串是指一个字符串只是另一个字符串的重新排列。例如,'heart' 和 'earth' 就是乱序字符串。'python' 和 'typhon' 也是。为了简单起见,我们假设所讨论的两个字符串具有相等的长度,并且他们由 26 个小写字母集合组成。我们的目标是写一个布尔函数,它将两个字符串做参数并返回它们是不是乱序。 + +### 2.4.1.解法1:检查 +我们对乱序问题的第一个解法是检查第一个字符串是不是出现在第二个字符串中。如果可以检验到每一个字符,那这两个字符串一定是乱序。可以通过用 None 替换字符来了解一个字符是否完成检查。但是,由于 Python 字符串是不可变的,所以第一步是将第二个字符串转换为列表。检查第一个字符串中的每个字符是否存在于第二个列表中,如果存在,替换成 None。见 ActiveCode1 + +```` +def anagramSolution1(s1,s2): + alist = list(s2) + + pos1 = 0 + stillOK = True + + while pos1 < len(s1) and stillOK: + pos2 = 0 + found = False + while pos2 < len(alist) and not found: + if s1[pos1] == alist[pos2]: + found = True + else: + pos2 = pos2 + 1 + + if found: + alist[pos2] = None + else: + stillOK = False + + pos1 = pos1 + 1 + + return stillOK + +print(anagramSolution1('abcd','dcba')) +```` +*ActiveCode1* + +为了分析这个算法,我们注意到 s1 的每个字符都会在 s2 中进行最多 n 个字符的迭代。s2 列表中的 n 个位置将被访问一次来匹配来自 s1 的字符。访问次数可以写成 1 到 n 整数的和,可以写成 +![2.4.1 求和](assets/2.4.1%20%E6%B1%82%E5%92%8C.png) + +当 n 变大,n^2 这项占据主导,1/2 可以忽略。所以这个算法复杂度为 O(n^2 )。 + +### 2.4.2.解法2:排序和比较 + +另一个解决方案是利用这么一个事实:即使 s1,s2 不同,它们都是由完全相同的字符组成的。所以,我们按照字母顺序从 a 到 z 排列每个字符串,如果两个字符串相同,那这两个字符串就是乱序字符串。见 ActiveCode2。 + +```` +def anagramSolution2(s1,s2): + alist1 = list(s1) + alist2 = list(s2) + + alist1.sort() + alist2.sort() + + pos = 0 + matches = True + + while pos < len(s1) and matches: + if alist1[pos]==alist2[pos]: + pos = pos + 1 + else: + matches = False + + return matches + +print(anagramSolution2('abcde','edcba')) +```` +*ActiveCode2* + +首先你可能认为这个算法是 O(n),因为只有一个简单的迭代来比较排序后的 n 个字符。但是,调用 Python 排序不是没有成本。正如我们将在后面的章节中看到的,排序通常是 O(n^2) 或 O(nlogn)。所以排序操作比迭代花费更多。最后该算法跟排序过程有同样的量级。 + +### 2.4.3.解法3: 穷举法 + +解决这类问题的强力方法是穷举所有可能性。对于乱序检测,我们可以生成 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! 个字符串。 + +事实证明,n! 比 n^2 增长还快,事实上,如果 s1 有 20个字符长,则将有 20! = 2,432,902,008,176,640,000 个字符串产生。如果我们每秒处理一种可能字符串,那么需要 77,146,816,596 年才能过完整个列表。所以这不是很好的解决方案。 + +### 2.4.4.解法4: 计数和比较 + +我们最终的解决方法是利用两个乱序字符串具有相同数目的 a, b, c 等字符的事实。我们首先计算的是每个字母出现的次数。由于有 26 个可能的字符,我们就用 一个长度为 26 的列表,每个可能的字符占一个位置。每次看到一个特定的字符,就增加该位置的计数器。最后如果两个列表的计数器一样,则字符串为乱序字符串。见 ActiveCode 3 + +```` python +def anagramSolution4(s1,s2): + c1 = [0]*26 + c2 = [0]*26 + + for i in range(len(s1)): + pos = ord(s1[i])-ord('a') + c1[pos] = c1[pos] + 1 + + for i in range(len(s2)): + pos = ord(s2[i])-ord('a') + c2[pos] = c2[pos] + 1 + + j = 0 + stillOK = True + while j<26 and stillOK: + if c1[j]==c2[j]: + j = j + 1 + else: + stillOK = False + + return stillOK + +print(anagramSolution4('apple','pleap')) + +```` +*ActiveCode 3* + +同样,这个方案有多个迭代,但是和第一个解法不一样,它不是嵌套的。两个迭代都是 n, 第三个迭代,比较两个计数列表,需要 26 步,因为有 26 个字母。一共 T(n)=2n+26T(n)=2n+26,即 O(n),我们找到了一个线性量级的算法解决这个问题。 + +在结束这个例子之前,我们来讨论下空间花费,虽然最后一个方案在线性时间执行,但它需要额外的存储来保存两个字符计数列表。换句话说,该算法牺牲了空间以获得时间。 + +很多情况下,你需要在空间和时间之间做出权衡。这种情况下,额外空间不重要,但是如果有数百万个字符,就需要关注下。作为一个计算机科学家,当给定一个特定的算法,将由你决定如何使用计算资源。 + + + diff --git "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.4.\344\270\200\344\270\252\345\233\236\346\226\207\345\255\227\347\254\246\344\270\262\346\243\200\346\237\245\347\232\204\344\276\213\345\255\220/assets/2.4.1 \346\261\202\345\222\214.png" "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.4.\344\270\200\344\270\252\344\271\261\345\272\217\345\255\227\347\254\246\344\270\262\346\243\200\346\237\245\347\232\204\344\276\213\345\255\220/assets/2.4.1 \346\261\202\345\222\214.png" similarity index 100% rename from "2.\347\256\227\346\263\225\345\210\206\346\236\220/2.4.\344\270\200\344\270\252\345\233\236\346\226\207\345\255\227\347\254\246\344\270\262\346\243\200\346\237\245\347\232\204\344\276\213\345\255\220/assets/2.4.1 \346\261\202\345\222\214.png" rename to "2.\347\256\227\346\263\225\345\210\206\346\236\220/2.4.\344\270\200\344\270\252\344\271\261\345\272\217\345\255\227\347\254\246\344\270\262\346\243\200\346\237\245\347\232\204\344\276\213\345\255\220/assets/2.4.1 \346\261\202\345\222\214.png" diff --git "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.4.\344\270\200\344\270\252\345\233\236\346\226\207\345\255\227\347\254\246\344\270\262\346\243\200\346\237\245\347\232\204\344\276\213\345\255\220/README.md" "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.4.\344\270\200\344\270\252\345\233\236\346\226\207\345\255\227\347\254\246\344\270\262\346\243\200\346\237\245\347\232\204\344\276\213\345\255\220/README.md" deleted file mode 100644 index e93c52d..0000000 --- "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.4.\344\270\200\344\270\252\345\233\236\346\226\207\345\255\227\347\254\246\344\270\262\346\243\200\346\237\245\347\232\204\344\276\213\345\255\220/README.md" +++ /dev/null @@ -1,116 +0,0 @@ -## 2.4.一个回文字符串检查的例子 - -显示不同量级的算法的一个很好的例子是字符串的回文检查。一个字符串是另一个字符串的回文。如果第二个字符串只是第一个的重新排列,例如,'heart' 和 'earth' 就是回文字符串。'python' 和 'typhon' 也是。为了简单起见,我们假设所讨论的两个字符串具有相等的长度,并且他们由 26 个小写字母集合组成。我们的目标是写一个布尔函数,它将两个字符串做参数并返回它们是不是回文。 - -### 2.4.1.解法1:检查 -我们对回文问题的第一个解法是检查第一个字符串是不是出现在第二个字符串中。如果可以检验到每一个字符,那两个字符串一定是回文。可以通过用 None 替换字符来完成检查。但是,由于 Python 字符串是不可变的,所以第一步是将第二个字符串转换为列表。第一个字符串中的每个字符可以通过检查在第二个列表中检查元素是否存在,如果存在,替换成None。见 ActiveCode1 - -```` -def anagramSolution1(s1,s2): - alist = list(s2) - - pos1 = 0 - stillOK = True - - while pos1 < len(s1) and stillOK: - pos2 = 0 - found = False - while pos2 < len(alist) and not found: - if s1[pos1] == alist[pos2]: - found = True - else: - pos2 = pos2 + 1 - - if found: - alist[pos2] = None - else: - stillOK = False - - pos1 = pos1 + 1 - - return stillOK - -print(anagramSolution1('abcd','dcba')) -```` -*ActiveCode1* - -为了分析这个算法,我们注意到 s1 的每个字符都会在 s2 进行最多 n 个字符的迭代。列表中的 n 个位置将被访问一次来匹配来自 s1 的字符。访问次数可以写成 1 到 n 整数的和,可以写成 -![2.4.1 求和](assets/2.4.1%20%E6%B1%82%E5%92%8C.png) - -当 n 变大,n^2 这项占据主导,1/2 可以忽略。所以这个算法复杂度为 O(n^2 )。 - -### 2.4.2.解法2:排序和比较 - -另一个解决方案是利用这么一个事实,即使 s1,s2 不同,它们只有由完全相同的字符组成,它们才是回文。所以,如果我们按照字母顺序排列每个字符串,从 a 到 z, 如果两个字符串相同,则这两个字符串为回文。见 ActiveCode2。 - -```` -def anagramSolution2(s1,s2): - alist1 = list(s1) - alist2 = list(s2) - - alist1.sort() - alist2.sort() - - pos = 0 - matches = True - - while pos < len(s1) and matches: - if alist1[pos]==alist2[pos]: - pos = pos + 1 - else: - matches = False - - return matches - -print(anagramSolution2('abcde','edcba')) -```` -*ActiveCode2* - -首先你可能认为这个算法是 O(n), 因为只有一个简单的迭代来比较排序后的n个字符。但是,调用 Python 排序不是没有成本。正如我们将在后面的章节中看到的,排序通常是 O(n^2)或 O(nlogn)。所以排序操作比迭代花费更多。最后该算法跟排序过程有同样的量级。 - -### 2.4.3.解法3: 穷举法 - -解决这类问题的强力方法是穷举所有可能性。对于回文检测,我们可以生成所有 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! 个字符串。 - -事实证明,n! 比 n^2 增长还快,事实上,如果 s1 有 20个字符长,则将有 20! = 2,432,902,008,176,640,000 个字符串产生。如果我们每秒处理一种可能字符串,那么需要 77,146,816,596 才能过完整个列表。所以这不是很好的解决方案。 - -### 2.4.4.解法4: 计数和比较 - -我们最终解决回文的方法是利用两个回文字符串具有相同的 a, b, c等等的事实。我们首先计算的是每个字母出现的次数。由于有 26 个可能的字符,我们就用 26 个列表,每个可能的字符一个。每次看到一个特定的字符,就增加该位置的计数器。最后如果两个列表的计数器一样,则字符串为回文字符串。见 ActiveCode 3 - -```` python -def anagramSolution4(s1,s2): - c1 = [0]*26 - c2 = [0]*26 - - for i in range(len(s1)): - pos = ord(s1[i])-ord('a') - c1[pos] = c1[pos] + 1 - - for i in range(len(s2)): - pos = ord(s2[i])-ord('a') - c2[pos] = c2[pos] + 1 - - j = 0 - stillOK = True - while j<26 and stillOK: - if c1[j]==c2[j]: - j = j + 1 - else: - stillOK = False - - return stillOK - -print(anagramSolution4('apple','pleap')) - -```` -*ActiveCode 3* - -同样,这个方案有多个迭代,但是和第一个解法不一样,它不是嵌套的。两个迭代都是 n, 第三个迭代,比较两个计数列表,需要 26个步,因为有 26个字母。一共 T(n)=2n+26T(n)=2n+26,即 O(n),我们找到了一个线性量级的算法解决这个问题。 - -在结束这个例子之前,我们来讨论下空间花费,虽然最后一个方案在线性时间执行,但它需要额外的存储来保存两个字符计数列表。换句话说,该算法牺牲了空间以获得时间。 - -很多情况下,你需要在空间和时间之间做出权衡。这种情况下,额外空间不重要,但是如果有数百万个字符,就需要关注下。作为一个计算机科学家,当给定一个特定的算法,将由你决定如何使用计算资源。 - - - diff --git "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.6.\345\210\227\350\241\250/README.md" "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.6.\345\210\227\350\241\250/README.md" index 2fa6b35..bdbdb6c 100644 --- "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.6.\345\210\227\350\241\250/README.md" +++ "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.6.\345\210\227\350\241\250/README.md" @@ -28,7 +28,7 @@ def test4(): 要捕获我们的每个函数执行所需的时间,我们将使用 Python 的 timeit 模块。timeit 模块旨在允许 Python 开发人员通过在一致的环境中运行函数并使用尽可能相似的操作系统的时序机制来进行跨平台时序测量。 -要使用 timeit,您需要创建一个 Timer 对象,其参数是两个 Python 语句。第一个参数是一个你想要执行时间的 Python 语句; 第二个参数是一个将运行一次以设置测试的语句。然后 timeit 模块将计算执行语句所需的时间。默认情况下,timeit 将尝试运行语句一百万次。 当它完成时,它返回时间作为表示总秒数的浮点值。由于它执行语句一百万次,可以读取结果作为执行测试一次的微秒数。你还可以传递 timeit 一个参数名字为 number,允许您指定执行测试语句的次数。以下显示了运行我们的每个测试功能 1000 次需要多长时间。 +要使用 timeit,你需要创建一个 Timer 对象,其参数是两个 Python 语句。第一个参数是一个你想要执行时间的 Python 语句; 第二个参数是一个将运行一次以设置测试的语句。然后 timeit 模块将计算执行语句所需的时间。默认情况下,timeit 将尝试运行语句一百万次。 当它完成时,它返回时间作为表示总秒数的浮点值。由于它执行语句一百万次,可以读取结果作为执行测试一次的微秒数。你还可以传递 timeit 一个参数名字为 number,允许你指定执行测试语句的次数。以下显示了运行我们的每个测试功能 1000 次需要多长时间。 ```` t1 = Timer("test1()", "from __main__ import test1") @@ -52,15 +52,15 @@ list range 0.0655000209808 milliseconds 最后一点,你上面看到的时间都是包括实际调用函数的一些开销,但我们可以假设函数调用开销在四种情况下是相同的,所以我们仍然得到的是有意义的比较。因此,拼接字符串操作需要 6.54 毫秒并不准确,而是拼接字符串这个函数需要 6.54 毫秒。你可以测试调用空函数所需要的时间,并从上面的数字中减去它。 -现在我们已经看到了如何具体测试性能,见 Table2, 你可能想知道 pop 两个不同的时间。当列表末尾调用 pop 时,它需要 O(1), 但是当在列表中第一个元素或者中间任何地方调用 pop, 它是 O(n)。原因在于 Python 实现列表的方式,当一个项从列表前面取出,列表中的其他元素靠近开始移动一个位置。你会看到索引操作为 O(1)。python的实现者会权衡选择一个好的方案。 +现在我们已经看到了如何具体测试性能,见 Table2, 你可能想知道 pop 两个不同的时间。当列表末尾调用 pop 时,它需要 O(1), 但是当在列表中第一个元素或者中间任何地方调用 pop, 它是 O(n)。原因在于 Python 实现列表的方式,当一个项从列表前面取出,列表中的其他元素靠近起始位置移动一个位置。你会看到索引操作为 O(1)。python的实现者会权衡选择一个好的方案。 ![2.6.列表 Table2](assets/2.6.%E5%88%97%E8%A1%A8%20Table2.png) -作为一种演示性能差异的方法,我们用 timeit 来做一个实验。我们的目标是验证从列表从末尾 pop 元素和从开始 pop 元素的性能。同样,我们也想测量不同列表大小对这个时间的影响。我们期望看到的是,从列表末尾处弹出所需时间将保持不变,即使列表不断增长。而从列表开始出弹出元素时间将随列表增长而增加。 +作为一种演示性能差异的方法,我们用 timeit 来做一个实验。我们的目标是验证从列表从末尾 pop 元素和从开始 pop 元素的性能。同样,我们也想测量不同列表大小对这个时间的影响。我们期望看到的是,从列表末尾处弹出所需时间将保持不变,即使列表不断增长。而从列表开始处弹出元素时间将随列表增长而增加。 -Listing 4 展示了两种 pop 方式的比较。从第一个示例看书,从末尾弹出需要 0.0003 毫秒。从开始弹出要花费 4.82毫秒。对于一个 200万的元素列表,相差 16000 倍。 +Listing 4 展示了两种 pop 方式的比较。从第一个示例看出,从末尾弹出需要 0.0003 毫秒。从开始弹出要花费 4.82 毫秒。对于一个 200 万的元素列表,相差 16000 倍。 -Listing 4 需要注意的几点,第一, `from __main__ import x` , 虽然我们没有定义一个函数,我们确实希望能够在我们的测试中使用列表对象 x, 这种方法允许我们只计算单个弹出语句,获得该操作最精确的测量时间。因为 timer 重复了 1000 次,所以该列表每次循环大小都减1。但是由于初始列表大小为 200万,我们只减少总体大小的 0.05%。 +Listing 4 需要注意的几点,第一, `from __main__ import x` , 虽然我们没有定义一个函数,我们确实希望能够在我们的测试中使用列表对象 x, 这种方法允许我们只计算单个弹出语句,获得该操作最精确的测量时间。因为 timer 重复了 1000 次,该列表每次循环大小都减 1。但是由于初始列表大小为 200万,我们只减少总体大小的 0.05%。 ```` popzero = timeit.Timer("x.pop(0)", @@ -95,7 +95,7 @@ for i in range(1000000,100000001,1000000): ```` *Listing 5* -Figure 3 展示了我们实验的结果,你可以看到,随着列表变长,pop(0) 时间也增加,而pop() 时间保持非常平坦。这正是我们期望看到的 O(n)和 O(1) +Figure 3 展示了我们实验的结果,你可以看到,随着列表变长,pop(0) 时间也增加,而 pop() 时间保持非常平坦。这正是我们期望看到的 O(n)和 O(1) ![2.6.列表.poptime](assets/2.6.%E5%88%97%E8%A1%A8.poptime.png) diff --git "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.7.\345\255\227\345\205\270/README.md" "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.7.\345\255\227\345\205\270/README.md" index 89721a1..dca3360 100644 --- "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.7.\345\255\227\345\205\270/README.md" +++ "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.7.\345\255\227\345\205\270/README.md" @@ -5,9 +5,9 @@ python 中第二个主要的数据结构是字典。你可能记得,字典和 ![2.7.字典.table3](assets/2.7.%E5%AD%97%E5%85%B8.table3.png) *Table 3* -我们会在最后的实验中,将比较列表和字典之间的 contains 操作的性能。在此过程中,我们将确认列表的 contains 操作符是O(n),字典的 contains 操作符是O(1)。我们将在实验中列出一系列数字。然后随机选择数字,并检查数字是否在列表中。如果我们的性能表是正确的,列表越大,确定列表中是否包含任何一个数字应该花费的时间越长。 +我们会在最后的实验中,将比较列表和字典之间的 contains 操作的性能。在此过程中,我们将确认列表的 contains 操作符是 O(n),字典的 contains 操作符是 O(1)。我们将在实验中列出一系列数字。然后随机选择数字,并检查数字是否在列表中。如果我们的性能表是正确的,列表越大,确定列表中是否包含任意一个数字应该花费的时间越长。 -Listing 6 实现了这个比较。注意,我们对容器中的数字执行完全相同的操作。区别在于在第7行上x是一个列表,第9行上的x是一个字典。 +Listing 6 实现了这个比较。注意,我们对容器中的数字执行完全相同的操作。区别在于在第 7 行上 x 是一个列表,第9行上的 x 是一个字典。 ```` import timeit @@ -25,7 +25,7 @@ for i in range(10000,1000001,20000): *Listing 6* -Figure 4 展示了 Listing6 的结果。你可以看到字典一直更快。 对于最小的列表大小为10,000个元素,字典是列表的89.4倍。对于最大的列表大小为990,000 个元素。字典是列表的11,603倍!你还可以看到列表上的contains运算符所花费的时间与列表的大小成线性增长。这验证了列表上的contains运算符是O(n)的断言。还可以看出,字典中的contains运算符的时间是恒定的,即使字典大小不断增长。事实上,对于字典大小为10,000个元素,contains操作占用0.004毫秒,对于字典大小为990,000个元素,它也占用0.004毫秒。 +Figure 4 展示了 Listing6 的结果。你可以看到字典一直更快。 对于最小的列表大小为10,000个元素,字典是列表的89.4倍。对于最大的列表大小为990,000 个元素。字典是列表的11,603倍!你还可以看到列表上的contains运算符所花费的时间与列表的大小成线性增长。这验证了列表上的contains运算符是 O(n) 的断言。还可以看出,字典中的 contains 运算符的时间是恒定的,即使字典大小不断增长。事实上,对于字典大小为10,000个元素,contains操作占用0.004毫秒,对于字典大小为990,000个元素,它也占用0.004毫秒。 ![2.7.字典.figure4](assets/2.7.%E5%AD%97%E5%85%B8.figure4.png) diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.1.\347\233\256\346\240\207/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.1.\347\233\256\346\240\207/README.md" index 60fef85..cd6a103 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.1.\347\233\256\346\240\207/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.1.\347\233\256\346\240\207/README.md" @@ -1,14 +1,14 @@ ## 3.1.目标 -* 理解抽象数据类型的栈,队列,deque和列表。 +* 理解抽象数据类型的栈,队列,deque 和列表。 * 能够使用 Python 列表实现 ADT 堆栈,队列和 deque。 * 了解基本线性数据结构实现的性能。 * 了解前缀,中缀和后缀表达式格式。 -* 使用堆栈来实现后缀表达式。 -* 使用堆栈将表达式从中缀转换为后缀。 +* 使用栈来实现后缀表达式。 +* 使用栈将表达式从中缀转换为后缀。 * 使用队列进行基本时序仿真。 -* 能够识别问题中堆栈,队列和 deques 数据结构的适当使用。 -* 能够使用节点和参考模式将抽象数据类型列表实现为链接列表。 +* 能够识别问题中栈,队列和 deques 数据结构的适当使用。 +* 能够使用节点和引用将抽象数据类型列表实现为链表。 * 能够比较我们的链表实现与 Python 的列表实现的性能。 diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.10.\344\273\200\344\271\210\346\230\257\351\230\237\345\210\227/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.10.\344\273\200\344\271\210\346\230\257\351\230\237\345\210\227/README.md" index d233300..9ae9dde 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.10.\344\273\200\344\271\210\346\230\257\351\230\237\345\210\227/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.10.\344\273\200\344\271\210\346\230\257\351\230\237\345\210\227/README.md" @@ -6,6 +6,8 @@ 队列的最简单的例子是我们平时不时会参与的列。排队等待电影,在杂货店的收营台等待,在自助餐厅排队等待(这样我们可以弹出托盘栈)。行为良好的线或队列是有限制的,因为它只有一条路,只有一条出路。不能插队,也不能离开。你只有等待了一定的时间才能到前面。Figure 1 展示了一个简单的 Python 对象队列。 ![3.10.什么是队列.figure1](assets/3.10.%E4%BB%80%E4%B9%88%E6%98%AF%E9%98%9F%E5%88%97.figure1.png) + + *Figure 1* 计算机科学也有常见的队列示例。我们的计算机实验室有 30 台计算机与一台打印机联网。当学生想要打印时,他们的打印任务与正在等待的所有其他打印任务“一致”。第一个进入的任务是先完成。如果你是最后一个,你必须等待你前面的所有其他任务打印。我们将在后面更详细地探讨这个有趣的例子。 diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.11.\351\230\237\345\210\227\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.11.\351\230\237\345\210\227\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" index b607405..56d608b 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.11.\351\230\237\345\210\227\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.11.\351\230\237\345\210\227\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" @@ -10,5 +10,6 @@ ![3.11.队列抽象数据类型.table1](assets/3.11.%E9%98%9F%E5%88%97%E6%8A%BD%E8%B1%A1%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B.table1.png) + *Table 1* diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.13.\346\250\241\346\213\237\357\274\232\347\203\253\346\211\213\345\261\261\350\212\213/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.13.\346\250\241\346\213\237\357\274\232\347\203\253\346\211\213\345\261\261\350\212\213/README.md" index 7e972bd..254d512 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.13.\346\250\241\346\213\237\357\274\232\347\203\253\346\211\213\345\261\261\350\212\213/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.13.\346\250\241\346\213\237\357\274\232\347\203\253\346\211\213\345\261\261\350\212\213/README.md" @@ -6,11 +6,13 @@ 这个游戏相当于著名的约瑟夫问题,一个一世纪著名历史学家弗拉维奥·约瑟夫斯的传奇故事。故事讲的是,他和他的 39 个战友被罗马军队包围在洞中。他们决定宁愿死,也不成为罗马人的奴隶。他们围成一个圈,其中一人被指定为第一个人,顺时针报数到第七人,就将他杀死。约瑟夫斯是一个成功的数学家,他立即想出了应该坐到哪才能成为最后一人。最后,他加入了罗马的一方,而不是杀了自己。你可以找到这个故事的不同版本,有些说是每次报数 3 个人,有人说允许最后一个人逃跑。无论如何,思想是一样的。 -我们将模拟这个烫山芋的过程。我们的程序将输入名称列表和一个称为 num 常量用于报数。它将返回以 num 为单位重复报数后剩余的最后一个人的姓名。 +我们将模拟这个烫山芋的过程。我们的程序将输入名称列表和一个称为 num 常量用于报数。它将返回以 `num` 为单位重复报数后剩余的最后一个人的姓名。 为了模拟这个圈,我们使用队列(见 Figure3)。假设拿着山芋的孩子在队列的前面。当拿到山芋的时候,这个孩子将先出列再入队列,把他放在队列的最后。经过 num 次的出队入队后,前面的孩子将被永久移除队列。并且另一个周期开始,继续此过程,直到只剩下一个名字(队列的大小为 1)。 ![3.13.模拟:烫手山芋.figure3](assets/3.13.%E6%A8%A1%E6%8B%9F%EF%BC%9A%E7%83%AB%E6%89%8B%E5%B1%B1%E8%8A%8B.figure3.png) + + *Figure 3* ```` python @@ -34,6 +36,6 @@ print(hotPotato(["Bill","David","Susan","Jane","Kent","Brad"],7)) ```` *Active code 1* -请注意,在此示例中,计数常数的值大于列表中的名称数。这不是一个问题,因为队列像一个圈,计数会重新回到开始,直到达到计数值。另外,请注意,列表加载到队列中以使列表上的名字位于队列的前面。在这种情况下,Bill 是列表中的第一个项,因此他在队列的前面。 +请注意,在此示例中,计数常数的值大于列表中的名称数。这不是一个问题,因为队列像一个圈,计数会重新回到开始,直到达到计数值。另外,请注意,列表加载到队列中以使列表上的名字位于队列的前面。在这种情况下,`Bill` 是列表中的第一个项,因此他在队列的前面。 diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.14.\346\250\241\346\213\237\357\274\232\346\211\223\345\215\260\346\234\272/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.14.\346\250\241\346\213\237\357\274\232\346\211\223\345\215\260\346\234\272/README.md" index 0dd2da3..f2ebb1b 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.14.\346\250\241\346\213\237\357\274\232\346\211\223\345\215\260\346\234\272/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.14.\346\250\241\346\213\237\357\274\232\346\211\223\345\215\260\346\234\272/README.md" @@ -5,24 +5,26 @@ 我们可以通过建立一个模拟实验来决定。我们将需要为学生,打印任务和打印机构建表现表示(Figure 4)。当学生提交打印任务时,我们将把他们添加到等待列表中,一个打印任务的队列。 当打印机完成任务时,它将检查队列,以检查是否有剩余的任务要处理。我们感兴趣的是学生等待他们的论文打印的平均时间。这等于任务在队列中等待的平均时间量。 ![3.14.模拟:打印机.figure4](assets/3.14.%E6%A8%A1%E6%8B%9F%EF%BC%9A%E6%89%93%E5%8D%B0%E6%9C%BA.figure4.png) + + *Figure 4* -为了建模这种情况,我们需要使用一些概率。例如,学生可以打印长度从1到20页的纸张。如果从1到20的每个长度同样可能,则可以通过使用1和20之间的随机数来模拟打印任务的实际长度。这意味着出现从1到20的任何长度的机会是平等的。 +为了为这种情况建模,我们需要使用一些概率。例如,学生可以打印长度从 1 到 20 页的纸张。如果从 1 到 20 的每个长度有同样的可能性,则可以通过使用 1 和 20 之间的随机数来模拟打印任务的实际长度。这意味着出现从 1 到 20 的任何长度的机会是平等的。 -如果实验室中有10个学生,每人打印两次,则平均每小时有20个打印任务。 在任何给定的秒,打印任务将被创建的机会是什么? 回答这个问题的方法是考虑任务与时间的比率。 每小时20个任务意味着平均每180秒将有一个任务: +如果实验室中有 10 个学生,每人打印两次,则平均每小时有 20 个打印任务。 在任何给定的秒,打印任务将被创建的机会是什么? 回答这个问题的方法是考虑任务与时间的比率。每小时 20 个任务意味着平均每 180 秒将有一个任务: ![3.14.模拟:打印机.figure1](assets/3.14.%E6%A8%A1%E6%8B%9F%EF%BC%9A%E6%89%93%E5%8D%B0%E6%9C%BA.figure1.png) -对于每一秒,我们可以通过生成1到180之间的随机数来模拟打印任务发生的机会。如果数字是180,我们说一个任务已经创建。请注意,可能会在一下子创建许多任务,或者需要等待一段时间才有任务。这就是模拟的本质。你想模拟真实的情况就需要尽可能接近一般参数。 +对于每一秒,我们可以通过生成 1 到 180 之间的随机数来模拟打印任务发生的机会。如果数字是 180,我们说一个任务已经创建。请注意,可能会在一下子创建许多任务,或者需要等待一段时间才有任务。这就是模拟的本质。你想模拟真实的情况就需要尽可能接近一般参数。 ### 3.14.1.主要模拟步骤 1. 创建打印任务的队列,每个任务都有个时间戳。队列启动的时候为空。 2. 每秒(currentSecond): - * 是否创建新的打印任务?如果是,将 currentSecond 作为时间戳添加到队列。 + * 是否创建新的打印任务?如果是,将 `currentSecond` 作为时间戳添加到队列。 * 如果打印机不忙并且有任务在等待 * 从打印机队列中删除一个任务并将其分配给打印机 - * 从 currentSecond 中减去时间戳,以计算该任务的等待时间。 + * 从 `currentSecond` 中减去时间戳,以计算该任务的等待时间。 * 将该任务的等待时间附件到列表中稍后处理。 * 根据打印任务的页数,确定需要多少时间。 * 打印机需要一秒打印,所以得从该任务的所需的等待时间减去一秒。 @@ -31,9 +33,9 @@ ### 3.14.2 Python 实现 -为了设计此模拟,我们将为上述三个真实世界对象创建类:Printer, Task, PrintQueue +为了设计此模拟,我们将为上述三个真实世界对象创建类:`Printer`, `Task`, `PrintQueue` -Printer 类(Listing 2)需要跟踪它当前是否有任务。如果有,则它处于忙碌状态(13-17 行),并且可以从任务的页数计算所需的时间。构造函数允许初始化每分钟页面的配置,tick 方法将内部定时器递减直到打印机设置为空闲(11 行) +`Printer` 类(Listing 2)需要跟踪它当前是否有任务。如果有,则它处于忙碌状态(13-17 行),并且可以从任务的页数计算所需的时间。构造函数允许初始化每分钟页面的配置,`tick` 方法将内部定时器递减直到打印机设置为空闲(11 行) ```` python class Printer: @@ -60,7 +62,7 @@ class Printer: ```` *Listing 2* -Task 类(Listing 3)表示单个打印任务。创建任务时,随机数生成器将提供 1 到 20 页的长度。我们选择使用随机模块中的 randrange 函数。 +`Task` 类(Listing 3)表示单个打印任务。创建任务时,随机数生成器将提供 1 到 20 页的长度。我们选择使用随机模块中的 `randrange` 函数。 ```` >>> import random @@ -70,7 +72,7 @@ Task 类(Listing 3)表示单个打印任务。创建任务时,随机数生 8 >>> ```` -每个任务还需要保存一个时间戳用于计算等待时间。此时间戳将表示任务被创建并放置到打印机队列中的时间。可以使用 waitTime 方法来检索在打印开始之前队列中花费的时间。 +每个任务还需要保存一个时间戳用于计算等待时间。此时间戳将表示任务被创建并放置到打印机队列中的时间。可以使用 `waitTime` 方法来检索在打印开始之前队列中花费的时间。 ```` python import random @@ -91,7 +93,7 @@ class Task: ```` *Listing 3* -Listing 4 实现了上述算法。PrintQueue 对象是我们现有队列 ADT 的一个实例。newPrintTask 决定是否创建一个新的打印任务。我们再次选择使用随机模块的 randrange 函数返回 1 到 180 之间的随机整数。打印任务每 180 秒到达一次。通过从随机整数(32 行)的范围中任意选择,我们可以模拟这个随机事件。模拟功能允许我们设置打印机的总时间和每分钟的页数。 +Listing 4 实现了上述算法。`PrintQueue` 对象是我们现有队列 ADT 的一个实例。`newPrintTask` 决定是否创建一个新的打印任务。我们再次选择使用随机模块的 `randrange` 函数返回 1 到 180 之间的随机整数。打印任务每 180 秒到达一次。通过从随机整数(32 行)的范围中任意选择,我们可以模拟这个随机事件。模拟功能允许我们设置打印机的总时间和每分钟的页数。 ```` from pythonds.basic.queue import Queue @@ -134,7 +136,7 @@ for i in range(10): 当我们运行模拟时,我们不应该担心每次的结果不同。这是由于随机数的概率性质决定的。 因为模拟的参数可以被调整,我们对调整后可能发生的趋势感兴趣。 这里有一些结果。 -首先,我们将使用每分钟五页的页面速率运行模拟60分钟(3,600秒)。 此外,我们将进行10次独立试验。记住,因为模拟使用随机数,每次运行将返回不同的结果。 +首先,我们将使用每分钟五页的页面速率运行模拟 60 分钟(3,600秒)。 此外,我们将进行 10 次独立试验。记住,因为模拟使用随机数,每次运行将返回不同的结果。 ```` >>>for i in range(10): @@ -151,9 +153,9 @@ Average Wait 48.33 secs 0 tasks remaining. Average Wait 39.31 secs 3 tasks remaining. Average Wait 376.05 secs 1 tasks remaining. ```` -在运行 10 次实验后,我们可以看到,平均等待时间为 122.09 秒。 还可以看到平均等待时间有很大的变化,最小值为17.27秒,最大值为376.05秒。 你也可能注意到,只有两种情况所有任务都完成。 +在运行 10 次实验后,我们可以看到,平均等待时间为 122.09 秒。 还可以看到平均等待时间有很大的变化,最小值为 17.27 秒,最大值为 376.05 秒。 你也可能注意到,只有两种情况所有任务都完成。 -现在,我们将页面速率调整为每分钟10页,再次运行10次测试,页面速度更快,我们希望在一小时内完成更多的任务。 +现在,我们将页面速率调整为每分钟 10 页,再次运行 10 次测试,页面速度更快,我们希望在一小时内完成更多的任务。 ```` >>>for i in range(10): @@ -175,15 +177,15 @@ Average Wait 18.17 secs 0 tasks remaining. 我们试图回答一个问题,即当前打印机是否可以处理任务负载,如果它设置为打印更好的质量,较慢的页面速率。我们采用的方法是编写一个模拟打印任务作为各种页数和到达时间的随机事件的模拟。 -上面的输出显示,每分钟打印5页,平均等待时间从低的17秒到高的376秒(约6分钟)。使用更快的打印速率,低值为1秒,高值仅为28。此外,在 10 次运行中的8次,每分钟5页,打印任务在结束时仍在队列中等待。 +上面的输出显示,每分钟打印 5 页,平均等待时间从低的 17 秒到高的 376 秒(约 6 分钟)。使用更快的打印速率,低值为 1 秒,高值仅为 28。此外,在 10 次运行中的 8 次,每分钟 5 页,打印任务在结束时仍在队列中等待。 因此,我们说减慢打印机的速度以获得更好的质量可能不是一个好主意。学生们不能等待他们的论文打印完,特别是当他们需要到下一个班级。六分钟的等待时间太长了。 这种类型的模拟分析允许我们回答许多问题,通常被称为“如果”的问题。我们需要做的是改变模拟使用的参数,我们可以模拟任何数量。例如 - * 如果入学人数增加,平均学生人数增加 20人 该怎么办? + * 如果入学人数增加,平均学生人数增加 20 人 该怎么办? * 如果是星期六,学生不需要上课怎么办?他们能负担得了吗? - * 如果平均打印任务的大小减少了,由于 Python是一个强大的语言,程序往往要短得多? + * 如果平均打印任务的大小减少了,由于 Python 是一个强大的语言,程序往往要短得多? -这些问题都可以通过修改上述模拟来回答。然而,重要的是要记住,模拟有效取决于构建它的假设是没问题的。关于每小时打印任务的数量和每小时的学生数量的真实数据对于构建鲁棒的模拟是必要的。 +这些问题都可以通过修改上述模拟来回答。然而,重要的是要记住,模拟有效取决于构建它的假设是没问题的。关于每小时打印任务的数量和每小时的学生数量的真实数据对于构建鲁棒性的模拟是必要的。 diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.15.\344\273\200\344\271\210\346\230\257Deque/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.15.\344\273\200\344\271\210\346\230\257Deque/README.md" index 126f5a8..ffc58df 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.15.\344\273\200\344\271\210\346\230\257Deque/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.15.\344\273\200\344\271\210\346\230\257Deque/README.md" @@ -5,6 +5,7 @@ deque(也称为双端队列)是与队列类似的项的有序集合。它有 要注意,即使 deque 可以拥有栈和队列的许多特性,它不需要由那些数据结构强制的 LIFO 和 FIFO 排序。这取决于你如何持续添加和删除操作。 ![3.15.什么是Deque.figure1](assets/3.15.%E4%BB%80%E4%B9%88%E6%98%AFDeque.figure1-1.png) + *Figure 1* diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.16.Deque\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.16.Deque\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" index 2267199..a4b3634 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.16.Deque\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.16.Deque\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" @@ -13,6 +13,7 @@ deque 抽象数据类型由以下结构和操作定义。如上所述,deque ![3.16.Deque抽象数据类型.table1](assets/3.16.Deque%E6%8A%BD%E8%B1%A1%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B.table1.png) + *Table 1* diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.17.Python\345\256\236\347\216\260Deque/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.17.Python\345\256\236\347\216\260Deque/README.md" index 4f3bcc9..5a4206f 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.17.Python\345\256\236\347\216\260Deque/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.17.Python\345\256\236\347\216\260Deque/README.md" @@ -29,6 +29,6 @@ class Deque: 在 removeFront 中,我们使用 pop 方法从列表中删除最后一个元素。 但是,在removeRear中,pop(0)方法必须删除列表的第一个元素。同样,我们需要在 addRear 中使用insert方法(第12行),因为 append 方法在列表的末尾添加一个新元素。 -你可以看到许多与栈和队列中描述的 Python 代码相似之处。你也可能观察到,在这个实现中,从前面添加和删除项目是 O(1),而从后面添加和删除是 O(n)。 考虑到添加和删除项目是出现的常见操作,这是可预期的。 同样,重要的是要确定我们知道在实现中前后都分配在哪里。 +你可以看到许多与栈和队列中描述的 Python 代码相似之处。你也可能观察到,在这个实现中,从前面添加和删除项是 O(1),而从后面添加和删除是 O(n)。 考虑到添加和删除项是出现的常见操作,这是可预期的。 同样,重要的是要确定我们知道在实现中前后都分配在哪里。 diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.18.\345\233\236\346\226\207\346\243\200\346\237\245/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.18.\345\233\236\346\226\207\346\243\200\346\237\245/README.md" index befdbab..aa0e4db 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.18.\345\233\236\346\226\207\346\243\200\346\237\245/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.18.\345\233\236\346\226\207\346\243\200\346\237\245/README.md" @@ -1,10 +1,11 @@ ## 3.18.回文检查 -使用 deque 数据结构可以容易地解决经典回文问题。 回文是一个字符串,读取首尾相同的字符,例如,`radar toot madam`。 我们想构造一个算法输入一个字符串,并检查它是否是一个回文。 +使用 deque 数据结构可以容易地解决经典回文问题。回文是一个字符串,读取首尾相同的字符,例如,`radar toot madam`。 我们想构造一个算法输入一个字符串,并检查它是否是一个回文。 该问题的解决方案将使用 deque 来存储字符串的字符。我们从左到右处理字符串,并将每个字符添加到 deque 的尾部。在这一点上,deque 像一个普通的队列。然而,我们现在可以利用 deque 的双重功能。 deque 的首部保存字符串的第一个字符,deque 的尾部保存最后一个字符(见 Figure 2)。 ![3.18.回文检查.figure2](assets/3.18.%E5%9B%9E%E6%96%87%E6%A3%80%E6%9F%A5.figure2.png) + *Figure 2* 我们可以直接删除并比较首尾字符,只有当它们匹配时才继续。如果可以持续匹配首尾字符,我们最终要么用完字符,要么留出大小为 1 的deque,取决于原始字符串的长度是偶数还是奇数。在任一情况下,字符串都是回文。 回文检查的完整功能在 ActiveCode 1 中。 diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.2.\344\273\200\344\271\210\346\230\257\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.2.\344\273\200\344\271\210\346\230\257\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/README.md" index 4f41ac7..b0e3688 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.2.\344\273\200\344\271\210\346\230\257\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.2.\344\273\200\344\271\210\346\230\257\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/README.md" @@ -1,7 +1,7 @@ ## 3.2.什么是线性数据结构 我们从四个简单但重要的概念开始研究数据结构。栈,队列,deques, 列表是一类数据的容器,它们数据项之间的顺序由添加或删除的顺序决定。一旦一个数据项被添加,它相对于前后元素一直保持该位置不变。诸如此类的数据结构被称为线性数据结构。 -线性数据结构有两端,有时被称为左右,某些情况被称为前后。你也可以称为顶部和底部,名字都不重要。将两个线性数据结构区分开的方法是添加和移除项目的方式,特别是添加和移除项目的位置。例如一些结构允许从一段添加项,另一些允许从另一端移除项。 +线性数据结构有两端,有时被称为左右,某些情况被称为前后。你也可以称为顶部和底部,名字都不重要。将两个线性数据结构区分开的方法是添加和移除项的方式,特别是添加和移除项的位置。例如一些结构允许从一端添加项,另一些允许从另一端移除项。 这些变种的形式产生了计算机科学最有用的数据结构。他们出现在各种算法中,并可以用于解决很多重要的问题。 diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250/README.md" index 4d080d9..cd5dc1a 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250/README.md" @@ -1,11 +1,15 @@ ## 3.21.实现无序列表:链表 -为了实现无序列表,我们将构造通常所知的链表。回想一下,我们需要确保我们可以保持项的相对定位。然而,没有要求我们维持在连续存储器中的定位。例如,考虑 Figure 1中所示的项的集合。看来这些值已被随机放置。如果我们可以在每个项中保持一些明确的信息,即下一个项的位置(参见 Figure 2),则每个项的相对位置可以通过简单地从一个项到下一个项的链接来表示。 +为了实现无序列表,我们将构造通常所知的链表。回想一下,我们需要确保我们可以保持项的相对定位。然而,没有要求我们维持在连续存储器中的定位。例如,考虑 Figure 1 中所示的项的集合。看来这些值已被随机放置。如果我们可以在每个项中保持一些明确的信息,即下一个项的位置(参见 Figure 2),则每个项的相对位置可以通过简单地从一个项到下一个项的链接来表示。 ![3.21.实现无序列表:链表.figure1](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure1.png) + + *Figure 1* ![3.21.实现无序列表:链表.figure2](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure2.png) + + *Figure 2* 要注意,必须明确地指定链表的第一项的位置。一旦我们知道第一个项在哪里,第一个项目可以告诉我们第二个是什么,等等。外部引用通常被称为链表的头。类似地,最后一个项需要知道没有下一个项。 @@ -45,9 +49,13 @@ class Node: Python 引用值 None 将在 Node 类和链表本身发挥重要作用。引用 None 代表没有下一个节点。请注意在构造函数中,最初创建的节点 next 被设置为 None。有时这被称为 `接地节点`,因此我们使用标准接地符号表示对 None 的引用。将 None 显式的分配给初始下一个引用值是个好主意。 ![3.21.实现无序列表:链表.figure3](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure3.png) + + *Figure 3* ![3.21.实现无序列表:链表.figure4](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure4.png) + + *Figure 4* ### 3.21.2.Unordered List 类 @@ -71,9 +79,13 @@ class UnorderedList: 创建如 Figure 5 所示的链表。正如我们在 Node 类中讨论的,特殊引用 None 将再次用于表示链表的头部不引用任何内容。最终,先前给出的示例列表如 Figure 6 所示的链接列表表示。链表的头指代列表的第一项的第一节点。反过来,该节点保存对下一个节点(下一个项)的引用,等等。重要的是注意链表类本身不包含任何节点对象。相反,它只包含对链接结构中第一个节点的单个引用。 ![3.21.实现无序列表:链表.figure5](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure5.png) + + *Figure 5* ![3.21.实现无序列表:链表.figure6](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure6.png) + + *Figure 6* Listing 3 中所示的 isEmpty 方法只是检查链表头是否是 None 的引用。 布尔表达式 self.head == None 的结果只有在链表中没有节点时才为真。由于新链表为空,因此构造函数和空检查必须彼此一致。这显示了使用引用 None 来表示链接结构的 `end` 的优点。在 Python 中,None 可以与任何引用进行比较。如果它们都指向相同的对象,则两个引用是相等的。我们将在其他方法中经常使用它。 @@ -84,7 +96,7 @@ def isEmpty(self): ```` *Listing 3* -那么,我们如何将项加入我们的链表?我们需要实现 add 方法。然而,在我们做这一点之前,我们需要解决在链表中哪个位置放置新目的重要问题。由于该链表是无序的,所以新项相对于已经在列表中的其他项目的特定位置并不重要。 新项可以在任何位置。考虑到这一点,将新项放在最简单的位置是有意义的。 +那么,我们如何将项加入我们的链表?我们需要实现 add 方法。然而,在我们做这一点之前,我们需要解决在链表中哪个位置放置新项的重要问题。由于该链表是无序的,所以新项相对于已经在列表中的其他项的特定位置并不重要。 新项可以在任何位置。考虑到这一点,将新项放在最简单的位置是有意义的。 回想一下,链表结构只为我们提供了一个入口点,即链表的头部。所有其他节点只能通过访问第一个节点,然后跟随下一个链接到达。这意味着添加新节点的最简单的地方就在链表的头部。 换句话说,我们将新项作为链表的第一项,现有项将需要链接到这个新项后。 @@ -115,13 +127,17 @@ def add(self,item): *Listing 4* ![3.21.实现无序列表:链表.figure7](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure7.png) + + *Figure 7* ![3.21.实现无序列表:链表.figure8](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure8.png) + + *Figure 8* 我们将实现的下面的方法 - `size`,`search` 和 `remove` - 都基于一种称为链表遍历的技术。遍历是指系统地访问每个节点的过程。为此,我们使用从链表中第一个节点开始的外部引用。当我们访问每个节点时,我们通过“遍历”下一个引用来移动到对下一个节点的引用。 -要实现 `size` 方法,我们需要遍历链表并对节点数计数。Listing 5 展示了用于计算列表中节点数的 Python 代码。外部引用称为 current,并在第二行被初始化到链表的头部。开始的时候,我们没有看到任何节点,所以计数设置为 0 。第 4-6 行实际上实现了遍历。只要当前引用没到链表的结束位置(None),我们通过第 6 行中的赋值语句将当前元素移动到下一个节点。再次,将引用与 None 进行比较的能力是非常有用的。每当 current 移动到一个新的节点,我们加 1 以计数。最后,count 在迭代停止后返回。Figure 9 展示了处理这个链表的过程。 +要实现 `size` 方法,我们需要遍历链表并对节点数计数。Listing 5 展示了用于计算列表中节点数的 Python 代码。外部引用称为 `current`,并在第二行被初始化到链表的头部。开始的时候,我们没有看到任何节点,所以计数设置为 0 。第 4-6 行实际上实现了遍历。只要当前引用没到链表的结束位置(None),我们通过第 6 行中的赋值语句将当前元素移动到下一个节点。再次,将引用与 None 进行比较的能力是非常有用的。每当 `current` 移动到一个新的节点,我们加 1 以计数。最后,`count` 在迭代停止后返回。Figure 9 展示了处理这个链表的过程。 ```` python def size(self): @@ -136,11 +152,13 @@ def size(self): *Listing 5* ![3.21.实现无序列表:链表.figure9](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure9.png) + + *Figure 9* 在链表中搜索也使用遍历技术。当我们访问链表中的每个节点时,我们将询问存储在其中的数据是否与我们正在寻找的项匹配。然而,在这种情况下,我们不必一直遍历到列表的末尾。事实上,如果我们到达链表的末尾,这意味着我们正在寻找的项不存在。此外,如果我们找到项,没有必要继续。 -Listing 6 展示了搜索方法的实现。和在 `size` 方法中一样,遍历从列表的头部开始初始化(行2)。我们还使用一个布尔变量叫 `found`,标记我们是否找到了正在寻找的项。因为我们还没有在遍历开始时找到该项,found 设置为 False(第3行)。第4行中的迭代考虑了上述两个条件。只要有更多的节点访问,而且我们没有找到正在寻找的项,我们就继续检查下一个节点。第5行检查数据项是否存在于当前节点中。如果存在,`found` 设置为 True。 +Listing 6 展示了搜索方法的实现。和在 `size` 方法中一样,遍历从列表的头部开始初始化(行2)。我们还使用一个布尔变量叫 `found`,标记我们是否找到了正在寻找的项。因为我们还没有在遍历开始时找到该项,`found` 设置为 False(第3行)。第4行中的迭代考虑了上述两个条件。只要有更多的节点访问,而且我们没有找到正在寻找的项,我们就继续检查下一个节点。第 5 行检查数据项是否存在于当前节点中。如果存在,`found` 设置为 True。 ```` def search(self,item): @@ -163,10 +181,11 @@ def search(self,item): True ```` -因为 17 在列表中,所以遍历过程需要移动到包含 17 的节点。此时,found 变量设置为 True,while 条件将失败,返回值。 这个过程可以在 Figure 10中看到。 +因为 17 在列表中,所以遍历过程需要移动到包含 17 的节点。此时,`found` 变量设置为 True,while 条件将失败,返回值。 这个过程可以在 Figure 10中看到。 ![3.21.实现无序列表:链表.figure10](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure10.png) + *Figure 10* `remove` 方法需要两个逻辑步骤。首先,我们需要遍历列表寻找我们要删除的项。一旦我们找到该项(我们假设它存在),删除它。第一步非常类似于搜索。从设置到链表头部的外部引用开始,我们遍历链接,直到我们发现正在寻找的项。因为我们假设项存在,我们知道迭代将在 `current` 变为 `None` 之前停止。这意味着我们可以简单地使用 `found` 布尔值。 @@ -175,7 +194,7 @@ True 为了删除包含项的节点,我们需要修改上一个节点中的链接,以便它指向当前之后的节点。不幸的是,链表遍历没法回退。因为 `current` 指我们想要进行改变的节点之前的节点,所以进行修改太迟了。 -这个困境的解决方案是在我们遍历链表时使用两个外部引用。`current` 将像之前一样工作,标记遍历的当前位置。新的引用,我们叫 `previous`,将总是传递 `current`后面的一个节点 。这样,当 `current` 停止在要被去除的节点时,`previoud` 将引用链表中用于修改的位置。 +这个困境的解决方案是在我们遍历链表时使用两个外部引用。`current` 将像之前一样工作,标记遍历的当前位置。新的引用,我们叫 `previous`,将总是传递 `current`后面的一个节点 。这样,当 `current` 停止在要被去除的节点时,`previous` 将引用链表中用于修改的位置。 Listing 7 展示了完整的 `remove` 方法。第 2-3 行给这两个引用赋初始值。注意,`current` 在链表头处开始,和在其他遍历示例中一样。然而,`previous` 假定总是在 `current `之后一个节点。因此,由于在 `previous` 之前没有节点,所以之前的值将为 None(见 Figure 11)。`found` 的布尔变量将再次用于控制迭代。 @@ -201,19 +220,26 @@ def remove(self,item): *Listing 7* ![3.21.实现无序列表:链表.figure11](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure11.png) - *Figure 11* + + +*Figure 11* ![3.21.实现无序列表:链表.figure12](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure12.png) + *Figure 12* 一旦 `remove` 的搜索步骤已经完成,我们需要从链表中删除该节点。 Figure 13 展示了要修改的链接。但是,有一个特殊情况需要解决。 如果要删除的项目恰好是链表中的第一个项,则 `current` 将引用链接列表中的第一个节点。这也意味着 `previous` 是 None。 我们先前说过,`previous` 是一个节点,它的下一个节点需要修改。在这种情况下,不是 `previous` ,而是链表的 `head` 需要改变(见 Figure 14)。 ![3.21.实现无序列表:链表.figure13](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure13.png) + + *Figure 13* ![3.21.实现无序列表:链表.figure14](assets/3.21.%E5%AE%9E%E7%8E%B0%E6%97%A0%E5%BA%8F%E5%88%97%E8%A1%A8%EF%BC%9A%E9%93%BE%E8%A1%A8.figure14.png) + + *Figure 14* -第 12 行检查是否处理上述的特殊情况。如果 `previous` 没有移动,当 `found` 的布尔变为 True 时,它仍是 None。 在这种情况下(行13),链表的 `head` 被修改以指代当前节点之后的节点,实际上是从链表中移除第一节点。 但是,如果 `previous`不为 None,则要删除的节点位于链表结构的下方。 在这种情况下,`previous` 的引用为我们提供了下一个引用更改的节点。第 15 行使用之前的 `setNext` 方法完成删除。注意,在这两种情况下,引用更改的目标是 `current.getNext()`。 经常出现的一个问题是,这里给出的两种情况是否也将处理要移除的项在链表的最后节点中的情况。我们留给你考虑。 +第 12 行检查是否处理上述的特殊情况。如果 `previous` 没有移动,当 `found` 的布尔变为 True 时,它仍是 None。 在这种情况下(行13),链表的 `head` 被修改以指代当前节点之后的节点,实际上是从链表中移除第一节点。 但是,如果 `previous` 不为 None,则要删除的节点位于链表结构的下方。 在这种情况下,`previous` 的引用为我们提供了下一个引用更改的节点。第 15 行使用之前的 `setNext` 方法完成删除。注意,在这两种情况下,引用更改的目标是 `current.getNext()`。 经常出现的一个问题是,这里给出的两种情况是否也将处理要移除的项在链表的最后节点中的情况。我们留给你思考。 diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.22.\346\234\211\345\272\217\345\210\227\350\241\250\346\212\275\350\261\241\346\225\260\346\215\256\347\273\223\346\236\204/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.22.\346\234\211\345\272\217\345\210\227\350\241\250\346\212\275\350\261\241\346\225\260\346\215\256\347\273\223\346\236\204/README.md" index 4317730..d00da76 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.22.\346\234\211\345\272\217\345\210\227\350\241\250\346\212\275\350\261\241\346\225\260\346\215\256\347\273\223\346\236\204/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.22.\346\234\211\345\272\217\345\210\227\350\241\250\346\212\275\350\261\241\346\225\260\346\215\256\347\273\223\346\236\204/README.md" @@ -1,5 +1,5 @@ ## 3.22.有序列表抽象数据结构 -我们现在将考虑一种称为有序列表的列表类型。例如,如果上面所示的整数列表是有序列表(升序),则它可以写为`17,26,31,54,77和93`。由于 17 是最小项,它占据第一位置。同样,由于 93 是最大的,它占据最后的位置。 +我们现在将考虑一种称为有序列表的列表类型。例如,如果上面所示的整数列表是有序列表(升序),则它可以写为 `17,26,31,54,77和93`。由于 17 是最小项,它占据第一位置。同样,由于 93 是最大的,它占据最后的位置。 有序列表的结构是项的集合,其中每个项保存基于项的一些潜在特性的相对位置。排序通常是升序或降序,并且我们假设列表项具有已经定义的有意义的比较运算。许多有序列表操作与无序列表的操作相同。 diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250/README.md" index 50cfd80..2b7b0c5 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250/README.md" @@ -1,6 +1,8 @@ ## 3.23.实现有序列表 为了实现有序列表,我们必须记住项的相对位置是基于一些潜在的特性。上面给出的整数的有序列表`17,26,31,54,77` 和 `93` 可以由 Figure 15 所示的链接结构表示。节点和链接结构表示项的相对位置。 ![3.23.实现有序列表.figure15](assets/3.23.%E5%AE%9E%E7%8E%B0%E6%9C%89%E5%BA%8F%E5%88%97%E8%A1%A8.figure15.png) + + *Figure 15* 为了实现 OrderedList 类,我们将使用与前面看到的无序列表相同的技术。再次,`head` 的引用为 `None` 表示为空链表(参见 Listing 8)。 @@ -18,9 +20,11 @@ class OrderedList: 例如,Figure 16 展示了有序链表搜索值 45 。从链表的头部开始遍历,首先与 `17` 进行比较。由于 `17` 不是我们正在寻找的项,移动到下一个节点 `26` 。再次,这不是我们想要的,继续到 `31`,然后再到 `54` 。在这一点上,有一些不同。由于 `54` 不是我们正在寻找的项,我们以前的方法是继续向前迭代。然而,由于这是有序列表,一旦节点中的值变得大于我们正在搜索的项,搜索就可以停止并返回 False 。该项不可能存在于后面的链表中。 ![3.23.实现有序列表.figure16](assets/3.23.%E5%AE%9E%E7%8E%B0%E6%9C%89%E5%BA%8F%E5%88%97%E8%A1%A8.figure16.png) + + *Figure 16* -Listing 9 展示了完整的搜索方法。通过添加另一个布尔变量,`stop` 并将其初始化为 False(第4行),很容易合并上述新条件。 当 `stop` 是False(不停止)时,我们可以继续在列表中前进(第5行)。如果发现任何节点包含大于我们正在寻找的项的数据,我们将 `stop` 设置为 True(第9-10行)。其余行与无序列表搜索相同。 +Listing 9 展示了完整的搜索方法。通过添加另一个布尔变量 `stop` 并将其初始化为 `False`(第4行),很容易合并上述新条件。 当 `stop` 是`False`(不停止)时,我们可以继续在列表中前进(第5行)。如果发现任何节点包含大于我们正在寻找的项的数据,我们将 `stop` 设置为 `True`(第9-10行)。其余行与无序列表搜索相同。 ```` def search(self,item): @@ -42,9 +46,11 @@ def search(self,item): 最重要的需要修改的方法是 `add`。 回想一下,对于无序列表,`add` 方法可以简单地将新节点放置在链表的头部。 这是最简单的访问点。 不幸的是,这将不再适用于有序列表。需要在现有的有序列表中查找新项所属的特定位置。 -假设我们有由 `17,26,54,77` 和 `93` 组成的有序列表,并且我们要添加值`31` 。 `add` 方法必须确定新项属于 `26`到 `54` 。Figure 17 展示了我们需要的设置。正如我们前面解释的,我们需要遍历链表,寻找添加新节点的地方。我们知道,当我们迭代完节点( `current` 变为 None)或 `current` 节点的值变得大于我们希望添加的项时,我们就找到了该位置。在我们的例子中,看到值 `54` 我们停止迭代。 +假设我们有由 `17,26,54,77` 和 `93` 组成的有序列表,并且我们要添加值`31` 。 `add` 方法必须确定新项属于 `26` 到 `54` 之间。Figure 17 展示了我们需要的设置。正如我们前面解释的,我们需要遍历链表,寻找添加新节点的地方。我们知道,当我们迭代完节点( `current` 变为 None)或 `current` 节点的值变得大于我们希望添加的项时,我们就找到了该位置。在我们的例子中,看到值 `54` 我们停止迭代。 ![3.23.实现有序列表.figure17](assets/3.23.%E5%AE%9E%E7%8E%B0%E6%9C%89%E5%BA%8F%E5%88%97%E8%A1%A8.figure17.png) + + *Figure 17* 正如我们看到的无序列表,有必要有一个额外的引用,再次称为 `previous`,因为 `current` 不会提供对修改的节点的访问。 Listing 10 展示了完整的`add` 方法。 行 2-3 设置两个外部引用,行 9-10 允许 `previous` 每次通过迭代跟随 `current` 节点后面 。 条件(行5)允许迭代继续,只要有更多的节点,并且当前节点中的值不大于该项。 在任一种情况下,当迭代失败时,我们找到了新节点的位置。 @@ -74,7 +80,7 @@ def add(self,item): *Listing 10* ### 3.23.1.链表分析 -为了分析链表操作的复杂性,我们需要考虑它们是否需要遍历。考虑具有 n 个节点的链表。 `isEmpty` 方法是 O(1),因为它需要一个步骤来检查头的引用为 `None`。另一方面,`size` 将总是需要 n 个步骤,因为不从头到尾地移动没法知道有多少节点在链表中。因此,长度为O(n)。将项添加到无序列表始终是O(1),因为我们只是将新节点放置在链表的头部。但是,搜索和删除,以及添加有序列表,都需要遍历过程。虽然平均他们可能只需要遍历节点的一半,这些方法都是O(n),因为在最坏的情况下,都将处理列表中的每个节点。 +为了分析链表操作的复杂性,我们需要考虑它们是否需要遍历。考虑具有 n 个节点的链表。 `isEmpty` 方法是 O(1),因为它需要一个步骤来检查头的引用为 `None`。另一方面,`size` 将总是需要 n 个步骤,因为不从头到尾地移动没法知道有多少节点在链表中。因此,长度为 O(n)。将项添加到无序列表始终是O(1),因为我们只是将新节点放置在链表的头部。但是,搜索和删除,以及添加有序列表,都需要遍历过程。虽然平均他们可能只需要遍历节点的一半,这些方法都是 O(n),因为在最坏的情况下,都将处理列表中的每个节点。 你可能还注意到此实现的性能与早前针对 Python 列表给出的实际性能不同。这表明链表不是 Python 列表的实现方式。 Python 列表的实际实现基于数组的概念。我们在第 8 章中更详细地讨论这个问题。 diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.3.\344\273\200\344\271\210\346\230\257\346\240\210/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.3.\344\273\200\344\271\210\346\230\257\346\240\210/README.md" index 777fb44..7a94314 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.3.\344\273\200\344\271\210\346\230\257\346\240\210/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.3.\344\273\200\344\271\210\346\230\257\346\240\210/README.md" @@ -1,20 +1,26 @@ ## 3.3.什么是栈 -栈(有时称为“后进先出栈”)是一个项的有序集合,其中添加移除新项目总发生在同一端。这一端通常称为“顶部”。与顶部对应的端称为“底部”。 +栈(有时称为“后进先出栈”)是一个项的有序集合,其中添加移除新项总发生在同一端。这一端通常称为“顶部”。与顶部对应的端称为“底部”。 栈的底部很重要,因为在栈中靠近底部的项是存储时间最长的。最近添加的项是最先会被移除的。这种排序原则有时被称为 LIFO,后进先出。它基于在集合内的时间长度做排序。较新的项靠近顶部,较旧的项靠近底部。 栈的例子很常见。几乎所有的自助餐厅都有一堆托盘或盘子,你从顶部拿一个,就会有一个新的托盘给下一个客人。想象桌上有一堆书(Figure 1), 只有顶部的那本书封面可见,要看到其他书的封面,只有先移除他们上面的书。Figure 2 展示了另一个栈,包含了很多 Python 对象。 ![3.3.什么是栈](assets/3.3.%E4%BB%80%E4%B9%88%E6%98%AF%E6%A0%88.png) + + *Figure 1* ![3.3.什么是栈.primitive](assets/3.3.%E4%BB%80%E4%B9%88%E6%98%AF%E6%A0%88.primitive.png) + + *Figure 2* 和栈相关的最有用的想法之一来自对它的观察。假设从一个干净的桌面开始,现在把书一本本叠起来,你在构造一个栈。考虑下移除一本书会发生什么。移除的顺序跟刚刚被放置的顺序相反。栈之所以重要是因为它能反转项的顺序。插入跟删除顺序相反,Figure 3 展示了 Python 数据对象创建和删除的过程,注意观察他们的顺序。 ![3.3.什么是栈.simplereversa](assets/3.3.%E4%BB%80%E4%B9%88%E6%98%AF%E6%A0%88.simplereversal.png) + + *Figure 3* 想想这种反转的属性,你可以想到使用计算机的时候所碰到的例子。例如,每个 web 浏览器都有一个返回按钮。当你浏览网页时,这些网页被放置在一个栈中(实际是网页的网址)。你现在查看的网页在顶部,你第一个查看的网页在底部。如果按‘返回’按钮,将按相反的顺序浏览刚才的页面。 diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.4.\346\240\210\347\232\204\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.4.\346\240\210\347\232\204\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" index d2d3c20..ea07689 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.4.\346\240\210\347\232\204\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.4.\346\240\210\347\232\204\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" @@ -12,6 +12,7 @@ 例如,s 是已经创建的空栈,Table1 展示了栈操作序列的结果。栈中,顶部项列在最右边。 ![3.4.栈的抽象数据类型.table1](assets/3.4.%E6%A0%88%E7%9A%84%E6%8A%BD%E8%B1%A1%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B.table1.png) + *Table1* diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.5.Python\345\256\236\347\216\260\346\240\210/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.5.Python\345\256\236\347\216\260\346\240\210/README.md" index 630c0d1..be321ec 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.5.Python\345\256\236\347\216\260\346\240\210/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.5.Python\345\256\236\347\216\260\346\240\210/README.md" @@ -30,11 +30,11 @@ class Stack: ```` *ActiveCode 1* -记住我们只定义类的实现,我们需要创建一个栈,然后使用它。ActiveCode 2 展示了我们通过实例化 Stack 类执行Table 1中的操作。注意,Stack 类的定义是从 pythonds 模块导入的。 +记住我们只定义类的实现,我们需要创建一个栈,然后使用它。ActiveCode 2 展示了我们通过实例化 Stack 类执行 Table 1中的操作。注意,Stack 类的定义是从 pythonds 模块导入的。 > **Note** -pythonds 模块包含本书中讨论的所有数据结构的实现。它根据以下部分构造:基本数据类型,树和图形。 该模块可以从 [pythonworks.org](http://www.pythonworks.org/pythonds)下载。 +pythonds 模块包含本书中讨论的所有数据结构的实现。它根据以下部分构造:基本数据类型,树和图。 该模块可以从 [pythonworks.org](http://www.pythonworks.org/pythonds)下载。 ```` from pythonds.basic.stack import Stack diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.6.\347\256\200\345\215\225\346\213\254\345\217\267\345\214\271\351\205\215/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.6.\347\256\200\345\215\225\346\213\254\345\217\267\345\214\271\351\205\215/README.md" index d71d986..8a8dbae 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.6.\347\256\200\345\215\225\346\213\254\345\217\267\345\214\271\351\205\215/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.6.\347\256\200\345\215\225\346\213\254\345\217\267\345\214\271\351\205\215/README.md" @@ -1,5 +1,5 @@ ## 3.6.简单括号匹配 -我们现在把注意力转向使用栈来解决真正的计算机问题。你会这么写算术表达式 +我们现在把注意力转向使用栈解决真正的计算机问题。你会这么写算术表达式 `(5+6)∗(7+8)/(4+3)` @@ -33,9 +33,11 @@ 区分括号是否匹配的能力是识别很多编程语言结构的重要部分。具有挑战的是如何编写一个算法,能够从左到右读取一串符号,并决定符号是否平衡。为了解决这个问题,我们需要做一个重要的观察。从左到右处理符号时,最近开始符号必须与下一个关闭符号相匹配(见 Figure 4)。此外,处理的第一个开始符号必须等待直到其匹配最后一个符号。结束符号以相反的顺序匹配开始符号。他们从内到外匹配。这是一个可以用栈解决问题的线索。 ![3.6.简单括号匹配.simpleparcheck](assets/3.6.%E7%AE%80%E5%8D%95%E6%8B%AC%E5%8F%B7%E5%8C%B9%E9%85%8D.simpleparcheck.png) + + *Figure 4* -一旦你认为栈是保存括号的恰当的数据结构,算法是很直接的。从空栈开始,从左到右处理括号字符串。如果一个符号是一个开始符号,将其作为一个信号,对应的结束符号稍后会出现。另一方面,如果符号是结束符号,弹出栈,只要可以弹出栈的开始符号可以匹配每个结束符号,则括号保持匹配状态。如果任何时候栈上没有出现符合结束符号的开始符号,则字符串不匹配。最后,当所有符号都被处理后,栈应该是空的。实现此算法的 Python 代码见 ActiveCode 1。 +一旦你认为栈是保存括号的恰当的数据结构,算法是很直接的。从空栈开始,从左到右处理括号字符串。如果一个符号是一个开始符号,将其作为一个信号,对应的结束符号稍后会出现。另一方面,如果符号是结束符号,弹出栈,只要弹出栈的开始符号可以匹配每个结束符号,则括号保持匹配状态。如果任何时候栈上没有出现符合开始符号的结束符号,则字符串不匹配。最后,当所有符号都被处理后,栈应该是空的。实现此算法的 Python 代码见 ActiveCode 1。 ```` from pythonds.basic.stack import Stack diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266/README.md" index e747c3b..3a5f75d 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266/README.md" @@ -2,7 +2,7 @@ 在你学习计算机的过程中,你可能已经接触了二进制。二进制在计算机科学中是很重要的,因为存储在计算机内的所有值都是以 0 和 1 存储的。如果没有能力在二进制数和普通字符串之间转换,我们与计算机之间的交互非常棘手。 -整数值是常见的数据项。他们一直用于计算机程序和计算。我们在数学类中学习它们,当然最后用十进制或者基数 10 来表示它们。十进制 233^10 以及对应的二进制表示 11101001^2 分别解释为 +整数值是常见的数据项。他们一直用于计算机程序和计算。我们在数学课上学习它们,当然最后用十进制或者基数 10 来表示它们。十进制 233^10 以及对应的二进制表示 11101001^2 分别解释为 ![3.8.十进制转换成二进制.2](assets/3.8.%E5%8D%81%E8%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2%E6%88%90%E4%BA%8C%E8%BF%9B%E5%88%B6.2.png) @@ -13,6 +13,8 @@ “除 2” 算法假定我们从大于 0 的整数开始。不断迭代的将十进制除以 2,并跟踪余数。第一个除以 2 的余数说明了这个值是偶数还是奇数。偶数有 0 的余数,记为 0。奇数有余数 1,记为 1.我们将得到的二进制构建为数字序列,第一个余数实际上是序列中的最后一个数字。见 Figure 5 , 我们再次看到了反转的属性,表示栈可能是解决这个问题的数据结构。 ![3.8.十进制转换成二进制.figure5](assets/3.8.%E5%8D%81%E8%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2%E6%88%90%E4%BA%8C%E8%BF%9B%E5%88%B6.figure5.png) + + *Figure 5* Activecode 1 中的 Python 代码实现了 “除 2” 算法,函数 divideBy2 传入了一个十进制的参数,并重复除以 2。第 7 行使用内置的模运算符 % 来提取余数,第 8 行将余数压到栈上。当除到 0 后,11-13 行构造了一个二进制字符串。 @@ -39,9 +41,9 @@ print(divideBy2(42)) ```` *ActiveCode 1* -这个用于二进制转换的算法可以很容易的扩展以执行任何基数的转换。在计算可科学中,通常会使用很多不同的编码。其中最常见的是二级制,八进制和十六进制。 +这个用于二进制转换的算法可以很容易的扩展以执行任何基数的转换。在计算机科学中,通常会使用很多不同的编码。其中最常见的是二级制,八进制和十六进制。 -十进制 233 和它对应的八进制和十六进制 352^8, E9^16 +十进制 233 和它对应的八进制和十六进制 351^8, E9^16 ![3.8.十进制转换成二进制.3](assets/3.8.%E5%8D%81%E8%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2%E6%88%90%E4%BA%8C%E8%BF%9B%E5%88%B6.3.png) ![3.8.十进制转换成二进制.4](assets/3.8.%E5%8D%81%E8%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2%E6%88%90%E4%BA%8C%E8%BF%9B%E5%88%B6.4.png) diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/README.md" similarity index 99% rename from "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/README.md" rename to "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/README.md" index 4a84d2e..1661a86 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/README.md" @@ -18,17 +18,24 @@ 在后缀中,表达式将是 `A B C * +`,再次,操作的顺序被保留,因为 `*` 紧接在 B 和 C 之后出现,表示 `*` 具有高优先级,`+` 优先级低。虽然操作符在它们各自的操作数前后移动,但是顺序相对于彼此保持完全相同。 ![3.9.中缀后缀和后缀表达式.table2](assets/3.9.%E4%B8%AD%E7%BC%80%E5%90%8E%E7%BC%80%E5%92%8C%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F.table2.png) + + *Table 2* + 现在考虑中缀表达式 `(A + B) * C`,回想下,在这种情况下,中缀需要括号在乘法之前强制执行加法。然而,当 A+B 写到前缀中时,加法运算符简单的移动到操作数 `+ A B` 之前。这个操作的结果成为乘法的第一个操作数。乘法运算符移动到整个表达式的前面,得出 `* + A B C`,同样,在后缀 `A B +`中,强制先加法。可以直接对该结果和剩余的操作数 C 相乘。然后,得出后缀表达式为 `A B + C *`。 再次考虑这三个表达式(见 Table 3),括号不见了。为什么在前缀和后缀的时候不需要括号了呢?答案是操作符对于他们的操作数不再模糊,只有中缀才需要括号,前缀和后缀表达式的操作顺序完全由操作符的顺序决定。 ![3.9.中缀后缀和后缀表达式.table3](assets/3.9.%E4%B8%AD%E7%BC%80%E5%90%8E%E7%BC%80%E5%92%8C%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F.table3.png) + + *Table 3* Table 4 展示了一些其他的例子 ![3.9.中缀后缀和后缀表达式.table4](assets/3.9.%E4%B8%AD%E7%BC%80%E5%90%8E%E7%BC%80%E5%92%8C%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F.table4.png) + + *Table 4* ### 3.9.1.中缀表达式转换前缀和后缀 @@ -39,10 +46,14 @@ Table 4 展示了一些其他的例子 看上面的子表达式`(B * C)`中的右括号。 如果我们将乘法符号移动到那个位置,并删除匹配的左括号,得到 `B C * `,我们实际上已经将子表达式转换为后缀符号。 如果加法运算符也被移动到其相应的右括号位置并且匹配的左括号被去除,则将得到完整的后缀表达式(见 Figure 6)。 ![3.9.中缀后缀和后缀表达式.figure6](assets/3.9.%E4%B8%AD%E7%BC%80%E5%90%8E%E7%BC%80%E5%92%8C%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F.figure6.png) + + *Figure 6* 如果我们不是将符号移动到右括号的位置,我们将它向左移动,我们得到前缀符号(见 Figure 7)。圆括号对的位置实际上是包含的运算符的最终位置的线索。 ![3.9.中缀后缀和后缀表达式.figure7](assets/3.9.%E4%B8%AD%E7%BC%80%E5%90%8E%E7%BC%80%E5%92%8C%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F.figure7.png) + + *Figure 7* 所以为了转换表达式,无论是对前缀还是后缀符号,先根据操作的顺序把表达式转换成完全括号表达式。然后将包含的运算符移动到左或右括号的位置,具体取决于需要前缀或后缀符号。 @@ -51,6 +62,7 @@ Table 4 展示了一些其他的例子 ![3.9.中缀后缀和后缀表达式.figure8](assets/3.9.%E4%B8%AD%E7%BC%80%E5%90%8E%E7%BC%80%E5%92%8C%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F.figure8.png) + *Figure 8* ### 3.9.2.中缀转后缀通用法 @@ -58,7 +70,7 @@ Table 4 展示了一些其他的例子 再次考虑表达式 `A + B * C`。如上所示,`A B C * +`是等价的后缀表达式。 我们已经注意到,操作数 A,B 和 C 保持在它们的相对位置。只有操作符改变位置。再看中缀表达式中的运算符。从左到右出现的第一个运算符为 +。 然而,在后缀表达式中,+ 在结束位置,因为下一个运算符 * 的优先级高于加法。 原始表达式中的运算符的顺序在生成的后缀表达式中相反。 -当我们处理表达式时,操作符必须保存在某处,因为它们相应的右操作数还没有看到。 此外,这些保存的操作符的顺序可能由于它们的优先级而需要反转。这是在该示例中的加法和乘法的情况,由于加法运算符在乘法运算符之前,并且具有较低的优先级,因此需要在使用乘法运算符之后出现。 由于这种顺序的反转,考虑使用栈来保持运算符直到用到它们是有意义的。 +当我们处理表达式时,操作符必须保存在某处,因为它们相应的右操作数还没有看到。 此外,这些保存的操作符的顺序可能由于它们的优先级而需要反转。这是在该示例中的加法和乘法的情况,由于加法运算符在乘法运算符之前,并且具有较低的优先级,因此需要在使用乘法运算符之后出现。 由于这种顺序的反转,考虑使用栈来保存运算符直到用到它们是有意义的。 `(A + B)* C`的情况会是什么样呢? 回想一下,`A B + C *`是等价的后缀表达式。从左到右处理此中缀表达式,我们先看到 `+`。 在这种情况下,当我们看到 `*`,` + `已经放置在结果表达式中,由于括号它的优先级高于`*`。 我们现在可以开始看看转换算法如何工作。当我们看到左括号时,我们保存它,表示高优先级的另一个运算符将出现。该操作符需要等到相应的右括号出现以表示其位置(回忆完全括号的算法)。 当右括号出现时,可以从栈中弹出操作符。 @@ -79,6 +91,7 @@ Figure 9 展示了对表达式 `A * B + C * D` 的转换算法。注意,第一 ![3.9.中缀后缀和后缀表达式.figure9](assets/3.9.%E4%B8%AD%E7%BC%80%E5%90%8E%E7%BC%80%E5%92%8C%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F.figure9.png) + *Figure 9* 为了在 Python 中编写算法,我们使用一个名为 prec 的字典来保存操作符的优先级。这个字典将每个运算符映射到一个整数,可以与其他运算符的优先级(我们使用整数3,2和1)进行比较。左括号将赋予最低的值。这样,与其进行比较的任何运算符将具有更高的优先级,将被放置在它的顶部。第15行将操作数定义为任何大写字符或数字。完整的转换函数见 ActiveCode 1。 @@ -145,12 +158,14 @@ print(infixToPostfix("( A + B ) * C - ( D - E ) * ( F + G )")) 我们现在可以通过将其放回栈中来处理此结果,以便它可以表示为表达式后面的运算符的操作数。当处理最后一个操作符时,栈上只有一个值,弹出并返回它作为表达式的结果。Figure 10 展示了整个示例表达式的栈的内容。 ![3.9.中缀后缀和后缀表达式.figure10](assets/3.9.%E4%B8%AD%E7%BC%80%E5%90%8E%E7%BC%80%E5%92%8C%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F.figure10.png) + *Figure 10* Figure 11 是个稍微复杂的示例,`7 8 + 3 2 + /` 。在这个例子中有两点需要注意,首先,栈的大小增长收缩,然后再子表达式求值的时候再次增长。第二,除法操作需要自信处理。回想下,后缀表达式的操作符顺序没变,仅仅改变操作符的位置。当用于除法的操作符从栈中弹出时,它们被反转。由于除法不是交换运算符,换句话说 `15/5`和 `5/15` 不同,因此我们必须保证操作数的顺序不会交换。 ![3.9.中缀后缀和后缀表达式.figure11](assets/3.9.%E4%B8%AD%E7%BC%80%E5%90%8E%E7%BC%80%E5%92%8C%E5%90%8E%E7%BC%80%E8%A1%A8%E8%BE%BE%E5%BC%8F.figure11.png) + *Figure 11* 假设后缀表达式是一个由空格分隔的标记字符串。 运算符为`*,/,+`和 `-` ,操作数假定为单个整数值。 输出将是一个整数结果。 diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure10.png" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure10.png" similarity index 100% rename from "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure10.png" rename to "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure10.png" diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure11.png" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure11.png" similarity index 100% rename from "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure11.png" rename to "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure11.png" diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure6.png" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure6.png" similarity index 100% rename from "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure6.png" rename to "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure6.png" diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure7.png" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure7.png" similarity index 100% rename from "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure7.png" rename to "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure7.png" diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure8.png" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure8.png" similarity index 100% rename from "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure8.png" rename to "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure8.png" diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure9.png" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure9.png" similarity index 100% rename from "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure9.png" rename to "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure9.png" diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table2.png" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table2.png" similarity index 100% rename from "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table2.png" rename to "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table2.png" diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table3-1.png" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table3-1.png" similarity index 100% rename from "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table3-1.png" rename to "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table3-1.png" diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table3.png" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table3.png" similarity index 100% rename from "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table3.png" rename to "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table3.png" diff --git "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table4.png" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table4.png" similarity index 100% rename from "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table4.png" rename to "3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.9.\344\270\255\347\274\200\345\211\215\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table4.png" diff --git "a/4.\351\200\222\345\275\222/4.10.\346\262\263\345\206\205\345\241\224\346\270\270\346\210\217/README.md" "b/4.\351\200\222\345\275\222/4.10.\346\261\211\350\257\272\345\241\224\346\270\270\346\210\217/README.md" similarity index 85% rename from "4.\351\200\222\345\275\222/4.10.\346\262\263\345\206\205\345\241\224\346\270\270\346\210\217/README.md" rename to "4.\351\200\222\345\275\222/4.10.\346\261\211\350\257\272\345\241\224\346\270\270\346\210\217/README.md" index 388d592..579928a 100644 --- "a/4.\351\200\222\345\275\222/4.10.\346\262\263\345\206\205\345\241\224\346\270\270\346\210\217/README.md" +++ "b/4.\351\200\222\345\275\222/4.10.\346\261\211\350\257\272\345\241\224\346\270\270\346\210\217/README.md" @@ -1,23 +1,25 @@ -## 4.10.河内塔游戏 +## 4.10.汉诺塔游戏 -河内塔是由法国数学家爱德华·卢卡斯在 1883 年发明的。他的灵感来自一个传说,有一个印度教寺庙,将谜题交给年轻的牧师。在开始的时候,牧师们被给予三根杆和一堆 64 个金碟,每个盘比它下面一个小一点。他们的任务是将所有 64 个盘子从三个杆中一个转移到另一个,有两个重要的约束。它们一次只能移动一个盘子,并且它们不能在较小的盘子顶部上放置更大的盘子。牧师日夜不停每秒钟移动一块盘子。当他们完成工作时,传说,寺庙会变成灰尘,世界将消失。 +汉诺塔是由法国数学家爱德华·卢卡斯在 1883 年发明的。他的灵感来自一个传说,有一个印度教寺庙,将谜题交给年轻的牧师。在开始的时候,牧师们被给予三根杆和一堆 64 个金碟,每个盘比它下面一个小一点。他们的任务是将所有 64 个盘子从三个杆中一个转移到另一个。有两个重要的约束,它们一次只能移动一个盘子,并且它们不能在较小的盘子顶部上放置更大的盘子。牧师日夜不停每秒钟移动一块盘子。当他们完成工作时,传说,寺庙会变成灰尘,世界将消失。 虽然传说是有趣的,你不必担心世界不久的将来会消失。移动 64 个盘子的塔所需的步骤数是 `2^64 -1 = 18,446,744,073,709,551,615264-1 = 18,446,744,073,709,551,615`。以每秒一次的速度,即`584,942,417,355584,942,417,355` 年!。 -Figure 1 展示了在从第一杆移动到第三杆的过程中的盘的示例。请注意,如规则指定,每个杆上的盘子都被堆叠起来,以使较小的盘子始终位于较大磁盘的顶部。如果你以前没有尝试过解决这个难题,你现在应该尝试下。你不需要花哨的盘子,一堆书或纸张都可以。 +Figure 1 展示了在从第一杆移动到第三杆的过程中的盘的示例。请注意,如规则指定,每个杆上的盘子都被堆叠起来,以使较小的盘子始终位于较大盘的顶部。如果你以前没有尝试过解决这个难题,你现在应该尝试下。你不需要花哨的盘子,一堆书或纸张都可以。 + +![4.10.汉诺塔游戏.figure1](assets/4.10.%E6%B1%89%E8%AF%BA%E5%A1%94%E6%B8%B8%E6%88%8F.figure1.png) + -![4.10.河内塔游戏.figure1](assets/4.10.%E6%B2%B3%E5%86%85%E5%A1%94%E6%B8%B8%E6%88%8F.figure1.png) *Figure 1* 我们如何递归地解决这个问题?我们的基本情况是什么?让我们从下到上考虑这个问题。假设你有一个五个盘子的塔,在杆一上。如果你已经知道如何将四个盘子移动到杆二上,那么你可以轻松地将最底部的盘子移动到杆三,然后再将四个盘子从杆二移动到杆三。但是如果你不知道如何移动四个盘子怎么办?假设你知道如何移动三个盘子到杆三;那么很容易将第四个盘子移动到杆二,并将它们从杆三移动到它们的顶部。但是如果你不知道如何移动三个盘子呢?如何将两个盘子移动到杆二,然后将第三个盘子移动到杆三,然后移动两个盘子到它的顶部?但是如果你还不知道该怎么办呢?当然你会知道移动一个盘子到杆三足够容易。这听起来像是基本情况。 -这里是如何使用中间杆将塔从起始杆移动到目标杆的概要: +这里是如何使用中间杆将塔从起始杆移动到目标杆的步骤: 1. 使用目标杆将 `height-1` 的塔移动到中间杆。 2. 将剩余的盘子移动到目标杆。 3. 使用起始杆将 `height-1` 的塔从中间杆移动到目标杆。 -只要我们遵守规则,较大的盘子保留在栈的底部,我们可以使用递归的三个步骤,处理任何更大的盘子。上面概要中唯一缺失的是识别基本情况。最简单的河内塔是一个盘子的塔。在这种情况下,我们只需要将一个盘子移动到其最终目的地。 一个盘子的塔将是我们的基本情况。 此外,上述步骤通过在步骤1和3中减小塔的高度,使我们趋向基本情况。Listing 1 展示了解决河内塔的 Python 代码。 +只要我们遵守规则,较大的盘子保留在栈的底部,我们可以使用递归的三个步骤,处理任何更大的盘子。上面概要中唯一缺失的是识别基本情况。最简单的汉诺塔是一个盘子的塔。在这种情况下,我们只需要将一个盘子移动到其最终目的地。 一个盘子的塔将是我们的基本情况。 此外,上述步骤通过在步骤1和3中减小塔的高度,使我们趋向基本情况。Listing 1 展示了解决汉诺塔的 Python 代码。 ```` def moveTower(height,fromPole, toPole, withPole): @@ -28,7 +30,7 @@ def moveTower(height,fromPole, toPole, withPole): ```` *Listing 1* -请注意,Listing 1 中的代码与描述几乎相同。算法的简单性的关键在于我们进行两个不同的递归调用,一个在第 3 行上,另一个在第 5 行。在第 3 行上,我们将初始塔上的底部圆盘移动到中间。下一行简单地将底部盘移动到其最终的位置。然后在第 5 行上,我们将塔从中间杆移动到最大盘子的顶部。当塔高度为 0 时检测到基本情况; 在这种情况下不需要做什么,所以 `moveTower` 函数简单地返回。关于以这种方式处理基本情况的重点是,从 `moveTower` 简单地返回以使 `moveDisk` 函数被调用。 +请注意,Listing 1 中的代码与描述几乎相同。算法的简单性的关键在于我们进行两个不同的递归调用,一个在第 3 行上,另一个在第 5 行。在第 3 行上,我们将初始杆上的底部圆盘移动到中间。下一行简单地将底部盘移动到其最终的位置。然后在第 5 行上,我们将塔从中间杆移动到最大盘子的顶部。当塔高度为 0 时检测到基本情况; 在这种情况下不需要做什么,所以 `moveTower` 函数简单地返回。关于以这种方式处理基本情况的重点是,从 `moveTower` 简单地返回以使 `moveDisk` 函数被调用。 函数 `moveDisk`,如 Listing 2 所示,非常简单。它所做的就是打印出一个盘子从一杆移动到另一杆。 如果你输入并运行 `moveTower` 程序,你可以看到它给你一个非常有效的解决方案。 @@ -38,7 +40,7 @@ def moveDisk(fp,tp): ```` *Listing 2* -现在你已经看到了 `moveTower` 和 `moveDisk` 的代码,你可能会想知道为什么我们没有明确记录什么盘子在什么杆上的数据结构。这里有一个提示:如果你要明确地跟踪盘子,你会使用三个 Stack 对象,每个杆一个。 答案是 Python 提供了我们需要调用隐含的栈。 +现在你已经看到了 `moveTower` 和 `moveDisk` 的代码,你可能会想知道为什么我们没有明确记录什么盘子在什么杆上的数据结构。这里有一个提示:如果你要明确地跟踪盘子,你会使用三个 Stack 对象,每个杆一个。 答案是 Python 提供了我们需要调用的隐含的栈。 diff --git "a/4.\351\200\222\345\275\222/4.10.\346\262\263\345\206\205\345\241\224\346\270\270\346\210\217/assets/4.10.\346\262\263\345\206\205\345\241\224\346\270\270\346\210\217.figure1.png" "b/4.\351\200\222\345\275\222/4.10.\346\261\211\350\257\272\345\241\224\346\270\270\346\210\217/assets/4.10.\346\261\211\350\257\272\345\241\224\346\270\270\346\210\217.figure1.png" similarity index 100% rename from "4.\351\200\222\345\275\222/4.10.\346\262\263\345\206\205\345\241\224\346\270\270\346\210\217/assets/4.10.\346\262\263\345\206\205\345\241\224\346\270\270\346\210\217.figure1.png" rename to "4.\351\200\222\345\275\222/4.10.\346\261\211\350\257\272\345\241\224\346\270\270\346\210\217/assets/4.10.\346\261\211\350\257\272\345\241\224\346\270\270\346\210\217.figure1.png" diff --git "a/4.\351\200\222\345\275\222/4.11.\346\216\242\347\264\242\350\277\267\345\256\253/README.md" "b/4.\351\200\222\345\275\222/4.11.\346\216\242\347\264\242\350\277\267\345\256\253/README.md" index b9558fb..577bbb7 100644 --- "a/4.\351\200\222\345\275\222/4.11.\346\216\242\347\264\242\350\277\267\345\256\253/README.md" +++ "b/4.\351\200\222\345\275\222/4.11.\346\216\242\347\264\242\350\277\267\345\256\253/README.md" @@ -4,17 +4,19 @@ ![4.11.探索迷宫.figure2](assets/4.11.%E6%8E%A2%E7%B4%A2%E8%BF%B7%E5%AE%AB.figure2.png) + + *Figure 2* 为了使问题容易些,我们假设我们的迷宫被分成“正方形”。迷宫的每个正方形是开放的或被一段墙壁占据。乌龟只能通过迷宫的空心方块。 如果乌龟撞到墙上,它必须尝试不同的方向。乌龟将需要一个程序,以找到迷宫的出路。这里是过程: 1. 从我们的起始位置,我们将首先尝试向北一格,然后从那里递归地尝试我们的程序。 2. 如果我们通过尝试向北作为第一步没有成功,我们将向南一格,并递归地重复我们的程序。 -3. 如果向南也不行,那么我们将尝试向西一格,并递归地应用我们的程序。 -4. 如果北,南和西都没有成功,则应用程序从一个位置递归向东。 +3. 如果向南也不行,那么我们将尝试向西一格,并递归地重复我们的程序。 +4. 如果北,南和西都没有成功,则应用程序从当前位置递归向东。 5. 如果这些方向都没有成功,那么没有办法离开迷宫,我们失败。 -现在,这听起来很容易,但有几个细节先谈谈。假设我们第一步是向北走。按照我们的程序,我们的下一步也将是向北。但如果北面被一堵墙阻挡,我们必须看看程序的下一步,并试着向南。不幸的是,向南使我们回到我们原来的起点。如果我们应用从那里的递归过程,我们将又回到向北一格,并陷入无限循环。所以,我们必须有一个策略来记住我们去过哪。在这种情况下,我们假设有一袋面包屑可以撒在我们走过的路上。如果我们沿某个方向迈出一步,发现那个位置上已经有面包屑,我们应该立即后退并尝试我们的程序中的下一个方向。我们看看这个算法的代码,就像从递归函数调用返回一样简单。 +现在,这听起来很容易,但有几个细节先谈谈。假设我们第一步是向北走。按照我们的程序,我们的下一步也将是向北。但如果北面被一堵墙阻挡,我们必须看看程序的下一步,并试着向南。不幸的是,向南使我们回到原来的起点。如果我们从那里再次应用递归过程,我们将又回到向北一格,并陷入无限循环。所以,我们必须有一个策略来记住我们去过哪。在这种情况下,我们假设有一袋面包屑可以撒在我们走过的路上。如果我们沿某个方向迈出一步,发现那个位置上已经有面包屑,我们应该立即后退并尝试程序中的下一个方向。我们看看这个算法的代码,就像从递归函数调用返回一样简单。 正如我们对所有递归算法所做的一样,让我们回顾一下基本情况。其中一些你可能已经根据前一段的描述猜到了。在这种算法中,有四种基本情况要考虑: @@ -64,11 +66,11 @@ def searchFrom(maze, startRow, startColumn): ```` *Listing 3* -你会看到代码的第一行(行 2)调用 `updatePosition`。这只是为了可视化算法,以便你可以看到龟如何探索通过迷宫。接下来算法检查四种基本情况中的前三种:龟是否碰到墙(行 5)?乌龟是否回到已经探索过的格子(行 8)?乌龟又没有到达出口(行 11)?如果这些条件都不为真,则我们继续递归搜索。 +你会看到代码的第一行(行 2)调用 `updatePosition`。这只是为了可视化算法,以便你可以看到乌龟如何探索通过迷宫。接下来算法检查四种基本情况中的前三种:乌龟是否碰到墙(行 5)?乌龟是否回到已经探索过的格子(行 8)?乌龟有没有到达出口(行 11)?如果这些条件都不为真,则我们继续递归搜索。 -你会注意到,在递归步骤中有四个对 `searchFrom` 的递归调用。很难预测将使用多少个递归调用,因为它们都由 `or` 语句连接。如果对 `searchFrom` 的第一次调用返回 `True` ,则不需要最后三个调用。你可以理解这一步到 `(row-1,column)`(或北,如果你从地理位置上思考)是在迷宫的路径上。如果没有一个好的路径向北,那么尝试下一个向南的递归调用。如果向南失败,然后尝试向西,最后向东。如果所有四个递归调用返回 `false`,那么认为是一个死胡同。你应该下载或输入整个程序,并通过更改这些调用的顺序进行实验。 +你会注意到,在递归步骤中有四个对 `searchFrom` 的递归调用。很难预测将有多少个递归调用,因为它们都由 `or` 语句连接。如果对 `searchFrom` 的第一次调用返回 `True` ,则不需要最后三个调用。你可以理解这一步向 `(row-1,column)`(或北,如果你从地理位置上思考)是在迷宫的路径上。如果没有一个好的路径向北,那么尝试下一个向南的递归调用。如果向南失败,然后尝试向西,最后向东。如果所有四个递归调用返回 `False`,那么认为是一个死胡同。你应该下载或输入整个程序,并通过更改这些调用的顺序进行实验。 -`Maze` 类的代码如 Listing 4,Listing 5和 Listing 6 所示。`__init__` 方法将文件的名称作为其唯一参数。此文件是一个文本文件,通过使用 `+` 字符表示墙壁,空格表示空心方块,并使用字母 `S` 表示起始位置。Figure 3 是迷宫数据文件的示例。迷宫的内部表示是列表的列表。 `mazelist` 实例变量的每一行也是一个列表。此辅助列表使用上述字符每格表示一个字符。Figure 3 中的数据文件,内部表示如下所示: +`Maze` 类的代码如 Listing 4,Listing 5 和 Listing 6 所示。`__init__` 方法将文件的名称作为其唯一参数。此文件是一个文本文件,通过使用 `+` 字符表示墙壁,空格表示空心方块,并使用字母 `S` 表示起始位置。Figure 3 是迷宫数据文件的示例。迷宫的内部表示是列表的列表。 `mazelist` 实例变量的每一行也是一个列表。此辅助列表使用上述字符,每格表示一个字符。Figure 3 中的数据文件,内部表示如下所示: ```` [ ['+','+','+','+',...,'+','+','+','+','+','+','+'], diff --git "a/4.\351\200\222\345\275\222/4.12.\345\212\250\346\200\201\350\247\204\345\210\222/README.md" "b/4.\351\200\222\345\275\222/4.12.\345\212\250\346\200\201\350\247\204\345\210\222/README.md" index 37a1dab..3cfb21b 100644 --- "a/4.\351\200\222\345\275\222/4.12.\345\212\250\346\200\201\350\247\204\345\210\222/README.md" +++ "b/4.\351\200\222\345\275\222/4.12.\345\212\250\346\200\201\350\247\204\345\210\222/README.md" @@ -2,13 +2,13 @@ 计算机科学中的许多程序是为了优化一些值而编写的; 例如,找到两个点之间的最短路径,找到最适合一组点的线,或找到满足某些标准的最小对象集。计算机科学家使用许多策略来解决这些问题。本书的目标之一是向你展示几种不同的解决问题的策略。`动态规划` 是这些类型的优化问题的一个策略。 -优化问题的典型例子包括使用最少的硬币找零。假设你是一个自动售货机制造商的程序员。你的公司希望通过给每个交易的最少硬币来简化工作。假设客户放入 1 美元的钞票并购买 37 美分的商品。你可以用来找零的最小数量的硬币是多少?答案是六个硬币:两个 25 美分,一个 10美分 和 三便士。我们如何得到六个硬币的答案?我们从最大的硬币(25 美分)开始,并尽可能多,然后我们去找下一个小点的硬币,并使用尽可能多的那些。这第一种方法被称为贪婪方法,因为我们试图尽快解决尽可能大的问题。 +优化问题的典型例子包括使用最少的硬币找零。假设你是一个自动售货机制造商的程序员。你的公司希望通过给每个交易最少硬币来简化工作。假设客户放入 1 美元的钞票并购买 37 美分的商品。你可以用来找零的最小数量的硬币是多少?答案是六个硬币:两个 25 美分,一个 10美分 和 三个一美分。我们如何得到六个硬币的答案?我们从最大的硬币(25 美分)开始,并尽可能多,然后我们去找下一个小点的硬币,并尽可能多的使用它们。这第一种方法被称为贪婪方法,因为我们试图尽快解决尽可能大的问题。 当我们使用美国货币时,贪婪的方法工作正常,但是假设你的公司决定在埃尔博尼亚部署自动贩卖机,除了通常的 1,5,10 和 25 分硬币,他们还有一个 21 分硬币 。在这种情况下,我们的贪婪的方法找不到 63 美分的最佳解决方案。 随着加入 21分硬币,贪婪的方法仍然会找到解决方案是六个硬币。然而,最佳答案是三个 21 分。 让我们看一个方法,我们可以确定会找到问题的最佳答案。由于这一节是关于递归的,你可能已经猜到我们将使用递归解决方案。让我们从基本情况开始,如果我们可以与我们硬币的价值相同的金额找零,答案很容易,一个硬币。 -如果金额不匹配,我们有几个选项。我们想要的是最低一分钱加上原始金额减去一分钱所需的硬币数量,或者 5 美分加上原始金额减去 5 美分所需的硬币数量,或者10 美分加上原始金额减去 10 美分所需的硬币数量,等等。因此,需要对原始金额找零硬币数量可以根据下式计算: +如果金额不匹配,我们有几个选项。我们想要的是最低一个一分钱加上原始金额减去一分钱所需的硬币数量,或者一个 5 美分加上原始金额减去 5 美分所需的硬币数量,或者一个 10 美分加上原始金额减去 10 美分所需的硬币数量,等等。因此,需要对原始金额找零硬币数量可以根据下式计算: ![4.12.动态规划.1](assets/4.12.%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92.1.png) @@ -32,9 +32,11 @@ print(recMC([1,5,10,25],63)) Listing 7 中的算法是非常低效的。事实上,它需要 `67,716,925` 个递归调用来找到 4 个硬币的最佳解决 63 美分问题的方案。要理解我们方法中的致命缺陷,请参见 Figure 5,其中显示了 377 个函数调用所需的一小部分,找到支付 26 美分的最佳硬币。 -图中的每个节点对应于对 `recMC` 的调用。节点上的标签表示硬币数量的变化量。箭头上的标签表示我们刚刚使用的硬币。通过跟随图表,我们可以看到硬币的组合。主要的问题是我们重复做了太多的计算。例如,该图表示该算法重复计算了至少三次支付 15 美分。每个这些计算找到 15 美分的最佳硬币数量本身需要52个函数调用。显然,我们浪费了大量的时间和精力重新计算旧的结果。 +图中的每个节点对应于对 `recMC` 的调用。节点上的标签表示硬币数量的变化量。箭头上的标签表示我们刚刚使用的硬币。通过跟随图表,我们可以看到硬币的组合。主要的问题是我们重复做了太多的计算。例如,该图表示该算法重复计算了至少三次支付 15 美分。这些计算找到 15 美分的最佳硬币数量的步骤本身需要52个函数调用。显然,我们浪费了大量的时间和精力重新计算旧的结果。 ![4.12.动态规划.figure5](assets/4.12.%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92.figure5.png) + + *Figure 5* 减少我们工作量的关键是记住一些过去的结果,这样我们可以避免重新计算我们已经知道的结果。一个简单的解决方案是将最小数量的硬币的结果存储在表中。然后在计算新的最小值之前,我们首先检查表,看看结果是否已知。如果表中已有结果,我们使用表中的值,而不是重新计算。 ActiveCode 1 显示了一个修改的算法,以合并我们的表查找方案。 @@ -66,11 +68,11 @@ print(recDC([1,5,10,25],63,[0]*64)) 一个真正的动态编程算法将采取更系统的方法来解决这个问题。我们的动态编程解决方案将从找零一分钱开始,并系统地找到我们需要的找零额。这保证我们在算法的每一步,已经知道为任何更小的数量进行找零所需的最小硬币数量。 -让我们看看如何找到 11 美分所需的最小找零数量。Figure 4 说明了该过程。我们从一分钱开始。唯一的解决方案是一个硬币(一分钱)。下一行显示一分和两分的最小值。再次,唯一的解决方案是两分钱。第五行事情变得有趣。现在我们有两个选择,五个一分钱或一个五分钱。我们如何决定哪个是最好的?我们查阅表,看到需要找零四美分的硬币数量是四,再加一个一分钱是五,等于五个硬币。或者我们可以看看零分加一个五分,五分钱等于一个硬币。由于一和五最小的是一,我们在表中存储为一。再次快进到表的末尾,考虑 11 美分。Figure 5 展示了我们要考虑的三个选项: +让我们看看如何找到 11 美分所需的最小找零数量。Figure 4 说明了该过程。我们从一分钱开始。唯一的解决方案是一个硬币(一分钱)。下一行显示一分和两分的最小值。再次,唯一的解决方案是两分钱。第五行事情变得有趣。现在我们有两个选择,五个一分钱或一个五分钱。我们如何决定哪个是最好的?我们查阅表,看到需要找零四美分的硬币数量是四,再加一个一分钱是五,等于五个硬币。或者我们可以尝试 0 分加一个五分,五分钱等于一个硬币。由于一和五最小的是一,我们在表中存储为一。再次快进到表的末尾,考虑 11 美分。Figure 5 展示了我们要考虑的三个选项: -1. 一分钱加上 `11-1 = 10分(1)` 的最小硬币数 -2. 五分钱加上 `11-5 = 6分(2)`的最小硬币数 -3. 十分钱加上 `11-10 = 1 分(1)`最小硬币数 +1. 一个一分钱加上 `11-1 = 10分(1)` 的最小硬币数 +2. 一个五分钱加上 `11-5 = 6分(2)`的最小硬币数 +3. 一个十分钱加上 `11-10 = 1 分(1)`最小硬币数 选项 1 或 3 总共需要两个硬币,这是 11 美分的最小硬币数。 ![4.12.动态规划.figure6](assets/4.12.%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92.figure6.png) @@ -89,13 +91,13 @@ def dpMakeChange(coinValueList,change,minCoins): ```` *Listing 8* -注意,`dpMakeChange` 不是递归函数,即使我们开始使用递归解决这个问题。重要的是要意识到,你可以为问题写一个递归解决方案并不意味着它是最好的或最有效的解决方案。在这个函数中的大部分工作是通过从第 4 行开始的循环来完成的。在这个循环中,我们考虑使用所有可能的硬币对指定的金额进行找零。就像我们上面的 11 分的例子,我们记住最小值,并将其存储在我们的 `minCoins` 列表。 +注意,`dpMakeChange` 不是递归函数,即使我们开始使用递归解决这个问题。重要的是要意识到,你可以为问题写一个递归解决方案但并不意味着它是最好的或最有效的解决方案。在这个函数中的大部分工作是通过从第 4 行开始的循环来完成的。在这个循环中,我们考虑使用所有可能的硬币对指定的金额进行找零。就像我们上面的 11 分的例子,我们记住最小值,并将其存储在我们的 `minCoins` 列表。 虽然我们的找零算法很好地找出最小数量的硬币,但它不帮助我们找零,因为我们不跟踪我们使用的硬币。我们可以轻松地扩展 `dpMakeChange` 来跟踪硬币使用,只需记住我们为每个条目添加的最后一个硬币到 `minCoins` 表。如果我们知道添加的最后一个硬币值,我们可以简单地减去硬币的值,在表中找到前一个条目,找到该金额的最后一个硬币。我们可以通过表继续跟踪,直到我们开始的位置。 ActiveCode 2 展示了 `dpMakeChange` 算法修改为跟踪使用的硬币,以及一个函数 `printCoins` 通过表打印出使用的每个硬币的值。前两行主要设置要找零的金额,并创建使用的硬币列表。 接下来的两行创建了我们需要存储结果的列表。`coinsUsed` 是用于找零的硬币的列表,并且 `coinCount` 是与列表中的位置相对应进行找零的最小硬币数。 -注意,我们打印的硬币直接来自 `coinsUsed` 数组。对于第一次调用,我们从数组位置 `63` 开始,然后打印 `21`。然后我们取 `63-21 = 42`,看看列表的第 42 个元素。我们再次找到 21存储在那里。 最后,数组第 21 个元素21也包含 21,得到三个21。 +注意,我们打印的硬币直接来自 `coinsUsed` 数组。对于第一次调用,我们从数组位置 `63` 开始,然后打印 `21`。然后我们取 `63-21 = 42`,看看列表的第 42 个元素。我们再次找到 21 存储在那里。 最后,数组第 21 个元素21 也包含 21,得到三个 21。 ```` def dpMakeChange(coinValueList,change,minCoins,coinsUsed): diff --git "a/4.\351\200\222\345\275\222/4.13.\346\200\273\347\273\223/README.md" "b/4.\351\200\222\345\275\222/4.13.\346\200\273\347\273\223/README.md" index 66f2b37..e5eed6a 100644 --- "a/4.\351\200\222\345\275\222/4.13.\346\200\273\347\273\223/README.md" +++ "b/4.\351\200\222\345\275\222/4.13.\346\200\273\347\273\223/README.md" @@ -3,9 +3,9 @@ 在本章中,我们讨论了几个递归算法的例子。 选择这些算法来揭示几个不同的问题,其中递归是一种有效的问题解决技术。 本章要记住的要点如下: * 所有递归算法都必须具有基本情况。 -* 递归算法必须改变其状态并朝向基本情况进展。 +* 递归算法必须改变其状态并朝基本情况发展。 * 递归算法必须调用自身(递归)。 * 递归在某些情况下可以代替迭代。 * 递归算法通常可以自然地映射到你尝试解决的问题的表达式。 -* 递归并不总是答案。有时,递归解决方案可能比替代算法在计算上更昂贵。 +* 递归并不总是答案。有时,递归解决方案可能比迭代算法在计算上更昂贵。 diff --git "a/4.\351\200\222\345\275\222/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214/README.md" "b/4.\351\200\222\345\275\222/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214/README.md" index 35dbf73..a1a83e2 100644 --- "a/4.\351\200\222\345\275\222/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214/README.md" +++ "b/4.\351\200\222\345\275\222/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214/README.md" @@ -1,6 +1,6 @@ ## 4.3.计算整数列表和 -我们将以一个简单的问题开始,你已经知道如何不使用递归解决。 假设您想计算整数列表的总和,例如:`[1,3,5,7,9]`。 计算总和的迭代函数 见ActiveCode 1。函数使用累加器变量(`theSum`)来计算列表中所有整数的和,从 0 开始,并加上列表中的每个数字。 +我们将以一个简单的问题开始,你已经知道如何不使用递归解决。 假设你想计算整数列表的总和,例如:`[1,3,5,7,9]`。 计算总和的迭代函数见ActiveCode 1。函数使用累加器变量(`theSum`)来计算列表中所有整数的和,从 0 开始,加上列表中的每个数字。 ```` def listsum(numList): @@ -14,7 +14,7 @@ print(listsum([1,3,5,7,9])) ```` *Activecode 1* -假设没有 `while` 循环或 `for` 循环。你将如何计算整数列表的总和?如果你是一个数学家,你可能开始回忆加法是一个函数,定义为两个整数类型的参数。将列表和问题重新定义加一对整数,我们可以把列表重写为为一个完全括号表达式。如下所示: +假设没有 `while` 循环或 `for` 循环。你将如何计算整数列表的总和?如果你是一个数学家,你可能开始回忆加法是一个函数,这个函数定义了两个整数类型的参数。故将列表和问题从加一个列表重新定义为加一对整数,我们可以把列表重写为一个完全括号表达式。如下所示: ![4.3.计算整数列表和.1](assets/4.3.%E8%AE%A1%E7%AE%97%E6%95%B4%E6%95%B0%E5%88%97%E8%A1%A8%E5%92%8C.1.png) 我们也可以把表达式用另一种方式括起来 @@ -23,10 +23,10 @@ print(listsum([1,3,5,7,9])) 注意,最内层的括号(7 + 9)我们可以没有循环或任何特殊的结构来解决它。 事实上,我们可以使用以下的简化序列来计算最终的和。 ![4.3.计算整数列表和.3](assets/4.3.%E8%AE%A1%E7%AE%97%E6%95%B4%E6%95%B0%E5%88%97%E8%A1%A8%E5%92%8C.3.png) -我们如何能把这个想法变成一个 Python 程序? 首先,让我们以 Python 列表的形式重述求和的问题。 我们可以说列表 `numList` 的和是列表的第一个元素`numList[0]` 和列表其余部分`numList [1:]` 之和的总和。 以函数形式表述: +我们如何能把这个想法变成一个 Python 程序? 首先,让我们以 Python 列表的形式重述求和问题。 我们可以说列表 `numList` 的和是列表的第一个元素`numList[0]` 和列表其余部分`numList [1:]` 之和的总和。 以函数形式表述: ![4.3.计算整数列表和.4](assets/4.3.%E8%AE%A1%E7%AE%97%E6%95%B4%E6%95%B0%E5%88%97%E8%A1%A8%E5%92%8C.4.png) -在这个方程式中,`first(numList)` 返回列表的第一个元素,`rest(numList)` 返回除第一个元素之外的所有列表。这很容易在 Python 中表示,如 ActiveCode 2 中所示。 +在这个方程式中,`first(numList)` 返回列表的第一个元素,`rest(numList)` 返回除第一个元素之外的所有元素列表。这很容易在 Python 中表示,如 ActiveCode 2 中所示。 ```` def listsum(numList): @@ -39,15 +39,19 @@ print(listsum([1,3,5,7,9])) ```` *Active code 2* -在这个清单中有几个关键地方。 首先,在第 2 行,我们检查列表是否为一个元素。这个检查是至关重要的,是我们的函数的转义子句。 长度 1 的列表的和是微不足道的; 它只是列表中的数字。 第二,在第 5 行我们的函数调用自己! 这就是我们调用 listum 算法递归的原因。递归函数是调用自身的函数。 +在这个清单中有几个关键地方。 首先,在第 2 行,我们检查列表是否为一个元素。这个检查是至关重要的,是我们的函数的转折子句。 长度为 1 的列表和是微不足道的; 它只是列表中的数字。 第二,在第 5 行函数调用自己! 这就是我们称 listum 算法递归的原因。递归函数是调用自身的函数。 Figure 1 展示了对列表`[1,3,5,7,9]` 求和所需的一系列递归调用。 你应该把这一系列的调用想象成一系列的简化。 每次我们进行递归调用时,我们都会解决一个较小的问题,直到达到问题不能减小的程度。 ![4.3.计算整数列表和.figure1](assets/4.3.%E8%AE%A1%E7%AE%97%E6%95%B4%E6%95%B0%E5%88%97%E8%A1%A8%E5%92%8C.figure1.png) + + *Figure 1* -当我们到达简单问题的点,我们开始拼凑每个小问题的答案,直到初始问题解决。Figure 2 展示了在 `listsum` 通过一系列调用返回的过程中执行的 add 操作。当 `listsum` 从最顶层的问题返回时,我们有整个问题的答案。 +当我们到达简单问题的点,我们开始拼凑每个小问题的答案,直到初始问题解决。Figure 2 展示了在 `listsum` 通过一系列调用返回的过程中执行的 add 操作。当 `listsum` 从最顶层返回时,我们就有了整个问题的答案。 ![4.3.计算整数列表和.figure2](assets/4.3.%E8%AE%A1%E7%AE%97%E6%95%B4%E6%95%B0%E5%88%97%E8%A1%A8%E5%92%8C.figure2.png) + + *Figure 2* diff --git "a/4.\351\200\222\345\275\222/4.4.\351\200\222\345\275\222\347\232\204\344\270\211\345\256\232\345\276\213/README.md" "b/4.\351\200\222\345\275\222/4.4.\351\200\222\345\275\222\347\232\204\344\270\211\345\256\232\345\276\213/README.md" index 0fd844d..74e2ab3 100644 --- "a/4.\351\200\222\345\275\222/4.4.\351\200\222\345\275\222\347\232\204\344\270\211\345\256\232\345\276\213/README.md" +++ "b/4.\351\200\222\345\275\222/4.4.\351\200\222\345\275\222\347\232\204\344\270\211\345\256\232\345\276\213/README.md" @@ -1,16 +1,16 @@ ## 4.4.递归的三定律 -像阿西莫夫的机器人,所有递归算法必须服从三个重要的定律: +像阿西莫夫机器人,所有递归算法必须服从三个重要的定律: 1. 递归算法必须具有基本情况。 2. 递归算法必须改变其状态并向基本情况靠近。 3. 递归算法必须以递归方式调用自身。 -让我们更详细地看看每一个定律,看看它如何在 `listsum` 算法中使用。首先,基本情况是算法停止递归的条件。基本情况通常足够小以直接求解的问题。在`listsum` 算法中,基本情况是长度为 1 的列表。 +让我们更详细地看看每一个定律,看看它如何在 `listsum` 算法中使用。首先,基本情况是算法停止递归的条件。基本情况通常是足够小以直接求解的问题。在`listsum` 算法中,基本情况是长度为 1 的列表。 -为了遵守第二定律,我们必须将算法向基本情况的状态改变。状态的改变意味着该算法正在使用的一些数据被修改。通常,表示我们问题的数据在某种程度上变小。在 `listsum` 算法中,我们的主要数据结构是一个列表,因此我们必须将我们的状态转换工作集中在列表上。因为基本情况是长度 1 的列表,所以朝向基本情况的自然进展是缩短列表。在 Activecode 2 第五行,当我们调用 `listsum` 生成一个较短的列表。 +为了遵守第二定律,我们必须将算法向基本情况的状态改变。状态的改变意味着该算法正在使用的一些数据被修改。通常,表示问题的数据在某种程度上变小。在 `listsum` 算法中,我们的主要数据结构是一个列表,因此我们必须将我们的状态转换工作集中在列表上。因为基本情况是长度 1 的列表,所以朝向基本情况的自然进展是缩短列表。在 Activecode 2 第五行,我们调用 `listsum` 生成一个较短的列表。 -最后的法则是算法必须调用自身。这是递归的定义。递归对于许多开始的程序员来说是一个混乱的概念。作为一个新手程序员,你已经知道函数是有益的,因为你可以将一个大问题分解成较小的问题。较小的问题可以通过编写一个函数来解决。我们用一个函数解决问题,但该函数通过调用自己解决问题!但逻辑不是循环;递归的逻辑是通过将问题分解成更小和更容易的问题来解决的优雅表达。 +最后的法则是算法必须调用自身。这是递归的定义。递归对于许多新手程序员来说是一个混乱的概念。作为一个新手程序员,你已经知道函数是有益的,因为你可以将一个大问题分解成较小的问题。较小的问题可以通过编写一个函数来解决。我们用一个函数解决问题,但该函数通过调用自己解决问题!该逻辑不是循环;递归的逻辑是通过将问题分解成更小和更容易的问题来解决的优雅表达。 在本章的剩余部分,我们将讨论更多递归的例子。在每种情况下,我们将集中于使用递归的三个定律来设计问题的解决方案。 diff --git "a/4.\351\200\222\345\275\222/4.5.\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\273\273\346\204\217\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262/README.md" "b/4.\351\200\222\345\275\222/4.5.\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\273\273\346\204\217\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262/README.md" index 9445aa1..40c2c98 100644 --- "a/4.\351\200\222\345\275\222/4.5.\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\273\273\346\204\217\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262/README.md" +++ "b/4.\351\200\222\345\275\222/4.5.\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\273\273\346\204\217\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262/README.md" @@ -16,6 +16,7 @@ ![4.5.整数转换为任意进制字符串.figure3](assets/4.5.%E6%95%B4%E6%95%B0%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%BB%BB%E6%84%8F%E8%BF%9B%E5%88%B6%E5%AD%97%E7%AC%A6%E4%B8%B2.figure3.png) + *Figure 3* ActiveCode 1 展示了实现上述算法的 Python 代码, 以 2 到 16 之间的任何基数为参数。 @@ -36,6 +37,8 @@ print(toStr(1453,16)) 让我们再次跟踪算法; 这次我们将数字 10 转换为其基数为 2 的字符串(“1010”)。 ![4.5.整数转换为任意进制字符串.figure4](assets/4.5.%E6%95%B4%E6%95%B0%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%BB%BB%E6%84%8F%E8%BF%9B%E5%88%B6%E5%AD%97%E7%AC%A6%E4%B8%B2.figure4.png) + + *Figure 4* Figure 4 显示我们得到的结果,但看起来数字是错误的顺序。该算法是正确的,因为我们首先在第 6 行进行递归调用,然后我们添加余数的字符串形式。 如果我们反向返回 convertString 查找并返回 toStr 调用,则生成的字符串将是反向的!通过延后连接操作直到递归调用返回,我们可以得到正确顺序的结果。这应该能使你想起你在上一章中讨论的栈。 diff --git "a/4.\351\200\222\345\275\222/4.6.\346\240\210\345\270\247\357\274\232\345\256\236\347\216\260\351\200\222\345\275\222/README.md" "b/4.\351\200\222\345\275\222/4.6.\346\240\210\345\270\247\357\274\232\345\256\236\347\216\260\351\200\222\345\275\222/README.md" index ac5c474..018b78d 100644 --- "a/4.\351\200\222\345\275\222/4.6.\346\240\210\345\270\247\357\274\232\345\256\236\347\216\260\351\200\222\345\275\222/README.md" +++ "b/4.\351\200\222\345\275\222/4.6.\346\240\210\345\270\247\357\274\232\345\256\236\347\216\260\351\200\222\345\275\222/README.md" @@ -27,15 +27,19 @@ print(toStr(1453,16)) 每次我们调用 toStr,我们在栈上推入一个字符。回到前面的例子,我们可以看到在第四次调用 toStr 之后,栈看起来像 Figure 5 。注意,现在我们可以简单地将字符从栈中弹出,并将它们连接成最终结果 “1010”。 ![4.6.栈帧:实现递归.figure5](assets/4.6.%E6%A0%88%E5%B8%A7%EF%BC%9A%E5%AE%9E%E7%8E%B0%E9%80%92%E5%BD%92.figure5.png) + + *Figure 5* 前面的例子让我们了解了 Python 如何实现一个递归函数调用。 当在 Python 中调用函数时,会分配一个栈来处理函数的局部变量。当函数返回时,返回值留在栈的顶部,以供调用函数访问。 Figure 6 说明了第 4 行返回语句后的调用栈。 ![4.6.栈帧:实现递归.figure6](assets/4.6.%E6%A0%88%E5%B8%A7%EF%BC%9A%E5%AE%9E%E7%8E%B0%E9%80%92%E5%BD%92.figure6.png) + + *Figure 6* -注意,对 `toStr(2//2,2)` 的调用在栈上返回值为 “1”。 然后,在表达式 `“1” + convertString[2%2]` 中使用此返回值替换函数调用`(toStr(1,2))`,这将在栈顶部留下字符串 “10”。 这样,Python 调用栈就代替了我们在 Listing 4 中明确使用的栈。在我们的列表求和示例中,您可以认为栈上的返回值取代了累加器变量。 +注意,对 `toStr(2//2,2)` 的调用在栈上返回值为 “1”。 然后,在表达式 `“1” + convertString[2%2]` 中使用此返回值替换函数调用`(toStr(1,2))`,这将在栈顶部留下字符串 “10”。 这样,Python 调用栈就代替了我们在 Listing 4 中明确使用的栈。在我们的列表求和示例中,你可以认为栈上的返回值取代了累加器变量。 栈帧还为函数使用的变量提供了一个作用域。 即使我们重复地调用相同的函数,每次调用都会为函数本地的变量创建一个新的作用域。 diff --git "a/4.\351\200\222\345\275\222/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222/README.md" "b/4.\351\200\222\345\275\222/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222/README.md" index bb6d6ba..603fdec 100644 --- "a/4.\351\200\222\345\275\222/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222/README.md" +++ "b/4.\351\200\222\345\275\222/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222/README.md" @@ -26,7 +26,7 @@ myWin.exitonclick() 要理解这如何工作,需要想一想如何使用分形词汇来描述树。记住,我们上面说过,分形是在所有不同的放大倍率下看起来是一样的。如果我们将它翻译成树木和灌木,我们可能会说,即使一个小树枝也有一个整体树的相同的形状和特征。有了这个想法,我们可以说一棵树是树干,一棵较小的树向右走,另一棵较小的树向左走。如果你用递归的思想考虑这个定义,这意味着我们将树的递归定义应用到较小的左树和右树。 -让我们把这个想法转换成一些 Python 代码。Listing 1 展示了我们如何使用我们的乌龟来生成分形树。让我们更仔细地看一下代码。你会看到在第 5 行和第 7 行,我们正在进行递归调用。在第 5 行,我们在乌龟向右转 20 度之后立即进行递归调用;这是上面提到的右树。然后在第 7 行,乌龟进行另一个递归调用,但这一次后左转 40 度。乌龟必须向左转 40 度的原因是,它需要撤消原来的向右转 20 度,然后再向左转 20 度,以绘制左树。还要注意,每次我们对树进行递归调用时,我们从 `branchLen` 参数中减去一些量;这是为了确保递归树越来越小。你还应该看到到第 2 行的初始 if 语句是检查 `branchLen` 的基本情况大小。 +让我们把这个想法转换成一些 Python 代码。Listing 1 展示了如何使用我们的乌龟来生成分形树。让我们更仔细地看一下代码。你会看到在第 5 行和第 7 行,我们正在进行递归调用。在第 5 行,我们在乌龟向右转 20 度之后立即进行递归调用;这是上面提到的右树。然后在第 7 行,乌龟进行另一个递归调用,但这一次后左转 40 度。乌龟必须向左转 40 度的原因是,它需要撤消原来的向右转 20 度,然后再向左转 20 度,以绘制左树。还要注意,每次我们对树进行递归调用时,我们从 `branchLen` 参数中减去一些量; 这是为了确保递归树越来越小。你还应该看到到第 2 行的初始 if 语句是检查 `branchLen` 的基本情况大小。 ```` python def tree(branchLen,t): @@ -41,7 +41,7 @@ def tree(branchLen,t): ```` *Listing 1* -此树示例的完整程序在 ActiveCode 2 中。在运行代码之前,请考虑你希望看到的树形状。看着递归调用,并想想这棵树将如何展开。它会对称地绘制树的右半边和左半边吗? 它会先绘制右侧然后左侧? +此树示例的完整程序在 ActiveCode 2 中。在运行代码之前,请思考你希望看到的树形状。看着递归调用,并想想这棵树将如何展开。它会对称地绘制树的右半边和左半边吗? 它会先绘制右侧然后左侧? ```` import turtle @@ -75,10 +75,14 @@ main() ![4.7.介绍:可视化递归.a](assets/4.7.%E4%BB%8B%E7%BB%8D%EF%BC%9A%E5%8F%AF%E8%A7%86%E5%8C%96%E9%80%92%E5%BD%92.ac1.png) -注意树上的每个分支点如何对应于递归调用,并注意树的右半部分如何一直绘制到它的最短的树枝。你可以在 Figure 1 中看到这一点。现在,注意程序如何工作,它的方式是备份树干直到树的整个右侧绘制完成。你可以在 Figure 2 中看到树的右半部分。然后绘制树的左侧,但不是尽可能远地向左移动。相反,直到我们进入到左树最小的枝干,左树的右半部分才开始绘制。 +注意树上的每个分支点如何对应于递归调用,并注意树的右半部分如何一直绘制到它的最短的树枝。你可以在 Figure 1 中看到这一点。现在,注意程序如何工作,它的方式是直到树的整个右侧绘制完成回到树干。你可以在 Figure 2 中看到树的右半部分。然后绘制树的左侧,但不是尽可能远地向左移动。相反,直到我们进入到左树最小的枝干,左树的右半部分才开始绘制。 ![4.7.介绍:可视化递归.figure1](assets/4.7.%E4%BB%8B%E7%BB%8D%EF%BC%9A%E5%8F%AF%E8%A7%86%E5%8C%96%E9%80%92%E5%BD%92.figure1.png) + + *Figure 1* + + ![4.7.介绍:可视化递归.figure2](assets/4.7.%E4%BB%8B%E7%BB%8D%EF%BC%9A%E5%8F%AF%E8%A7%86%E5%8C%96%E9%80%92%E5%BD%92.figure2.png) *Figure 2* diff --git "a/4.\351\200\222\345\275\222/4.8.\350\260\242\345\260\224\345\256\276\346\226\257\345\237\272\344\270\211\350\247\222\345\275\242/README.md" "b/4.\351\200\222\345\275\222/4.8.\350\260\242\345\260\224\345\256\276\346\226\257\345\237\272\344\270\211\350\247\222\345\275\242/README.md" index de9d096..acfe66f 100644 --- "a/4.\351\200\222\345\275\222/4.8.\350\260\242\345\260\224\345\256\276\346\226\257\345\237\272\344\270\211\350\247\222\345\275\242/README.md" +++ "b/4.\351\200\222\345\275\222/4.8.\350\260\242\345\260\224\345\256\276\346\226\257\345\237\272\344\270\211\350\247\222\345\275\242/README.md" @@ -1,11 +1,13 @@ ## 4.8.谢尔宾斯基三角形 -另一个展现自相似性的分形是谢尔宾斯基三角形。 Figure 3 是一个示例。谢尔宾斯基三角形阐明了三路递归算法。用手绘制谢尔宾斯基三角形的过程很简单。 从一个大三角形开始。 通过连接每一边的中点,将这个大三角形分成四个新的三角形。忽略刚刚创建的中间三角形,对三个角三角形中的每一个应用相同的过程。 每次创建一组新的三角形时,都会将此过程递归应用于三个较小的角三角形。 如果你有足够的铅笔,你可以无限重复这个过程。在继续阅读之前,你可以尝试运用所描述的方法自己绘制谢尔宾斯基三角形。 +另一个展现自相似性的分形是谢尔宾斯基三角形。 Figure 3 是一个示例。谢尔宾斯基三角形阐明了三路递归算法。用手绘制谢尔宾斯基三角形的过程很简单。 从一个大三角形开始。通过连接每一边的中点,将这个大三角形分成四个新的三角形。忽略刚刚创建的中间三角形,对三个小三角形中的每一个应用相同的过程。 每次创建一组新的三角形时,都会将此过程递归应用于三个较小的角三角形。 如果你有足够的铅笔,你可以无限重复这个过程。在继续阅读之前,你可以尝试运用所描述的方法自己绘制谢尔宾斯基三角形。 ![4.8.谢尔宾斯基三角形.figure3](assets/4.8.%E8%B0%A2%E5%B0%94%E5%AE%BE%E6%96%AF%E5%9F%BA%E4%B8%89%E8%A7%92%E5%BD%A2.figure3.png) + + *Figure 3* -因为我们可以无限地应用算法,什么是基本情况? 我们将看到,基本情况被任意设置为我们想要将三角形划分成块的次数。有时我们把这个数字称为分形的“度”。 每次我们进行递归调用时,我们从度中减去 1,直到 0.当我们达到 0 度时,我们停止递归。在 Figure 3 中生成谢尔宾斯基三角形的代码见 ActiveCode 1。 +因为我们可以无限地应用算法,什么是基本情况? 我们将看到,基本情况被任意设置为我们想要将三角形划分成块的次数。有时我们把这个数字称为分形的“度”。 每次我们进行递归调用时,我们从度中减去 1,直到 0。当我们达到 0 度时,我们停止递归。在 Figure 3 中生成谢尔宾斯基三角形的代码见 ActiveCode 1。 ```` python import turtle @@ -54,13 +56,15 @@ main() ```` *Activecode 1* -ActiveCode 1 中的程序遵循上述概念。谢尔宾斯基的第一件事是绘制外三角形。接下来,有三个递归调用,每个我们在连接中点获得新的三角形。我们再次使用 Python 附带的 `turtle` 模块。你可以通过使用 `help('turtle')` 了解 `turtle` 可用的方法的详细信息。 +ActiveCode 1 中的程序遵循上述概念。谢尔宾斯基的第一件事是绘制外三角形。接下来,有三个递归调用,每个使我们在连接中点获得新的三角形。我们再次使用 Python 附带的 `turtle` 模块。你可以通过使用 `help('turtle')` 了解 `turtle` 可用方法的详细信息。 -看下代码,想想绘制三角形的顺序。虽然三角的确切顺序取决于如何指定初始集,我们假设三角按左下,上,右下顺序。由于谢尔宾斯基函数调用自身,谢尔宾斯基以它的方式递归到左下角最小的三角形,然后开始填充其余的三角形。填充左下角中顶角中的小三角形。最后,它填充在左下角中右下角的最小三角形。 +看下代码,想想绘制三角形的顺序。虽然三角的确切顺序取决于如何指定初始集,我们假设三角按左下,上,右下顺序。由于谢尔宾斯基函数调用自身,谢尔宾斯基以它的方式递归到左下角最小的三角形,然后开始填充其余的三角形。填充左下角顶角中的小三角形。最后,它填充在左下角中右下角的最小三角形。 有时,根据函数调用图来考虑递归算法是有帮助的。Figure 4 展示了递归调用总是向左移动。活动函数以黑色显示,非活动函数显示为灰色。向 Figure 4 底部越近,三角形越小。该功能一次完成一次绘制; 一旦它完成了绘制,它移动到左下方底部中间位置,然后继续这个过程。 ![4.8.谢尔宾斯基三角形.figure4](assets/4.8.%E8%B0%A2%E5%B0%94%E5%AE%BE%E6%96%AF%E5%9F%BA%E4%B8%89%E8%A7%92%E5%BD%A2.figure4.png) + + *Figure 4* 谢尔宾斯基函数在很大程度上依赖于 `getMid` 函数。 `getMid` 接受两个端点作为参数,并返回它们之间的中点。 此外,ActiveCode 1 还有一个函数,使用 `begin_fill` 和 `end_fill` 方法绘制填充一个三角形。 diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.1.\347\233\256\346\240\207/README.md" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.1.\347\233\256\346\240\207/README.md" index 9bef1ac..bc3705a 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.1.\347\233\256\346\240\207/README.md" +++ "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.1.\347\233\256\346\240\207/README.md" @@ -1,7 +1,7 @@ ## 5.1.目标 * 能够解释和实现顺序查找和二分查找。 * 能够解释和实现选择排序,冒泡排序,归并排序,快速排序,插入排序和 shell 排序。 -* 理解哈希作为搜索技术的想法。 +* 理解哈希作为搜索技术的思想。 * 引入映射抽象数据类型。 -* 使用哈希实现地图抽象数据类型。 +* 使用哈希实现 Map 抽象数据类型。 diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.shell\346\216\222\345\272\217/README.md" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.\345\270\214\345\260\224\346\216\222\345\272\217/README.md" similarity index 55% rename from "5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.shell\346\216\222\345\272\217/README.md" rename to "5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.\345\270\214\345\260\224\346\216\222\345\272\217/README.md" index a4685e3..91a46d4 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.shell\346\216\222\345\272\217/README.md" +++ "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.\345\270\214\345\260\224\346\216\222\345\272\217/README.md" @@ -1,22 +1,27 @@ -## 5.10.shell排序 +## 5.10.希尔排序 -shell排序(有时称为“递减递增排序”)通过将原始列表分解为多个较小的子列表来改进插入排序,每个子列表使用插入排序进行排序。 选择这些子列表的方式是shell排序的关键。不是将列表拆分为连续项的子列表,shell排序使用增量i(有时称为 `gap`),通过选择 i 个项的所有项来创建子列表。 +希尔排序(有时称为“递减递增排序”)通过将原始列表分解为多个较小的子列表来改进插入排序,每个子列表使用插入排序进行排序。 选择这些子列表的方式是希尔排序的关键。不是将列表拆分为连续项的子列表,希尔排序使用增量i(有时称为 `gap`),通过选择 i 个项的所有项来创建子列表。 这可以在 Figure 6 中看到。该列表有九个项。如果我们使用三的增量,有三个子列表,每个子列表可以通过插入排序进行排序。完成这些排序后,我们得到如 Figure 7 所示的列表。虽然这个列表没有完全排序,但发生了很有趣的事情。 通过排序子列表,我们已将项目移动到更接近他们实际所属的位置。 -![5.10.shell排序.figure6](assets/5.10.shell%E6%8E%92%E5%BA%8F.figure6.png) +![5.10.希尔排序.figure6](assets/5.10.shell%E6%8E%92%E5%BA%8F.figure6.png) -*Figure 6* *Figure 7* + +*Figure 6-7* Figure 8 展示了使用增量为 1 的插入排序; 换句话说,标准插入排序。注意,通过执行之前的子列表排序,我们减少了将列表置于其最终顺序所需的移位操作的总数。对于这种情况,我们只需要四次移位完成该过程。 -![5.10.shell排序.figure8](assets/5.10.shell%E6%8E%92%E5%BA%8F.figure8.png) +![5.10.希尔排序.figure8](assets/5.10.shell%E6%8E%92%E5%BA%8F.figure8.png) + + *Figure 8* -![5.10.shell排序.figure9](assets/5.10.shell%E6%8E%92%E5%BA%8F.figure9.png) +![5.10.希尔排序.figure9](assets/5.10.shell%E6%8E%92%E5%BA%8F.figure9.png) + + *Figure 9* -我们之前说过,增量的选择方式是 shell排序的独特特征。 ActiveCode 1中展示的函数使用不同的增量集。在这种情况下,我们从 n/2 子列表开始。下一次,n/4 子列表排序。 最后,单个列表按照基本插入排序进行排序。 Figure 9 展示了我们使用此增量的示例的第一个子列表。 +我们之前说过,增量的选择方式是希尔排序的独特特征。 ActiveCode 1中展示的函数使用不同的增量集。在这种情况下,我们从 n/2 子列表开始。下一次,n/4 子列表排序。 最后,单个列表按照基本插入排序进行排序。 Figure 9 展示了我们使用此增量的示例的第一个子列表。 ```` def shellSort(alist): @@ -50,7 +55,7 @@ print(alist) *Activecode 1* -乍一看,你可能认为 shell 排序不会比插入排序更好,因为它最后一步执行了完整的插入排序。 然而,结果是,该最终插入排序不需要进行非常多的比较(或移位),因为如上所述,该列表已经被较早的增量插入排序预排序。 换句话说,每个遍历产生比前一个“更有序”的列表。 这使得最终遍历非常有效。 +乍一看,你可能认为希尔排序不会比插入排序更好,因为它最后一步执行了完整的插入排序。 然而,结果是,该最终插入排序不需要进行非常多的比较(或移位),因为如上所述,该列表已经被较早的增量插入排序预排序。 换句话说,每个遍历产生比前一个“更有序”的列表。 这使得最终遍历非常有效。 -虽然对 shell 排序的一般分析远远超出了本文的范围,我们可以说,它倾向于落在 O(n) 和 O(n^2 ) 之间的某处,基于以上所描述的行为。对于 Listing 5中显示的增量,性能为 O(n^2 ) 。 通过改变增量,例如使用`2^k -1(1,3,7,15,31等等)`,shell排序可以在 O(n^3/2 )处执行。 +虽然对希尔排序的一般分析远远超出了本文的范围,我们可以说,它倾向于落在 O(n) 和 O(n^2 ) 之间的某处,基于以上所描述的行为。对于 Listing 5中显示的增量,性能为 O(n^2 ) 。 通过改变增量,例如使用`2^k -1(1,3,7,15,31等等)`,希尔排序可以在 O(n^3/2 )处执行。 diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.shell\346\216\222\345\272\217/assets/5.10.shell\346\216\222\345\272\217.figure6.png" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.\345\270\214\345\260\224\346\216\222\345\272\217/assets/5.10.shell\346\216\222\345\272\217.figure6.png" similarity index 100% rename from "5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.shell\346\216\222\345\272\217/assets/5.10.shell\346\216\222\345\272\217.figure6.png" rename to "5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.\345\270\214\345\260\224\346\216\222\345\272\217/assets/5.10.shell\346\216\222\345\272\217.figure6.png" diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.shell\346\216\222\345\272\217/assets/5.10.shell\346\216\222\345\272\217.figure8.png" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.\345\270\214\345\260\224\346\216\222\345\272\217/assets/5.10.shell\346\216\222\345\272\217.figure8.png" similarity index 100% rename from "5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.shell\346\216\222\345\272\217/assets/5.10.shell\346\216\222\345\272\217.figure8.png" rename to "5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.\345\270\214\345\260\224\346\216\222\345\272\217/assets/5.10.shell\346\216\222\345\272\217.figure8.png" diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.shell\346\216\222\345\272\217/assets/5.10.shell\346\216\222\345\272\217.figure9.png" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.\345\270\214\345\260\224\346\216\222\345\272\217/assets/5.10.shell\346\216\222\345\272\217.figure9.png" similarity index 100% rename from "5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.shell\346\216\222\345\272\217/assets/5.10.shell\346\216\222\345\272\217.figure9.png" rename to "5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.10.\345\270\214\345\260\224\346\216\222\345\272\217/assets/5.10.shell\346\216\222\345\272\217.figure9.png" diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.11.\345\275\222\345\271\266\346\216\222\345\272\217/README.md" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.11.\345\275\222\345\271\266\346\216\222\345\272\217/README.md" index 10f3913..f4782bb 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.11.\345\275\222\345\271\266\346\216\222\345\272\217/README.md" +++ "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.11.\345\275\222\345\271\266\346\216\222\345\272\217/README.md" @@ -4,8 +4,12 @@ ![5.11.归并排序.figure10](assets/5.11.%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F.figure10.png) + + *Figure 10* ![5.11.归并排序.figure11](assets/5.11.%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F.figure11.png) + + *Figure 11* ActiveCode 1 中展示的 `mergeSort` 函数从询问基本情况开始。 如果列表的长度小于或等于1,则我们已经有有序的列表,并且不需要更多的处理。另一方面,长度大于 1,那么我们使用 Python 切片操作来提取左右两半。 重要的是要注意,列表可能没有偶数个项。这并不重要,因为长度最多相差一个。 diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.12.\345\277\253\351\200\237\346\216\222\345\272\217/README.md" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.12.\345\277\253\351\200\237\346\216\222\345\272\217/README.md" index 77e2d57..78ad05d 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.12.\345\277\253\351\200\237\346\216\222\345\272\217/README.md" +++ "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.12.\345\277\253\351\200\237\346\216\222\345\272\217/README.md" @@ -8,11 +8,15 @@ Figure 12 展示 54 将作为我们的第一个枢纽值。由于我们已经看 ![5.12.快速排序.figure12](assets/5.12.%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F.figure12.png) + + *Figure 12* 分区从通过在列表中剩余项目的开始和结束处定位两个位置标记(我们称为左标记和右标记)开始(Figure 13中的位置 1 和 8 )。分区的目标是移动相对于枢轴值位于错误侧的项,同时也收敛于分裂点。 Figure13展示了我们定位54的位置的过程。 ![5.12.快速排序.figure13](assets/5.12.%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F.figure13.png) + + *Figure 13* 我们首先增加左标记,直到我们找到一个大于枢轴值的值。 然后我们递减右标,直到我们找到小于枢轴值的值。我们发现了两个相对于最终分裂点位置不适当的项。 对于我们的例子,这发生在 93 和 20。现在我们可以交换这两个项目,然后重复该过程。 @@ -20,6 +24,8 @@ Figure 12 展示 54 将作为我们的第一个枢纽值。由于我们已经看 在右标变得小于左标记的点,我们停止。右标记的位置现在是分割点。枢轴值可以与拆分点的内容交换,枢轴值现在就位(Figure 14)。 此外,分割点左侧的所有项都小于枢轴值,分割点右侧的所有项都大于枢轴值。现在可以在分割点处划分列表,并且可以在两半上递归调用快速排序。 ![5.12.快速排序.figure14](assets/5.12.%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F.figure14.png) + + *Figure14* ActiveCode 1中显示 `quickSort` 函数调用递归函数`quickSortHelper`。 `quickSortHelper` 以与合并排序相同的基本情况开始。如果列表的长度小于或等于一,它已经排序。 如果它更大,那么它可以被分区和递归排序。 分区函数实现前面描述的过程。 diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.3.\351\241\272\345\272\217\346\237\245\346\211\276/README.md" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.3.\351\241\272\345\272\217\346\237\245\346\211\276/README.md" index 5c8b04d..b2b466f 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.3.\351\241\272\345\272\217\346\237\245\346\211\276/README.md" +++ "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.3.\351\241\272\345\272\217\346\237\245\346\211\276/README.md" @@ -5,9 +5,11 @@ Figure 1 展示了这种搜索的工作原理。 从列表中的第一个项目开始,我们按照基本的顺序排序,简单地从一个项移动到另一个项,直到找到我们正在寻找的项或遍历完整个列表。如果我们遍历完整个列表,则说明正在搜索的项不存在。 ![5.3.顺序查找.figure1](assets/5.3.%E9%A1%BA%E5%BA%8F%E6%9F%A5%E6%89%BE.figure1.png) + + *Figure 1* -该算法的 Python 实现见 CodeLens 1。该函数需要一个列表和我们正在寻找的项,并返回一个是否存在的布尔值。`found` 布尔变量初始化为 False,如果我们发现列表中的项,则赋值为 True。 +该算法的 Python 实现见 CodeLens 1。该函数需要一个列表和我们正在寻找的项作为参数,并返回一个是否存在的布尔值。`found` 布尔变量初始化为 False,如果我们发现列表中的项,则赋值为 True。 ```` def sequentialSearch(alist, item): @@ -33,11 +35,13 @@ print(sequentialSearch(testlist, 13)) 为了分析搜索算法,我们需要定一个基本计算单位。回想一下,这通常是为了解决问题要重复的共同步骤。对于搜索,计算比较操作数是有意义的。每个比较都有可能找到我们正在寻找的项目。此外,我们在这里做另一个假设。项列表不以任何方式排序。项随机放置到列表中。换句话说,项在列表任何位置的概率是一样的。 -如果项目不在列表中,知道它的唯一方法是将其与存在的每个项进行比较。如果有n 个项目,则顺序查找需要 n 个比较来发现项不存在。在项在列表中的情况下,分析不是那么简单。实际上有三种不同的情况可能发生。在最好的情况下,我们在列表的开头找到所需的项,只需要一个比较。在最坏的情况下,我们直到最后的比较才找到项,第 n 个比较。 +如果项不在列表中,知道它的唯一方法是将其与存在的每个项进行比较。如果有n 个项,则顺序查找需要 n 个比较来发现项不存在。在项在列表中的情况下,分析不是那么简单。实际上有三种不同的情况可能发生。在最好的情况下,我们在列表的开头找到所需的项,只需要一个比较。在最坏的情况下,我们直到最后的比较才找到项,第 n 个比较。 -平均情况怎么样?平均来说,我们会在列表的一半找到该项; 也就是说,我们将比较 n/2 项。然而,回想一下,当 n 变大时,系数,无论它们是什么,在我们的近似中变得不重要,因此顺序查找的复杂度是O(n)。Table 1 总结了这些结果。 +平均情况怎么样?平均来说,我们会在列表的一半找到该项; 也就是说,我们将比较 n/2 项。然而,回想一下,当 n 变大时,系数,无论它们是什么,在我们的近似中变得不重要,因此顺序查找的复杂度是 O(n)。Table 1 总结了这些结果。 ![5.3.顺序查找.table1](assets/5.3.%E9%A1%BA%E5%BA%8F%E6%9F%A5%E6%89%BE.table1.png) + + *Table 1* 我们之前假设,我们列表中的项是随机放置的,因此在项之间没有相对顺序。如果项以某种方式排序,顺序查找会发生什么?我们能够在搜索技术中取得更好的效率吗? @@ -45,6 +49,8 @@ print(sequentialSearch(testlist, 13)) 假设项的列表按升序排列。如果我们正在寻找的项存在于列表中,它在 n 个位置中的概率依旧相同。我们仍然会有相同数量的比较来找到该项。然而,如果该项不存在,则有一些优点。Figure 2 展示了这个过程,寻找项 50。注意,项仍然按顺序进行比较直到 54。此时,我们知道一些额外的东西。不仅 54 不是我们正在寻找的项,也没有超过 54 的其他元素可以匹配到该项,因为列表是有序的。在这种情况下,算法不必继续查看所有项。它可以立即停止。 CodeLens 2 展示了顺序查找功能的这种变化。 ![5.3.顺序查找.figure2](assets/5.3.%E9%A1%BA%E5%BA%8F%E6%9F%A5%E6%89%BE.figure2.png) + + *Figure 2* ```` @@ -72,6 +78,8 @@ print(orderedSequentialSearch(testlist, 13)) Table 2 总结了这些结果。 请注意,在最好的情况下,我们通过只查看一项会发现该项不在列表中。 平均来说,我们将只了解 n/2 项就知道。然而,这种复杂度仍然是 O(n)。 总之,只有在我们没有找到该项的情况下,才通过对列表排序来改进顺序查找。 ![5.3.顺序查找.table2](assets/5.3.%E9%A1%BA%E5%BA%8F%E6%9F%A5%E6%89%BE.table2.png) + + *Table 2* diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.4.\344\272\214\345\210\206\346\237\245\346\211\276/README.md" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.4.\344\272\214\345\210\206\346\237\245\346\211\276/README.md" index 9032e6b..c4e40a7 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.4.\344\272\214\345\210\206\346\237\245\346\211\276/README.md" +++ "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.4.\344\272\214\345\210\206\346\237\245\346\211\276/README.md" @@ -1,9 +1,11 @@ ## 5.4.二分查找 -有序列表对于我们的比较是很有用的。在顺序查找中,当我们与第一个项进行比较时,如果第一个项不是我们要查找的,则最多还有 n-1 个项目。 二分查找从中间项开始,而不是按顺序查找列表。 如果该项是我们正在寻找的项,我们就完成了查找。 如果它不是,我们可以使用列表的有序性质来消除剩余项的一半。如果我们正在查找的项大于中间项,就可以消除中间项以及比中间项小的一半元素。如果该项在列表中,肯定在大的那半部分。 +有序列表对于我们的比较是很有用的。在顺序查找中,当我们与第一个项进行比较时,如果第一个项不是我们要查找的,则最多还有 `n-1` 个项目。 二分查找从中间项开始,而不是按顺序查找列表。 如果该项是我们正在寻找的项,我们就完成了查找。 如果它不是,我们可以使用列表的有序性质来消除剩余项的一半。如果我们正在查找的项大于中间项,就可以消除中间项以及比中间项小的一半元素。如果该项在列表中,肯定在大的那半部分。 -然后我们可以用大的半部分重复这个过程。从中间项开始,将其与我们正在寻找的内容进行比较。再次,我们找到元素或将列表分成两半,消除我们可能的搜索空间的另一部分。Figure 3 展示了该算法如何快速找到值 54 。完整的函数见CodeLens 3中。 +然后我们可以用大的半部分重复这个过程。从中间项开始,将其与我们正在寻找的内容进行比较。再次,我们找到元素或将列表分成两半,消除可能的搜索空间的另一部分。Figure 3 展示了该算法如何快速找到值 54 。完整的函数见CodeLens 3中。 ![5.4.二分查找.figure3](assets/5.4.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.figure3.png) + + *Figure 3* ```` python @@ -31,7 +33,7 @@ print(binarySearch(testlist, 13)) *CodeLens 3* -在我们继续分析之前,我们应该注意到,这个算法是分而治之策略的一个很好的例子。 分和治意味着我们将问题分成更小的部分,以某种方式解决更小的部分,然后重新组合整个问题以获得结果。 当我们执行列表的二分查找时,我们首先检查中间项。如果我们正在搜索的项小于中间项,我们可以简单地对原始列表的左半部分执行二分查找。同样,如果项大,我们可以执行右半部分的二分查找。 无论哪种方式,都是递归调用二分查找函数。 CodeLens 4 展示了这个递归版本。 +在我们继续分析之前,我们应该注意到,这个算法是分而治之策略的一个很好的例子。分和治意味着我们将问题分成更小的部分,以某种方式解决更小的部分,然后重新组合整个问题以获得结果。 当我们执行列表的二分查找时,我们首先检查中间项。如果我们正在搜索的项小于中间项,我们可以简单地对原始列表的左半部分执行二分查找。同样,如果项大,我们可以执行右半部分的二分查找。 无论哪种方式,都是递归调用二分查找函数。 CodeLens 4 展示了这个递归版本。 ```` python def binarySearch(alist, item): @@ -55,9 +57,11 @@ print(binarySearch(testlist, 13)) ### 5.4.1.二分查找分析 -为了分析二分查找算法,我们需要记住,每个比较消除了大约一半的剩余项。该算法检查整个列表的最大比较数是多少?如果我们从 n 项开始,大约 n/2 项将在第一次比较后留下。第二次比较后,会有约 n/4。 然后 n/8,n/16,等等。 我们可以拆分列表多少次? Table 3帮助我们找到答案。 +为了分析二分查找算法,我们需要记住,每个比较消除了大约一半的剩余项。该算法检查整个列表的最大比较数是多少?如果我们从 n 项开始,大约 n/2 项将在第一次比较后留下。第二次比较后,会有约 n/4。 然后 n/8,n/16,等等。 我们可以拆分列表多少次? Table 3 帮助我们找到答案。 ![5.4.二分查找.table3](assets/5.4.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.table3.png) + + *Table 3* 当我们切分列表足够多次时,我们最终得到只有一个项的列表。 要么是我们正在寻找的项,要么不是。达到这一点所需的比较数是 i,当 n/2^i = 1 时。 求解 i 得出 i = log^n 。 最大比较数相对于列表中的项是对数的。 因此,二分查找是 O( log^n )。 @@ -68,7 +72,7 @@ print(binarySearch(testlist, 13)) binarySearch(alist[:midpoint],item) ```` -使用切片运算符创建列表的左半部分,然后传递到下一个调用(同样对于右半部分)。我们上面做的分析假设切片操作符是恒定时间的。然而,我们知道 Python中的 slice 运算符实际上是 O(k)。这意味着使用 slice 的二分查找将不会在严格的对数时间执行。幸运的是,这可以通过传递列表连同开始和结束索引来纠正。可以像 Listing 3 中所做的那样计算索引。我们将此实现作为练习。 +使用切片运算符创建列表的左半部分,然后传递到下一个调用(同样对于右半部分)。我们上面做的分析假设切片操作符是恒定时间的。然而,我们知道 Python中的 slice 运算符实际上是 O(k)。这意味着使用 slice 的二分查找将不会在严格的对数时间执行。幸运的是,这可以通过传递列表连同开始和结束索引来纠正。可以像 CodeLens 3 中所做的那样计算索引。我们将此实现作为练习。 即使二分查找通常比顺序查找更好,但重要的是要注意,对于小的 n 值,排序的额外成本可能不值得。事实上,我们应该经常考虑采取额外的分类工作是否使搜索获得好处。如果我们可以排序一次,然后查找多次,排序的成本就不那么重要。然而,对于大型列表,一次排序可能是非常昂贵,从一开始就执行顺序查找可能是最好的选择。 diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.5.Hash\346\237\245\346\211\276/README.md" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.5.Hash\346\237\245\346\211\276/README.md" index c3c507b..080e192 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.5.Hash\346\237\245\346\211\276/README.md" +++ "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.5.Hash\346\237\245\346\211\276/README.md" @@ -7,15 +7,21 @@ `哈希表` 是以一种容易找到它们的方式存储的项的集合。哈希表的每个位置,通常称为一个槽,可以容纳一个项,并且由从 0 开始的整数值命名。例如,我们有一个名为 0 的槽,名为 1 的槽,名为 2 的槽,以上。最初,哈希表不包含项,因此每个槽都为空。我们可以通过使用列表来实现一个哈希表,每个元素初始化为`None` 。Figure 4 展示了大小 m = 11 的哈希表。换句话说,在表中有 m 个槽,命名为 0 到 10。 ![5.5.Hash查找.figure4](assets/5.5.Hash%E6%9F%A5%E6%89%BE.figure4.png) + + *Figure 4* 项和该项在散列表中所属的槽之间的映射被称为 `hash 函数`。 hash 函数将接收集合中的任何项,并在槽名范围内(0和 m-1之间)返回一个整数。假设我们有整数项 `54,26,93,17,77` 和 `31` 的集合。我们的第一个 hash 函数,有时被称为 `余数法` ,只需要一个项并将其除以表大小,返回剩余部分作为其散列值`(h(item) = item%11)`。 Table 4 给出了我们的示例项的所有哈希值。注意,这种余数方法(模运算)通常以某种形式存在于所有散列函数中,因为结果必须在槽名的范围内。 ![5.5.Hash查找.table4](assets/5.5.Hash%E6%9F%A5%E6%89%BE.table4.png) + + *Table 4* 一旦计算了哈希值,我们可以将每个项插入到指定位置的哈希表中,如 Figure 5 所示。注意,11 个插槽中的 6 个现在已被占用。这被称为负载因子,通常表示为 `λ=项数/表大小`, 在这个例子中,`λ = 6/11` 。 ![5.5.Hash查找.figure5](assets/5.5.Hash%E6%9F%A5%E6%89%BE.figure5.png) + + *Figure 5* 现在,当我们要搜索一个项时,我们只需使用哈希函数来计算项的槽名称,然后检查哈希表以查看它是否存在。该搜索操作是 O(1),因为需要恒定的时间量来计算散列值,然后在该位置索引散列表。如果一切都正确的话,我们已经找到了一个恒定时间搜索算法。 @@ -35,6 +41,8 @@ 用于构造散列函数的另一数值技术被称为 `平方取中法`。我们首先对该项平方,然后提取一部分数字结果。例如,如果项是 44,我们将首先计算 `44^2 = 1,936` 。通过提取中间两个数字 `93` ,我们得到 `5(93%11)`。Table 5 展示了余数法和中间平方法下的项。 ![5.5.Hash查找.table5](assets/5.5.Hash%E6%9F%A5%E6%89%BE.table5.png) + + *Table 5* 我们还可以为基于字符的项(如字符串)创建哈希函数。 词 `cat` 可以被认为是 ascii 值的序列。 @@ -51,6 +59,8 @@ 然后,我们可以获取这三个 ascii 值,将它们相加,并使用余数方法获取散列值(参见 Figure 6)。 Listing 1 展示了一个名为 hash 的函数,它接收字符串和表大小 作为参数,并返回从 `0` 到 `tablesize-1` 的范围内的散列值。 ![5.5.Hash查找.table5](assets/5.5.Hash%E6%9F%A5%E6%89%BE.table5-1.png) + + *Figure 6* ```` python @@ -66,6 +76,8 @@ def hash(astring, tablesize): 有趣的是,当使用此散列函数时,字符串总是返回相同的散列值。 为了弥补这一点,我们可以使用字符的位置作为权重。 Figure 7 展示了使用位置值作为加权因子的一种可能的方式。 ![5.5.Hash查找.figure7](assets/5.5.Hash%E6%9F%A5%E6%89%BE.figure7.png) + + *Figure 7* 你可以思考一些其他方法来计算集合中项的散列值。重要的是要记住,哈希函数必须是高效的,以便它不会成为存储和搜索过程的主要部分。如果哈希函数太复杂,则计算槽名称的程序要比之前所述的简单地进行基本的顺序或二分搜索更耗时。 这将打破散列的目的。 @@ -80,6 +92,8 @@ Figure 8展示了在简单余数法散列函数`(54,26,93,17,77,31,44,55,20) 再次,`55` 应该在槽 0 中,但是必须放置在槽 2 中,因为它是下一个开放位置。值 20 散列到槽 9 。由于槽 9 已满,我们进行线性探测。我们访问槽`10,0,1`和 `2`,最后在位置 3 找到一个空槽。 ![5.5.Hash查找.figure8](assets/5.5.Hash%E6%9F%A5%E6%89%BE.figure8.png) + + *Figure 8* 一旦我们使用开放寻址和线性探测建立了哈希表,我们就必须使用相同的方法来搜索项。假设我们想查找项 `93` 。当我们计算哈希值时,我们得到 `5` 。查看槽 5 得到 `93`,返回 True。如果我们正在寻找 `20`, 现在哈希值为 `9`,而槽 `9` 当前项为 `31` 。我们不能简单地返回 False,因为我们知道可能存在冲突。我们现在被迫做一个顺序搜索,从位置 `10` 开始寻找,直到我们找到项 `20` 或我们找到一个空槽。 @@ -87,11 +101,15 @@ Figure 8展示了在简单余数法散列函数`(54,26,93,17,77,31,44,55,20) 线性探测的缺点是聚集的趋势;项在表中聚集。这意味着如果在相同的散列值处发生很多冲突,则将通过线性探测来填充多个周边槽。这将影响正在插入的其他项,正如我们尝试添加上面的项 `20` 时看到的。必须跳过一组值为 `0` 的值,最终找到开放位置。该聚集如 Figure 9 所示。 ![5.5.Hash查找.figure9](assets/5.5.Hash%E6%9F%A5%E6%89%BE.figure9.png) + + *Figure 9* 处理聚集的一种方式是扩展线性探测技术,使得不是顺序地查找下一个开放槽,而是跳过槽,从而更均匀地分布已经引起冲突的项。这将潜在地减少发生的聚集。 Figure 10 展示了使用 `加3` 探头进行碰撞识别时的项。 这意味着一旦发生碰撞,我们将查看第三个槽,直到找到一个空。 ![5.5.Hash查找.figure10](assets/5.5.Hash%E6%9F%A5%E6%89%BE.figure10.png) + + *Figure 10* 在冲突后寻找另一个槽的过程叫 `重新散列`。使用简单的线性探测,rehash 函数是 `newhashvalue = rehash(oldhashvalue)`其中 `rehash(pos)=(pos + 1)%sizeoftable`。 `加3 `rehash 可以定义为`rehash(pos)=(pos + 3)%sizeoftable`。一般来说,`rehash(pos)=(pos + skip)%sizeoftable`。重要的是要注意,“跳过”的大小必须使得表中的所有槽最终都被访问。否则,表的一部分将不被使用。为了确保这一点,通常建议表大小是素数。这是我们在示例中使用 11 的原因。 @@ -99,11 +117,15 @@ Figure 8展示了在简单余数法散列函数`(54,26,93,17,77,31,44,55,20) 线性探测思想的一个变种称为二次探测。代替使用常量 “跳过” 值,我们使用rehash 函数,将散列值递增 `1,3,5,7,9,` 依此类推。这意味着如果第一哈希值是 `h`,则连续值是`h + 1,h + 4,h + 9,h + 16`,等等。换句话说,二次探测使用由连续完全正方形组成的跳跃。Figure 11 展示了使用此技术放置之后的示例值。 ![5.5.Hash查找.figure11](assets/5.5.Hash%E6%9F%A5%E6%89%BE.figure11.png) + + *Figure 11* 用于处理冲突问题的替代方法是允许每个槽保持对项的集合(或链)的引用。链接允许许多项存在于哈希表中的相同位置。当发生冲突时,项仍然放在散列表的正确槽中。随着越来越多的项哈希到相同的位置,搜索集合中的项的难度增加。 Figure 12 展示了添加到使用链接解决冲突的散列表时的项。 ![5.5.Hash查找.figure12](assets/5.5.Hash%E6%9F%A5%E6%89%BE.figure12.png) + + *Figure 12* 当我们要搜索一个项时,我们使用散列函数来生成它应该在的槽。由于每个槽都有一个集合,我们使用一种搜索技术来查找该项是否存在。优点是,平均来说,每个槽中可能有更少的项,因此搜索可能更有效。我们将在本节结尾处查看散列的分析。 @@ -237,7 +259,7 @@ None ### 5.5.4.hash法分析 -我们之前说过,在最好的情况下,散列将提供O(1),恒定时间搜索。然而,由于冲突,比较的数量通常不是那么简单。即使对散列的完整分析超出了本文的范围,我们可以陈述一些近似搜索项所需的比较数量的已知结果。 +我们之前说过,在最好的情况下,散列将提供 O(1),恒定时间搜索。然而,由于冲突,比较的数量通常不是那么简单。即使对散列的完整分析超出了本文的范围,我们可以陈述一些近似搜索项所需的比较数量的已知结果。 我们需要分析散列表的使用的最重要的信息是负载因子 λ。概念上,如果 λ 小,则碰撞的机会较低,这意味着项更可能在它们所属的槽中。如果 λ 大,意味着表正在填满,则存在越来越多的冲突。这意味着冲突解决更困难,需要更多的比较来找到一个空槽。使用链接,增加的碰撞意味着每个链上的项数量增加。 diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.7.\345\206\222\346\263\241\346\216\222\345\272\217/README.md" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.7.\345\206\222\346\263\241\346\216\222\345\272\217/README.md" index 9bbb067..b819ab5 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.7.\345\206\222\346\263\241\346\216\222\345\272\217/README.md" +++ "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.7.\345\206\222\346\263\241\346\216\222\345\272\217/README.md" @@ -5,6 +5,8 @@ Figure 1 展示了冒泡排序的第一次遍历。阴影项正在比较它们是否乱序。如果在列表中有 n 个项目,则第一遍有 n-1 个项需要比较。重要的是要注意,一旦列表中的最大值是一个对的一部分,它将不断地被移动,直到遍历完成。 ![5.7.冒泡排序.figure1](assets/5.7.%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F.figure1.png) + + *Figure 1* 在第二次遍历的开始,现在最大的值已经在正确的位置。有 n-1 个项留下排序,意味着将有 n-2 对。由于每次通过将下一个最大值放置在适当位置,所需的遍历的总数将是 n-1。 在完成 n-1 遍之后,最小的项肯定在正确的位置,不需要进一步处理。 ActiveCode 1 显示完整的 `bubbleSort` 函数。它将列表作为参数,并根据需要交换项来修改它。 @@ -24,6 +26,8 @@ alist[j] = temp ActiveCode 1 中的行 5-7 使用先前描述的三步过程执行 i 和第 i + 1 个项的交换。 注意,我们也可以使用同时分配来交换项目。 ![5.7.冒泡排序.figure2](assets/5.7.%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F.figure2.png) + + *Figure 2* ```` python @@ -44,6 +48,8 @@ print(alist) 为了分析气泡排序,我们应该注意,不管项如何在初始列表中排列,将进行 n-1 次遍历以排序大小为 n 的列表。 Figure 1 展示了每次通过的比较次数。比较的总数是第 n-1 个整数的和。回想起来,前 n 个整数的和是 1/2n^2 + 1/2n。 第 n-1 个整数的和为 1/2n^2 + 1/2n -n,其为 1/2n^2 - 1/2n。 这仍然是 O(n^2 )比较。在最好的情况下,如果列表已经排序,则不会进行交换。 但是,在最坏的情况下,每次比较都会导致交换元素。 平均来说,我们交换了一半时间。 ![5.7.冒泡排序.table1](assets/5.7.%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F.table1.png) + + *Table1* 冒泡排序通常被认为是最低效的排序方法,因为它必须在最终位置被知道之前交换项。 这些“浪费”的交换操作是非常昂贵的。 然而,因为冒泡排序遍历列表的整个未排序部分,它有能力做大多数排序算法不能做的事情。特别地,如果在遍历期间没有交换,则我们知道该列表已排序。 如果发现列表已排序,可以修改冒泡排序提前停止。这意味着对于只需要遍历几次列表,冒泡排序具有识别排序列表和停止的优点。 ActiveCode 2 展示了这种修改,通常称为 `短冒泡排序`。 diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.9.\346\217\222\345\205\245\346\216\222\345\272\217/README.md" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.9.\346\217\222\345\205\245\346\216\222\345\272\217/README.md" index 9185b2f..4b1305f 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.9.\346\217\222\345\205\245\346\216\222\345\272\217/README.md" +++ "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.9.\346\217\222\345\205\245\346\216\222\345\272\217/README.md" @@ -3,6 +3,8 @@ 插入排序,尽管仍然是 O(n^2 ),工作方式略有不同。它始终在列表的较低位置维护一个排序的子列表。然后将每个新项 “插入” 回先前的子列表,使得排序的子列表称为较大的一个项。Figure 4 展示了插入排序过程。 阴影项表示算法进行每次遍历时的有序子列表。 ![5.9.插入排序.figure4](assets/5.9.%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F.figure4.png) + + *Figure 4* 我们开始假设有一个项(位置 0 )的列表已经被排序。在每次遍历时,对于每个项 1至 n-1,将针对已经排序的子列表中的项检查当前项。当我们回顾已经排序的子列表时,我们将那些更大的项移动到右边。 当我们到达较小的项或子列表的末尾时,可以插入当前项。 @@ -10,6 +12,8 @@ Figure 5 详细展示了第五次遍历。在该算法中的这一点,存在由 `17,26,54,77` 和 `93` 组成的五个项的排序子列表。我们插入 `31` 到已经排序的项。第一次与 93 比较导致 93 向右移位。 77 和 54 也移位。 当遇到 26 时,移动过程停止,并且 31 被置于开放位置。现在我们有一个六个项的排序子列表。 ![5.9.插入排序.figure5](assets/5.9.%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F.figure5.png) + + *Figure 5* `insertSort`(ActiveCode 1)的实现展示了 存在 n-1 个遍历以对 n 个排序。从位置 1 开始迭代并移动位置到 n-1,因为这些是需要插回到排序子列表中的项。第 8 行执行移位操作,将值向上移动到列表中的一个位置,在其后插入。请记住,这不是像以前的算法中的完全交换。 diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/README.md" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/README.md" new file mode 100644 index 0000000..e69de29 diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.10.\344\272\214\345\217\211\345\240\206\345\256\236\347\216\260/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.10.\344\272\214\345\217\211\345\240\206\345\256\236\347\216\260/README.md" index 8b96ef1..b9ea793 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.10.\344\272\214\345\217\211\345\240\206\345\256\236\347\216\260/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.10.\344\272\214\345\217\211\345\240\206\345\256\236\347\216\260/README.md" @@ -7,17 +7,19 @@ ![6.10.二叉堆实现.figure1](assets/6.10.%E4%BA%8C%E5%8F%89%E5%A0%86%E5%AE%9E%E7%8E%B0.figure1.png) + *Figure 1* 完整二叉树的另一个有趣的属性是,我们可以使用单个列表来表示它。 我们不需要使用节点和引用,甚至列表的列表。因为树是完整的,父节点的左子节点(在位置 p 处)是在列表中位置 2p 中找到的节点。 类似地,父节点的右子节点在列表中的位置 2p + 1。为了找到树中任意节点的父节点,我们可以简单地使用Python 的整数除法。 假定节点在列表中的位置 n,则父节点在位置 n/2。 Figure 2 展示了一个完整的二叉树,并给出了树的列表表示。 请注意父级和子级之间是 2p 和 2p+1 关系。 树的列表表示以及完整的结构属性允许我们仅使用几个简单的数学运算来高效地遍历一个完整的二叉树。 我们将看到,这也是我们的二叉堆的有效实现。 ### 6.10.2.堆的排序属性 -我们将用于在堆中的存储项的方法依赖于维护堆的排序属性。 堆的排序属性如下:在堆中,对于具有父 p 的每个节点 x,p 中的键小于或等于 x 中的键。 Figure 2 还展示了具有堆顺序属性的完整二叉树。 +我们用于堆中存储项的方法依赖于维护堆的排序属性。 堆的排序属性如下:在堆中,对于具有父 p 的每个节点 x,p 中的键小于或等于 x 中的键。 Figure 2 展示了具有堆顺序属性的完整二叉树。 ![6.10.二叉堆实现.figure2](assets/6.10.%E4%BA%8C%E5%8F%89%E5%A0%86%E5%AE%9E%E7%8E%B0.figure2.png) + *Figure 2* ### 6.10.3.堆操作 @@ -32,10 +34,11 @@ class BinHeap: ``` *Listing 1* -我们将实现的下一个方法是 `insert` 。 将项添加到列表中最简单,最有效的方法是将项附加到列表的末尾。 它保证我们将维护完整的树属性。但可能违反堆结构属性。可以编写一个方法,通过比较新添加的项与其父项,我们可以重新获得堆结构属性。 如果新添加的项小于其父项,则我们可以将项与其父项交换。 Figure 2 展示了将新添加的项替换到其在树中的适当位置所需的操作。 +我们将实现的下一个方法是 `insert` 。 将项添加到列表中最简单,最有效的方法是将项附加到列表的末尾。 它维护完整的树属性。但可能违反堆结构属性。可以编写一个方法,通过比较新添加的项与其父项,我们可以重新获得堆结构属性。 如果新添加的项小于其父项,则我们可以将项与其父项交换。 Figure 2 展示了将新添加的项替换到其在树中的适当位置所需的操作。 ![6.10.二叉堆实现.figure2-1](assets/6.10.%E4%BA%8C%E5%8F%89%E5%A0%86%E5%AE%9E%E7%8E%B0.figure2-1.png) + *Figure 2* 注意,当我们完成一个项时,我们需要恢复新添加的项和父项之间的堆属性。 我们还需保留任何兄弟节点的堆属性。当然,如果新添加的项非常小,我们可能仍需要将其交换另一上层。事实上,我们可能需要交换到树的顶部。 Listing 2 展示了 `percUp` 方法,它在树中向上遍历一个新项,因为它需要去维护堆属性。 注意,我们可以通过使用简单的整数除法来计算任意节点的父节点。 当前节点的父节点可以通过将当前节点的索引除以 2 来计算。 @@ -62,8 +65,10 @@ def insert(self,k): *Listing 3* 使用正确定义的 `insert` 方法,我们现在可以看 `delMin` 方法。 因为堆属性要求树的根是树中的最小项,所以找到最小项很容易。`delMin` 的难点在根被删除后恢复堆结构和堆顺序属性。 我们可以分两步恢复我们的堆。首先,我们将通过获取列表中的最后一个项并将其移动到根位置来恢复根项,保持我们的堆结构属性。 但是,我们可能已经破坏了我们的二叉堆的堆顺序属性。 第二,我们通过将新的根节点沿着树向下推到其正确位置来恢复堆顺序属性。 Figure 3展示了将新的根节点移动到堆中的正确位置所需的交换序列。 - +ee ![6.10.二叉堆实现.figure3](assets/6.10.%E4%BA%8C%E5%8F%89%E5%A0%86%E5%AE%9E%E7%8E%B0.figure3.png) + + *Figure 3* 为了维护堆顺序属性,我们所需要做的是将根节点和最小的子节点交换。在初始交换之后,我们可以将节点和其子节点重复交换,直到节点被交换到正确的位置,使它小于两个子节点。树交换节点的代码可以在 Listing 4中的 `percDown`和`minChild` 方法中找到。 @@ -117,6 +122,8 @@ def buildHeap(self,alist): ![6.10.二叉堆实现.figure4](assets/6.10.%E4%BA%8C%E5%8F%89%E5%A0%86%E5%AE%9E%E7%8E%B0.figure4.png) + + *Figure 4* Figure 4 展示了 `buildHeap` 方法在 `[9,6,5,2,3]` 的初始树中的节点移动到其正确位置时所做的交换。虽然我们从树的中间开始,并以我们的方式回到根节点,`percDown` 方法确保最大的子节点总是沿着树向下移动。因为堆是一个完整的二叉树,超过中途点的任何节点都将是树叶,因此没有子节点。注意,当`i = 1` 时,我们从树的根节点向下交换,因此可能需要多次交换。正如你在 Figure 4 最右边的两个树中可以看到的,首先 9 从根位置移出,但是 9 在树中向下移动一级之后,`percDown` 检查下一组子树,以确保它被推到下一层。在这种情况下,它与 3 进行第二次交换。现在 9 已经移动到树的最低层,不能进行进一步交换。将 Figure 4 所示的这一系列交换的列表与树进行比较是有用的。 diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.13.\346\237\245\346\211\276\346\240\221\345\256\236\347\216\260/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.13.\346\237\245\346\211\276\346\240\221\345\256\236\347\216\260/README.md" index 37a7435..06cc5ca 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.13.\346\237\245\346\211\276\346\240\221\345\256\236\347\216\260/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.13.\346\237\245\346\211\276\346\240\221\345\256\236\347\216\260/README.md" @@ -3,6 +3,8 @@ 二叉搜索树依赖于在左子树中找到的键小于父节点的属性,并且在右子树中找到的键大于父代。 我们将这个称为 bst属性。 当我们如上所述实现 Map 接口时,bst 属性将指导我们的实现。 Figure 1说明了二叉搜索树的此属性,展示了没有任何关联值的键。请注意,该属性适用于每个父级和子级。 左子树中的所有键小于根中的键。 右子树中的所有键都大于根。 ![6.13.查找树实现.figure1](assets/6.13.%E6%9F%A5%E6%89%BE%E6%A0%91%E5%AE%9E%E7%8E%B0.figure1.png) + + *Figure1* 现在你知道什么是二叉搜索树,我们将看看如何构造二叉搜索树。Figure 1中的搜索树表示在按照所示的顺序插入以下键之后存在的节点:`70,31,93,94,14,23,73`。因为 70 是插入树中的第一个键,它是根。接下来,31 小于 70,所以它成为 70 的左孩子。接下来,93 大于 70,所以它成为 70 的右孩子。现在我们有两层的树填充,所以下一个键 94 ,因为 94 大于70 和 93,它成为 93 的右孩子。类似地,14 小于 70 和 31,所以它变成 31 的左孩子。23 也小于 31,所以它必须在左子树 31 中。但是,它大于14,所以它成为 14 的右孩子。 @@ -79,13 +81,13 @@ class TreeNode: 现在我们有了 `BinarySearchTree` shell 和 `TreeNode`,现在是时候编写 `put` 方法,这将允许我们构建二叉搜索树。 `put` 方法是`BinarySearchTree` 类的一个方法。此方法将检查树是否已具有根。如果没有根,那么 `put` 将创建一个新的 `TreeNode` 并将其做为树的根。如果根节点已经就位,则 put 调用私有递归辅助函数 `_put` 根据以下算法搜索树: -* 从树的根开始,搜索二叉树,将新键与当前节点中的键进行比较。如果新键小于当前节点,则搜索左子树。如果新键大于当前节点,则搜索右侧子树。 +* 从树的根开始,搜索二叉树,将新键与当前节点中的键进行比较。如果新键小于当前节点,则搜索左子树。如果新键大于当前节点,则搜索右子树。 * 当没有左(或右)孩子要搜索时,我们在树中找到应该建立新节点的位置。 * 要向树中添加节点,请创建一个新的 `TreeNode` 对象,并将对象插入到上一步发现的节点。 Listing 3 展示了在树中插入一个新节点的 Python 代码。`_put` 函数按照上述步骤递归编写。请注意,当一个新的子节点插入到树中时,`currentNode` 将作为父节点传递给新的树节点。 -我们实现插入的一个重要问题是重复的键不能正确处理。当我们的树被实现时,重复键将在具有原始键的节点的右子树中创建具有相同键值的新节点。这样做的结果是,具有新键的节点将永远不会在搜索期间被找到。处理插入重复键的更好方法是将新键相关联的值替换旧值。我们将修复这个bug作为一个练习。 +我们实现插入的一个重要问题是重复的键不能正确处理。当我们的树被实现时,重复键将在具有原始键的节点的右子树中创建具有相同键值的新节点。这样做的结果是,具有新键的节点将永远不会在搜索期间被找到。处理插入重复键的更好方法是将新键相关联的值替换旧值。我们将修复这个bug作为练习。 ``` def put(self,key,val): @@ -120,6 +122,8 @@ def __setitem__(self,k,v): Figure 2 展示了用于将新节点插入二叉搜索树的过程。 浅阴影的节点指示在插入过程期间访问的节点。 ![6.13.查找树实现.figure2](assets/6.13.%E6%9F%A5%E6%89%BE%E6%A0%91%E5%AE%9E%E7%8E%B0.figure2.png) + + *Figure 2* 一旦树被构造,下一个任务是实现对给定键的值的检索。`get` 方法比 `put` 方法更容易,因为它只是递归地搜索树,直到它到达不匹配的叶节点或找到匹配的键。当找到匹配的键时,返回存储在节点的有效载荷中的值。 @@ -202,6 +206,7 @@ def __delitem__(self,key): 第一种情况很简单(见 Listing 8)。 如果当前节点没有子节点,我们需要做的是删除节点并删除对父节点中该节点的引用。 此处的代码如下所示。 + ``` if currentNode.isLeaf(): if currentNode == currentNode.parent.leftChild: @@ -212,6 +217,8 @@ if currentNode.isLeaf(): *Listing 8* ![6.13.查找树实现.figure3](assets/6.13.%E6%9F%A5%E6%89%BE%E6%A0%91%E5%AE%9E%E7%8E%B0.figure3.png) + + *Figure 3* 第二种情况只是稍微复杂一点(见 Listing 9)。如果一个节点只有一个孩子,那么我们可以简单地促进孩子取代其父。此案例的代码展示在下一个列表中。当你看这个代码,你会看到有六种情况要考虑。由于这些情况相对于左孩子或右孩子对称,我们将仅讨论当前节点具有左孩子的情况。决策如下: @@ -250,11 +257,15 @@ else: # this node has one child *Listing 9* ![6.13.查找树实现.figure4](assets/6.13.%E6%9F%A5%E6%89%BE%E6%A0%91%E5%AE%9E%E7%8E%B0.figure4.png) + + *Figure 4* 第三种情况是最难处理的情况(见Listing 10)。 如果一个节点有两个孩子,那么我们不太可能简单地提升其中一个节点来占据节点的位置。 然而,我们可以在树中搜索可用于替换被调度删除的节点的节点。 我们需要的是一个节点,它将保留现有的左和右子树的二叉搜索树关系。 执行此操作的节点是树中具有次最大键的节点。 我们将这个节点称为后继节点,我们将看一种方法来很快找到后继节点。 继承节点保证没有多于一个孩子,所以我们知道使用已经实现的两种情况删除它。 一旦删除了后继,我们只需将它放在树中,代替要删除的节点。 ![6.13.查找树实现.figure5](assets/6.13.%E6%9F%A5%E6%89%BE%E6%A0%91%E5%AE%9E%E7%8E%B0.figure5.png) + + *Figure 5* 处理第三种情况的代码展示在下一个列表中。 注意,我们使用辅助方法`findSuccessor` 和 `findMin` 来找到后继。 要删除后继,我们使用`spliceOut` 方法。 我们使用 `spliceOut` 的原因是它直接找到我们想要拼接的节点,并做出正确的更改。 我们可以递归调用删除,但是我们将浪费时间重新搜索关键节点。 @@ -332,7 +343,7 @@ Python 为我们提供了一个非常强大的函数,在创建迭代器时使 def __iter__(self): if self: if self.hasLeftChild(): - for elem in self.leftChiLd: + for elem in self.leftChild: yield elem yield self.key if self.hasRightChild(): diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.14.\346\237\245\346\211\276\346\240\221\345\210\206\346\236\220/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.14.\346\237\245\346\211\276\346\240\221\345\210\206\346\236\220/README.md" index c135403..d64f4b9 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.14.\346\237\245\346\211\276\346\240\221\345\210\206\346\236\220/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.14.\346\237\245\346\211\276\346\240\221\345\210\206\346\236\220/README.md" @@ -1,7 +1,7 @@ ## 6.14.查找树分析 -随着二叉搜索树的实现完成,我们将对我们已经实现的方法进行快速分析。让我们先来看看 `put` 方法。其性能的限制因素是二叉树的高度。从词汇部分回忆一下树的高度是根和最深叶节点之间的边的数量。高度是限制因素,因为当我们寻找合适的位置将一个节点插入到树中时,我们需要在树的每个级别最多进行一次比较。 +随着二叉搜索树的实现完成,我们将对已经实现的方法进行快速分析。让我们先来看看 `put` 方法。其性能的限制因素是二叉树的高度。从词汇部分回忆一下树的高度是根和最深叶节点之间的边的数量。高度是限制因素,因为当我们寻找合适的位置将一个节点插入到树中时,我们需要在树的每个级别最多进行一次比较。 二叉树的高度可能是多少?这个问题的答案取决于如何将键添加到树。如果按照随机顺序添加键,树的高度将在 log2^⁡n 附近,其中 n 是树中的节点数。这是因为如果键是随机分布的,其中大约一半将小于根,一半大于根。请记住,在二叉树中,根节点有一个节点,下一级节点有两个节点,下一个节点有四个节点。任何特定级别的节点数为 2^d ,其中 d 是级别的深度。完全平衡的二叉树中的节点总数为 2^h+1 - 1,其中 h 表示树的高度。 @@ -10,6 +10,8 @@ 不幸的是,可以通过以排序顺序插入键来构造具有高度 n 的搜索树!这样的树的示例见 Figure 6。在这种情况下,put方法的性能是 O(n)。 ![6.14.查找树分析.figure6](assets/6.14.%E6%9F%A5%E6%89%BE%E6%A0%91%E5%88%86%E6%9E%90.figure6.png) + + *Figure 6* 现在你明白了 `put` 方法的性能受到树的高度的限制,你可能猜测其他方法 `get`,`in` 和 `del` 也是有限制的。 由于 `get` 搜索树以找到键,在最坏的情况下,树被一直搜索到底部,并且没有找到键。 乍一看,`del` 似乎更复杂,因为它可能需要在删除操作完成之前搜索后继。 但请记住,找到后继者的最坏情况也只是树的高度,这意味着你只需要加倍工作。 因为加倍是一个常数因子,它不会改变最坏的情况 diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.15.\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.15.\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221/README.md" index b4d1461..057d718 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.15.\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.15.\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221/README.md" @@ -10,6 +10,8 @@ AVL树实现 Map 抽象数据类型就像一个常规的二叉搜索树,唯一 使用上面给出的平衡因子的定义,我们说如果平衡因子大于零,则子树是左重的。如果平衡因子小于零,则子树是右重的。如果平衡因子是零,那么树是完美的平衡。为了实现AVL树,并且获得具有平衡树的好处,如果平衡因子是 -1,0 或 1,我们将定义树平衡。一旦树中的节点的平衡因子是在这个范围之外,我们将需要一个程序来使树恢复平衡。Figure 1展示了不平衡,右重树和每个节点的平衡因子的示例。 ![6.15.平衡二叉搜索树.figure1](assets/6.15.%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.figure1.png) + + *Figure 1* diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221/README.md" index 5eb7532..a25a2a4 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221/README.md" @@ -3,6 +3,8 @@ 在我们继续之前,我们来看看执行这个新的平衡因子要求的结果。我们的主张是,通过确保树总是具有 -1,0或1 的平衡因子,我们可以获得更好的操作性能的关键操作。 让我们开始思考这种平衡条件如何改变最坏情况的树。有两种可能性,一个左重树和一个右重树。 如果我们考虑高度0,1,2和3的树,Figure 2 展示了在新规则下可能的最不平衡的左重树。 ![6.16.AVL平衡二叉搜索树.figure1](assets/6.16.AVL%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.figure1.png) + + *Figure 2* 看树中节点的总数,我们看到对于高度为0的树,有1个节点,对于高度为1的树,有1 + 1 = 2个节点,对于高度为2的树 是1 + 1 + 2 = 4,对于高度为3的树,有1 + 2 + 4 = 7。 更一般地,我们看到的高度h(Nh) 的树中的节点数量的模式是: @@ -11,7 +13,7 @@ 这种可能看起来很熟悉,因为它非常类似于斐波纳契序列。 给定树中节点的数量,我们可以使用这个事实来导出AVL树的高度的公式。 回想一下,对于斐波纳契数列,第i个斐波纳契数字由下式给出: ![6.16.AVL平衡二叉搜索树.2](assets/6.16.AVL%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.2.png) -一个重要的数学结果是,随着斐波纳契数列越来越大,Fi/Fi-1 的比率越来越接近黄金比率 `Φ= (1 +√5)/2`。 如果要查看上一个方程的导数,可以查阅数学文本。 我们将简单地使用该方程来近似 Fi,如 Fi =Φ^i / 5。 如果我们利用这个近似,我们可以重写 Nh 的方程为: +一个重要的数学结果是,随着斐波纳契数列越来越大,Fi/Fi-1 的比率越来越接近黄金比率 `Φ= (1 +√5)/2`。 如果要查看上一个方程的导数,可以查阅数学文本。 我们将简单地使用该方程来近似 Fi,如 Fi =Φ^i / 5。 如果我们利用这个近似,我们可以重写 Nh 的方程为: ![6.16.AVL平衡二叉搜索树.3](assets/6.16.AVL%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.3.png) 通过用其黄金比例近似替换斐波那契参考,我们得到: diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.17.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\256\236\347\216\260/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.17.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\256\236\347\216\260/README.md" index 7cc6ce7..0f6b027 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.17.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\256\236\347\216\260/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.17.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\256\236\347\216\260/README.md" @@ -3,7 +3,7 @@ 现在我们已经证明保持 AVL树的平衡将是一个很大的性能改进,让我们看看如何增加过程来插入一个新的键到树。由于所有新的键作为叶节点插入到树中,并且我们知道新叶的平衡因子为零,所以刚刚插入的节点没有新的要求。但一旦添加新叶,我们必须更新其父的平衡因子。这个新叶如何影响父的平衡因子取决于叶节点是左孩子还是右孩子。如果新节点是右子节点,则父节点的平衡因子将减少1。如果新节点是左子节点,则父节点的平衡因子将增加1。这个关系可以递归地应用到新节点的祖父节点,并且应用到每个祖先一直到树的根。由于这是一个递归过程,我们来看一下用于更新平衡因子的两种基本情况: * 递归调用已到达树的根。 -* 母公司的平衡因子已调整为零。你应该说服自己,一旦一个子树的平衡因子为零,那么它的祖先节点的平衡不会改变。 +* 父节点的平衡因子已调整为零。你应该说服自己,一旦一个子树的平衡因子为零,那么它的祖先节点的平衡不会改变。 我们将实现 AVL 树作为 `BinarySearchTree` 的子类。首先,我们将覆盖`_put` 方法并编写一个新的 `updateBalance` 辅助方法。这些方法如Listing 1所示。你将注意到,`_put` 的定义与简单二叉搜索树中的完全相同,除了第 7 行和第 13 行上对 `updateBalance` 的调用的添加。 @@ -44,6 +44,8 @@ def updateBalance(self,node): 要理解旋转是什么让我们看一个非常简单的例子。考虑 Figure 3左半部分的树。这棵树平衡因子为 -2,不平衡。为了使这棵树平衡,我们将使用以节点 A 为根的子树的左旋转。 ![6.16.平衡二叉搜索树实现.figure3](assets/6.16.%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%AE%9E%E7%8E%B0.figure3.png) + + *Figure 3* 要执行左旋转,我们基本上执行以下操作: @@ -62,6 +64,8 @@ def updateBalance(self,node): * 如果新根(C)已经有一个正确的孩子(D),那么使它成为新的右孩子(E)的左孩子。注意:由于新根(C)是 E 的左子节点,因此 E 的左子节点在此时保证为空。这允许我们添加一个新节点作为左孩子,不需进一步的考虑。 ![6.16.平衡二叉搜索树实现.figure4](assets/6.16.%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%AE%9E%E7%8E%B0.figure4.png) + + *Figure 4* @@ -93,6 +97,8 @@ def rotateLeft(self,rotRoot): 最后,第16-17行需要一些解释。 在这两行中,我们更新旧根和新根的平衡因子。 由于所有其他移动都是移动整个子树,所以所有其他节点的平衡因子不受旋转的影响。 但是我们如何在不完全重新计算新子树的高度的情况下更新平衡因子呢? 以下推导应该能说服你这些行是正确的。 ![6.16.平衡二叉搜索树实现.figure5](assets/6.16.%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%AE%9E%E7%8E%B0.figure5.png) + + *Figure 5* Figure 5 展示了左旋转。 B 和 D 是关键节点,A,C,E 是它们的子树。 设hx 表示以节点 x 为根的特定子树的高度。 根据定义,我们知道以下: @@ -132,10 +138,14 @@ rotRoot.balanceFactor = rotRoot.balanceFactor + 1 - min(0,newRoot.balanceFactor) 现在你可能认为我们已经完成了。 我们知道如何做左右旋转,我们知道什么时候应该做左旋或右旋,但是看看 Figure 6。由于节点 A 的平衡因子为-2,我们应该做左旋转。 但是,当我们围绕A做左旋转时会发生什么? ![6.16.平衡二叉搜索树实现.figure6](assets/6.16.%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%AE%9E%E7%8E%B0.figure6.png) + + *Figure 6* Figure 7 展示了我们在左旋后,我们现在已经在另一方面失去平衡。 如果我们做右旋以纠正这种情况,我们就回到我们开始的地方。 ![6.16.平衡二叉搜索树实现.figure7](assets/6.16.%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%AE%9E%E7%8E%B0.figure7.png) + + *Figure 7* 要纠正这个问题,我们必须使用以下规则集: @@ -148,6 +158,8 @@ Figure 8展示了这些规则如何解决我们在Figure 6和 Figure 7中遇到 ![6.16.平衡二叉搜索树实现.figure8](assets/6.16.%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E5%AE%9E%E7%8E%B0.figure8.png) +*Figure 8* + 实现这些规则的代码可以在我们的重新平衡方法中找到,如 Listing 3所示。上面的规则编号 1 是从第2行开始的if语句实现的。规则编号2是由第8行开始的elif语句实现的 。 diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.18.Map\346\212\275\350\261\241\346\225\260\346\215\256\347\273\223\346\236\204\346\200\273\347\273\223/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.18.Map\346\212\275\350\261\241\346\225\260\346\215\256\347\273\223\346\236\204\346\200\273\347\273\223/README.md" index 8ca99cf..d9f8152 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.18.Map\346\212\275\350\261\241\346\225\260\346\215\256\347\273\223\346\236\204\346\200\273\347\273\223/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.18.Map\346\212\275\350\261\241\346\225\260\346\215\256\347\273\223\346\236\204\346\200\273\347\273\223/README.md" @@ -1,6 +1,6 @@ ## 6.18.Map抽象数据结构总结 -在前面两章中,我们已经研究了可以用于实现 Map 抽象数据类型的几个数据结构。 二叉搜索表,散列表,二叉搜索树和平衡二叉搜索树。 总结这一节,让我们总结 Map ADT 定义的关键操作的每个数据结构的性能(见Table 1)。 +在前面两章中,我们已经研究了可以用于实现 Map 抽象数据类型的几个数据结构。二叉搜索表,散列表,二叉搜索树和平衡二叉搜索树。 总结这一节,让我们总结 Map ADT 定义的关键操作的每个数据结构的性能(见Table 1)。 ![6.18.Map抽象数据结构总结.table1](assets/6.18.Map%E6%8A%BD%E8%B1%A1%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%80%BB%E7%BB%93.table1.png) diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.2.\346\240\221\347\232\204\344\276\213\345\255\220/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.2.\346\240\221\347\232\204\344\276\213\345\255\220/README.md" index 697e0d2..72d1122 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.2.\346\240\221\347\232\204\344\276\213\345\255\220/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.2.\346\240\221\347\232\204\344\276\213\345\255\220/README.md" @@ -5,6 +5,8 @@ 在我们开始研究树形数据结构之前,让我们来看几个常见的例子。我们树的第一个例子是生物学的分类树。Figure 1 展示了一些动物的生物分类的实例。从这个简单的例子,我们可以了解树的几个属性。此示例演示的第一个属性是树是分层的。通过分层,我们的意思是树的层次结构,更接近顶部的是抽象的东西和底部附近是更具体的东西。层次结构的顶部是 `Kingdom`,树的下一层(上面的层的“Children”)是 `Phylum`,然后是 `Class`,等等。然而,无论我们在分类树中有多深,所有的生物仍然是 `animals`。 ![6.2.树的例子.figure1](assets/6.2.%E6%A0%91%E7%9A%84%E4%BE%8B%E5%AD%90.figure1.png) + + *Figure 1* 注意,你可以从树的顶部开始,并沿着圆圈和箭头一直到底部的路径。在树的每一层,我们可能问自己一个问题,然后遵循与我们的答案一致的路径。例如,我们可能会问,“这个动物是Chordate(脊椎动物)还是Arthropod(节肢动物)?”如果答案是“Chordate”,那么我们遵循这条路径,问“这个Chordate是 Mammal(哺乳动物)吗?”如果不是,我们就卡住了这个简化的例子)。当我们在哺乳动物那层时,我们问“这个哺乳动物是Primate(灵长类动物)还是 Carnivore(食肉动物)?”我们可以遵循以下路径,直到我们到达树的最底部,在那里我们有共同的名字。 @@ -16,6 +18,8 @@ 你可能每天使用的树结构的另一个示例是文件系统。在文件系统中,目录或文件夹被构造为树。Figure 2 说明了 Unix文件系统层次结构的一小部分。 ![6.2.树的例子.figure2](assets/6.2.%E6%A0%91%E7%9A%84%E4%BE%8B%E5%AD%90.figure2.png) + + *Figure2* 文件系统树与生物分类树有很多共同之处。你可以遵循从根目录到任何目录的路径。 该路径将唯一标识该子目录(及其中的所有文件)。 树的另一个重要属性来源于它们的层次性质,你可以将树的整个部分(称为子树)移动到树中的不同位置,而不影响层次结构的较低级别。 例如,我们可以使用整个子树 /etc/,从根节点分离,并重新附加在 usr/ 下。 这将把 httpd 的唯一路径名从 /etc/httpd 更改为 /usr/etc/httpd,但不会影响 httpd 目录的内容或任何子级。 @@ -42,6 +46,8 @@ ``` ![6.2.树的例子.figure3](assets/6.2.%E6%A0%91%E7%9A%84%E4%BE%8B%E5%AD%90.figure3.png) + + *Figure 3* HTML源代码和伴随源的树说明了另一个层次结构。请注意,树的每个级别都对应于HTML标记内的嵌套级别。源中的第一个标记是 ,最后一个是 页面中的所有其余标记都是成对的。 如果你检查,你会看到这个嵌套属性在树的所有级别都是 true。 diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.3.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.3.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211/README.md" index d29a94e..f981983 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.3.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.3.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211/README.md" @@ -35,11 +35,15 @@ Figure 3 展示了适合定义一的树。边上的箭头指示连接的方向。 ![6.3.词汇和定义.figure3](assets/6.3.%E8%AF%8D%E6%B1%87%E5%92%8C%E5%AE%9A%E4%B9%89.figure3.png) + + *Figure 3* 定义二:树是空的,或者由一个根节点和零个或多个子树组成,每个子树也是一棵树。每个子树的根节点通过边连接到父树的根节点。 Figure 4 说明了树的这种递归定义。使用树的递归定义,我们知道 Figure 4 中的树至少有四个节点,因为表示一个子树的每个三角形必须有一个根节点。 它可能有比这更多的节点,但我们不知道,除非我们更深入树。 ![6.3.词汇和定义.figure4](assets/6.3.%E8%AF%8D%E6%B1%87%E5%92%8C%E5%AE%9A%E4%B9%89.figure4.png) + + *Figure 4* diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.4.\345\210\227\350\241\250\350\241\250\347\244\272/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.4.\345\210\227\350\241\250\350\241\250\347\244\272/README.md" index 01c62f6..563fe68 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.4.\345\210\227\350\241\250\350\241\250\347\244\272/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.4.\345\210\227\350\241\250\350\241\250\347\244\272/README.md" @@ -3,6 +3,8 @@ 在由列表表示的树中,我们将从 Python 的列表数据结构开始,并编写上面定义的函数。虽然将接口作为一组操作在列表上编写与我们实现的其他抽象数据类型有点不同,但这样做是有趣的,因为它为我们提供了一个简单的递归数据结构,我们可以直接查看和检查。在列表树的列表中,我们将根节点的值存储为列表的第一个元素。列表的第二个元素本身将是一个表示左子树的列表。列表的第三个元素将是表示右子树的另一个列表。为了说明这种存储技术,让我们看一个例子。 Figure 1 展示了一个简单的树和相应的列表实现。 ![6.4.列表表示.figure1](assets/6.4.%E5%88%97%E8%A1%A8%E8%A1%A8%E7%A4%BA.figure1.png) + + *Figure 1* ``` python diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.5.\350\212\202\347\202\271\350\241\250\347\244\272/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.5.\350\212\202\347\202\271\350\241\250\347\244\272/README.md" index fc23370..c1a8a91 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.5.\350\212\202\347\202\271\350\241\250\347\244\272/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.5.\350\212\202\347\202\271\350\241\250\347\244\272/README.md" @@ -5,6 +5,8 @@ 使用节点和引用,我们认为树结构类似于 Figure 2 所示。 ![6.5.节点表示.figure2](assets/6.5.%E8%8A%82%E7%82%B9%E8%A1%A8%E7%A4%BA.figure2.png) + + *Figure 2* 我们将从节点和引用方法的一个简单的类定义开始,如 Listing 4 所示。要记住这个表示重要的事情是 `left` 和 `right` 的属性将成为对 `BinaryTree` 类的其他实例的引用。 例如,当我们在树中插入一个新的左子节点时,我们创建另一个 `BinaryTree` 实例,并在根节点中修改`self.leftChild` 来引用新树节点。 diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.6.\345\210\206\346\236\220\346\240\221/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.6.\345\210\206\346\236\220\346\240\221/README.md" index 3b4cb7d..3f6be21 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.6.\345\210\206\346\236\220\346\240\221/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.6.\345\210\206\346\236\220\346\240\221/README.md" @@ -3,16 +3,22 @@ 随着我们的树数据结构的实现完成,我们现在看一个例子,说明如何使用树来解决一些真正的问题。在本节中,我们将讨论分析树。 分析树可以用于表示诸如句子或数学表达式的真实世界构造。 ![6.6.分析树.figure1](assets/6.6.%E5%88%86%E6%9E%90%E6%A0%91.figure1.png) + + *Figure 1* Figure 1 展示了一个简单句子的层次结构。 将句子表示为树结构允许我们通过使用子树来处理句子的各个部分。 ![6.6.分析树.figure2](assets/6.6.%E5%88%86%E6%9E%90%E6%A0%91.figure2.png) + + *Figure 2* 我们还可以表示诸如 `((7 + 3)*(5-2))` 数学表达式作为分析树,如 Figure 2 所示。我们早看过完全括号表达式,所以我们知道这个表达式是什么?我们知道乘法具有比加法或减法更高的优先级。由于括号,我们知道在做乘法之前,我们必须计算括号里面的加法和减法表达式。树的层次结构有助于我们了解整个表达式的求值顺序。在我们计算顶层乘法之前,我们必须计算子树中的加法和减法。作为左子树的加法结果为10。减法,即右子树,计算结果为3。使用树的层次结构,我们可以简单地用一个节点替换整个子树,一旦我们计算了表达式中这些子树。这个替换过程给出了 Figure 3 所示的简化树。 ![6.6.分析树.figure3](assets/6.6.%E5%88%86%E6%9E%90%E6%A0%91.figure3-1.png) + + *Figure 3* 在本节的其余部分,我们将更详细地检查分析树。 特别的,我们会看 @@ -35,19 +41,39 @@ Figure 1 展示了一个简单句子的层次结构。 将句子表示为树结 ![6.6.分析树.figure4-1](assets/6.6.%E5%88%86%E6%9E%90%E6%A0%91.figure4-1.png) ![6.6.分析树.figure4-2](assets/6.6.%E5%88%86%E6%9E%90%E6%A0%91.figure4-2.png) + + *Figure 4* 使用 Figure 4,让我们一步一步地浏览示例: a. 创建一个空树。 + + b. 读取 ( 作为第一个标记。按规则1,创建一个新节点作为根的左子节点。使当前节点到这个新子节点。 + + c. 读取 3 作为下一个符号。按照规则3,将当前节点的根值设置为3,使当前节点返回到父节点。 + + d. 读取 + 作为下一个符号。根据规则2,将当前节点的根值设置为+,并添加一个新节点作为右子节点。新的右子节点成为当前节点。 + + e. 读取 ( 作为下一个符号,按规则1,创建一个新节点作为当前节点的左子节点,新的左子节点成为当前节点。 + + f. 读取 4 作为下一个符号。根据规则3,将当前节点的值设置为 4。使当前节点返回到父节点。 + + g. 读取 * 作为下一个符号。根据规则2,将当前节点的根值设置为 * ,并创建一个新的右子节点。新的右子节点成为当前节点。 + + h. 读取 5 作为下一个符号。根据规则3,将当前节点的根值设置为5。使当前节点返回到父节点。 + + i. 读取 ) 作为下一个符号。根据规则4,使当前节点返回到父节点。 + + j. 读取 ) 作为下一个符号。根据规则4,使当前节点返回到父节点 + 。没有+ 的父节点,所以我们完成创建。 从上面的例子,很明显,我们需要跟踪当前节点以及当前节点的父节点。树接口为我们提供了一种通过 `getLeftChild` 和 `getRightChild` 方法获取节点的子节点的方法,但是我们如何跟踪父节点呢?当我们遍历树时,保持跟踪父对象的简单解决方案是使用栈。每当我们想下降到当前节点的子节点时,我们首先将当前节点入到栈上。当我们想要返回到当前节点的父节点时,我们将父节点从栈中弹出。 diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.7.\346\240\221\347\232\204\351\201\215\345\216\206/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.7.\346\240\221\347\232\204\351\201\215\345\216\206/README.md" index 04cfee3..5357419 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.7.\346\240\221\347\232\204\351\201\215\345\216\206/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.7.\346\240\221\347\232\204\351\201\215\345\216\206/README.md" @@ -1,6 +1,6 @@ ## 6.7.树的遍历 -现在我们已经检查了树数据结构的基本功能,现在是查看树的一些额外使用模式的时候了。这些使用模式可以分为我们访问树的节点的三种方式。有三种常用的模式来访问树中的所有节点。这些模式之间的差异是每个节点被访问的顺序。我们称这种访问节点方式为“遍历”。我们将看到的三个遍历称为`前序,后序`和`后序` 。让我们通过更仔细地定义这三个遍历,然后看看这些模式有用的一些例子。 +我们已经见到了树数据结构的基本功能,现在是看树的一些额外使用模式的时候了。这些使用模式可以分为我们访问树节点的三种方式。有三种常用的模式来访问树中的所有节点。这些模式之间的差异是每个节点被访问的顺序。我们称这种访问节点方式为“遍历”。我们将看到三种遍历方式称为`前序,中序`和`后序` 。让我们更仔细地定义这三种遍历方式,然后看看这些模式有用的一些例子。 **前序** 在前序遍历中,我们首先访问根节点,然后递归地做左侧子树的前序遍历,随后是右侧子树的递归前序遍历。 @@ -12,6 +12,8 @@ 让我们看一些例子,来说明这三种遍历。首先看前序遍历。作为遍历的树的示例,我们将把这本书表示为树。这本书是树的根,每一章都是根节点的一个孩子。章节中的每个章节都是章节的子节点,每个小节都是章节的子节点,依此类推。Figure 5 展示了一本只有两章的书的有限版本。注意,遍历算法适用于具有任意数量子节点的树,但是我们现在使用二叉树。 ![6.7.树的遍历.figure5](assets/6.7.%E6%A0%91%E7%9A%84%E9%81%8D%E5%8E%86.figure5.png) + + *Figure 5* 假设你想从前到后读这本书。前序遍历给你正确的顺序。从树的根(Book节点)开始,我们将遵循前序遍历指令。我们递归调用左孩子的 `preorder`,在这种情况下是 `Chapter1`。我们再次递归调用左孩子的 `preorder` 来得到 `Section 1.1` 。由于 `Section 1.1` 没有子节点,我们不再进行任何额外的递归调用。当我们完成 `Section 1.1`,我们将树向上移动到`Chapter1`。此时,我们仍然需要访问 `Chapter1` 的右子树 `Section 1.2`。和前面一样,我们访问左子树,它将我们带到 ` Section 1.2.1`,然后访问 `Section 1.2.2`。在 `Section 1.2` 完成后,我们返回到 `Chapter1`。然后,我们返回到 `Book` 节点,并按照相同过程遍历 `Chapter2`。 @@ -41,7 +43,7 @@ def preorder(self): ``` *Listing 3* -以下哪两种方式实现前序最好? 答案是在这种情况下,实现前序作为外部函数可能更好。原因是你很少只是想遍历树。在大多数情况下,将要使用其中一个基本的遍历模式来完成其他任务。 事实上,我们将在下面的例子中看到后序遍历模式与我们前面编写的用于计算分析树的代码非常接近。 因此,我们用外部函数实现其余的遍历。 +以上哪种方式实现前序最好? 答案是在这种情况下,实现前序作为外部函数可能更好。原因是你很少只是想遍历树。在大多数情况下,将要使用其中一个基本的遍历模式来完成其他任务。 事实上,我们将在下面的例子中看到后序遍历模式与我们前面编写的用于计算分析树的代码非常接近。 因此,我们用外部函数实现其余的遍历。 Listing 4 中所示的后序遍历算法几乎与前序遍历顺序相同,只是将 print 调用移动到函数的末尾。 @@ -71,7 +73,7 @@ def postordereval(tree): ``` *Listing 5* -请注意,Listing 4中的形式与 Listing 5中的形式相同,只是不是在函数的末尾打印值,而是返回它。 这允许我们保存从第 6 行和第 7 行的递归调用返回的值。然后,我们使用这些保存的值以及第 9 行的运算符一起计算结果。 +请注意,Listing 4中的形式与 Listing 5中的形式相同,只是不在函数的末尾打印值,而是返回它。 这允许我们保存从第 6 行和第 7 行的递归调用返回的值。然后,我们使用这些保存的值以及第 9 行的运算符一起计算结果。 在本节中我们最终将看到中序遍历。 在中序遍历中,我们访问左子树,其次是根,最后是右子树。 Listing 6 展示了我们的中序遍历的代码。 注意,在所有三个遍历函数中,我们只是改变 print 语句相对于两个递归函数调用的位置。 diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.8.\345\237\272\344\272\216\344\272\214\345\217\211\345\240\206\347\232\204\344\274\230\345\205\210\351\230\237\345\210\227/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.8.\345\237\272\344\272\216\344\272\214\345\217\211\345\240\206\347\232\204\344\274\230\345\205\210\351\230\237\345\210\227/README.md" index c4be08a..4d2da38 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.8.\345\237\272\344\272\216\344\272\214\345\217\211\345\240\206\347\232\204\344\274\230\345\205\210\351\230\237\345\210\227/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.8.\345\237\272\344\272\216\344\272\214\345\217\211\345\240\206\347\232\204\344\274\230\345\205\210\351\230\237\345\210\227/README.md" @@ -1,6 +1,6 @@ ## 6.8.基于二叉堆的优先队列 -在前面的部分中,你了解了称为队列的先进先出数据结构。队列的一个重要变种称为优先级队列。优先级队列的作用就像一个队列,你可以通过从前面删除一个项目来出队。然而,在优先级队列中,队列中的项目的逻辑顺序由它们的优先级确定。最高优先级项在队列的前面,最低优先级的项在后面。因此,当你将项排入优先级队列时,新项可能会一直移动到前面。我们将在下一章中研究一些图算法看到优先级队列是有用的数据结构。 +在前面的部分中,你了解了称为队列的先进先出数据结构。队列的一个重要变种称为优先级队列。优先级队列的作用就像一个队列,你可以通过从前面删除一个项目来出队。然而,在优先级队列中,队列中的项的逻辑顺序由它们的优先级确定。最高优先级项在队列的前面,最低优先级的项在后面。因此,当你将项排入优先级队列时,新项可能会一直移动到前面。我们将在下一章中研究一些图算法看到优先级队列是有用的数据结构。 你可能想到了几种简单的方法使用排序函数和列表实现优先级队列。然而,插入列表是 O(n) 并且排序列表是 O(nlogn)。我们可以做得更好。实现优先级队列的经典方法是使用称为二叉堆的数据结构。二叉堆将允许我们在 O(logn) 中排队和取出队列。 diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.11.\351\252\221\345\243\253\344\271\213\346\227\205/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.11.\351\252\221\345\243\253\344\271\213\346\227\205/README.md" index e97ede5..db66b85 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.11.\351\252\221\345\243\253\344\271\213\346\227\205/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.11.\351\252\221\345\243\253\344\271\213\346\227\205/README.md" @@ -1,6 +1,6 @@ ## 7.11.骑士之旅 -另一个经典问题,我们可以用来说明第二个通用图算法称为 “骑士之旅”。骑士之旅图是在一个棋盘上用一个棋子让骑士玩。图的目的是找到一系列的动作,让骑士访问板上的每格一次。一个这样的序列被称为“旅游”。骑士的旅游难题已经吸引了象棋玩家,数学家和计算机科学家多年。一个 8×8 棋盘的可能的游览次数的上限为 1.305×10^35 ;然而,还有更多可能的死胡同。显然,这是一个需要脑力,计算能力,或两者都需要的问题。 +另一个经典问题,我们可以用来说明第二个通用图算法称为 “骑士之旅”。骑士之旅图是在一个棋盘上用一个棋子当骑士玩。图的目的是找到一系列的动作,让骑士访问板上的每格一次。一个这样的序列被称为“旅游”。骑士的旅游难题已经吸引了象棋玩家,数学家和计算机科学家多年。一个 8×8 棋盘的可能的游览次数的上限为 1.305×10^35 ;然而,还有更多可能的死胡同。显然,这是一个需要脑力,计算能力,或两者都需要的问题。 虽然研究人员已经研究了许多不同的算法来解决骑士的旅游问题,图搜索是最容易理解的程序之一。再次,我们将使用两个主要步骤解决问题: diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.12.\346\236\204\345\273\272\351\252\221\345\243\253\344\271\213\346\227\205\345\233\276/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.12.\346\236\204\345\273\272\351\252\221\345\243\253\344\271\213\346\227\205\345\233\276/README.md" index 44801b0..4dea64c 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.12.\346\236\204\345\273\272\351\252\221\345\243\253\344\271\213\346\227\205\345\233\276/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.12.\346\236\204\345\273\272\351\252\221\345\243\253\344\271\213\346\227\205\345\233\276/README.md" @@ -3,6 +3,8 @@ 为了将骑士的旅游问题表示为图,我们将使用以下两个点:棋盘上的每个正方形可以表示为图形中的一个节点。 骑士的每个合法移动可以表示为图形中的边。 Figure 1 展示了骑士的移动以及图中的对应边。 ![7.12.构建骑士之旅图.figure1](assets/7.12.%E6%9E%84%E5%BB%BA%E9%AA%91%E5%A3%AB%E4%B9%8B%E6%97%85%E5%9B%BE.figure1.png) + + *Figure 1* 要构建一个 `n*n` 的完整图,我们可以使用 Listing 1 中所示的 Python 函数。`knightGraph` 函数在整个板上进行一次遍历。 在板上的每个方块上,`knightGraph` 函数调用 `genLegalMoves` ,为板上的位置创建一个移动列表。 所有移动在图形中转换为边。 另一个帮助函数 `posToNodeId` 按照行和列将板上的位置转换为类似于 Figure 1 所示的顶点数的线性顶点数。 @@ -53,6 +55,8 @@ def legalCoord(x,bdSize): Figure 2 展示了一个 8×8 板的可能移动的完整图。图中有正好 336 个边。 注意,与板的边相对应的顶点具有比板中间的顶点更少的连接(移动数)。 再次我们可以看到图的稀疏。 如果图形完全连接,则会有 4,096 个边。 由于只有336 个边,邻接矩阵只有 8.2% 填充率。 ![7.12.构建骑士之旅图.figure2](assets/7.12.%E6%9E%84%E5%BB%BA%E9%AA%91%E5%A3%AB%E4%B9%8B%E6%97%85%E5%9B%BE.figure2.png) + + *Figure 2* diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.13.\345\256\236\347\216\260\351\252\221\345\243\253\344\271\213\346\227\205/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.13.\345\256\236\347\216\260\351\252\221\345\243\253\344\271\213\346\227\205/README.md" index ef1be2e..1097d30 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.13.\345\256\236\347\216\260\351\252\221\345\243\253\344\271\213\346\227\205/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.13.\345\256\236\347\216\260\351\252\221\345\243\253\344\271\213\346\227\205/README.md" @@ -36,10 +36,14 @@ Figure 中 `knightTour` 从节点 A 开始.与 A 相邻的节点是 B 和 D。 ![7.13.实现骑士之旅.figure3-1](assets/7.13.%E5%AE%9E%E7%8E%B0%E9%AA%91%E5%A3%AB%E4%B9%8B%E6%97%85.figure3-1.png) ![7.13.实现骑士之旅.figure3-2](assets/7.13.%E5%AE%9E%E7%8E%B0%E9%AA%91%E5%A3%AB%E4%B9%8B%E6%97%85.figure3-2.png) + + *Figure 3-10* Figure 11 展示了一个 8×8 板的完整遍历。有许多可能的路径; 一些是对称的。 通过一些修改,你可以使遍历开始和结束在同一个正方形。 ![7.13.实现骑士之旅.figure3](assets/7.13.%E5%AE%9E%E7%8E%B0%E9%AA%91%E5%A3%AB%E4%B9%8B%E6%97%85.figure3.png) + + *Figure 10* diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.14.\351\252\221\345\243\253\344\271\213\346\227\205\345\210\206\346\236\220/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.14.\351\252\221\345\243\253\344\271\213\346\227\205\345\210\206\346\236\220/README.md" index 8e178a8..8bcc2fc 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.14.\351\252\221\345\243\253\344\271\213\346\227\205\345\210\206\346\236\220/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.14.\351\252\221\345\243\253\344\271\213\346\227\205\345\210\206\346\236\220/README.md" @@ -2,6 +2,8 @@ 有最后关于骑士之旅一个有趣的话题,然后我们将继续到深度优先搜索的通用版本。主题是性能。特别是,`knightTour` 对于你选择下一个要访问的顶点的方法非常敏感。例如,在一个5乘5的板上,你可以在快速计算机上处理路径花费大约1.5秒。但是如果你尝试一个 8×8 的板,会发生什么?在这种情况下,根据计算机的速度,你可能需要等待半小时才能获得结果!这样做的原因是我们到目前为止所实现的骑士之旅问题是大小为 O(k^N ) 的指数算法,其中 N 是棋盘上的方格数,k 是小常数。Figure 12 可以帮助我们搞清楚为什么会这样。树的根表示搜索的起点。从那里,算法生成并检查骑士可以做出的每个可能的移动。正如我们之前注意到的,可能的移动次数取决于骑士在板上的位置。在角落只有两个合法的动作,在角落邻近的正方形有三个,在板的中间有八个。Figure 13 展示了板上每个位置可能的移动次数。在树的下一级,再次有 2 到 8 个可能的下一个移动。要检查的可能位置的数量对应于搜索树中的节点的数量。 ![7.14.骑士之旅分析.figure12-13](assets/7.14.%E9%AA%91%E5%A3%AB%E4%B9%8B%E6%97%85%E5%88%86%E6%9E%90.figure12-13.png) + + *Figure 12-13* 我们已经看到,高度 N 的二叉树中的节点数量是 2^N+1 - 1。对于具有可以具有多达八个孩子而不是两个节点的树,节点的数量要大得多。因为每个节点的分支因子是可变的,我们可以使用平均分支因子估计节点的数量。重要的是要注意,这个算法是指数:k^N+1 - 1,其中 k 是板的平均分支因子。让我们看看这增长有多快!对于 5×5 的板,树将是 25 级深,或者 N = 24,将第一级算为级 0。平均分支因子是 k = 3.8 因此,搜索树中的节点数量是 3.8^25 - 1 或3.12×10^14 。对于 6x6 板,k = 4.4,有 1.5×10^23 个节点,对于常规的 8x8 棋盘,k = 5.25 ,有 1.3×10^46 。当然,由于问题有多个解决方案,我们不必去探索每个节点,但是我们必须探索的节点的小数部分只是一个不会改变问题的指数性质的常数乘数。我们将把它作为一个练习,看看你是否可以表示k 作为板的大小的函数。 diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.15.\351\200\232\347\224\250\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.15.\351\200\232\347\224\250\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242/README.md" index 04bf0c9..fc890f1 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.15.\351\200\232\347\224\250\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.15.\351\200\232\347\224\250\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242/README.md" @@ -55,11 +55,14 @@ class DFSGraph(Graph): ![7.15.通用深度优先搜索.figure14-2](assets/7.15.%E9%80%9A%E7%94%A8%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2.figure14-2.png) ![7.15.通用深度优先搜索.figure14-3](assets/7.15.%E9%80%9A%E7%94%A8%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2.figure14-3.png) + *Figure 14-25* 每个节点的开始和结束时间展示一个称为 `括号属性` 的属性。 该属性意味着深度优先树中的特定节点的所有子节点具有比它们的父节点更晚的发现时间和更早的完成时间。 Figure 26 展示了由深度优先搜索算法构造的树。 ![7.15.通用深度优先搜索.figure26](assets/7.15.%E9%80%9A%E7%94%A8%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2.figure26.png) + + *Figure 26* diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.17.\346\213\223\346\211\221\346\216\222\345\272\217/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.17.\346\213\223\346\211\221\346\216\222\345\272\217/README.md" index a7e013a..99a17b7 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.17.\346\213\223\346\211\221\346\216\222\345\272\217/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.17.\346\213\223\346\211\221\346\216\222\345\272\217/README.md" @@ -3,6 +3,8 @@ 为了表明计算机科学家可以把任何东西变成一个图问题,让我们考虑做一批煎饼的问题。 菜谱真的很简单:1个鸡蛋,1杯煎饼粉,1汤匙油 和 3/4 杯牛奶。 要制作煎饼,你必须加热炉子,将所有的成分混合在一起,勺子搅拌。 当开始冒泡,你把它们翻过来,直到他们底部变金黄色。 在你吃煎饼之前,你会想要加热一些糖浆。 Figure 27将该过程示为图。 ![7.17.拓扑排序.figure27](assets/7.17.%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F.figure27.png) + + *Figure 27* 制作煎饼的困难是知道先做什么。从 Figure 27 可以看出,你可以从加热煎饼开始,或通过添加任何成分到煎饼。为了帮助我们决定应该做的每一个步骤的精确顺序,我们转向一个图算法称为 `拓扑排序`。 @@ -18,11 +20,15 @@ Figure 28 展示了在 Figure 26 所示的薄煎饼制作图上由 dfs 构建的深度优先森林。 ![7.17.拓扑排序.figure28](assets/7.17.%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F.figure28.png) + + *Figure 28* 最后,Figure 29 展示了将拓扑排序算法应用于我们的图形的结果。 现在所有的分支已被删除,我们知道确切的做煎饼的步骤顺序。 ![7.17.拓扑排序.figure29](assets/7.17.%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F.figure29.png) + + *Figure 29* diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.18.\345\274\272\350\277\236\351\200\232\345\210\206\351\207\217/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.18.\345\274\272\350\277\236\351\200\232\345\210\206\351\207\217/README.md" index a93c15a..a282d33 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.18.\345\274\272\350\277\236\351\200\232\345\210\206\351\207\217/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.18.\345\274\272\350\277\236\351\200\232\345\210\206\351\207\217/README.md" @@ -5,22 +5,30 @@ 像 Google 和 Bing 这样的搜索引擎利用了网页上的页面形成非常大的有向图。 为了将万维网变换为图,我们将把一个页面视为一个顶点,并将页面上的超链接作为将一个顶点连接到另一个顶点的边缘。 Figure 30 展示了从 Luther College 的计算机科学主页开始,通过跟踪从一页到下一页的链接产生的图的非常小的部分。当然,这个图可能是巨大的,所以我们把它限制在距离 CS 主页不超过 10 个链接的网站。 ![7.18.强连接组件.figure30](assets/7.18.%E5%BC%BA%E8%BF%9E%E6%8E%A5%E7%BB%84%E4%BB%B6.figure30.png) + + *Figure 30* 如果你看 Figure 30中的图形,你可能会有一些有趣的观察。首先你可能会注意到,图上的许多其他网站是其他路德学院网站。第二,你可能注意到有几个链接到爱荷华州的其他学院。第三,你可能注意到有几个链接到其他文理学院。你可能会得出这样的结论,网络集群上的网站在一些级别上底层结构类似。 可以帮助找到图中高度互连的顶点的集群的一种图算法被称为强连通分量算法(SCC)。我们正式定义图 G 的强连通分量 C 作为顶点 C⊂V 的最大子集,使得对于每对顶点 v,w∈C,我们具有从 v 到 w 的路径和从 w 到 v 的路径。Figure 27 展示了具有三个强连接分量的简单图。强连接分量由不同的阴影区域标识。 ![7.18.强连通分量.figure27](assets/7.18.%E5%BC%BA%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F.figure27.png) + + *Figure 27* 一旦确定了强连通分量,我们就可以通过将一个强连通分量中的所有顶点组合成一个较大的顶点来显示该图的简化视图。 Figure 31中的曲线图的简化版本如 Figure 32所示。 ![7.18.强连通分量.figure31](assets/7.18.%E5%BC%BA%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F.figure31.png) + + *Figure 32* 我们再次看到,我们可以通过使用深度优先搜索来创建一个非常强大和高效的算法。 在我们处理主 SCC 算法之前,我们必须考虑另一个定义。 图 G 的转置被定义为图 G^T ,其中图中的所有边已经反转。 也就是说,如果在原始图中存在从节点 A 到节点 B 的有向边,则 G^T 将包含从节点 B 到节点 A 的边。Figure 33和 Figure 34 展示了简单图及其变换。 ![7.18.强连通分量.figure32](assets/7.18.%E5%BC%BA%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F.figure32.png) + + *Figure 33-34* 再看看数字。 请注意,Figure 33中的图形有两个强连通分量。 现在看看Figure 34。注意它也有两个强连通分量。 @@ -35,11 +43,15 @@ 让我们在 Figure 31中的示例图上跟踪上述步骤的操作。Figure 35 展示了由DFS 算法为原始图计算的开始和结束时间。 Figure 36 展示了通过在转置图上运行 DFS 计算的开始和结束时间。 ![7.18.强连通分量.figure35-36](assets/7.18.%E5%BC%BA%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F.figure35-36.png) + + *Figure 36* 最后,Figure 37 展示了在强连通分量算法的步骤 3 中产生的三棵树的森林。 你会注意到,我们不为你提供 SCC 算法的 Python 代码,我们将此程序作为练习。 ![7.18.强连通分量.figure37](assets/7.18.%E5%BC%BA%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F.figure37.png) + + *Figure 37* diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.19.\346\234\200\347\237\255\350\267\257\345\276\204\351\227\256\351\242\230/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.19.\346\234\200\347\237\255\350\267\257\345\276\204\351\227\256\351\242\230/README.md" index e5c3e83..4e49777 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.19.\346\234\200\347\237\255\350\267\257\345\276\204\351\227\256\351\242\230/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.19.\346\234\200\347\237\255\350\267\257\345\276\204\351\227\256\351\242\230/README.md" @@ -3,6 +3,8 @@ 当你在网上冲浪,发送电子邮件,或从校园的另一个地方登录实验室计算机时,大量的工作正在幕后进行,以获取你计算机上的信息传输到另一台计算机。 深入研究信息如何通过互联网从一台计算机流向另一台计算机是计算机网络中的一个主要课题。 然而,我们将讨论互联网如何工作足以理解另一个非常重要的图算法。 ![7.19.最短路径问题.figure1](assets/7.19.%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84%E9%97%AE%E9%A2%98.figure1.png) + + *Figure 1* Figure 1 展示了 Internet 上的通信如何工作的高层概述。当使用浏览器从服务器请求网页时,请求必须通过局域网传输,并通过路由器传输到 Internet上。 该请求通过因特网传播,并最终到达服务器所在的局域网路由器。 请求的网页然后通过相同的路由器回到您的浏览器。 在 Figure 1中标记为 “因特网” 的云是附加的路由器。所有这些路由器一起工作,让信息从一个地方到另一个地方。 可以看到有许多路由器,如果你的计算机支持 `traceroute` 命令。下面的文本显示 `traceroute` 命令的输出,说明在 `Luther College` 的Web服务器和明尼苏达大学的邮件服务器之间有13个路由器 @@ -29,6 +31,8 @@ Routers from One Host to the Next over the Internet 互联网上的每个路由器都连接到一个或多个路由器。因此,如果在一天的不同时间运行 traceroute 命令,你很可能会看到你的信息在不同的时间流经不同的路由器。这是因为存在与一对路由器之间的每个连接相关联的成本,这取决于业务量,一天中的时间以及许多其他因素。到这个时候,你不会惊讶,我们可以将路由器的网络表示为带有加权边的图形。 ![7.19.最短路径问题.figure2](assets/7.19.%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84%E9%97%AE%E9%A2%98.figure2.png) + + *Figure 2* Figure 2 展示了表示互联网中的路由器的互连的加权图的一个小例子。我们要解决的问题是找到具有最小总权重的路径,沿着该路径路由传送任何给定的消息。这个问题听起来很熟悉,因为它类似于我们使用广度优先搜索解决的问题,我们这里关心的是路径的总权重,而不是路径中的跳数。应当注意,如果所有权重相等,则问题是相同的。 diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.2.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.2.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211/README.md" index 01f4e21..2651883 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.2.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.2.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211/README.md" @@ -1,12 +1,12 @@ ## 7.2.词汇和定义 -现在我们已经看了一些图的示例,我们将更正式地定义图及其组件。我们已经从对树的讨论中知道了一些这些术语。 +现在我们已经看了一些图的示例,我们将更正式地定义图及其组件。我们已经从对树的讨论中知道了一些术语。 **顶点** 顶点(也称为“节点”)是图的基本部分。它可以有一个名称,我们将称为“键”。一个顶点也可能有额外的信息。我们将这个附加信息称为“有效载荷”。 **边** -边(也称为“弧”)是图的另一个基本部分。边连接两个顶点,以表明它们之间存在关系。边可以是单向的或双向的。如果图中的边都是单向的,我们称该图是`有向图`。上面显示的课程先决条件图显然是一个图,因为你必须在其他课程之前学习一些课程。 +边(也称为“弧”)是图的另一个基本部分。边连接两个顶点,以表明它们之间存在关系。边可以是单向的或双向的。如果图中的边都是单向的,我们称该图是`有向图`。上面显示的课程先决条件显然是一个图,因为你必须在其他课程之前学习一些课程。 **权重** 边可以被加权以示出从一个顶点到另一个顶点的成本。例如,在将一个城市连接到另一个城市的道路的图表中,边上的权重可以表示两个城市之间的距离。 diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.22.Prim\347\224\237\346\210\220\346\240\221\347\256\227\346\263\225/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.22.Prim\347\224\237\346\210\220\346\240\221\347\256\227\346\263\225/README.md" index a3fcbc8..cb783e7 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.22.Prim\347\224\237\346\210\220\346\240\221\347\256\227\346\263\225/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.22.Prim\347\224\237\346\210\220\346\240\221\347\256\227\346\263\225/README.md" @@ -3,6 +3,8 @@ 对于我们最后的图算法,让我们考虑一个在线游戏设计师和网络收音机提供商面临的问题。 问题是他们想有效地将一条信息传递给任何人和每个可能在听的人。 这在游戏中是重要的,使得所有玩家知道每个其他玩家的最新位置。 对于网络收音机是重要的,以便所有该调频的收听者获得他们需要的所有数据来刷新他们正在收听的歌曲。 Figure 9 说明了广播问题。 ![7.22.Prim生成树算法.figure9](assets/7.22.Prim%E7%94%9F%E6%88%90%E6%A0%91%E7%AE%97%E6%B3%95.figure9.png) + + *Figure 9* 这个问题有一些强力的解决方案,所以先看看他们如何更好地理解广播问题。这也将帮助你理解我们最后提出的解决方案。首先,广播主机有一些收听者都需要接收的信息。最简单的解决方案是广播主机保存所有收听者的列表并向每个收听者发送单独的消息。在 Figure 9中,我们展示了有广播公司和一些收听者的小型网络。使用第一种方法,将发送每个消息的四个副本。假设使用最小成本路径,让我们看看每个路由器处理同一消息的次数。 @@ -17,6 +19,8 @@ Figure 10 展示了广播图的简化版本并突出了生成图的最小生成 ![7.22.Prim生成树算法.figure10](assets/7.22.Prim%E7%94%9F%E6%88%90%E6%A0%91%E7%AE%97%E6%B3%95.figure10.png) + + *Figure 10* 我们将用来解决这个问题的算法称为 Prim 算法。 Prim 算法属于称为 “贪婪算法” 一系列算法,,因为在每个步骤,我们将选择最小权重的下一步。 在这种情况下,最小权重的下一步是以最小的权重跟随边。 我们的最后一步是开发 Prim 算法。 diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.4.\351\202\273\346\216\245\347\237\251\351\230\265/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.4.\351\202\273\346\216\245\347\237\251\351\230\265/README.md" index 892e246..c7f51ac 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.4.\351\202\273\346\216\245\347\237\251\351\230\265/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.4.\351\202\273\346\216\245\347\237\251\351\230\265/README.md" @@ -3,6 +3,8 @@ 实现图的最简单的方法之一是使用二维矩阵。在该矩阵实现中,每个行和列表示图中的顶点。存储在行 v 和列 w 的交叉点处的单元中的值表示是否存在从顶点 v 到顶点 w 的边。 当两个顶点通过边连接时,我们说它们是相邻的。 Figure 3 展示了 Figure 2 中的图的邻接矩阵。单元格中的值表示从顶点 v 到顶点 w 的边的权重。 ![7.4.邻接矩阵.figure3](assets/7.4.%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5.figure3.png) + + *Figure 3* 邻接矩阵的优点是简单,对于小图,很容易看到哪些节点连接到其他节点。 然而,注意矩阵中的大多数单元格是空的。 因为大多数单元格是空的,我们说这个矩阵是“稀疏的”。矩阵不是一种非常有效的方式来存储稀疏数据。 事实上,在Python中,你甚至要创建一个如 Figure 3所示的矩阵结构。 diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.5.\351\202\273\346\216\245\350\241\250/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.5.\351\202\273\346\216\245\350\241\250/README.md" index 9e9f494..9156f40 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.5.\351\202\273\346\216\245\350\241\250/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.5.\351\202\273\346\216\245\350\241\250/README.md" @@ -2,6 +2,8 @@ 实现稀疏连接图的更空间高效的方法是使用邻接表。在邻接表实现中,我们保存Graph 对象中的所有顶点的主列表,然后图中的每个顶点对象维护连接到的其他顶点的列表。 在我们的顶点类的实现中,我们将使用字典而不是列表,其中字典键是顶点,值是权重。 Figure 4 展示了 Figure 2中的图的邻接列表示。 ![7.5.邻接表.figure4](assets/7.5.%E9%82%BB%E6%8E%A5%E8%A1%A8.figure4.png) + + *Figure 4* 邻接表实现的优点是它允许我们紧凑地表示稀疏图。 邻接表还允许我们容易找到直接连接到特定顶点的所有链接。 diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.6.\345\256\236\347\216\260/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.6.\345\256\236\347\216\260/README.md" index 866a79c..4359bc4 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.6.\345\256\236\347\216\260/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.6.\345\256\236\347\216\260/README.md" @@ -15,7 +15,7 @@ class Vertex: def __str__(self): return str(self.id) + ' connectedTo: ' + str([x.id for x in self.connectedTo]) - + def getConnections(self): return self.connectedTo.keys() diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.8.\346\236\204\345\273\272\345\255\227\346\242\257\345\233\276/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.8.\346\236\204\345\273\272\345\255\227\346\242\257\345\233\276/README.md" index 5e507d3..a899a76 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.8.\346\236\204\345\273\272\345\255\227\346\242\257\345\233\276/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.8.\346\236\204\345\273\272\345\255\227\346\242\257\345\233\276/README.md" @@ -3,6 +3,8 @@ 我们的第一个问题是弄清楚如何将大量的单词集合转换为图。 如果两个词只有一个字母不同,我们就创建从一个词到另一个词的边。如果我们可以创建这样的图,则从一个词到另一个词的任意路径就是词梯子拼图的解决方案。 Figure 1展示了一些解决 `FOOL` 到 `SAGE` 字梯问题的单词的小图。 请注意,图是无向图,边未加权。 ![7.8.构建字梯图.figure1](assets/7.8.%E6%9E%84%E5%BB%BA%E5%AD%97%E6%A2%AF%E5%9B%BE.figure1.png) + + *Figure 1* @@ -11,6 +13,8 @@ 我们可以通过以下方法做得更好。假设我们有大量的桶,每个桶在外面有一个四个字母的单词,除了标签中的一个字母已经被下划线替代。例如,看 Figure 2,我们可能有一个标记为 “pop_” 的桶。当我们处理列表中的每个单词时,我们使用 “_” 作为通配符比较每个桶的单词,所以 “pope” 和 “pops “ 将匹配 ”pop_“。每次我们找到一个匹配的桶,我们就把单词放在那个桶。一旦我们把所有单词放到适当的桶中,就知道桶中的所有单词必须连接。 ![7.8.构建字梯图.figure2](assets/7.8.%E6%9E%84%E5%BB%BA%E5%AD%97%E6%A2%AF%E5%9B%BE.figure2.png) + + *Figure 2* 在 Python 中,我们使用字典来实现我们刚才描述的方案。我们刚才描述的桶上的标签是我们字典中的键。该键存储的值是单词列表。 一旦我们建立了字典,我们可以创建图。 我们通过为图中的每个单词创建一个顶点来开始图。 然后,我们在字典中的相同键下找到的所有顶点创建边。 Listing 1 展示了构建图所需的 Python 代码。 diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.9.\345\256\236\347\216\260\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.9.\345\256\236\347\216\260\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242/README.md" index 66e3d1a..9cdac90 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.9.\345\256\236\347\216\260\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.9.\345\256\236\347\216\260\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242/README.md" @@ -41,18 +41,23 @@ def bfs(g,start): 让我们看看 bfs 函数如何构造对应于 Figure 1 中的图的广度优先树。开始我们取所有与 `fool` 相邻的节点,并将它们添加到树中。 相邻节点包括 `pool`, `foil`, `foul`, `cool`。 这些节点被添加到新节点的队列以进行扩展。 Figure 3 展示了在此步骤之后树以及队列的状态。 ![7.9.实现广度优先搜索.figure3](assets/7.9.%E5%AE%9E%E7%8E%B0%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2.figure3.png) + + *Figure 3* 在下一步骤中,bfs 从队列的前面删除下一个节点(`pool`),并对其所有相邻节点重复该过程。 然而,当 bfs 检查节点 `cool` 时,它发现 `cool` 的颜色已经改变为灰色。这表明有一条较短的路径到 `cool`,并且 `cool` 已经在队列上进一步扩展。在检查 `pool` 期间添加到队列的唯一新节点是 `poll`。 树和队列的新状态如 Figure 4所示。 ![7.9.实现广度优先搜索.figure4](assets/7.9.%E5%AE%9E%E7%8E%B0%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2.figure4.png) + + *Figure 4* 队列上的下一个顶点是 `foil`。 `foil` 可以添加到树中的唯一新节点是 `fail`。 当 bfs 继续处理队列时,接下来的两个节点都不向队列或树添加新内容。 Figure 5 展示了在树的第二级上展开所有顶点之后的树和队列。 ![7.9.实现广度优先搜索.figure5](assets/7.9.%E5%AE%9E%E7%8E%B0%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2.figure5.png) -*Figure 5* -*Figure 6* + + +*Figure 5-6* 你应该自己继续完成算法,以便能够熟练使用它。Figure 6 展示了在 Figure 3 中的所有顶点都被扩展之后的最终广度优先搜索树。关于广度优先搜索解决方案的令人惊讶的事情是,我们不仅解决了我们开始的 `FOOL-SAGE` 问题,还解决了许多其他问题。 我们可以从广度优先搜索树中的任何顶点开始,并沿着前导箭头回到根,找到从任何字回到 `fool` 的最短的词梯。 下面的函数(Listing 3)展示了如何按前导链接打印出字梯。 diff --git a/README.md b/README.md index 432f16b..e2a1578 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ * 博客: https://facert.github.io #### 许可证 -本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。传播此文档时请注意遵循以上许可协议。 关于本许可证的更多详情可参考 http://creativecommons.org/licenses/by-sa/4.0/ +本作品采用 署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。传播此文档时请注意遵循以上许可协议。 关于本许可证的更多详情可参考 https://creativecommons.org/licenses/by-nc-sa/4.0/ diff --git a/SUMMARY.md b/SUMMARY.md index f36bd65..58eca1e 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,6 +1,6 @@ # 目录 -* [1.介绍](README.md) +* [1.介绍](1.介绍/README.md) * [1.1.目标](1.介绍/1.1.目标/README.md) * [1.2.快速开始](1.介绍/1.2.快速开始/README.md) * [1.3.什么是计算机科学](1.介绍/1.3.什么是计算机科学/README.md) @@ -9,16 +9,16 @@ * [1.6.为什么要学习算法](1.介绍/1.6.为什么要学习算法/README.md) * [1.7.回顾Python基础](1.介绍/1.7.回顾Python基础/README.md) -* [2.算法分析](README.md) +* [2.算法分析](2.算法分析/README.md) * [2.1.目标](2.算法分析/2.1.目标/README.md) * [2.2.什么是算法分析](2.算法分析/2.2.什么是算法分析/README.md) * [2.3.大O符号](2.算法分析/2.3.大O符号/README.md) - * [2.4.一个回文字符串检查的例子](2.算法分析/2.4.一个回文字符串检查的例子/README.md) + * [2.4.一个乱序字符串检查的例子](2.算法分析/2.4.一个乱序字符串检查的例子/README.md) * [2.5.Python数据结构的性能](2.算法分析/2.5.Python数据结构的性能/README.md) * [2.6.列表](2.算法分析/2.6.列表/README.md) * [2.7.字典](2.算法分析/2.7.字典/README.md) * [2.8.总结](2.算法分析/2.8.总结/README.md) -* [3.基本数据结构](README.md) +* [3.基本数据结构](3.基本数据结构/README.md) * [3.1.目标](3.基本数据结构/3.1.目标/README.md) * [3.2.什么是线性数据结构](3.基本数据结构/3.2.什么是线性数据结构/README.md) * [3.3.什么是栈](3.基本数据结构/3.3.什么是栈/README.md) @@ -27,7 +27,7 @@ * [3.6.简单括号匹配](3.基本数据结构/3.6.简单括号匹配/README.md) * [3.7.符号匹配](3.基本数据结构/3.7.符号匹配/README.md) * [3.8.十进制转换成二进制](3.基本数据结构/3.8.十进制转换成二进制/README.md) - * [3.9.中缀后缀和后缀表达式](3.基本数据结构/3.9.中缀后缀和后缀表达式/README.md) + * [3.9.中缀前缀和后缀表达式](3.基本数据结构/3.9.中缀前缀和后缀表达式/README.md) * [3.10.什么是队列](3.基本数据结构/3.10.什么是队列/README.md) * [3.11.队列抽象数据类型](3.基本数据结构/3.11.队列抽象数据类型/README.md) * [3.12.Python实现队列](3.基本数据结构/3.12.Python实现队列/README.md) @@ -43,7 +43,7 @@ * [3.22.有序列表抽象数据结构](3.基本数据结构/3.22.有序列表抽象数据结构/README.md) * [3.23.实现有序列表](3.基本数据结构/3.23.实现有序列表/README.md) * [3.24.总结](3.基本数据结构/3.24.总结/README.md) -* [4.递归](README.md) +* [4.递归](4.递归/README.md) * [4.1.目标](4.递归/4.1.目标/README.md) * [4.2.什么是递归](4.递归/4.2.什么是递归/README.md) * [4.3.计算整数列表和](4.递归/4.3.计算整数列表和/README.md) @@ -52,11 +52,11 @@ * [4.6.栈帧:实现递归](4.递归/4.6.栈帧:实现递归/README.md) * [4.7.介绍:可视化递归](4.递归/4.7.介绍:可视化递归/README.md) * [4.8.谢尔宾斯基三角形](4.递归/4.8.谢尔宾斯基三角形/README.md) - * [4.10.河内塔游戏](4.递归/4.10.河内塔游戏/README.md) + * [4.10.汉诺塔游戏](4.递归/4.10.汉诺塔游戏/README.md) * [4.11.探索迷宫](4.递归/4.11.探索迷宫/README.md) * [4.12.动态规划](4.递归/4.12.动态规划/README.md) * [4.13.总结](4.递归/4.13.总结/README.md) -* [5.排序和搜索](README.md) +* [5.排序和搜索](5.排序和搜索/README.md) * [5.1.目标](5.排序和搜索/5.1.目标/README.md) * [5.2.搜索](5.排序和搜索/5.2.搜索/README.md) * [5.3.顺序查找](5.排序和搜索/5.3.顺序查找/README.md) @@ -66,12 +66,12 @@ * [5.7.冒泡排序](5.排序和搜索/5.7.冒泡排序/README.md) * [5.8.选择排序](5.排序和搜索/5.8.选择排序/README.md) * [5.9.插入排序](5.排序和搜索/5.9.插入排序/README.md) - * [5.10.shell排序](5.排序和搜索/5.10.shell排序/README.md) + * [5.10.希尔排序](5.排序和搜索/5.10.希尔排序/README.md) * [5.11.归并排序](5.排序和搜索/5.11.归并排序/README.md) * [5.12.快速排序](5.排序和搜索/5.12.快速排序/README.md) * [5.13.总结](5.排序和搜索/5.13.总结/README.md) -* [6.树和树的算法](README.md) +* [6.树和树的算法](6.树和树的算法/README.md) * [6.1.目标](6.树和树的算法/6.1.目标/README.md) * [6.2.树的例子](6.树和树的算法/6.2.树的例子/README.md) * [6.3.词汇和定义](6.树和树的算法/6.3.词汇和定义/README.md) @@ -92,7 +92,7 @@ * [6.18.Map抽象数据结构总结](6.树和树的算法/6.18.Map抽象数据结构总结/README.md) * [6.19.总结](6.树和树的算法/6.19.总结/README.md) -* [7.图和图的算法](README.md) +* [7.图和图的算法](7.图和图的算法/README.md) * [7.1.目标](7.图和图的算法/7.1.目标/README.md) * [7.2.词汇和定义](7.图和图的算法/7.2.词汇和定义/README.md) * [7.3.图抽象数据类型](7.图和图的算法/7.3.图抽象数据类型/README.md) diff --git "a/assets/2.4.1 \346\261\202\345\222\214.png" "b/assets/2.4.1 \346\261\202\345\222\214.png" deleted file mode 100644 index f7313bc..0000000 Binary files "a/assets/2.4.1 \346\261\202\345\222\214.png" and /dev/null differ diff --git "a/assets/2.6.\345\210\227\350\241\250 Table2.png" "b/assets/2.6.\345\210\227\350\241\250 Table2.png" deleted file mode 100644 index 8a69c50..0000000 Binary files "a/assets/2.6.\345\210\227\350\241\250 Table2.png" and /dev/null differ diff --git "a/assets/2.6.\345\210\227\350\241\250.poptime.png" "b/assets/2.6.\345\210\227\350\241\250.poptime.png" deleted file mode 100644 index 90e6f02..0000000 Binary files "a/assets/2.6.\345\210\227\350\241\250.poptime.png" and /dev/null differ diff --git "a/assets/2.7.\345\255\227\345\205\270.figure4.png" "b/assets/2.7.\345\255\227\345\205\270.figure4.png" deleted file mode 100644 index b7ef5e3..0000000 Binary files "a/assets/2.7.\345\255\227\345\205\270.figure4.png" and /dev/null differ diff --git "a/assets/2.7.\345\255\227\345\205\270.table3.png" "b/assets/2.7.\345\255\227\345\205\270.table3.png" deleted file mode 100644 index ebdf687..0000000 Binary files "a/assets/2.7.\345\255\227\345\205\270.table3.png" and /dev/null differ diff --git "a/assets/3.10.\344\273\200\344\271\210\346\230\257\351\230\237\345\210\227.figure1.png" "b/assets/3.10.\344\273\200\344\271\210\346\230\257\351\230\237\345\210\227.figure1.png" deleted file mode 100644 index f5908d0..0000000 Binary files "a/assets/3.10.\344\273\200\344\271\210\346\230\257\351\230\237\345\210\227.figure1.png" and /dev/null differ diff --git "a/assets/3.11.\351\230\237\345\210\227\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213.table1.png" "b/assets/3.11.\351\230\237\345\210\227\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213.table1.png" deleted file mode 100644 index c2afbfd..0000000 Binary files "a/assets/3.11.\351\230\237\345\210\227\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213.table1.png" and /dev/null differ diff --git "a/assets/3.13.\346\250\241\346\213\237\357\274\232\347\203\253\346\211\213\345\261\261\350\212\213.figure2.png" "b/assets/3.13.\346\250\241\346\213\237\357\274\232\347\203\253\346\211\213\345\261\261\350\212\213.figure2.png" deleted file mode 100644 index 3efce6c..0000000 Binary files "a/assets/3.13.\346\250\241\346\213\237\357\274\232\347\203\253\346\211\213\345\261\261\350\212\213.figure2.png" and /dev/null differ diff --git "a/assets/3.13.\346\250\241\346\213\237\357\274\232\347\203\253\346\211\213\345\261\261\350\212\213.figure3.png" "b/assets/3.13.\346\250\241\346\213\237\357\274\232\347\203\253\346\211\213\345\261\261\350\212\213.figure3.png" deleted file mode 100644 index 78de99c..0000000 Binary files "a/assets/3.13.\346\250\241\346\213\237\357\274\232\347\203\253\346\211\213\345\261\261\350\212\213.figure3.png" and /dev/null differ diff --git "a/assets/3.14.\346\250\241\346\213\237\357\274\232\346\211\223\345\215\260\346\234\272.figure1.png" "b/assets/3.14.\346\250\241\346\213\237\357\274\232\346\211\223\345\215\260\346\234\272.figure1.png" deleted file mode 100644 index daec5ff..0000000 Binary files "a/assets/3.14.\346\250\241\346\213\237\357\274\232\346\211\223\345\215\260\346\234\272.figure1.png" and /dev/null differ diff --git "a/assets/3.14.\346\250\241\346\213\237\357\274\232\346\211\223\345\215\260\346\234\272.figure4.png" "b/assets/3.14.\346\250\241\346\213\237\357\274\232\346\211\223\345\215\260\346\234\272.figure4.png" deleted file mode 100644 index cb638e7..0000000 Binary files "a/assets/3.14.\346\250\241\346\213\237\357\274\232\346\211\223\345\215\260\346\234\272.figure4.png" and /dev/null differ diff --git "a/assets/3.15.\344\273\200\344\271\210\346\230\257Deque.figure1.png" "b/assets/3.15.\344\273\200\344\271\210\346\230\257Deque.figure1.png" deleted file mode 100644 index d4fc6b7..0000000 Binary files "a/assets/3.15.\344\273\200\344\271\210\346\230\257Deque.figure1.png" and /dev/null differ diff --git "a/assets/3.16.Deque\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213.table1.png" "b/assets/3.16.Deque\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213.table1.png" deleted file mode 100644 index 3fd5c4a..0000000 Binary files "a/assets/3.16.Deque\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213.table1.png" and /dev/null differ diff --git "a/assets/3.18.\345\233\236\346\226\207\346\243\200\346\237\245.figure2.png" "b/assets/3.18.\345\233\236\346\226\207\346\243\200\346\237\245.figure2.png" deleted file mode 100644 index c21b667..0000000 Binary files "a/assets/3.18.\345\233\236\346\226\207\346\243\200\346\237\245.figure2.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure1.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure1.png" deleted file mode 100644 index b0bc6f7..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure1.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure10.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure10.png" deleted file mode 100644 index cda7258..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure10.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure11.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure11.png" deleted file mode 100644 index 8421a0b..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure11.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure12.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure12.png" deleted file mode 100644 index 48894ad..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure12.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure13.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure13.png" deleted file mode 100644 index 3958f62..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure13.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure14.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure14.png" deleted file mode 100644 index cea16fd..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure14.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure2.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure2.png" deleted file mode 100644 index 9ba338b..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure2.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure3.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure3.png" deleted file mode 100644 index f3bb518..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure3.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure4.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure4.png" deleted file mode 100644 index 94b27ab..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure4.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure5.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure5.png" deleted file mode 100644 index bf2e98f..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure5.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure6.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure6.png" deleted file mode 100644 index bc7ff4c..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure6.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure7.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure7.png" deleted file mode 100644 index 4ca8ef1..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure7.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure8.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure8.png" deleted file mode 100644 index 6d8dc1e..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure8.png" and /dev/null differ diff --git "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure9.png" "b/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure9.png" deleted file mode 100644 index a7a2962..0000000 Binary files "a/assets/3.21.\345\256\236\347\216\260\346\227\240\345\272\217\345\210\227\350\241\250\357\274\232\351\223\276\350\241\250.figure9.png" and /dev/null differ diff --git "a/assets/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250.figure15.png" "b/assets/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250.figure15.png" deleted file mode 100644 index d22e15f..0000000 Binary files "a/assets/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250.figure15.png" and /dev/null differ diff --git "a/assets/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250.figure16.png" "b/assets/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250.figure16.png" deleted file mode 100644 index d8de256..0000000 Binary files "a/assets/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250.figure16.png" and /dev/null differ diff --git "a/assets/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250.figure17.png" "b/assets/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250.figure17.png" deleted file mode 100644 index 381d5d9..0000000 Binary files "a/assets/3.23.\345\256\236\347\216\260\346\234\211\345\272\217\345\210\227\350\241\250.figure17.png" and /dev/null differ diff --git "a/assets/3.3.\344\273\200\344\271\210\346\230\257\346\240\210.png" "b/assets/3.3.\344\273\200\344\271\210\346\230\257\346\240\210.png" deleted file mode 100644 index 09cfc7e..0000000 Binary files "a/assets/3.3.\344\273\200\344\271\210\346\230\257\346\240\210.png" and /dev/null differ diff --git "a/assets/3.3.\344\273\200\344\271\210\346\230\257\346\240\210.primitive.png" "b/assets/3.3.\344\273\200\344\271\210\346\230\257\346\240\210.primitive.png" deleted file mode 100644 index 3f79177..0000000 Binary files "a/assets/3.3.\344\273\200\344\271\210\346\230\257\346\240\210.primitive.png" and /dev/null differ diff --git "a/assets/3.3.\344\273\200\344\271\210\346\230\257\346\240\210.simplereversal.png" "b/assets/3.3.\344\273\200\344\271\210\346\230\257\346\240\210.simplereversal.png" deleted file mode 100644 index 61d7bb2..0000000 Binary files "a/assets/3.3.\344\273\200\344\271\210\346\230\257\346\240\210.simplereversal.png" and /dev/null differ diff --git "a/assets/3.4.\346\240\210\347\232\204\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213.table1.png" "b/assets/3.4.\346\240\210\347\232\204\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213.table1.png" deleted file mode 100644 index c15d4af..0000000 Binary files "a/assets/3.4.\346\240\210\347\232\204\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213.table1.png" and /dev/null differ diff --git "a/assets/3.6.\347\256\200\345\215\225\346\213\254\345\217\267\345\214\271\351\205\215.simpleparcheck.png" "b/assets/3.6.\347\256\200\345\215\225\346\213\254\345\217\267\345\214\271\351\205\215.simpleparcheck.png" deleted file mode 100644 index 44b0093..0000000 Binary files "a/assets/3.6.\347\256\200\345\215\225\346\213\254\345\217\267\345\214\271\351\205\215.simpleparcheck.png" and /dev/null differ diff --git "a/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.1.png" "b/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.1.png" deleted file mode 100644 index 8f98ae4..0000000 Binary files "a/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.1.png" and /dev/null differ diff --git "a/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.2.png" "b/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.2.png" deleted file mode 100644 index a5f02cc..0000000 Binary files "a/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.2.png" and /dev/null differ diff --git "a/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.3.png" "b/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.3.png" deleted file mode 100644 index ee97e04..0000000 Binary files "a/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.3.png" and /dev/null differ diff --git "a/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.4.png" "b/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.4.png" deleted file mode 100644 index 33c01eb..0000000 Binary files "a/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.4.png" and /dev/null differ diff --git "a/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.figure5.png" "b/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.figure5.png" deleted file mode 100644 index a307464..0000000 Binary files "a/assets/3.8.\345\215\201\350\277\233\345\210\266\350\275\254\346\215\242\346\210\220\344\272\214\350\277\233\345\210\266.figure5.png" and /dev/null differ diff --git "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure10.png" "b/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure10.png" deleted file mode 100644 index 3392b0f..0000000 Binary files "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure10.png" and /dev/null differ diff --git "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure11.png" "b/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure11.png" deleted file mode 100644 index 2e9171b..0000000 Binary files "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure11.png" and /dev/null differ diff --git "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure6.png" "b/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure6.png" deleted file mode 100644 index 07da9fc..0000000 Binary files "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure6.png" and /dev/null differ diff --git "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure7.png" "b/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure7.png" deleted file mode 100644 index d6e4e71..0000000 Binary files "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure7.png" and /dev/null differ diff --git "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure8.png" "b/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure8.png" deleted file mode 100644 index 7ba3899..0000000 Binary files "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure8.png" and /dev/null differ diff --git "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure9.png" "b/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure9.png" deleted file mode 100644 index 4df3f60..0000000 Binary files "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.figure9.png" and /dev/null differ diff --git "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table2.png" "b/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table2.png" deleted file mode 100644 index d48ec89..0000000 Binary files "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table2.png" and /dev/null differ diff --git "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table3.png" "b/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table3.png" deleted file mode 100644 index 6cb8db3..0000000 Binary files "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table3.png" and /dev/null differ diff --git "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table4.png" "b/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table4.png" deleted file mode 100644 index bd616c1..0000000 Binary files "a/assets/3.9.\344\270\255\347\274\200\345\220\216\347\274\200\345\222\214\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217.table4.png" and /dev/null differ diff --git "a/assets/4.10.\346\262\263\345\206\205\345\241\224\346\270\270\346\210\217.figure1.png" "b/assets/4.10.\346\262\263\345\206\205\345\241\224\346\270\270\346\210\217.figure1.png" deleted file mode 100644 index 4d0caf4..0000000 Binary files "a/assets/4.10.\346\262\263\345\206\205\345\241\224\346\270\270\346\210\217.figure1.png" and /dev/null differ diff --git "a/assets/4.11.\346\216\242\347\264\242\350\277\267\345\256\253.figure2.png" "b/assets/4.11.\346\216\242\347\264\242\350\277\267\345\256\253.figure2.png" deleted file mode 100644 index 029f2b7..0000000 Binary files "a/assets/4.11.\346\216\242\347\264\242\350\277\267\345\256\253.figure2.png" and /dev/null differ diff --git "a/assets/4.12.\345\212\250\346\200\201\350\247\204\345\210\222.1.png" "b/assets/4.12.\345\212\250\346\200\201\350\247\204\345\210\222.1.png" deleted file mode 100644 index c2ea388..0000000 Binary files "a/assets/4.12.\345\212\250\346\200\201\350\247\204\345\210\222.1.png" and /dev/null differ diff --git "a/assets/4.12.\345\212\250\346\200\201\350\247\204\345\210\222.figure5.png" "b/assets/4.12.\345\212\250\346\200\201\350\247\204\345\210\222.figure5.png" deleted file mode 100644 index 6cc0a1b..0000000 Binary files "a/assets/4.12.\345\212\250\346\200\201\350\247\204\345\210\222.figure5.png" and /dev/null differ diff --git "a/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.1.png" "b/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.1.png" deleted file mode 100644 index 0f7a216..0000000 Binary files "a/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.1.png" and /dev/null differ diff --git "a/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.2.png" "b/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.2.png" deleted file mode 100644 index 06a4d5d..0000000 Binary files "a/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.2.png" and /dev/null differ diff --git "a/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.3.png" "b/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.3.png" deleted file mode 100644 index fab2b0f..0000000 Binary files "a/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.3.png" and /dev/null differ diff --git "a/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.4.png" "b/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.4.png" deleted file mode 100644 index 6f13dcf..0000000 Binary files "a/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.4.png" and /dev/null differ diff --git "a/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.figure1.png" "b/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.figure1.png" deleted file mode 100644 index f3252c5..0000000 Binary files "a/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.figure1.png" and /dev/null differ diff --git "a/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.figure2.png" "b/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.figure2.png" deleted file mode 100644 index ef1b15a..0000000 Binary files "a/assets/4.3.\350\256\241\347\256\227\346\225\264\346\225\260\345\210\227\350\241\250\345\222\214.figure2.png" and /dev/null differ diff --git "a/assets/4.5.\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\273\273\346\204\217\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262.figure3.png" "b/assets/4.5.\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\273\273\346\204\217\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262.figure3.png" deleted file mode 100644 index 16ca036..0000000 Binary files "a/assets/4.5.\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\273\273\346\204\217\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262.figure3.png" and /dev/null differ diff --git "a/assets/4.5.\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\273\273\346\204\217\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262.figure4.png" "b/assets/4.5.\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\273\273\346\204\217\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262.figure4.png" deleted file mode 100644 index b77dc1c..0000000 Binary files "a/assets/4.5.\346\225\264\346\225\260\350\275\254\346\215\242\344\270\272\344\273\273\346\204\217\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262.figure4.png" and /dev/null differ diff --git "a/assets/4.6.\346\240\210\345\270\247\357\274\232\345\256\236\347\216\260\351\200\222\345\275\222.figure5.png" "b/assets/4.6.\346\240\210\345\270\247\357\274\232\345\256\236\347\216\260\351\200\222\345\275\222.figure5.png" deleted file mode 100644 index 61a6434..0000000 Binary files "a/assets/4.6.\346\240\210\345\270\247\357\274\232\345\256\236\347\216\260\351\200\222\345\275\222.figure5.png" and /dev/null differ diff --git "a/assets/4.6.\346\240\210\345\270\247\357\274\232\345\256\236\347\216\260\351\200\222\345\275\222.figure6.png" "b/assets/4.6.\346\240\210\345\270\247\357\274\232\345\256\236\347\216\260\351\200\222\345\275\222.figure6.png" deleted file mode 100644 index e66635e..0000000 Binary files "a/assets/4.6.\346\240\210\345\270\247\357\274\232\345\256\236\347\216\260\351\200\222\345\275\222.figure6.png" and /dev/null differ diff --git "a/assets/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222.ac1.png" "b/assets/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222.ac1.png" deleted file mode 100644 index 6ca4c31..0000000 Binary files "a/assets/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222.ac1.png" and /dev/null differ diff --git "a/assets/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222.figure1.png" "b/assets/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222.figure1.png" deleted file mode 100644 index 1c9302f..0000000 Binary files "a/assets/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222.figure1.png" and /dev/null differ diff --git "a/assets/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222.figure2.png" "b/assets/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222.figure2.png" deleted file mode 100644 index 811df6c..0000000 Binary files "a/assets/4.7.\344\273\213\347\273\215\357\274\232\345\217\257\350\247\206\345\214\226\351\200\222\345\275\222.figure2.png" and /dev/null differ diff --git "a/assets/4.8.\350\260\242\345\260\224\345\256\276\346\226\257\345\237\272\344\270\211\350\247\222\345\275\242.figure3.png" "b/assets/4.8.\350\260\242\345\260\224\345\256\276\346\226\257\345\237\272\344\270\211\350\247\222\345\275\242.figure3.png" deleted file mode 100644 index aad7f06..0000000 Binary files "a/assets/4.8.\350\260\242\345\260\224\345\256\276\346\226\257\345\237\272\344\270\211\350\247\222\345\275\242.figure3.png" and /dev/null differ diff --git "a/assets/4.8.\350\260\242\345\260\224\345\256\276\346\226\257\345\237\272\344\270\211\350\247\222\345\275\242.figure4.png" "b/assets/4.8.\350\260\242\345\260\224\345\256\276\346\226\257\345\237\272\344\270\211\350\247\222\345\275\242.figure4.png" deleted file mode 100644 index 744e4bb..0000000 Binary files "a/assets/4.8.\350\260\242\345\260\224\345\256\276\346\226\257\345\237\272\344\270\211\350\247\222\345\275\242.figure4.png" and /dev/null differ diff --git "a/assets/5.10.shell\346\216\222\345\272\217.figure6.png" "b/assets/5.10.shell\346\216\222\345\272\217.figure6.png" deleted file mode 100644 index 4bb7c28..0000000 Binary files "a/assets/5.10.shell\346\216\222\345\272\217.figure6.png" and /dev/null differ diff --git "a/assets/5.10.shell\346\216\222\345\272\217.figure8.png" "b/assets/5.10.shell\346\216\222\345\272\217.figure8.png" deleted file mode 100644 index fb1db42..0000000 Binary files "a/assets/5.10.shell\346\216\222\345\272\217.figure8.png" and /dev/null differ diff --git "a/assets/5.10.shell\346\216\222\345\272\217.figure9.png" "b/assets/5.10.shell\346\216\222\345\272\217.figure9.png" deleted file mode 100644 index 0d8fa7a..0000000 Binary files "a/assets/5.10.shell\346\216\222\345\272\217.figure9.png" and /dev/null differ diff --git "a/assets/5.11.\345\275\222\345\271\266\346\216\222\345\272\217.figure10.png" "b/assets/5.11.\345\275\222\345\271\266\346\216\222\345\272\217.figure10.png" deleted file mode 100644 index 53f72c4..0000000 Binary files "a/assets/5.11.\345\275\222\345\271\266\346\216\222\345\272\217.figure10.png" and /dev/null differ diff --git "a/assets/5.11.\345\275\222\345\271\266\346\216\222\345\272\217.figure11.png" "b/assets/5.11.\345\275\222\345\271\266\346\216\222\345\272\217.figure11.png" deleted file mode 100644 index feff141..0000000 Binary files "a/assets/5.11.\345\275\222\345\271\266\346\216\222\345\272\217.figure11.png" and /dev/null differ diff --git "a/assets/5.12.\345\277\253\351\200\237\346\216\222\345\272\217.figure12.png" "b/assets/5.12.\345\277\253\351\200\237\346\216\222\345\272\217.figure12.png" deleted file mode 100644 index 332102c..0000000 Binary files "a/assets/5.12.\345\277\253\351\200\237\346\216\222\345\272\217.figure12.png" and /dev/null differ diff --git "a/assets/5.3.\351\241\272\345\272\217\346\237\245\346\211\276.figure1.png" "b/assets/5.3.\351\241\272\345\272\217\346\237\245\346\211\276.figure1.png" deleted file mode 100644 index 67d67d3..0000000 Binary files "a/assets/5.3.\351\241\272\345\272\217\346\237\245\346\211\276.figure1.png" and /dev/null differ diff --git "a/assets/5.3.\351\241\272\345\272\217\346\237\245\346\211\276.figure2.png" "b/assets/5.3.\351\241\272\345\272\217\346\237\245\346\211\276.figure2.png" deleted file mode 100644 index 349d9ea..0000000 Binary files "a/assets/5.3.\351\241\272\345\272\217\346\237\245\346\211\276.figure2.png" and /dev/null differ diff --git "a/assets/5.3.\351\241\272\345\272\217\346\237\245\346\211\276.table1.png" "b/assets/5.3.\351\241\272\345\272\217\346\237\245\346\211\276.table1.png" deleted file mode 100644 index 31c6171..0000000 Binary files "a/assets/5.3.\351\241\272\345\272\217\346\237\245\346\211\276.table1.png" and /dev/null differ diff --git "a/assets/5.3.\351\241\272\345\272\217\346\237\245\346\211\276.table2.png" "b/assets/5.3.\351\241\272\345\272\217\346\237\245\346\211\276.table2.png" deleted file mode 100644 index dd160a9..0000000 Binary files "a/assets/5.3.\351\241\272\345\272\217\346\237\245\346\211\276.table2.png" and /dev/null differ diff --git "a/assets/5.4.\344\272\214\345\210\206\346\237\245\346\211\276.figure3.png" "b/assets/5.4.\344\272\214\345\210\206\346\237\245\346\211\276.figure3.png" deleted file mode 100644 index 336891c..0000000 Binary files "a/assets/5.4.\344\272\214\345\210\206\346\237\245\346\211\276.figure3.png" and /dev/null differ diff --git "a/assets/5.4.\344\272\214\345\210\206\346\237\245\346\211\276.table3.png" "b/assets/5.4.\344\272\214\345\210\206\346\237\245\346\211\276.table3.png" deleted file mode 100644 index 79057a1..0000000 Binary files "a/assets/5.4.\344\272\214\345\210\206\346\237\245\346\211\276.table3.png" and /dev/null differ diff --git "a/assets/5.5.Hash\346\237\245\346\211\276.figure10.png" "b/assets/5.5.Hash\346\237\245\346\211\276.figure10.png" deleted file mode 100644 index 412509c..0000000 Binary files "a/assets/5.5.Hash\346\237\245\346\211\276.figure10.png" and /dev/null differ diff --git "a/assets/5.5.Hash\346\237\245\346\211\276.figure11.png" "b/assets/5.5.Hash\346\237\245\346\211\276.figure11.png" deleted file mode 100644 index 599a0ae..0000000 Binary files "a/assets/5.5.Hash\346\237\245\346\211\276.figure11.png" and /dev/null differ diff --git "a/assets/5.5.Hash\346\237\245\346\211\276.figure12.png" "b/assets/5.5.Hash\346\237\245\346\211\276.figure12.png" deleted file mode 100644 index f9a4d1f..0000000 Binary files "a/assets/5.5.Hash\346\237\245\346\211\276.figure12.png" and /dev/null differ diff --git "a/assets/5.5.Hash\346\237\245\346\211\276.figure4.png" "b/assets/5.5.Hash\346\237\245\346\211\276.figure4.png" deleted file mode 100644 index ac2472f..0000000 Binary files "a/assets/5.5.Hash\346\237\245\346\211\276.figure4.png" and /dev/null differ diff --git "a/assets/5.5.Hash\346\237\245\346\211\276.figure5.png" "b/assets/5.5.Hash\346\237\245\346\211\276.figure5.png" deleted file mode 100644 index 25ea459..0000000 Binary files "a/assets/5.5.Hash\346\237\245\346\211\276.figure5.png" and /dev/null differ diff --git "a/assets/5.5.Hash\346\237\245\346\211\276.figure6.png" "b/assets/5.5.Hash\346\237\245\346\211\276.figure6.png" deleted file mode 100644 index 0aa6c2b..0000000 Binary files "a/assets/5.5.Hash\346\237\245\346\211\276.figure6.png" and /dev/null differ diff --git "a/assets/5.5.Hash\346\237\245\346\211\276.figure7.png" "b/assets/5.5.Hash\346\237\245\346\211\276.figure7.png" deleted file mode 100644 index 36253d4..0000000 Binary files "a/assets/5.5.Hash\346\237\245\346\211\276.figure7.png" and /dev/null differ diff --git "a/assets/5.5.Hash\346\237\245\346\211\276.figure8.png" "b/assets/5.5.Hash\346\237\245\346\211\276.figure8.png" deleted file mode 100644 index 8893ade..0000000 Binary files "a/assets/5.5.Hash\346\237\245\346\211\276.figure8.png" and /dev/null differ diff --git "a/assets/5.5.Hash\346\237\245\346\211\276.figure9.png" "b/assets/5.5.Hash\346\237\245\346\211\276.figure9.png" deleted file mode 100644 index f13e84f..0000000 Binary files "a/assets/5.5.Hash\346\237\245\346\211\276.figure9.png" and /dev/null differ diff --git "a/assets/5.5.Hash\346\237\245\346\211\276.table4.png" "b/assets/5.5.Hash\346\237\245\346\211\276.table4.png" deleted file mode 100644 index b99f070..0000000 Binary files "a/assets/5.5.Hash\346\237\245\346\211\276.table4.png" and /dev/null differ diff --git "a/assets/5.5.Hash\346\237\245\346\211\276.table5.png" "b/assets/5.5.Hash\346\237\245\346\211\276.table5.png" deleted file mode 100644 index 3f491e2..0000000 Binary files "a/assets/5.5.Hash\346\237\245\346\211\276.table5.png" and /dev/null differ diff --git "a/assets/5.7.\345\206\222\346\263\241\346\216\222\345\272\217.figure1.png" "b/assets/5.7.\345\206\222\346\263\241\346\216\222\345\272\217.figure1.png" deleted file mode 100644 index 9a277f2..0000000 Binary files "a/assets/5.7.\345\206\222\346\263\241\346\216\222\345\272\217.figure1.png" and /dev/null differ diff --git "a/assets/5.7.\345\206\222\346\263\241\346\216\222\345\272\217.figure2.png" "b/assets/5.7.\345\206\222\346\263\241\346\216\222\345\272\217.figure2.png" deleted file mode 100644 index 6241d6c..0000000 Binary files "a/assets/5.7.\345\206\222\346\263\241\346\216\222\345\272\217.figure2.png" and /dev/null differ diff --git "a/assets/5.7.\345\206\222\346\263\241\346\216\222\345\272\217.table1.png" "b/assets/5.7.\345\206\222\346\263\241\346\216\222\345\272\217.table1.png" deleted file mode 100644 index e2e1e86..0000000 Binary files "a/assets/5.7.\345\206\222\346\263\241\346\216\222\345\272\217.table1.png" and /dev/null differ diff --git "a/assets/5.8.\351\200\211\346\213\251\346\216\222\345\272\217.activecode1.png" "b/assets/5.8.\351\200\211\346\213\251\346\216\222\345\272\217.activecode1.png" deleted file mode 100644 index f66d14b..0000000 Binary files "a/assets/5.8.\351\200\211\346\213\251\346\216\222\345\272\217.activecode1.png" and /dev/null differ diff --git "a/assets/5.9.\346\217\222\345\205\245\346\216\222\345\272\217.figure4.png" "b/assets/5.9.\346\217\222\345\205\245\346\216\222\345\272\217.figure4.png" deleted file mode 100644 index 9a32074..0000000 Binary files "a/assets/5.9.\346\217\222\345\205\245\346\216\222\345\272\217.figure4.png" and /dev/null differ diff --git "a/assets/5.9.\346\217\222\345\205\245\346\216\222\345\272\217.figure5.png" "b/assets/5.9.\346\217\222\345\205\245\346\216\222\345\272\217.figure5.png" deleted file mode 100644 index e1c3254..0000000 Binary files "a/assets/5.9.\346\217\222\345\205\245\346\216\222\345\272\217.figure5.png" and /dev/null differ diff --git "a/assets/6.2.\346\240\221\347\232\204\344\276\213\345\255\220.figure1.png" "b/assets/6.2.\346\240\221\347\232\204\344\276\213\345\255\220.figure1.png" deleted file mode 100644 index 8fdff6e..0000000 Binary files "a/assets/6.2.\346\240\221\347\232\204\344\276\213\345\255\220.figure1.png" and /dev/null differ diff --git "a/assets/6.2.\346\240\221\347\232\204\344\276\213\345\255\220.figure2.png" "b/assets/6.2.\346\240\221\347\232\204\344\276\213\345\255\220.figure2.png" deleted file mode 100644 index 2dce7ba..0000000 Binary files "a/assets/6.2.\346\240\221\347\232\204\344\276\213\345\255\220.figure2.png" and /dev/null differ diff --git "a/assets/6.2.\346\240\221\347\232\204\344\276\213\345\255\220.figure3.png" "b/assets/6.2.\346\240\221\347\232\204\344\276\213\345\255\220.figure3.png" deleted file mode 100644 index ae62f8b..0000000 Binary files "a/assets/6.2.\346\240\221\347\232\204\344\276\213\345\255\220.figure3.png" and /dev/null differ diff --git "a/assets/6.3.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211.figure3.png" "b/assets/6.3.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211.figure3.png" deleted file mode 100644 index cdbf83b..0000000 Binary files "a/assets/6.3.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211.figure3.png" and /dev/null differ diff --git "a/assets/6.3.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211.figure4.png" "b/assets/6.3.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211.figure4.png" deleted file mode 100644 index 2f83fa0..0000000 Binary files "a/assets/6.3.\350\257\215\346\261\207\345\222\214\345\256\232\344\271\211.figure4.png" and /dev/null differ diff --git "a/assets/6.4.\345\210\227\350\241\250\350\241\250\347\244\272.figure1.png" "b/assets/6.4.\345\210\227\350\241\250\350\241\250\347\244\272.figure1.png" deleted file mode 100644 index df75465..0000000 Binary files "a/assets/6.4.\345\210\227\350\241\250\350\241\250\347\244\272.figure1.png" and /dev/null differ diff --git "a/assets/6.5.\350\212\202\347\202\271\350\241\250\347\244\272.figure2.png" "b/assets/6.5.\350\212\202\347\202\271\350\241\250\347\244\272.figure2.png" deleted file mode 100644 index 44efdd6..0000000 Binary files "a/assets/6.5.\350\212\202\347\202\271\350\241\250\347\244\272.figure2.png" and /dev/null differ diff --git "a/assets/6.6.\345\210\206\346\236\220\346\240\221.figure1.png" "b/assets/6.6.\345\210\206\346\236\220\346\240\221.figure1.png" deleted file mode 100644 index 77e3660..0000000 Binary files "a/assets/6.6.\345\210\206\346\236\220\346\240\221.figure1.png" and /dev/null differ diff --git "a/assets/6.6.\345\210\206\346\236\220\346\240\221.figure2.png" "b/assets/6.6.\345\210\206\346\236\220\346\240\221.figure2.png" deleted file mode 100644 index 03f9040..0000000 Binary files "a/assets/6.6.\345\210\206\346\236\220\346\240\221.figure2.png" and /dev/null differ diff --git "a/assets/6.6.\345\210\206\346\236\220\346\240\221.figure3.png" "b/assets/6.6.\345\210\206\346\236\220\346\240\221.figure3.png" deleted file mode 100644 index 0d38f78..0000000 Binary files "a/assets/6.6.\345\210\206\346\236\220\346\240\221.figure3.png" and /dev/null differ diff --git "a/assets/6.6.\345\210\206\346\236\220\346\240\221.figure4-1.png" "b/assets/6.6.\345\210\206\346\236\220\346\240\221.figure4-1.png" deleted file mode 100644 index 2642c83..0000000 Binary files "a/assets/6.6.\345\210\206\346\236\220\346\240\221.figure4-1.png" and /dev/null differ diff --git "a/assets/6.6.\345\210\206\346\236\220\346\240\221.figure4-2.png" "b/assets/6.6.\345\210\206\346\236\220\346\240\221.figure4-2.png" deleted file mode 100644 index ba2983e..0000000 Binary files "a/assets/6.6.\345\210\206\346\236\220\346\240\221.figure4-2.png" and /dev/null differ diff --git "a/assets/6.7.\346\240\221\347\232\204\351\201\215\345\216\206.figure5.png" "b/assets/6.7.\346\240\221\347\232\204\351\201\215\345\216\206.figure5.png" deleted file mode 100644 index ae4fa16..0000000 Binary files "a/assets/6.7.\346\240\221\347\232\204\351\201\215\345\216\206.figure5.png" and /dev/null differ diff --git a/assets/blackbox.png b/assets/blackbox.png deleted file mode 100644 index 7d8dd82..0000000 Binary files a/assets/blackbox.png and /dev/null differ diff --git a/assets/newplot.png b/assets/newplot.png deleted file mode 100644 index 3f19c39..0000000 Binary files a/assets/newplot.png and /dev/null differ diff --git a/assets/newplot2.png b/assets/newplot2.png deleted file mode 100644 index 1e77d8b..0000000 Binary files a/assets/newplot2.png and /dev/null differ diff --git "a/assets/\346\225\260\351\207\217\347\272\247\345\207\275\346\225\260.png" "b/assets/\346\225\260\351\207\217\347\272\247\345\207\275\346\225\260.png" deleted file mode 100644 index 81d0406..0000000 Binary files "a/assets/\346\225\260\351\207\217\347\272\247\345\207\275\346\225\260.png" and /dev/null differ diff --git "a/assets/\346\261\202\345\222\214.png" "b/assets/\346\261\202\345\222\214.png" deleted file mode 100644 index 28fa02c..0000000 Binary files "a/assets/\346\261\202\345\222\214.png" and /dev/null differ