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.10.\346\216\247\345\210\266\347\273\223\346\236\204/README.md" "b/1.\344\273\213\347\273\215/1.10.\346\216\247\345\210\266\347\273\223\346\236\204/README.md" new file mode 100644 index 0000000..8c4f2cc --- /dev/null +++ "b/1.\344\273\213\347\273\215/1.10.\346\216\247\345\210\266\347\273\223\346\236\204/README.md" @@ -0,0 +1,185 @@ +## 1.10.控制结构 + +正如我们前面提到的,算法需要两个重要的控制结构:迭代和选择。Python以各种形式支持这两种方法。程序员可以对给定环境选择最有用的语句。 + +对于迭代,Python提供了一个标准的while语句和一个非常强大的for语句。只要条件为真,while语句就会重复一段代码。 例如, + +```` +>>> counter = 1 +>>> while counter <= 5: +... print("Hello, world") +... counter = counter + 1 + + +Hello, world +Hello, world +Hello, world +Hello, world +Hello, world +```` + +把“Hello, world”这个短语打印出来五次。在每次重复的开始时评估while语句的条件。如果条件为真,则将执行语句主体。由于语言强制执行的强制缩进模式,很容易看到Python while语句的结构。 + +while语句是一个非常通用的迭代结构,我们将在许多不同的算法中使用它。 在许多情况下,将会用复合条件来控制迭代。 片段如 + +```` +while counter <= 10 and not done: +... +```` + +只会在条件的两个部分都满足的情况下才会执行语句的主体。 变量counter的值需要小于或等于10,并且变量done的值需要为False(not False为True),以便True and True结果为True。 + +即使这种类型的构造在各种情况下都非常有用,但是另一个迭代结构for语句,可以与许多Python集合一起使用。 只要集合是序列,for语句就可以用于迭代集合的成员。 所以,例如, + +```` +>>> for item in [1,3,6,2,5]: +... print(item) +... +1 +3 +6 +2 +5 +```` + +逐次将列表[1,3,6,2,5]中的每个值赋给变量item。 然后执行迭代的主体。 这适用于任何序列集合(列表,元组和字符串)。 + +for语句的一个常见用途是在一系列值上实现明确的迭代。 如下 + +```` +>>> for item in range(5): +... print(item**2) +... +0 +1 +4 +9 +16 +>>> +```` + +该语句将执行五次print功能。 range函数将返回表示序列0,1,2,3,4的范围对象,并且每个值将分配给变量item。 然后将该值平方并打印。 + +此迭代结构的另一个非常有用的版本用于处理字符串的每个字符。 以下代码片段遍历字符串列表,并且每个字符串通过将每个字符追加到列表上来处理每个字符。 结果为所有单词中所有字母的列表。 + +```` +wordlist = ['cat','dog','rabbit'] +letterlist = [ ] +for aword in wordlist: + for aletter in aword: + letterlist.append(aletter) +print(letterlist) + +```` + +选择语句允许程序员提出问题,然后根据结果执行不同的操作。 大多数编程语言都提供了这个有用结构的两个版本:ifelse和if。 一个简单示例是使用ifelse语句实现二分选择。 + +```` +if score >= 90: + print('A') +else: + if score >=80: + print('B') + else: + if score >= 70: + print('C') + else: + if score >= 60: + print('D') + else: + print('F') +```` + +此片段将通过打印所获得的字母等级对名为score的值进行分类。 如果分数大于或等于90,则语句将打印A.如果不是(else),则询问下一个问题。 如果分数大于或等于80,那么它必须在80到89之间,因为第一个问题的答案是错误的。 在这种情况下,打印B。 您可以看到Python缩进模式有助于理解if和else之间的关联而无需任何其他语法元素。 + +此类嵌套选择的替代语法是使用elif关键字。 将else和下一个if组合起来,以消除对额外嵌套级别的需要。 请注意,如果所有其他条件都失败,则仍需要最终的else来提供默认情况。 + +```` +if score >= 90: + print('A') +elif score >=80: + print('B') +elif score >= 70: + print('C') +elif score >= 60: + print('D') +else: + print('F') +```` + +Python也有单向选择构造,即if语句。 使用此语句,如果条件为true,则执行操作。 在条件为假的情况下,简单地继续执行到if之后的下一个语句。 例如,以下片段将首先检查变量n的值是否为负数。 如果是,则由绝对值函数修改。 无论如何,下一步是计算平方根。 + +```` +if n<0: + n = abs(n) +print(math.sqrt(n)) +```` + +返回列表,有一种替代方法来创建一个列表,它使用迭代和选择构造,称为list comprehension。列表推导允许您根据某些处理或选择标准轻松创建列表。 例如,如果我们想要创建前10个完美正方形的列表,我们可以使用for语句: + +```` +>>> sqlist=[] +>>> for x in range(1,11): + sqlist.append(x*x) + +>>> sqlist +[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] +>>> +```` + +使用列表推导( 又称列表综合 ),我们可以一步完成 + +```` +>>> sqlist=[x*x for x in range(1,11)] +>>> sqlist +[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] +>>> +```` + +变量x采用for构造指定值1到10。 然后计算x * x的值并将其添加到正在构造的列表中。 列表推导的一般语法还允许添加选择标准,以便仅添加某些项。 例如, + +```` +>>> sqlist=[x*x for x in range(1,11) if x%2 != 0] +>>> sqlist +[1, 9, 25, 49, 81] +>>> +```` + +此列表推导构造了一个列表,该列表仅包含1到10范围内的奇数的平方。任何支持迭代的序列都可以在列表推导中用于构造新列表。 + +```` +>>>[ch.upper() for ch in 'comprehension' if ch not in 'aeiou'] +['C', 'M', 'P', 'R', 'H', 'N', 'S', 'N'] +>>> +```` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/1.\344\273\213\347\273\215/1.11.\345\244\204\347\220\206\345\274\202\345\270\270/README.md" "b/1.\344\273\213\347\273\215/1.11.\345\244\204\347\220\206\345\274\202\345\270\270/README.md" new file mode 100644 index 0000000..c7bf0cd --- /dev/null +++ "b/1.\344\273\213\347\273\215/1.11.\345\244\204\347\220\206\345\274\202\345\270\270/README.md" @@ -0,0 +1,64 @@ +## 1.11.处理异常 + +在编写程序时通常会出现两种类型的错误。第一个被称为语法错误,仅仅意味着程序员在语句或表达式的结构中犯了一个错误。例如,写一个for语句而忘记冒号是错误的。 + +```` +>>> for i in range(10) +SyntaxError: invalid syntax (, line 1) +```` + +在这种情况下,Python解释器发现它不能完成这个指令的处理,因为它不符合语言的规则。当你第一次学习一门语言时,语法错误通常会更频繁。 + +另一种类型的错误,称为逻辑错误,表示程序执行但给出错误结果的情况。 这可能是由于基础算法中的错误或该算法的转换错误。 在某些情况下,逻辑错误会导致非常糟糕的情况,例如尝试除以零或尝试访问列表中的索引超出列表边界的项目。 在这种情况下,逻辑错误会导致运行时错误,导致程序终止。 这些类型的运行时错误通常称为exceptions。 + +大多数时候,小白程序员只是把异常看作是导致执行结束的致命的运行时错误。然而,大多数编程语言提供了一种处理这些错误的方法,这些错误将允许程序员在选择时进行某种类型的干预。此外,如果程序员能保证检查到程序执行中的情况,就可以创建自己的异常。 + +当一个异常发生时,我们说它已经被“抛出”了。使用try语句处理所提出的异常。例如,思考下面的会话,让用户输入一个整数,然后从数学库调用平方根函数。如果用户输入大于或等于0的值,打印将显示平方根。但是,如果用户输入负值,平方根函数将报出ValueError异常。 + +```` +>>> anumber = int(input("Please enter an integer ")) +Please enter an integer -23 +>>> print(math.sqrt(anumber)) +Traceback (most recent call last): + File "", line 1, in + print(math.sqrt(anumber)) +ValueError: math domain error +>>> +```` + +我们可以通过在try块中调用print函数来处理这个异常。相应的except块“捕获”异常,并在发生异常时将消息打印给用户。例如: + +```` +>>> try: + print(math.sqrt(anumber)) + except: + print("Bad Value for square root") + print("Using absolute value instead") + print(math.sqrt(abs(anumber))) + +Bad Value for square root +Using absolute value instead +4.79583152331 +>>> +```` + +它将捕获sqrt引发的异常,将消息打印回用户并使用绝对值来确保我们采用非负数的平方根。 这意味着程序不会终止,而是继续执行下一个语句。 + +程序员也可以通过使用raise语句来引发运行时异常。 例如,我们可以先检查该值,然后引发自己的异常,而不是使用负数调用平方根函数。 下面的代码片段显示了创建新的RuntimeError异常的结果。 请注意,程序仍然会终止,但现在导致终止的异常是程序员明确创建的。 + +```` +>>> if anumber < 0: +... raise RuntimeError("You can't use a negative number") +... else: +... print(math.sqrt(anumber)) +... +Traceback (most recent call last): + File "", line 2, in +RuntimeError: You can't use a negative number +>>> +```` + +除了上面显示的RuntimeError之外,还可以引发多种异常。 有关所有可用异常类型的列表以及如何创建自己的异常类型,请参阅Python参考手册。 + + + diff --git "a/1.\344\273\213\347\273\215/1.12.\345\256\232\344\271\211\345\207\275\346\225\260/README.md" "b/1.\344\273\213\347\273\215/1.12.\345\256\232\344\271\211\345\207\275\346\225\260/README.md" new file mode 100644 index 0000000..f0413a6 --- /dev/null +++ "b/1.\344\273\213\347\273\215/1.12.\345\256\232\344\271\211\345\207\275\346\225\260/README.md" @@ -0,0 +1,63 @@ +## 1.12.定义函数 + +之前的过程抽象的例子调用了一个名为sqrt的Python函数,它来自math模块,用来计算平方根。一般来说,我们可以通过定义一个函数来隐藏任何计算的细节。一个函数定义需要一个名称、一组参数和一个函数体。它也可以显式地返回一个值。例如,下面定义的简单函数返回传入的值的平方。 + +```` +>>> def square(n): +... return n**2 +... +>>> square(3) +9 +>>> square(square(3)) +81 +>>> +```` + +这个函数定义的语法包括名称square和一个圆括号括起来的正式参数列表。对于这个函数,n是唯一的正式参数,这表明square只需要一个数据就可以完成它的工作。这些细节隐藏在“盒子里”,简单地计算出n**2的结果并返回它。为调用square函数,我们可以传入一个实际的参数值(在本例中为3)来让Python计算它。请注意,对square的调用会返回一个整数,该整数又可以被传递给另一个调用。 + +我们可以使用一种叫做“牛顿法”的著名技术来实现我们自己的平方根函数。牛顿法用来求近似平方根,它执行一种迭代运算,该计算的结果收敛于正确的值。等式newguess = 1/2 *(oldguess + n/oldguess)取n值,并通过在随后的迭代中使每个newguess成为oldguess来反复猜测平方根。这里最初始的猜测是n/2。 Listing 1显示了一个接受值n的函数定义,并在进行20次猜测后返回n的平方根。同样,牛顿法的细节隐藏在函数定义中,用户不需要知道任何关于实现的内容,就可以使用这个函数来实现它的预期目的。 Listing 1还显示了使用#字符作为注释标记。此行#后面的任何字符都将被忽略。 + +*Listing 1* + +```` +def squareroot(n): + root = n/2 #initial guess will be 1/2 of n + for k in range(20): + root = (1/2)*(root + (n / root)) + + return root +```` + +```` +>>>squareroot(9) +3.0 +>>>squareroot(4563) +67.549981495186216 +>>> +```` + +*自我检测* + +这是一个真正涵盖到目前为止所有内容的自我检查。 你可能听说过无限猴子定理? 该定理指出,猴子在打字机键盘上随机敲击键无限时间几乎肯定会输入一个给定的文本,例如威廉·莎士比亚的全集。好吧,假设我们用Python函数替换猴子。 你认为Python函数生成莎士比亚的一个句子需要多长时间? 我们要的目标句是:“我觉得它就像一只黄鼠狼” + +您别想在浏览器中运行这个,所以请启动您最喜欢的Python IDE。我们模拟这个的方法是写一个函数,通过从字母表中26个字母和空格中随机选择字母来生成一个27个字符长的字符串。我们将编写另一个函数,通过将随机生成的字符串与目标进行比较,从而为每个生成的字符串打分。 + +第三个函数会反复调用生成函数和打分函数,如果百分之百的字母是正确的,我们就完成了。如果字母不正确,我们就会生成一个全新的字符串。为了更方便地跟踪程序的进度,第三个函数应该打印出目前为止生成的最佳字符串及其每1000次尝试的分数。 + +*自我挑战* + +看看你是否可以通过保持正确的字母来改进程序,并且只修改目前最好的字符串中的一个字符。这是“爬坡”算法中的一种算法,如果它比前一个更好,我们只保留结果。 + + + + + + + + + + + + + + diff --git "a/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/README.md" "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/README.md" new file mode 100644 index 0000000..3b08022 --- /dev/null +++ "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/README.md" @@ -0,0 +1,632 @@ +## 1.13.Python中面向对象编程:定义类 + +我们之前说过,Python是一种面向对象的编程语言。到目前为止,我们已经使用了许多内置类来显示示例数据和控制结构。 面向对象编程语言中最强大的功能之一是允许程序员(问题解决者)创建新类,以模拟解决问题所需的数据。 + +请记住,我们使用抽象数据类型来提供数据对象的外观(其状态)及其可以执行的操作(其方法)的逻辑描述。 通过构建实现抽象数据类型的类,程序员可以利用抽象过程,同时提供在程序中实际使用抽象所需的详细信息。 每当我们想要实现抽象数据类型时,我们都会使用新类。 + + + +### 1.13.1.一个分数类 + +一个非常常见的例子,展示实现用户定义的类的细节,就是构造一个类来实现抽象的分数数据类型。我们已经看到Python提供了许多供我们使用的数字类。然而,有时候,能够创建“看起来像”分数的数据对象是最合适的。 + +像3/5这样的分数由两部分组成。最上面的值,也就是分子,可以是任意整数。底部的值,叫做分母,可以是大于0的整数(负分数有负的分子)。尽管可以为任何分数创建浮点近似,在这种情况下,我们想把分数表示成一个精确的值。 + +Fraction类型的操作将允许Fraction数据对象的行为与任何其他数值一样。 我们需要能够对分数进行加,减,乘和除。 我们还希望能够使用标准的“斜杠”形式显示分数,例如3/5。 此外,所有分数方法都应以最简分数返回结果,这样无论执行何种计算,结果总是最常见的形式。 + +在Python中,我们通过提供一个名称和一组在语法上类似于函数定义的方法定义来定义一个新类。 对于这个例子, + +```` +class Fraction: + + #the methods go here +```` + +为我们提供了定义方法的框架。 所有类应提供的第一个方法是构造函数。 构造函数定义了数据对象的创建方式。 要创建Fraction对象,我们需要提供两个数据,分子和分母。 在Python中,构造函数方法始终称为__init__(init之前和之后均有两个下划线),如Listing 2所示。 + +*Listing 1* + +```` +class Fraction: + + def __init__(self,top,bottom): + + self.num = top + self.den = bottom +```` + +请注意,形式参数列表包含三个项目(self,top,bottom)。 self是一个特殊参数,将始终用作返回对象本身的引用。 它必须始终是第一个形式参数; 但是,在调用时永远不会给出实际的参数值。 如前所述,分数需要两个状态数据,即分子和分母。构造函数中的符号self.num定义Fraction对象应具有名为num的内部数据对象作为其状态的一部分。同样,self.den创建了分母。两个正式参数做初始化赋值,允许新的分数对象知道它的起始值。 + +要创建分数类的实例,我们必须调用构造函数。调用方法是使用类名并传递必要状态的实际值(请注意,我们从不直接调用__init__)。 例如, + +```` +myfraction = Fraction(3,5) +```` + +创建一个名为myfraction的对象,表示分数3/5(五分之三)。 Figure 5显示了此对象,因为它当前已实现。 + +![1.13.面向对象编程-定义类.figure5](assets/1.13.面向对象编程-定义类.figure5.png) + +*Figure 5* + +我们需要做的下一件事是实现抽象数据类型所需的行为。 首先,考虑一下当我们尝试打印Fraction对象时会发生什么。 + +```` +>>> myf = Fraction(3,5) +>>> print(myf) +<__main__.Fraction instance at 0x409b1acc> +```` + +Fraction对象myf不知道如何响应此打印请求。 print函数要求对象将自身转换为字符串,以便可以将字符串写入输出。 myf唯一的选择是显示存储在变量中的实际引用(地址本身)。 这不是我们想要的。 + +我们有两种方法可以解决这个问题。 一种是定义一个名为show的方法,它允许Fraction对象将自己打印为字符串。 我们可以实现此方法,如Listing 3所示。如果我们像以前一样创建Fraction对象,我们可以要求它显示自己,换句话说,以适当的格式打印自己。 不幸的是,这一般不起作用。 为了使打印正常工作,我们需要告诉Fraction类如何将自身转换为字符串。 这是打印功能为了完成其工作所需要的。 + +*Listing 3* + +```` +def show(self): + print(self.num,"/",self.den) +```` + +```` +>>> myf = Fraction(3,5) +>>> myf.show() +3 / 5 +>>> print(myf) +<__main__.Fraction instance at 0x40bce9ac> +>>> +```` + +在Python中,所有类都提供了一组标准方法,但可能无法正常工作。 其中之一__str__是一种将对象转换为字符串的方法。 正如我们已经看到的,此方法的默认实现是返回实例地址字符串。我们需要做的是为这种方法提供“更好”的实现。我们会说这个实现重写了原有方法或重新定义方法的行为。 + +为此,我们只需定义一个名为__str__的方法,并为其提供一个新的实现,如Listing 4所示。除了特殊参数self之外,该定义不需要任何其他信息。 反过来,该方法将通过让每个内部状态数据转换为字符串然后使用字符串连接在字符串之间放置/字符来构建字符串表示。 每当要求Fraction对象将其自身转换为字符串时,将返回结果字符串。 请注意使用此功能的各种方法。 + +*Listing 4* +```` +def __str__(self): + return str(self.num)+"/"+str(self.den) +```` + +```` +>>> myf = Fraction(3,5) +>>> print(myf) +3/5 +>>> print("I ate", myf, "of the pizza") +I ate 3/5 of the pizza +>>> myf.__str__() +'3/5' +>>> str(myf) +'3/5' +>>> +```` + +我们可以重写新Fraction类的许多其他方法。 其中一些最重要的是基本的算术运算。 我们希望能够创建两个Fraction对象,然后使用标准的“+”符号将它们添加到一起。 目前,如果我们尝试将两个分数对象相加,我们得到以下结果: + +```` +>>> f1 = Fraction(1,4) +>>> f2 = Fraction(1,2) +>>> f1+f2 + +Traceback (most recent call last): + File "", line 1, in -toplevel- + f1+f2 +TypeError: unsupported operand type(s) for +: + 'instance' and 'instance' +>>> +```` + +如果仔细查看错误,您会发现问题是“+”运算符不理解Fraction这个操作数。 + +我们可以通过为Fraction类提供一个重写add方法的方法来解决这个问题。 在Python中,此方法称为__add__,它需要两个参数。 第一个是self,总是需要,第二个代表表达式中的另一个操作数。 例如, + +```` +f1.__add__(f2) +```` + +会要求Fraction对象f1将Fraction对象f2添加到自身。 这可以用标准写法f1 + f2调用。 + +两个分数必须具有相同的分母才能添加。 确保它们具有相同分母的最简单方法是简单地使用两个分母的乘积作为公分母,以便a/b + c/d = ad/bd + cb/bd = (ad + cb)/bd。实现如Listing 5所示。相加方法利用和的分子和分母创建并返回一个新的Fraction对象。我们可以用这个方法来写一个包含分数的标准算术表达式,赋值加法的结果,然后打印我们的结果。 + +*Listing 5* + +```` +def __add__(self,otherfraction): + + newnum = self.num*otherfraction.den + self.den*otherfraction.num + newden = self.den * otherfraction.den + + return Fraction(newnum,newden) +```` + +```` +>>> f1=Fraction(1,4) +>>> f2=Fraction(1,2) +>>> f3=f1+f2 +>>> print(f3) +6/8 +>>> +```` + +添加方法可以按照我们的意愿工作,但还可以改进。 请注意,6/8是正确的结果(1/4 + 1/2),但它不是“最简分数”表示。 最佳结果是3/4。 为了确保我们的结果总是最简分数,我们需要一个知道如何约分的辅助函数。 此函数需要查找最大公约数,简称GCD。 然后我们可以将分子和分母除以GCD,结果就会被约分为最简分数。 + +用于求出最大公约数的最著名算法是欧几里德算法,将在第8章中详细讨论。欧几里德算法表明,整数m如果可以被整数n整除,则m和n的最大公约数为n。 但是,如果m不能被n整除,结果便是 n 和 m除以n的余数 二者的最大公约数。 我们将在这里简单地提供一个迭代实现(参见ActiveCode 1)。 请注意,GCD算法的此实现仅在分母为正数时才有效。 这对于我们的分数类是可以接受的,因为我们已经说过负分数将由负分子表示。 + +*ActiveCode 1* +```` +def gcd(m,n): + while m%n != 0: + oldm = m + oldn = n + + m = oldn + n = oldm%oldn + return n + +print(gcd(20,10)) +```` + +现在我们可以使用此函数来约分任何分数。 为了将分数化为最简分数,我们将分子和分母除以它们的最大公约数。 因此,对于分数6/8,最大公约数为2.将分子和分母除以2会创建一个新的分数3/4(参见Listing 6)。 + +*Listing 6* + +```` +def __add__(self,otherfraction): + newnum = self.num*otherfraction.den + self.den*otherfraction.num + newden = self.den * otherfraction.den + common = gcd(newnum,newden) + return Fraction(newnum//common,newden//common) +```` + +```` +>>> f1=Fraction(1,4) +>>> f2=Fraction(1,2) +>>> f3=f1+f2 +>>> print(f3) +3/4 +>>> +```` + +![1.13.面向对象编程-定义类.figure6](assets/1.13.面向对象编程-定义类.figure6.png) + +*Figure 6* + +我们的分数对象现在有两个非常有用的方法,如Figure 6所显示。我们需要在示例Fraction类中包含的另一组方法将允许两个分数相互比较。假设我们有两个Fraction对象,f1和f2。 f1 == f2只有在引用同一个对象时才为True。具有相同分子和分母的两个不同对象在此实现下将不相等。这称为浅相等(shallow equality)(参见Figure 7)。 + +![1.13.面向对象编程-定义类.figure7](assets/1.13.面向对象编程-定义类.figure7.png) + +*Figure 7* + +我们可以通过重写__eq__方法来实现深相等(参见Figure 7) - 通过比较值来判断相等,而不是比较引用。 __eq__方法是另一个任何类中都可用的标准方法。 __eq__方法比较两个对象,如果它们的值相同则返回True,否则返回False。 + +在Fraction类中,我们可以通过再次将两个分数通分后比较分子来实现__eq__方法(参见Listing 7)。 值得注意的是,还有其他可以覆盖的关系运算符。 例如,__ le__方法提供的小于或等于功能。 + +*Listing 7* + +```` +def __eq__(self, other): + firstnum = self.num * other.den + secondnum = other.num * self.den + + return firstnum == secondnum +```` + +到目前为止,完整的Fraction类在ActiveCode 2中显示。我们把剩下的算术和关系方法作为练习。 + +*ActiveCode 2* + +```` +def gcd(m,n): + while m%n != 0: + oldm = m + oldn = n + + m = oldn + n = oldm%oldn + return n + +class Fraction: + def __init__(self,top,bottom): + self.num = top + self.den = bottom + + def __str__(self): + return str(self.num)+"/"+str(self.den) + + def show(self): + print(self.num,"/",self.den) + + def __add__(self,otherfraction): + newnum = self.num*otherfraction.den + \ + self.den*otherfraction.num + newden = self.den * otherfraction.den + common = gcd(newnum,newden) + return Fraction(newnum//common,newden//common) + + def __eq__(self, other): + firstnum = self.num * other.den + secondnum = other.num * self.den + + return firstnum == secondnum + +x = Fraction(1,2) +y = Fraction(2,3) +print(x+y) +print(x == y) +```` + + + +### 1.13.2.继承:逻辑门和电路 + +我们的最后一节将介绍面向对象编程的另一个重要方面。 继承是一个类与另一个类相关联的能力,与人们彼此相关的方式非常相似。 儿童继承父母的特征。 类似地,Python子类可以从父类继承特征数据和行为。 这些类通常称为子类和超类。 + +Figure 8显示了内置的Python集合及其相互之间的关系。 我们将这种关系结构称为继承结构。 例如,列表是有序集合的子类。 在这种情况下,我们将列表称为子级,将序列称为父级(或子类列表和超类序列)。 这通常被称为IS-A Relationship(列表IS-A游戏集合)。 这意味着列表从序列继承了重要的特征,即底层数据的排序和连接,重复和索引等操作。 + +![1.13.面向对象编程-定义类.figure8](assets/1.13.面向对象编程-定义类.figure8.png) + +*Figure 8* + +列表,元组和字符串都是有序集合类型。 它们都继承了常见的数据组织方式和操作。 但是,根据数据是否是同构的以及集合是否不可变,它们中的每一个都是不同的。 孩子们都从父母那里获益,并通过增加额外的特征来区分自己。 + +通过以这种继承方式来组织类,面向对象的编程语言允许扩展先前编写的代码以满足新情况的需要。 此外,通过以这种继承方式来组织数据,我们可以更好地理解存在的关系。 我们可以更有效地构建抽象表示。 + +为了进一步探索这个特性,我们将构造仿真,一个模拟数字电路的应用程序。 该模拟的基本构建块将是逻辑门。 这些电子开关代表其输入和输出之间的布尔代数关系。 通常,门具有单个输出线。 输出值取决于输入线上给出的值。 + +与门有两条输入线,每条输入线可以是0或1(分别代表False或True)。 如果两个输入行的值都为1,则结果输出为1.但是,如果输入行中的任何一个或两个为0,则结果为0.或者门也有两个输入行,如果一个或两个输入行都产生1 输入值的值为1.在两个输入行均为0的情况下,结果为0。 + +非门与其他两个门的不同之处在于它们只有一条输入线。 输出值与输入值完全相反。 如果输入上出现0,则在输出上产生1。 类似地,1产生0. Figure 9显示了这些门中的每一个通常如何表示。 每个门还有一个真值表,显示由门执行的输入到输出映射。 + +![1.13.面向对象编程-定义类.figure9](assets/1.13.面向对象编程-定义类.figure9.png) + +*Figure 9* + +通过以各种模式组合这些门然后应用一组输入值,我们可以构建具有逻辑功能的电路。 Figure 10显示了一个由两个与门,一个或门和一个非门组成的电路。 来自两个与门的输出线直接馈入或门,或门产生的输出提供给非门。 如果我们将一组输入值应用于四个输入行(每个与门两个),则处理这些值并在非门的输出处显示结果。 Figure 10还显示了一个带有值的示例。 + +![1.13.面向对象编程-定义类.figure10](assets/1.13.面向对象编程-定义类.figure10.png) + +*Figure 10* + +为了实现电路,我们将首先构建逻辑门的表示。 逻辑门很容易组织成一个类继承层次结构,如Figure 11所示。在继承结构的顶部,LogicGate类代表逻辑门的最一般特征:即门的标签和输出线。 下一级子类将逻辑门分为两个系列,即具有一个输入线的系列和具有两个输入线的系列。 在此之下,每个的特定逻辑功能出现。 + +![1.13.面向对象编程-定义类.figure11](assets/1.13.面向对象编程-定义类.figure11.png) + +*Figure 11* + +我们现在可以从最常见的LogicGate开始实现这些类。 如前所述,每扇门都有一个识别标签和一条输出线。 此外,我们还需要一种方法,允许门的用户从门上请求标签。。 + +每个逻辑门需要的另一个行为是知道其输出值的能力。 这将要求门基于当前输入执行适当的逻辑。 为了产生输出,门需要知道该逻辑具体是什么。 这意味着调用一个方法来执行逻辑计算。 完整的类如Listing 8所示。 + +*Listing 8* + +```` +class LogicGate: + + def __init__(self,n): + self.label = n + self.output = None + + def getLabel(self): + return self.label + + def getOutput(self): + self.output = self.performGateLogic() + return self.output +```` + +此时,我们不会实现performGateLogic函数。 原因是我们不知道每个门将如何执行自己的逻辑运算。 这些细节将包含在添加到层次结构中的每个单独的门中。 这是面向对象编程中非常强大的思想。 我们正在编写一种方法,它将使用尚不存在的代码。 参数self是对调用该方法的实际门对象的引用。 添加到层级结构中的任何新逻辑门都只需要实现performGateLogic函数,它将在适当的时候使用。 完成后,门可以提供其输出值。 这种扩展当前存在的层次结构并提供层次结构需要使用新类的特定功能的能力对于重用现有代码非常重要。 + +我们根据输入线的数量对逻辑门进行了分类。 与门有两条输入线。 或门也有两条输入线。 非门有一条输入线。 BinaryGate类将是LogicGate的子类,并将添加两个输入行。 UnaryGate类也将是LogicGate的子类,但只有一个输入行。 在计算机电路设计中,这些线有时被称为“引脚”,因此我们将在实现中使用该术语。 + +*Listing 9* + +```` +class BinaryGate(LogicGate): + + def __init__(self,n): + LogicGate.__init__(self,n) + + self.pinA = None + self.pinB = None + + def getPinA(self): + return int(input("Enter Pin A input for gate "+ self.getLabel()+"-->")) + + def getPinB(self): + return int(input("Enter Pin B input for gate "+ self.getLabel()+"-->")) +```` + +*Listing 10* + +```` +class UnaryGate(LogicGate): + + def __init__(self,n): + LogicGate.__init__(self,n) + + self.pin = None + + def getPin(self): + return int(input("Enter Pin input for gate "+ self.getLabel()+"-->")) +```` + +Listing 9和Listing 10实现了这两个类。 这两个类中的构造函数都是使用父类的__init__方法显式调用父类的构造函数。 在创建BinaryGate类的实例时,我们首先要初始化从LogicGate继承的任何数据项。 在这种情况下,这意味着门的标签。 然后构造函数继续添加两条输入线(pinA和pinB)。 这是构建类层次结构时应始终使用的非常常见的模式。 子类构造函数需要调用父类构造函数,然后继续使用它们自己的区分数据。 + +Python还有一个名为super的函数,可用于代替显式命名父类。 这是一种更通用的机制,并且被广泛使用,尤其是当一个类有多个父类时。 但是,这不是我们将在本简介中讨论的内容。 例如,在上面的示例中,LogicGate .__ init __(self,n)可以替换为super(UnaryGate,self).__ init __(n)。 + +BinaryGate类添加的唯一行为是从两个输入行获取值的能力。 由于这些值来自某些外部位置,我们只需通过输入语句询问用户即可。 除了只有一个输入行之外,UnaryGate类也会进行相同的实现。 + +现在我们有了一个针对门的通用类,这取决于输入行的数量,我们可以构建具有独特行为的特定门。例如,AndGate类将是BinaryGate的子类,因为与门有两条输入线。 和以前一样,构造函数的第一行调用父类构造函数(BinaryGate),后者又调用其父类构造函数(LogicGate)。 请注意,AndGate类不提供任何新数据,因为它继承了两个输入行,一个输出行和一个标签。 + +*Listing 11* + +```` +class AndGate(BinaryGate): + + def __init__(self,n): + BinaryGate.__init__(self,n) + + def performGateLogic(self): + + a = self.getPinA() + b = self.getPinB() + if a==1 and b==1: + return 1 + else: + return 0 +```` + +AndGate唯一需要添加的是执行前面描述的布尔操作的特定行为。 这是我们可以提供performGateLogic方法的地方。 对于AND门,此方法首先必须获取两个输入值,然后仅在两个输入值均为1时返回1.完整类如Listing 11所示。 + +我们可以通过创建实例并要求它计算其输出来显示AndGate类。 以下会话显示了一个AndGate对象g1,它具有内部标签“G1”。 当我们调用getOutput方法时,该对象必须首先调用其performGateLogic方法,该方法依次查询两个输入行。 提供值后,将显示正确的输出。 + +```` +>>> g1 = AndGate("G1") +>>> g1.getOutput() +Enter Pin A input for gate G1-->1 +Enter Pin B input for gate G1-->0 +0 +```` + +对于或门和非门,可以进行相同的开发。 OrGate类也将是BinaryGate的子类,NotGate类将扩展UnaryGate类。 这两个类都需要提供自己的performGateLogic函数,因为这是它们的特定行为。 + +我们可以使用单个门,首先构造一个门类的实例,然后得到门的输出(这将反过来需要提供输入)。 例如: + +```` +>>> g2 = OrGate("G2") +>>> g2.getOutput() +Enter Pin A input for gate G2-->1 +Enter Pin B input for gate G2-->1 +1 +>>> g2.getOutput() +Enter Pin A input for gate G2-->0 +Enter Pin B input for gate G2-->0 +0 +>>> g3 = NotGate("G3") +>>> g3.getOutput() +Enter Pin input for gate G3-->0 +1 +```` + +现在我们已经完成了基本的工作,我们可以将注意力转向构建电路。 为了创建一个电路,我们需要将门连接在一起,一个输出另一个输入。 为此,我们将实现一个名为Connector的新类。 + +Connector类不会驻留在门层次结构中。 然而,它将使用门层次结构,因为每个连接器将具有两个门,一个在两端(参见Figure 12)。 这种关系在面向对象编程中非常重要。 它被称为HAS-A关系。 回想一下,我们使用短语“IS-A Relationship”来表示子类与父类相关,例如UnaryGate IS-A LogicGate。 + +![1.13.面向对象编程-定义类.figure12](assets/1.13.面向对象编程-定义类.figure12.png) + +*Figure 12* + +现在,使用Connector类,我们说连接器HAS-A LogicGate意味着连接器将包含LogicGate类的实例但不是层次结构的一部分。 在设计类时,区分具有IS-A关系(需要继承)和具有HAS-A关系(没有继承)的类非常重要。 + +Listing 12显示了Connector类。 每个连接器对象内的两个门实例将被称为fromgate和togate,识别出数据值将从一个门的输出“流动”到下一个门的输入线。 对setNextPin的调用对于建立连接非常重要(参见Listing 13)。 我们需要将此方法添加到我们的门类中,以便每个togate可以为连接选择正确的输入行。 + +*Listing 12* + +```` +class Connector: + + def __init__(self, fgate, tgate): + self.fromgate = fgate + self.togate = tgate + + tgate.setNextPin(self) + + def getFrom(self): + return self.fromgate + + def getTo(self): + return self.togate +```` + +在BinaryGate类中,对于具有两条可能输入线的门,连接器必须仅连接到一条线。 如果它们都可用,我们将默认选择pinA。 如果pinA已经连接,那么我们将选择pinB。 无法连接到没有可用输入线的门。 + +*Listing 13* + +```` +def setNextPin(self,source): + if self.pinA == None: + self.pinA = source + else: + if self.pinB == None: + self.pinB = source + else: + raise RuntimeError("Error: NO EMPTY PINS") +```` + +现在可以从两个位置获取输入:外部,如前所述,以及连接到该输入线的门的输出。 这需要更改getPinA和getPinB方法(参见Listing 14)。 如果输入行没有连接到任何内容(空),则像以前一样向外部询问用户。 但是,如果存在连接,则访问连接并检索fromgate的输出值。 这反过来导致该门处理其逻辑。 这一直持续到所有输入都可用并且最终输出值成为所讨论的门的所需输入。 从某种意义上说,电路向后工作以找到最终产生输出所需的输入。 + +*Listing 14* + +```` +def getPinA(self): + if self.pinA == None: + return input("Enter Pin A input for gate " + self.getName()+"-->") + else: + return self.pinA.getFrom().getOutput() +```` + +以下片段构建了本节前面所示的电路: + +```` +>>> g1 = AndGate("G1") +>>> g2 = AndGate("G2") +>>> g3 = OrGate("G3") +>>> g4 = NotGate("G4") +>>> c1 = Connector(g1,g3) +>>> c2 = Connector(g2,g3) +>>> c3 = Connector(g3,g4) +```` + +两个与门(g1和g2)的输出连接到或门(g3),输出连接到非门(g4)。 非门的输出是整个电路的输出。 例如: + +```` +>>> g4.getOutput() +Pin A input for gate G1-->0 +Pin B input for gate G1-->1 +Pin A input for gate G2-->1 +Pin B input for gate G2-->1 +0 +```` + + +使用ActiveCode 4自己尝试一下: + +*ActiveCode 4* + +```` +class LogicGate: + + def __init__(self,n): + self.name = n + self.output = None + + def getName(self): + return self.name + + def getOutput(self): + self.output = self.performGateLogic() + return self.output + + +class BinaryGate(LogicGate): + + def __init__(self,n): + LogicGate.__init__(self,n) + + self.pinA = None + self.pinB = None + + def getPinA(self): + if self.pinA == None: + return int(input("Enter Pin A input for gate "+self.getName()+"-->")) + else: + return self.pinA.getFrom().getOutput() + + def getPinB(self): + if self.pinB == None: + return int(input("Enter Pin B input for gate "+self.getName()+"-->")) + else: + return self.pinB.getFrom().getOutput() + + def setNextPin(self,source): + if self.pinA == None: + self.pinA = source + else: + if self.pinB == None: + self.pinB = source + else: + print("Cannot Connect: NO EMPTY PINS on this gate") + + +class AndGate(BinaryGate): + + def __init__(self,n): + BinaryGate.__init__(self,n) + + def performGateLogic(self): + + a = self.getPinA() + b = self.getPinB() + if a==1 and b==1: + return 1 + else: + return 0 + +class OrGate(BinaryGate): + + def __init__(self,n): + BinaryGate.__init__(self,n) + + def performGateLogic(self): + + a = self.getPinA() + b = self.getPinB() + if a ==1 or b==1: + return 1 + else: + return 0 + +class UnaryGate(LogicGate): + + def __init__(self,n): + LogicGate.__init__(self,n) + + self.pin = None + + def getPin(self): + if self.pin == None: + return int(input("Enter Pin input for gate "+self.getName()+"-->")) + else: + return self.pin.getFrom().getOutput() + + def setNextPin(self,source): + if self.pin == None: + self.pin = source + else: + print("Cannot Connect: NO EMPTY PINS on this gate") + + +class NotGate(UnaryGate): + + def __init__(self,n): + UnaryGate.__init__(self,n) + + def performGateLogic(self): + if self.getPin(): + return 0 + else: + return 1 + + +class Connector: + + def __init__(self, fgate, tgate): + self.fromgate = fgate + self.togate = tgate + + tgate.setNextPin(self) + + def getFrom(self): + return self.fromgate + + def getTo(self): + return self.togate + + +def main(): + g1 = AndGate("G1") + g2 = AndGate("G2") + g3 = OrGate("G3") + g4 = NotGate("G4") + c1 = Connector(g1,g3) + c2 = Connector(g2,g3) + c3 = Connector(g3,g4) + print(g4.getOutput()) + +main() +```` + +*自我检测* + +创建两个新的门类,一个名为NorGate,另一个名为NandGate。 NandGates像AndGates一样工作,它没有附加到输出。 NorGates工作的湖OrGates没有附加到输出。 + +创建一系列门,证明以下等式NOT(( A and B) or (C and D))与NOT( A and B ) and NOT (C and D)相同。 确保在模拟中使用一些新的门。 diff --git "a/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure10.png" "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure10.png" new file mode 100644 index 0000000..b3c1793 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure10.png" differ diff --git "a/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure11.png" "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure11.png" new file mode 100644 index 0000000..072ec6a Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure11.png" differ diff --git "a/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure12.png" "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure12.png" new file mode 100644 index 0000000..9f24bb5 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure12.png" differ diff --git "a/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure5.png" "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure5.png" new file mode 100644 index 0000000..1be9e72 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure5.png" differ diff --git "a/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure6.png" "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure6.png" new file mode 100644 index 0000000..898ac43 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure6.png" differ diff --git "a/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure7.png" "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure7.png" new file mode 100644 index 0000000..a2a1841 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure7.png" differ diff --git "a/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure8.png" "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure8.png" new file mode 100644 index 0000000..82d71e9 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure8.png" differ diff --git "a/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure9.png" "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure9.png" new file mode 100644 index 0000000..2707e6d Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273/assets/1.13.\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213-\345\256\232\344\271\211\347\261\273.figure9.png" differ diff --git "a/1.\344\273\213\347\273\215/1.14.\346\200\273\347\273\223/README.md" "b/1.\344\273\213\347\273\215/1.14.\346\200\273\347\273\223/README.md" new file mode 100644 index 0000000..259a5a8 --- /dev/null +++ "b/1.\344\273\213\347\273\215/1.14.\346\200\273\347\273\223/README.md" @@ -0,0 +1,12 @@ +## 1.14.总结 + +* 计算机科学是解决问题的研究。 +* 计算机科学使用抽象作为表示过程和数据的工具。 +* 抽象的数据类型允许程序员通过隐藏数据的细节来管理问题领域的复杂性。 +* Python是一种强大但易于使用的面向对象语言。 +* 列表、元组和字符串都是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.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/assets/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.figure2.png" "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/assets/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.figure2.png" index 7566c5a..a4fe32a 100644 Binary files "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/assets/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.figure2.png" and "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/assets/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.figure2.png" differ 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/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/README.md" "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/README.md" new file mode 100644 index 0000000..6852db9 --- /dev/null +++ "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/README.md" @@ -0,0 +1,375 @@ +## 1.8.数据入门 + + +我们在上面说过,Python支持面向对象的编程范式。这意味着Python认为在解决问题的过程中的重点是数据。在Python中,以及在任何其他面向对象的编程语言中,我们定义一个类来描述数据的外观(状态)和数据能做什么(行为)。因为类的用户只看数据项的状态和行为,所以类类似于抽象的数据类型。数据项在面向对象的范式中称为对象。 对象是类的实例。 + + +### 1.8.1.内置的原子数据类型 + +通过思考原子数据类型来开始我们的回顾。Python有两个主要的内置数字类,它们实现了整型和浮点数据类型。这些Python类称为int和float。标准的算术运算,+,-,*,/,和**(取幂),可以用括号强制操作的顺序来规避正常的操作符优先级。其他很有用的操作是余数(模组)操作符%、和整数除法//。注意,当两个整数相除,结果是一个浮点数。整数除法运算符通过截断所有小数部分来返回商的整数部分。 + +布尔数据类型,作为Python bool类的实现,在表示真值时非常有用。在标准的布尔操作中,and、or、not,布尔类型的状态值可能是True和False。 + +```` +>>> True +True +>>> False +False +>>> False or True +True +>>> not (False or True) +False +>>> True and True +True +```` + +布尔数据对象也被用作比较运算符的结果,例如相等(==)和大于(>)。此外,关系运算符和逻辑运算符可以组合在一起形成复杂的逻辑问题。Table 1展示了关系和逻辑运算符,例子如下所示。 + +![1.8.数据入门.table1](assets/1.8.数据入门.table1.png) + +*Table 1* + +标识符在编程语言中作为名称使用。在Python中,标识符以字母或下划线(_)开头,大小写敏感,并且可以是任意长度的。请记住,使用表示含义的名称总是一个好主意,这样您的程序代码就更容易阅读和理解。 + +在赋值语句的左边第一次使用一个名称时,就会产生一个Python变量。赋值语句提供了一种将名称与值关联起来的方法。该变量将持有对一块数据的引用而不是数据本身。思考以下会话: + +```` +>>> theSum = 0 +>>> theSum +0 +>>> theSum = theSum + 1 +>>> theSum +1 +>>> theSum = True +>>> theSum +True +```` + +赋值语句theSum = 0创建一个变量称为theSum并让它持有对数据对象0的引用(参见Figure 3)。通常,会对赋值语句的右侧进行求值,并将对结果数据对象的引用“赋值”给左侧的名称。如上所示,如果数据的类型发生变化(参见Figure 3),布尔值为True,那么变量的类型也如此(theSum现在是布尔型)。赋值语句改变了变量所持有的引用。相同的变量可以引用许多不同类型的数据。 + +![1.8.数据入门.figure3](assets/1.8.数据入门.figure3.png) + +*Figure 3* + +![1.8.数据入门.figure4](assets/1.8.数据入门.figure4.png) + +*Figure 4* + + +### 1.8.2.内置的集合数据类型 + +除了数字和布尔类之外,Python还有许多非常强大的内置集合类。列表、字符串和元组是有序集合,它们在常用的结构中非常相似,但是有特定的差异,必须理解它们才能正确使用。集合和字典是无序的集合。 + +列表是对Python数据对象的零个或多个引用的有序集合。列表的写法是用方括号括起来、以逗号分隔。空列表简单的用[]表示。列表是异构的,这意味着数据对象不需要全部是同一类型,并且集合可以赋值给一个变量,如下所示。下面的代码展示了列表中的各种Python数据对象。 + +```` +>>> [1,3,True,6.5] +[1, 3, True, 6.5] +>>> myList = [1,3,True,6.5] +>>> myList +[1, 3, True, 6.5] +```` + +请注意,当Python对列表求值时,会返回列表本身。然而,为了记住后面的列表操作,它的引用需要赋值给一个变量。 + +由于列表被认为是按顺序排列,所以它们支持许多可以应用于任何Python序列的操作。Table 2回顾了这些操作,以下给出了它们的使用示例。 + +![1.8.数据入门.table2](assets/1.8.数据入门.table2.png) + +*Table 2* + +注意,列表(序列)的索引从0开始计数。切片操作,myList[1:3],返回一个包含索引从1到3的项的列表、但不包含索引为3的项。 + +有时,您需要初始化一个列表。这可以通过使用重复操作快速完成。例如, + +```` +>>> myList = [0] * 6 +>>> myList +[0, 0, 0, 0, 0, 0] +```` + +除了重复操作符之外,还有一个非常重要的问题是其结果是对序列中的数据对象的引用的重复。通过思考以下代码得到最好的印证: + +```` +myList = [1,2,3,4] +A = [myList]*3 +print(A) +myList[2]=45 +print(A) +```` + +变量A持有三个名为myList的原始列表的引用。注意,对myList的一个元素的更改,在A的所有三种情况中都体现了出来 + +列表支持许多用于构建数据结构的方法。Table 3提供了总结。下面是使用他们的例子。 + +![1.8.数据入门.table3](assets/1.8.数据入门.table3.png) + +*Table 3* + +```` +myList = [1024, 3, True, 6.5] +myList.append(False) +print(myList) +myList.insert(2,4.5) +print(myList) +print(myList.pop()) +print(myList) +print(myList.pop(1)) +print(myList) +myList.pop(2) +print(myList) +myList.sort() +print(myList) +myList.reverse() +print(myList) +print(myList.count(6.5)) +print(myList.index(4.5)) +myList.remove(6.5) +print(myList) +del myList[0] +print(myList) +```` + +你可以看到一些方法,比如pop,返回一个值,并修改列表。其他的,比如反向,只是简单地修改列表,没有返回值。pop会默返回列表的末尾项,也能删除并返回一个特定项。这些方法再次使用从0开始的索引范围。您还应该注意到熟悉的“dot”符号,使对象调用方法。myList.append(False)可以被认为是“请求对象myList执行它的append方法,并将False这个值传入”。即使是像整数这样的简单数据对象也可以以这种方式调用方法。 + +```` +>>> (54).__add__(21) +75 +>>> +```` + +在此段中,我们让整型对象54来执行它的add方法(在Python中称为__add__),并将它与传过去的21相加,结果是75。当然,我们通常把它写成54+21。稍后我们将在本节中详细介绍这些方法。 + +通常与列表一起讨论的一个常见的Python函数是range函数。range产生一个范围对象,表示一系列的值。通过使用list函数,我们可以将range对象的值看作一个列表。如下所示: + +```` +>>> range(10) +range(0, 10) +>>> list(range(10)) +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +>>> range(5,10) +range(5, 10) +>>> list(range(5,10)) +[5, 6, 7, 8, 9] +>>> list(range(5,10,2)) +[5, 7, 9] +>>> list(range(10,1,-1)) +[10, 9, 8, 7, 6, 5, 4, 3, 2] +>>> +```` + +range对象代表一个整数序列。默认情况下,它将从0开始。如果您提供了更多的参数,它将在特定的点开始和结束,甚至可以跳过一些项。在我们的第一个例子中,range(10),序列从0开始,递增到10但不包括10。在第二个例子中,范围(5,10)从5开始,递增到10但不包括10。范围(5,10,2)类似的执行,但是以2递增(同样,10不包括在内)。 + +字符串是由零个或多个字母、数字和其他符号组成的序列集合。我们称之为字母,数字和其他符号字符。通过使用引号(单引号或双引号)将文字字符串值与标识符区分开来。 + +```` +>>> "David" +'David' +>>> myName = "David" +>>> myName[3] +'i' +>>> myName*2 +'DavidDavid' +>>> len(myName) +5 +>>> +```` + +由于字符串是序列,所以上面描述的所有序列操作都按照您的预期工作。另外,字符串有许多方法,其中一些方法如Table 4所示。例如, + +```` +>>> myName +'David' +>>> myName.upper() +'DAVID' +>>> myName.center(10) +' David ' +>>> myName.find('v') +2 +>>> myName.split('v') +['Da', 'id'] +```` + +其中,split对于处理数据非常有用。split将使用一个字符串,并使用分割字符作为分隔点返回字符串列表。在这个例子中,v是分隔点。如果没有指定分隔点,split方法会寻找空格字符,如制表符、换行符和空格。 + +![1.8.数据入门.table4](assets/1.8.数据入门.table4.png) + +*Table 4* + +列表和字符串之间的主要区别是,列表可以被修改,而字符串不能。这被称为可变性。列表是可变的;字符串是不可变的。例如,您可以通过使用索引和赋值来更改列表中的项。而对字符串进行这样的更改是不允许的。 + +```` +>>> myList +[1, 3, True, 6.5] +>>> myList[0]=2**10 +>>> myList +[1024, 3, True, 6.5] +>>> +>>> myName +'David' +>>> myName[0]='X' + +Traceback (most recent call last): + File "", line 1, in -toplevel- + myName[0]='X' +TypeError: object doesn't support item assignment +>>> +```` + +元组与列表非常相似,因为它们是异构的数据序列。不同之处在于,元组是不可变的,就像字符串一样。任何元组都不能被改变。元组的写法是用括号括起来并以逗号来分隔值。作为序列,它们可以使用上面描述的任何操作。例如, + +```` +>>> myTuple = (2,True,4.96) +>>> myTuple +(2, True, 4.96) +>>> len(myTuple) +3 +>>> myTuple[0] +2 +>>> myTuple * 3 +(2, True, 4.96, 2, True, 4.96, 2, True, 4.96) +>>> myTuple[0:2] +(2, True) +>>> +```` + +但是,如果您试图改变元组中的一个项,您将会得到一个错误。注意,错误消息提供了问题的位置和原因。 + +```` +>>> myTuple[1]=False + +Traceback (most recent call last): + File "", line 1, in -toplevel- + myTuple[1]=False +TypeError: object doesn't support item assignment +>>> +```` + + +set是零到多个不可变的Python数据对象组成的无序集合。集合中的值不允许重复,以逗号分隔,写在大括号中。空集合由set()表示。集合是异构的并且可以被分配给一个变量,如下所示。 + +```` +>>> {3,6,"cat",4.5,False} +{False, 4.5, 3, 6, 'cat'} +>>> mySet = {3,6,"cat",4.5,False} +>>> mySet +{False, 4.5, 3, 6, 'cat'} +>>> +```` + +尽管集合不被认为是序列,但是它们确实支持前面提到的一些熟悉的操作。Table 5回顾了这些操作,以下给出了它们的使用示例。 + +![1.8.数据入门.table5](assets/1.8.数据入门.table5.png) + +*Table 5* + +```` +>>> mySet +{False, 4.5, 3, 6, 'cat'} +>>> len(mySet) +5 +>>> False in mySet +True +>>> "dog" in mySet +False +>>> +```` + +集合支持许多方法,使用过数学概念中集合的人应该对这些方法比较熟悉。 Table 6提供了总结。 它们的使用示例如下。 请注意,并集,交集,子集和差分都有可以使用的运算符。 + +![1.8.数据入门.table6](assets/1.8.数据入门.table6.png) + +*Table 6* + +```` +>>> mySet +{False, 4.5, 3, 6, 'cat'} +>>> yourSet = {99,3,100} +>>> mySet.union(yourSet) +{False, 4.5, 3, 100, 6, 'cat', 99} +>>> mySet | yourSet +{False, 4.5, 3, 100, 6, 'cat', 99} +>>> mySet.intersection(yourSet) +{3} +>>> mySet & yourSet +{3} +>>> mySet.difference(yourSet) +{False, 4.5, 6, 'cat'} +>>> mySet - yourSet +{False, 4.5, 6, 'cat'} +>>> {3,100}.issubset(yourSet) +True +>>> {3,100}<=yourSet +True +>>> mySet.add("house") +>>> mySet +{False, 4.5, 3, 6, 'house', 'cat'} +>>> mySet.remove(4.5) +>>> mySet +{False, 3, 6, 'house', 'cat'} +>>> mySet.pop() +False +>>> mySet +{3, 6, 'house', 'cat'} +>>> mySet.clear() +>>> mySet +set() +>>> +```` + +我们最后的Python集合是一个无序的结构,称为字典。字典是一组关联项,其中每一项由一个键和一个值组成。这个键-值对通常被写成key:value。字典的键值对以逗号分隔并用花括号括起来。例如, + +```` +>>> capitals = {'Iowa':'DesMoines','Wisconsin':'Madison'} +>>> capitals +{'Wisconsin': 'Madison', 'Iowa': 'DesMoines'} +>>> +```` + +我们可以通过它的键来访问一个值,或者通过添加另一个键-值对来操作字典。取值语法除了使用键值而不是使用项目的索引,看起来很像序列取值,添加新值类似。 + +```` +capitals = {'Iowa':'DesMoines','Wisconsin':'Madison'} +print(capitals['Iowa']) +capitals['Utah']='SaltLakeCity' +print(capitals) +capitals['California']='Sacramento' +print(len(capitals)) +for k in capitals: + print(capitals[k]," is the capital of ", k) +```` + +需要注意的是,字典在键上没有特定的顺序。第一组加入('Utah': 'SaltLakeCity')首先被放在字典里,第二组加了('California': 'Sacramento')。键的位置取决于“哈希”的概念,这将在第4章中更详细地解释。字典中length方法的作用与其他集合中的相同。 + +字典既有方法又有操作符。Table 7和Table 8描述了它们,会话显示了它们的作用。keys、values和items方法都返回包含感兴趣的值的对象。你可以使用list函数把它们转换成列表,还将看到get方法有两种变体。如果字典里没有对应键,get将返回空。然而,第二个可选参数可以指定一个返回值。 + +![1.8.数据入门.table7](assets/1.8.数据入门.table7.png) + +*Table 7* + +```` +>>> phoneext={'david':1410,'brad':1137} +>>> phoneext +{'brad': 1137, 'david': 1410} +>>> phoneext.keys() +dict_keys(['brad', 'david']) +>>> list(phoneext.keys()) +['brad', 'david'] +>>> phoneext.values() +dict_values([1137, 1410]) +>>> list(phoneext.values()) +[1137, 1410] +>>> phoneext.items() +dict_items([('brad', 1137), ('david', 1410)]) +>>> list(phoneext.items()) +[('brad', 1137), ('david', 1410)] +>>> phoneext.get("kent") +>>> phoneext.get("kent","NO ENTRY") +'NO ENTRY' +>>> +```` + +![1.8.数据入门.table8](assets/1.8.数据入门.table8.png) + +*Table 8* + diff --git "a/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.figure3.png" "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.figure3.png" new file mode 100644 index 0000000..dbd473f Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.figure3.png" differ diff --git "a/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.figure4.png" "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.figure4.png" new file mode 100644 index 0000000..eb387ef Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.figure4.png" differ diff --git "a/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table1.png" "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table1.png" new file mode 100644 index 0000000..53280a1 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table1.png" differ diff --git "a/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table2.png" "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table2.png" new file mode 100644 index 0000000..e6ea4b6 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table2.png" differ diff --git "a/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table3.png" "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table3.png" new file mode 100644 index 0000000..75c36b2 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table3.png" differ diff --git "a/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table4.png" "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table4.png" new file mode 100644 index 0000000..7f74401 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table4.png" differ diff --git "a/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table5.png" "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table5.png" new file mode 100644 index 0000000..79dec07 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table5.png" differ diff --git "a/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table6.png" "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table6.png" new file mode 100644 index 0000000..1767019 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table6.png" differ diff --git "a/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table7.png" "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table7.png" new file mode 100644 index 0000000..bd729fb Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table7.png" differ diff --git "a/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table8.png" "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table8.png" new file mode 100644 index 0000000..8867b5e Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.8.\346\225\260\346\215\256\345\205\245\351\227\250/assets/1.8.\346\225\260\346\215\256\345\205\245\351\227\250.table8.png" differ diff --git "a/1.\344\273\213\347\273\215/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272/README.md" "b/1.\344\273\213\347\273\215/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272/README.md" new file mode 100644 index 0000000..d4b359a --- /dev/null +++ "b/1.\344\273\213\347\273\215/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272/README.md" @@ -0,0 +1,120 @@ +## 1.9.输入和输出 + +我们经常需要与用户交互,要么获取数据,要么提供某种结果。现今的大多数程序都使用一个对话框来让用户提供某种类型的输入。虽然Python确实有创建对话框的方法,但是我们可以使用一个简单得多的函数。Python为我们提供了一个功能,允许我们要求用户输入一些数据,并以字符串的形式返回数据的引用。这个函数叫做input。 + +Python的输入函数接受单个参数,即字符串。这个字符串通常被称为提示符,因为它包含一些有用的文本,提示用户输入一些东西。例如,您可以按如下方式调用输入: + +```` +aName = input('Please enter your name: ') +```` + +现在,无论用户在提示符后面输入什么,都将存储在aName变量中。通过使用input函数,我们可以轻松地编写指令,提示用户输入数据,然后将这些数据合并到进一步的处理中。例如,在接下来的两个语句中,首先询问用户的名称,第二个语句打印基于所提供的字符串的一些简单处理的结果。 + +```` +aName = input("Please enter your name ") +print("Your name in all capitals is",aName.upper(), + "and has length", len(aName)) +```` + +需要注意的是,输入函数返回的值将是一个字符串,表示在提示后输入的确切字符。如果您想将此字符串解释为另一种类型,您必须提供显式的类型转换。在下面的语句中,由用户输入的字符串被转换为浮点数,以便它可以用于进一步的算术处理。 + +```` +sradius = input("Please enter the radius of the circle ") +radius = float(sradius) +diameter = 2 * radius +```` + + +### 1.9.1.格式化字符串 + +我们已经看到print方法提供了一种非常简单的方法来从Python程序输出值。print采用零个或更多的参数,并使用单个空格作为默认分隔符来显示它们。可以通过设置sep参数来更改分隔符。此外,每次打印在默认情况下都以换行符结尾。可以通过设置end参数来改变这种行为。这些变化将显示在以下会话中: + +```` +>>> print("Hello") +Hello +>>> print("Hello","World") +Hello World +>>> print("Hello","World", sep="***") +Hello***World +>>> print("Hello","World", end="***") +Hello World***>>> +```` + +更多的控制输出的外观通常是有用的。幸运的是,Python为我们提供了一种称为格式化字符串的替代方案。格式化的字符串是一个模板,在这个模板中,结合了保持不变的单词或空格与插入到字符串中的变量的占位符。例如,语句 + +```` +print(aName, "is", age, "years old.") +```` + +包含词语is 和 years old,但是名称和年龄将根据执行时的变量值而变化。使用格式化的字符串,我们将前面的语句写为 + +```` +print("%s is %d years old." % (aName, age)) +```` + +这个简单的例子说明了一个新的字符串表达式。%号是一个字符串操作符,称作格式化操作符。表达式的左边含有模板或格式字符串,右边包含将被替换成格式字符串的值的集合。请注意,右侧集合中的值的数量与格式字符串中的%字符数相对应。值从左到右依次从集合中获取并插入到格式字符串中。 + +让我们更详细地看一下这个格式化表达式的两边。格式字符串可以包含一个或多个转换规范。一个转换字符告诉格式化操作符将插入到字符串中的那个位置的值类型。在上面的例子中,%s指定一个字符串,而%d指定一个整数。其他可能的类型规范包括i、u、f、e、g、c或%。Table 9总结了各种类型的规范。 + +![1.9.输入和输出.table9](assets/1.9.输入和输出.table9.png) + +*Table 9* + +除了格式字符之外,您还可以在%和格式字符之间包含一个格式修饰符。格式修饰符可以用来对指定字段宽度的值进行左对齐或右对齐。修饰符也可以用来指定字段宽度,以及小数点后的数字。Table 10解释了这些格式修饰符 + +![1.9.输入和输出.table10](assets/1.9.输入和输出.table10.png) + +*Table 10* + +格式操作符的右边是一组值的集合,这些值将被插入到格式字符串中。这个集合可以是一个元组或一个字典。如果集合是一个元组,那么这些值就会按照位置的顺序插入。也就是说,元组中的第一个元素对应于格式字符串中的第一个格式字符。如果集合是一个字典,那么这些值将根据它们的键插入。在这种情况下,所有格式字符都必须使用(name)修饰符来指定键的名称。 + +```` +>>> price = 24 +>>> item = "banana" +>>> print("The %s costs %d cents"%(item,price)) +The banana costs 24 cents +>>> print("The %+10s costs %5.2f cents"%(item,price)) +The banana costs 24.00 cents +>>> print("The %+10s costs %10.2f cents"%(item,price)) +The banana costs 24.00 cents +>>> itemdict = {"item":"banana","cost":24} +>>> print("The %(item)s costs %(cost)7.1f cents"%itemdict) +The banana costs 24.0 cents +>>> +```` + +除了使用格式字符和格式修饰符的格式字符串之外,Python字符串还包括一种format方法,它可以与新的Formatter类一起使用来实现复杂的字符串格式化。有关这些功能的更多信息,请参阅Python库参考手册。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/1.\344\273\213\347\273\215/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272/assets/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272.table10.png" "b/1.\344\273\213\347\273\215/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272/assets/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272.table10.png" new file mode 100644 index 0000000..4069971 Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272/assets/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272.table10.png" differ diff --git "a/1.\344\273\213\347\273\215/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272/assets/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272.table9.png" "b/1.\344\273\213\347\273\215/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272/assets/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272.table9.png" new file mode 100644 index 0000000..da4d3de Binary files /dev/null and "b/1.\344\273\213\347\273\215/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272/assets/1.9.\350\276\223\345\205\245\345\222\214\350\276\223\345\207\272.table9.png" differ 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..f26833c 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,11 +1,7 @@ ## 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..999259d 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,68 @@ ## 2.2.什么是算法分析 -一些普遍的现象是,刚接触计算机科学的同学会将自己的程序和其他的相比较。你可能还注意到,计算机程序看起来很相似,尤其是简单的程序。经常出现一个有趣的问题。当两个程序解决同样的问题,但看起来不同,哪一个更好呢?为了回答这个问题,我们需要记住,程序和程序代表的底层算法之间有一个重要的区别。正如我们在第 1 章中所说,一个算法是一个通用的,解决问题的指令列表。它是用于解决问题的任何实例的方法,给定特定输入,产生期望的结果。另一方面,程序是已经被编码成某种编程语言的算法。根据所使用的编程器和编程语言,可能存在用于相同算法的许多程序。要进一步探讨这种差异,请参考 ActiveCode 1 中显示的函数。这个函数解决了一个熟悉的问题,计算前 n 个整数的和。该算法使用初始化为 0 的累加器变量。然后迭代 n 个整数,将每个添加到累加器。 -```` python +一些普遍的现象是,刚接触计算机科学的学生会将自己的程序和其他人的相比较。你可能还注意到,两个计算机程序可能看起来很相似,尤其是简单的程序。经常出现一个有趣的问题:当两个程序解决同样的问题,但看起来不同,哪一个更好呢? + +为了回答这个问题,我们需要记住,程序和程序代表的底层算法之间有一个重要的区别。正如我们在第 1 章中所说,一种算法是一个通用的,一步一步解决某种问题的指令列表。它是用于解决一种问题的任何实例的方法,给定特定输入,产生期望的结果。另一方面,程序是使用某种编程语言编码的算法。根据程序员和他们所使用的编程语言的不同,可能存在描述相同算法的许多不同的程序。 + +要进一步探讨这种差异,请参考 _ActiveCode 1_ 中显示的函数。这个函数解决了一个我们熟悉的问题,计算前 n 个整数的和。该算法使用初始化值为 0 的累加器(`accumulator`)变量。然后迭代 n 个整数,将每个依次添加到累加器。 + +```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 +```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 个整数的和,将得到如下结果: -```` +```bash >>>for i in range(5): print("Sum is %d required %10.7f seconds"%sumOfN(10000)) Sum is 50005000 required 0.0018950 seconds @@ -63,11 +70,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 个整数的和的函数呢? + +```bash >>>for i in range(5): print("Sum is %d required %10.7f seconds"%sumOfN(100000)) Sum is 5000050000 required 0.0199420 seconds @@ -76,11 +83,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,我们得到: -```` +```bash >>>for i in range(5): print("Sum is %d required %10.7f seconds"%sumOfN(1000000)) Sum is 500000500000 required 0.1948988 seconds @@ -89,37 +96,35 @@ Sum is 500000500000 required 0.1809771 seconds Sum is 500000500000 required 0.1729250 seconds Sum is 500000500000 required 0.1646299 seconds >>> -```` -在这种情况下,平均值也大约是前一次的10倍。现在考虑ActiveCode 3,它显示了求解求和问题的不同方法 -![求和](assets/%E6%B1%82%E5%92%8C.png) +``` + +在这种情况下,平均值也大约是前一次的10倍。现在考虑 *ActiveCode 3*,它显示了求解求和问题的不同方法。函数 *sumOfN3* 利用[封闭方程](https://en.wikipedia.org/wiki/1_%2B_2_%2B_3_%2B_4_%2B_%E2%8B%AF)而不是迭代来计算前n个整数的和。 +$$ +\sum_{i=1}^n i=\frac{(n)(n+1)}{2} +$$ -```` 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 +```bash 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.2.\344\273\200\344\271\210\346\230\257\347\256\227\346\263\225\345\210\206\346\236\220/assets/\346\261\202\345\222\214.png" "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/assets/\346\261\202\345\222\214.png" deleted file mode 100644 index 28fa02c..0000000 Binary files "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/assets/\346\261\202\345\222\214.png" and /dev/null differ 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..2027e4f 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 个问题规模大。因此,所需时间也更长。我们的目标是表示出算法的执行时间是如何相对问题规模大小而改变的。 -计算机科学家更喜欢将这种分析技术进一步。事实证明,操作步骤数量不如确定 \(T(n)\) 最主要的部分来的重要。换句话说,当问题规模变大时, \(T(n)\) 函数某些部分的分量会超过其他部分。函数的数量级表示了随着 n 的值增加而增加最快的那些部分。数量级通常称为大O符号,并写为 \(O(f(n))\)。它表示对计算中的实际步数的近似。函数 \(f(n)\) 提供了 \(T(n)\) 最主要部分的表示方法。 +当我们试图通过执行时间来表征算法的效率时,并且独立于任何特定程序或计算机,重要的是量化算法需要的操作或者步骤的数量。选择适当的基本计算单位是个复杂的问题,并且将取决于如何实现算法。对于先前的求和算法,一个比较好的基本计算单位是对执行语句进行计数。在 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)=1+n\)。当 n 变大时,常熟 1 对于最终结果变得越来越不重要。如果我们找的是 \(T(n)\) 的近似值,我们可以删除 1, 运行时间是\(O(n)\)。要注意,1 对于 \(T(n)\) 肯定是重要的。但是当 n 变大时,如果没有它,我们的近似也是准确的。 +计算机科学家更喜欢将这种分析技术进一步扩展。事实证明,操作步骤数量不如确定 T(n) 最主要的部分来的重要。换句话说,当问题规模变大时,T(n) 函数某些部分的分量会超过其他部分。函数的数量级表示了随着 n 的值增加而增加最快的那些部分。数量级通常称为大O符号,写为 $$O(f(n))$$。它表示对计算中的实际步数的近似。函数 f(n) 提供了 T(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)=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 )$$ 。 -虽然我们没有在求和示例中看到这一点,但有时算法的性能取决于数据的确切值,而不是问题规模的大小。对于这种类型的算法,我们需要根据最佳情况,最坏情况或平均情况来表征它们的性能。最坏情况是指算法性能特别差的特定数据集。而相同的算法不同数据集可能具有非常好的性能。大多数情况下,算法执行效率处在两个极端之间(平均情况)。对于计算机科学家而已,重要的是了解这些区别,使它们不被某一个特定的情况误导。 +虽然我们没有在求和示例中看到这一点,但有时算法的性能取决于数据的确切值,而不是问题规模的大小。对于这种类型的算法,我们需要根据最佳情况,最坏情况或平均情况来表征它们的性能。最坏情况是指算法性能特别差的特定数据集。而相同的算法不同数据集可能具有非常好的性能。大多数情况下,算法执行效率处在两个极端之间(平均情况)。对于计算机科学家而言,重要的是了解这些区别,使它们不被某一个特定的情况误导。 当你学习算法时,一些常见的数量级函数将会反复出现。见 Table 1。为了确定这些函数中哪个是最主要的部分,我们需要看到当 n 变大的时候它们如何相互比较。 @@ -16,14 +16,14 @@ *Table 1* -Figure 1 表示了 Table 1 中的函数图。注意,当 n 很小时,函数彼此间不是很好的定义。很难判断哪个是主导的。随着 n 的增长,就有一个很明确的关系,很容易看出它们之间的大小关系。 +Figure 1 表示了 Table 1 中的函数图。注意,当 n 很小时,函数彼此间不能很好的定义。很难判断哪个是主导的。随着 n 的增长,就有一个很明确的关系,很容易看出它们之间的大小关系。 ![newplot](assets/newplot.png) *Figure 1* 最后一个例子,假设我们有 Listing2 的代码段。虽然这个程序没有做任何事,但是对我们获取实际的代码和性能分析是有益的。 -```` +```python a=5 b=6 c=10 @@ -36,15 +36,13 @@ for k in range(n): w = a*k + 45 v = b*b 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..d04f0f1 --- /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,119 @@ +## 2.4.一个乱序字符串检查的例子 + +显示不同量级的算法的一个很好的例子是字符串的乱序检查。乱序字符串是指一个字符串只是另一个字符串的重新排列。例如,`'heart'` 和 `'earth'` 就是乱序字符串。`'python'` 和 `'typhon'` 也是。为了简单起见,我们假设所讨论的两个字符串具有相等的长度,并且他们由 26 个小写字母集合组成。我们的目标是写一个布尔函数,它将两个字符串做参数并返回它们是不是乱序。 + +### 2.4.1.解法1:检查 + +我们对乱序问题的第一个解法是检查第一个字符串是不是出现在第二个字符串中。如果可以检验到每一个字符,那这两个字符串一定是乱序。可以通过用 None 替换字符来了解一个字符是否完成检查。但是,由于 Python 字符串是不可变的,所以第一步是将第二个字符串转换为列表。检查第一个字符串中的每个字符是否存在于第二个列表中,如果存在,替换成 None。见 ActiveCode1 + +```python +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 + pos1 = pos1 + 1 + else: + stillOK = False + + return stillOK and (len(filter(None, alist)) == 0) + +print(anagramSolution1('abcd','dcba')) +``` + +*ActiveCode1* + +为了分析这个算法,我们注意到 s1 的每个字符都会在 s2 中进行最多 n 个字符的迭代。s2 列表中的 n 个位置将被访问一次来匹配来自 s1 的字符。访问次数可以写成 1 到 n 整数的和,可以写成 + +$$ +\sum_{i=1}^n i=\frac{ n(n+1) }{2}=\frac{1}{2}n^2+\frac{1}{2}n +$$ + +当 n 变大,$$n^2$$ 这项占据主导,1/2 可以忽略。所以这个算法复杂度为 $$O(n^2)$$。 + +### 2.4.2.解法2:排序和比较 + +另一个解决方案是利用这么一个事实:即使 s1,s2 不同,它们都是由完全相同的字符组成的。所以,我们按照字母顺序从 a 到 z 排列每个字符串,如果两个字符串相同,那这两个字符串就是乱序字符串。见 ActiveCode2。 + +```python +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-2 种,等等。总数为 $$n*(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+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/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.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\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" deleted file mode 100644 index f7313bc..0000000 Binary files "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" and /dev/null differ diff --git "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.5.Python\346\225\260\346\215\256\347\273\223\346\236\204\347\232\204\346\200\247\350\203\275/README.md" "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.5.Python\346\225\260\346\215\256\347\273\223\346\236\204\347\232\204\346\200\247\350\203\275/README.md" index 6c0a9bb..2bba9e6 100644 --- "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.5.Python\346\225\260\346\215\256\347\273\223\346\236\204\347\232\204\346\200\247\350\203\275/README.md" +++ "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.5.Python\346\225\260\346\215\256\347\273\223\346\236\204\347\232\204\346\200\247\350\203\275/README.md" @@ -1,5 +1,3 @@ ## 2.5.Python数据结构的性能 -现在你对 大O 算法和不同函数之间的差异有了了解。本节的目标是告诉你 Python 列表和字典操作的 大O 性能。然后我们将做一些基于时间的实验来说明每个数据结构的花销和使用这些数据结构的好处。重要的是了解这些数据结构的效率,因为它们是本书实现其他数据结构所用到的基础模块。本节中,我们将不会说明为什么是这个性能。在后面的章节中,你将看到列表和字典一些可能的实现,以及性能是如何取决于实现的。 - - +现在你对 大O 算法和不同函数之间的差异有了了解。本节的目标是告诉你 Python 列表和字典操作的 大O 性能。然后我们将做一些基于时间的实验来说明每个数据结构的花销和使用这些数据结构的好处。重要的是了解这些数据结构的效率,因为它们是本书实现其他数据结构所用到的基础模块。本节中,我们将不会说明为什么是这个性能。在后面的章节中,你将看到列表和字典一些可能的实现,以及性能是如何取决于实现的。 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..40d966b 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" @@ -1,13 +1,14 @@ ## 2.6.列表 + python 的设计者在实现列表数据结构的时候有很多选择。每一个这种选择都可能影响列表操作的性能。为了帮助他们做出正确的选择,他们查看了最常使用列表数据结构的方式,并且优化了实现,以便使得最常见的操作非常快。当然,他们还试图使较不常见的操作快速,但是当需要做出折衷时,较不常见的操作的性能通常牺牲以支持更常见的操作。 -两个常见的操作是索引和分配到索引位置。无论列表有多大,这两个操作都需要相同的时间。当这样的操作和列表的大小无关时,它们是 O(1)。 +两个常见的操作是索引和分配到索引位置。无论列表有多大,这两个操作都需要相同的时间。当这样的操作和列表的大小无关时,它们是 $$O(1)$$。 -另一个非常常见的编程任务是增加一个列表。有两种方法可以创建更长的列表,可以使用 append 方法或拼接运算符。append 方法是 O(1)。 然而,拼接运算符是 O(k),其中 k 是要拼接的列表的大小。这对你来说很重要,因为它可以帮助你通过选择合适的工具来提高你自己的程序的效率。 +另一个非常常见的编程任务是增加一个列表。有两种方法可以创建更长的列表,可以使用 append 方法或拼接运算符。append 方法是 $$O(1)$$。 然而,拼接运算符是 $$O(k)$$,其中 k 是要拼接的列表的大小。这对你来说很重要,因为它可以帮助你通过选择合适的工具来提高你自己的程序的效率。 让我们看看四种不同的方式,我们可以生成一个从0开始的n个数字的列表。首先,我们将尝试一个 for 循环并通过创建列表,然后我们将使用 append 而不是拼接。接下来,我们使用列表生成器创建列表,最后,也是最明显的方式,通过调用列表构造函数包装 range 函数。 -```` +```python def test1(): l = [] for i in range(1000): @@ -23,14 +24,13 @@ def test3(): def test4(): l = list(range(1000)) -```` - +``` 要捕获我们的每个函数执行所需的时间,我们将使用 Python 的 timeit 模块。timeit 模块旨在允许 Python 开发人员通过在一致的环境中运行函数并使用尽可能相似的操作系统的时序机制来进行跨平台时序测量。 -要使用 timeit,您需要创建一个 Timer 对象,其参数是两个 Python 语句。第一个参数是一个你想要执行时间的 Python 语句; 第二个参数是一个将运行一次以设置测试的语句。然后 timeit 模块将计算执行语句所需的时间。默认情况下,timeit 将尝试运行语句一百万次。 当它完成时,它返回时间作为表示总秒数的浮点值。由于它执行语句一百万次,可以读取结果作为执行测试一次的微秒数。你还可以传递 timeit 一个参数名字为 number,允许您指定执行测试语句的次数。以下显示了运行我们的每个测试功能 1000 次需要多长时间。 +要使用 timeit,你需要创建一个 Timer 对象,其参数是两个 Python 语句。第一个参数是一个你想要执行时间的 Python 语句; 第二个参数是一个将运行一次以设置测试的语句。然后 timeit 模块将计算执行语句所需的时间。默认情况下,timeit 将尝试运行语句一百万次。 当它完成时,它返回时间作为表示总秒数的浮点值。由于它执行语句一百万次,可以读取结果作为执行测试一次的微秒数。你还可以传递 timeit 一个参数名字为 number,允许你指定执行测试语句的次数。以下显示了运行我们的每个测试功能 1000 次需要多长时间。 -```` +```python t1 = Timer("test1()", "from __main__ import test1") print("concat ",t1.timeit(number=1000), "milliseconds") t2 = Timer("test2()", "from __main__ import test2") @@ -44,7 +44,7 @@ concat 6.54352807999 milliseconds append 0.306292057037 milliseconds comprehension 0.147661924362 milliseconds list range 0.0655000209808 milliseconds -```` +``` 在上面的例子中,我们对 test1(), test2() 等的函数调用计时,setup 语句可能看起来很奇怪,所以我们详细说明下。你可能非常熟悉 from ,import 语句,但这通常用在 python 程序文件的开头。在这种情况下,`from __main__ import test1` 从 `__main__` 命名空间导入到 timeit 设置的命名空间中。timeit 这么做是因为它想在一个干净的环境中做测试,而不会因为可能有你创建的任何杂变量,以一种不可预见的方式干扰你函数的性能。 @@ -52,17 +52,17 @@ 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%。 -```` +```python popzero = timeit.Timer("x.pop(0)", "from __main__ import x") popend = timeit.Timer("x.pop()", @@ -75,12 +75,13 @@ popzero.timeit(number=1000) x = list(range(2000000)) popend.timeit(number=1000) 0.0003161430358886719 -```` +``` + *Listing 4* 虽然我们第一个测试显示 pop(0) 比 pop() 慢, 但它没有证明 pop(0) 是 O(n), pop()是 O(1)。要验证它,我们需要看下一系列列表大小的调用效果。 -```` +```python popzero = Timer("x.pop(0)", "from __main__ import x") popend = Timer("x.pop()", @@ -92,12 +93,10 @@ for i in range(1000000,100000001,1000000): x = list(range(i)) pz = popzero.timeit(number=1000) print("%15.5f, %15.5f" %(pz,pt)) -```` +``` + *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..0843c22 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,11 +5,11 @@ 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 是一个字典。 -```` +```python import timeit import random @@ -21,16 +21,14 @@ for i in range(10000,1000001,20000): x = {j:None for j in range(i)} d_time = t.timeit(number=1000) print("%d,%10.3f,%10.3f" % (i, lst_time, d_time)) -```` +``` *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) *Figure 4* 由于 Python 是一种不断发展的语言,底层总是有变化的。 有关 Python 数据结构性能的最新信息可以在 Python 网站上找到。 在撰写本文时,Python wiki有一个很好的时间复杂性页面,可以在 [Time Complexity Wiki](https://wiki.python.org/moin/TimeComplexity) 中找到。 - - diff --git "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.8.\346\200\273\347\273\223/README.md" "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.8.\346\200\273\347\273\223/README.md" index bb32b8e..8d118e9 100644 --- "a/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.8.\346\200\273\347\273\223/README.md" +++ "b/2.\347\256\227\346\263\225\345\210\206\346\236\220/2.8.\346\200\273\347\273\223/README.md" @@ -1,5 +1,4 @@ ## 2.8.总结 + * 算法分析是一种独立的测量算法的方法。 * 大O表示法允许根据问题的大小,通过其主要部分来对算法进行分类。 - - 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..d71b989 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,17 +1,12 @@ ## 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..95bec62 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" @@ -1,4 +1,5 @@ ## 3.10.什么是队列 + 队列是项的有序结合,其中添加新项的一端称为队尾,移除项的一端称为队首。当一个元素从队尾进入队列时,一直向队首移动,直到它成为下一个需要移除的元素为止。 最近添加的元素必须在队尾等待。集合中存活时间最长的元素在队首,这种排序成为 FIFO,先进先出,也被成为先到先得。 @@ -6,11 +7,9 @@ 队列的最简单的例子是我们平时不时会参与的列。排队等待电影,在杂货店的收营台等待,在自助餐厅排队等待(这样我们可以弹出托盘栈)。行为良好的线或队列是有限制的,因为它只有一条路,只有一条出路。不能插队,也不能离开。你只有等待了一定的时间才能到前面。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..977fbc6 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" @@ -1,5 +1,7 @@ ## 3.11.队列抽象数据类型 + 队列抽象数据类型由以下结构和操作定义。如上所述,队列被构造为在队尾添加项的有序集合,并且从队首移除。队列保持 FIFO 排序属性。 队列操作如下。 + * Queue() 创建一个空的新队列。 它不需要参数,并返回一个空队列。 * enqueue(item) 将新项添加到队尾。 它需要 item 作为参数,并不返回任何内容。 * dequeue() 从队首移除项。它不需要参数并返回 item。 队列被修改。 @@ -11,4 +13,3 @@ ![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.12.Python\345\256\236\347\216\260\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.12.Python\345\256\236\347\216\260\351\230\237\345\210\227/README.md" index ba76ece..c78bec0 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.12.Python\345\256\236\347\216\260\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.12.Python\345\256\236\347\216\260\351\230\237\345\210\227/README.md" @@ -1,9 +1,9 @@ ## 3.12.Python实现队列 我们为了实现队列抽象数据类型创建一个新类。和前面一样,我们将使用列表集合来作为构建队列的内部表示。 -我们需要确定列表的哪一端作为队首,哪一端作为队尾。Listing 1 所示的实现假定队尾在列表中的位置为 0。这允许我们使用列表上的插入函数向队尾添加新元素。弹出操作可用于删除队首的元素(列表的最后一个元素)。回想一下,这也意味着入队为 O(n),出队为 O(1)。 +我们需要确定列表的哪一端作为队首,哪一端作为队尾。Listing 1 所示的实现假定队尾在列表中的位置为 0。这允许我们使用列表上的插入函数向队尾添加新元素。弹出操作可用于删除队首的元素(列表的最后一个元素)。回想一下,这也意味着入队为 $$O(n)$$,出队为 $$O(1)$$。 -```` python +```python class Queue: def __init__(self): self.items = [] @@ -19,12 +19,13 @@ class Queue: def size(self): return len(self.items) -```` +``` + *Listing 1* 进一步的操作这个队列产生如下结果: -```` python +```python >>> q.size() 3 >>> q.isEmpty() @@ -36,7 +37,4 @@ False 'dog' >>> q.size() 2 -```` - - - +``` 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..bcf4915 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" @@ -1,4 +1,5 @@ ## 3.13.模拟:烫手山芋 + 队列的典型应用之一是模拟需要以 FIFO 方式管理数据的真实场景。首先,让我们看看孩子们的游戏烫手山芋,在这个游戏中(见 Figure 2),孩子们围成一个圈,并尽可能快的将一个山芋递给旁边的孩子。在某一个时间,动作结束,有山芋的孩子从圈中移除。游戏继续开始直到剩下最后一个孩子。 ![3.13.模拟:烫手山芋.figure2](assets/3.13.%E6%A8%A1%E6%8B%9F%EF%BC%9A%E7%83%AB%E6%89%8B%E5%B1%B1%E8%8A%8B.figure2.png) @@ -6,14 +7,15 @@ 这个游戏相当于著名的约瑟夫问题,一个一世纪著名历史学家弗拉维奥·约瑟夫斯的传奇故事。故事讲的是,他和他的 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 +```python from pythonds.basic.queue import Queue def hotPotato(namelist, num): @@ -31,9 +33,8 @@ def hotPotato(namelist, num): print(hotPotato(["Bill","David","Susan","Jane","Kent","Brad"],7)) -```` -*Active code 1* - -请注意,在此示例中,计数常数的值大于列表中的名称数。这不是一个问题,因为队列像一个圈,计数会重新回到开始,直到达到计数值。另外,请注意,列表加载到队列中以使列表上的名字位于队列的前面。在这种情况下,Bill 是列表中的第一个项,因此他在队列的前面。 +``` +*Active code 1* +请注意,在此示例中,计数常数的值大于列表中的名称数。这不是一个问题,因为队列像一个圈,计数会重新回到开始,直到达到计数值。另外,请注意,列表加载到队列中以使列表上的名字位于队列的前面。在这种情况下,`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..eb52704 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" @@ -1,41 +1,45 @@ ## 3.14.模拟:打印机 + 一个更有趣的模拟是允许我们研究本节前面描述的打印机的行为,回想一下,当学生向共享打印机发送打印任务时,任务被放置在队列中以便以先来先服务的方式被处理。此配置会出现许多问题。其中最重要的点可能是打印机是否能够处理一定量的工作。如果它不能,学生将等待太长时间打印,可能会错过他们的下一节课。 在计算机科学实验室里考虑下面的情况。平均每天大约10名学生在任何给定时间在实验室工作。这些学生通常在此期间打印两次,这些任务的长度范围从1到20页。实验室中的打印机较旧,每分钟以草稿质量可以处理10页。打印机可以切换以提供更好的质量,但是它将每分钟只能处理五页。较慢的打印速度可能会使学生等待太久。应使用什么页面速率? 我们可以通过建立一个模拟实验来决定。我们将需要为学生,打印任务和打印机构建表现表示(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` 中减去时间戳,以计算该任务的等待时间。 * 将该任务的等待时间附件到列表中稍后处理。 * 根据打印任务的页数,确定需要多少时间。 * 打印机需要一秒打印,所以得从该任务的所需的等待时间减去一秒。 * 如果任务已经完成,换句话说,所需的时间已经达到零,打印机空闲。 3. 模拟完成后,从生成的等待时间列表中计算平均等待时间。 - ### 3.14.2 Python 实现 -为了设计此模拟,我们将为上述三个真实世界对象创建类:Printer, Task, PrintQueue -Printer 类(Listing 2)需要跟踪它当前是否有任务。如果有,则它处于忙碌状态(13-17 行),并且可以从任务的页数计算所需的时间。构造函数允许初始化每分钟页面的配置,tick 方法将内部定时器递减直到打印机设置为空闲(11 行) +为了设计此模拟,我们将为上述三个真实世界对象创建类:`Printer`, `Task`, `PrintQueue` + +`Printer` 类(Listing 2)需要跟踪它当前是否有任务。如果有,则它处于忙碌状态(13-17 行),并且可以从任务的页数计算所需的时间。构造函数允许初始化每分钟页面的配置,`tick` 方法将内部定时器递减直到打印机设置为空闲(11 行) -```` python +```python class Printer: def __init__(self, ppm): self.pagerate = ppm @@ -57,22 +61,24 @@ class Printer: def startNext(self,newtask): self.currentTask = newtask self.timeRemaining = newtask.getPages() * 60/self.pagerate -```` +``` + *Listing 2* -Task 类(Listing 3)表示单个打印任务。创建任务时,随机数生成器将提供 1 到 20 页的长度。我们选择使用随机模块中的 randrange 函数。 +`Task` 类(Listing 3)表示单个打印任务。创建任务时,随机数生成器将提供 1 到 20 页的长度。我们选择使用随机模块中的 `randrange` 函数。 -```` +```python >>> import random >>> random.randrange(1,21) 18 >>> random.randrange(1,21) 8 >>> -```` -每个任务还需要保存一个时间戳用于计算等待时间。此时间戳将表示任务被创建并放置到打印机队列中的时间。可以使用 waitTime 方法来检索在打印开始之前队列中花费的时间。 +``` + +每个任务还需要保存一个时间戳用于计算等待时间。此时间戳将表示任务被创建并放置到打印机队列中的时间。可以使用 `waitTime` 方法来检索在打印开始之前队列中花费的时间。 -```` python +```python import random class Task: @@ -88,12 +94,13 @@ class Task: def waitTime(self, currenttime): return currenttime - self.timestamp -```` +``` + *Listing 3* -Listing 4 实现了上述算法。PrintQueue 对象是我们现有队列 ADT 的一个实例。newPrintTask 决定是否创建一个新的打印任务。我们再次选择使用随机模块的 randrange 函数返回 1 到 180 之间的随机整数。打印任务每 180 秒到达一次。通过从随机整数(32 行)的范围中任意选择,我们可以模拟这个随机事件。模拟功能允许我们设置打印机的总时间和每分钟的页数。 +Listing 4 实现了上述算法。`PrintQueue` 对象是我们现有队列 ADT 的一个实例。`newPrintTask` 决定是否创建一个新的打印任务。我们再次选择使用随机模块的 `randrange` 函数返回 1 到 180 之间的随机整数。打印任务每 180 秒到达一次。通过从随机整数(32 行)的范围中任意选择,我们可以模拟这个随机事件。模拟功能允许我们设置打印机的总时间和每分钟的页数。 -```` +```python from pythonds.basic.queue import Queue import random @@ -129,14 +136,15 @@ def newPrintTask(): for i in range(10): simulation(3600,5) -```` +``` + *Listing 4* 当我们运行模拟时,我们不应该担心每次的结果不同。这是由于随机数的概率性质决定的。 因为模拟的参数可以被调整,我们对调整后可能发生的趋势感兴趣。 这里有一些结果。 -首先,我们将使用每分钟五页的页面速率运行模拟60分钟(3,600秒)。 此外,我们将进行10次独立试验。记住,因为模拟使用随机数,每次运行将返回不同的结果。 +首先,我们将使用每分钟五页的页面速率运行模拟 60 分钟(3,600秒)。 此外,我们将进行 10 次独立试验。记住,因为模拟使用随机数,每次运行将返回不同的结果。 -```` +```bash >>>for i in range(10): simulation(3600,5) @@ -150,12 +158,13 @@ Average Wait 75.11 secs 1 tasks remaining. 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 次测试,页面速度更快,我们希望在一小时内完成更多的任务。 -```` +```bash >>>for i in range(10): simulation(3600,10) @@ -169,21 +178,20 @@ Average Wait 22.33 secs 0 tasks remaining. Average Wait 12.39 secs 0 tasks remaining. Average Wait 7.27 secs 0 tasks remaining. Average Wait 18.17 secs 0 tasks remaining. -```` +``` ### 3.14.3.讨论 我们试图回答一个问题,即当前打印机是否可以处理任务负载,如果它设置为打印更好的质量,较慢的页面速率。我们采用的方法是编写一个模拟打印任务作为各种页数和到达时间的随机事件的模拟。 -上面的输出显示,每分钟打印5页,平均等待时间从低的17秒到高的376秒(约6分钟)。使用更快的打印速率,低值为1秒,高值仅为28。此外,在 10 次运行中的8次,每分钟5页,打印任务在结束时仍在队列中等待。 +上面的输出显示,每分钟打印 5 页,平均等待时间从低的 17 秒到高的 376 秒(约 6 分钟)。使用更快的打印速率,低值为 1 秒,高值仅为 28。此外,在 10 次运行中的 8 次,每分钟 5 页,打印任务在结束时仍在队列中等待。 因此,我们说减慢打印机的速度以获得更好的质量可能不是一个好主意。学生们不能等待他们的论文打印完,特别是当他们需要到下一个班级。六分钟的等待时间太长了。 这种类型的模拟分析允许我们回答许多问题,通常被称为“如果”的问题。我们需要做的是改变模拟使用的参数,我们可以模拟任何数量。例如 - * 如果入学人数增加,平均学生人数增加 20人 该怎么办? - * 如果是星期六,学生不需要上课怎么办?他们能负担得了吗? - * 如果平均打印任务的大小减少了,由于 Python是一个强大的语言,程序往往要短得多? - -这些问题都可以通过修改上述模拟来回答。然而,重要的是要记住,模拟有效取决于构建它的假设是没问题的。关于每小时打印任务的数量和每小时的学生数量的真实数据对于构建鲁棒的模拟是必要的。 +* 如果入学人数增加,平均学生人数增加 20 人 该怎么办? +* 如果是星期六,学生不需要上课怎么办?他们能负担得了吗? +* 如果平均打印任务的大小减少了,由于 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..498f8ef 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" @@ -3,10 +3,7 @@ deque(也称为双端队列)是与队列类似的项的有序集合。它有两个端部,首部和尾部,并且项在集合中保持不变。deque 不同的地方是添加和删除项是非限制性的。可以在前面或后面添加新项。同样,可以从任一端移除现有项。在某种意义上,这种混合线性结构提供了单个数据结构中的栈和队列的所有能力。 Figure 1 展示了一个 Python 数据对象的 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..6e51a40 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" @@ -1,4 +1,5 @@ ## 3.16.Deque抽象数据类型 + deque 抽象数据类型由以下结构和操作定义。如上所述,deque 被构造为项的有序集合,其中项从首部或尾部的任一端添加和移除。下面给出了 deque 操作。 * Deque() 创建一个空的新 deque。它不需要参数,并返回空的 deque。 @@ -14,6 +15,3 @@ 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..951c705 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" @@ -2,7 +2,7 @@ 正如我们在前面的部分中所做的,我们将为抽象数据类型 deque 的实现创建一个新类。同样,Python 列表将提供一组非常好的方法来构建 deque 的细节。我们的实现(Listing 1)将假定 deque 的尾部在列表中的位置为 0。 -```` +```python class Deque: def __init__(self): self.items = [] @@ -24,11 +24,10 @@ class Deque: def size(self): return len(self.items) -```` +``` + *Listing 1* 在 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..6dfc9fd 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,15 +1,16 @@ ## 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 中。 -```` python +```python from pythonds.basic.deque import Deque def palchecker(aString): @@ -30,6 +31,6 @@ def palchecker(aString): print(palchecker("lsdkjfskf")) print(palchecker("radar")) -```` -*ActiveCode 1* +``` +*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.19.\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.19.\345\210\227\350\241\250/README.md" index 16ad49d..c08d912 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.19.\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.19.\345\210\227\350\241\250/README.md" @@ -4,5 +4,4 @@ 列表是项的集合,其中每个项保持相对于其他项的相对位置。更具体地,我们将这种类型的列表称为无序列表。我们可以将列表视为具有第一项,第二项,第三项等等。我们还可以引用列表的开头(第一个项)或列表的结尾(最后一个项)。为了简单起见,我们假设列表不能包含重复项。 -例如,整数 `54,26,93,17,77` 和 `31` 的集合可以表示考试分数的简单无序列表。请注意,我们将它们用逗号分隔,这是列表结构的常用方式。当然,Python 会显示这个列表为 `[54,26,93,17,77,31] `。 - +例如,整数 `54,26,93,17,77` 和 `31` 的集合可以表示考试分数的简单无序列表。请注意,我们将它们用逗号分隔,这是列表结构的常用方式。当然,Python 会显示这个列表为 `[54,26,93,17,77,31]`。 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..7a59266 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,9 +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.20.\346\227\240\345\272\217\345\210\227\350\241\250\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.20.\346\227\240\345\272\217\345\210\227\350\241\250\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" index 59ed8bb..5b4c09f 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.20.\346\227\240\345\272\217\345\210\227\350\241\250\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.20.\346\227\240\345\272\217\345\210\227\350\241\250\346\212\275\350\261\241\346\225\260\346\215\256\347\261\273\345\236\213/README.md" @@ -1,4 +1,5 @@ ## 3.20.无序列表抽象数据类型 + 如上所述,无序列表的结构是项的集合,其中每个项保持相对于其他项的相对位置。下面给出了一些可能的无序列表操作。 * List() 创建一个新的空列表。它不需要参数,并返回一个空列表。 @@ -12,4 +13,3 @@ * insert(pos,item) 在位置 pos 处向列表中添加一个新项。它需要 item 作为参数并不返回任何内容。假设该项不在列表中,并且有足够的现有项使其有 pos 的位置。 * pop() 删除并返回列表中的最后一个项。假设该列表至少有一个项。 * pop(pos) 删除并返回位置 pos 处的项。它需要 pos 作为参数并返回项。假定该项在列表中。 - 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..fd146bf 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,13 @@ ## 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* 要注意,必须明确地指定链表的第一项的位置。一旦我们知道第一个项在哪里,第一个项目可以告诉我们第二个是什么,等等。外部引用通常被称为链表的头。类似地,最后一个项需要知道没有下一个项。 @@ -14,7 +16,7 @@ 链表实现的基本构造块是节点。每个节点对象必须至少保存两个信息。首先,节点必须包含列表项本身。我们将这个称为节点的数据字段。此外,每个节点必须保存对下一个节点的引用。 Listing 1 展示了 Python 实现。要构造一个节点,需要提供该节点的初始数据值。下面的赋值语句将产生一个包含值 93 的节点对象(见 Figure 3)。应该注意,我们通常会如 Figure 4 所示表示一个节点对象。Node 类还包括访问,修改数据和访问下一个引用的常用方法。 -```` python +```python class Node: def __init__(self,initdata): self.data = initdata @@ -31,73 +33,81 @@ class Node: def setNext(self,newnext): self.next = newnext -```` +``` + *Listing 1* 我们创建一个 Node 对象 -```` +```bash >>> temp = Node(93) >>> temp.getData() 93 -```` +``` 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 类 如上所述,无序列表将从一组节点构建,每个节点通过显式引用链接到下一个节点。只要我们知道在哪里找到第一个节点(包含第一个项),之后的每个项可以通过连续跟随下一个链接找到。考虑到这一点,UnorderedList 类必须保持对第一个节点的引用。Listing 2 显示了构造函数。注意,每个链表对象将维护对链表头部的单个引用。 -```` +```python class UnorderedList: def __init__(self): self.head = None -```` +``` + *Listing 2* 我们构建一个空的链表。赋值语句 -```` +```bash >>> mylist = 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 可以与任何引用进行比较。如果它们都指向相同的对象,则两个引用是相等的。我们将在其他方法中经常使用它。 -```` +```python def isEmpty(self): return self.head == None -```` +``` + *Listing 3* -那么,我们如何将项加入我们的链表?我们需要实现 add 方法。然而,在我们做这一点之前,我们需要解决在链表中哪个位置放置新目的重要问题。由于该链表是无序的,所以新项相对于已经在列表中的其他项目的特定位置并不重要。 新项可以在任何位置。考虑到这一点,将新项放在最简单的位置是有意义的。 +那么,我们如何将项加入我们的链表?我们需要实现 add 方法。然而,在我们做这一点之前,我们需要解决在链表中哪个位置放置新项的重要问题。由于该链表是无序的,所以新项相对于已经在列表中的其他项的特定位置并不重要。 新项可以在任何位置。考虑到这一点,将新项放在最简单的位置是有意义的。 回想一下,链表结构只为我们提供了一个入口点,即链表的头部。所有其他节点只能通过访问第一个节点,然后跟随下一个链接到达。这意味着添加新节点的最简单的地方就在链表的头部。 换句话说,我们将新项作为链表的第一项,现有项将需要链接到这个新项后。 Figure 6 展示了链表调用多次 add 函数的操作 -```` +```bash >>> mylist.add(31) >>> mylist.add(77) >>> mylist.add(17) >>> mylist.add(93) >>> mylist.add(26) >>> mylist.add(54) -```` +``` + *Figure 6* 因为 31 是添加到链表的第一个项,它最终将是链表中的最后一个节点,因为每个其他项在其前面添加。此外,由于 54 是添加的最后一项,它将成为链表的第一个节点中的数据值。 @@ -106,24 +116,28 @@ add 方法如 Listing 4 所示。链表的每项必须驻留在节点对象中 上述两个步骤的顺序非常重要。如果第 3 行和第 4 行的顺序颠倒,会发生什么?如果链表头部的修改首先发生,则结果可以在 Figure 8 中看到。由于 head 是链表节点的唯一外部引用,所有原始节点都将丢失并且不能再被访问。 -```` +```python def add(self,item): temp = Node(item) temp.setNext(self.head) self.head = temp -```` +``` + *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 +```python def size(self): current = self.head count = 0 @@ -132,17 +146,19 @@ def size(self): current = current.getNext() return count -```` +``` + *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。 -```` +```python def search(self,item): current = self.head found = False @@ -153,17 +169,18 @@ def search(self,item): current = current.getNext() return found -```` +``` + *Listing 6* 作为一个例子,试试调用 `search` 方法来查找 item 17 -```` +```bash >>> mylist.search(17) 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) @@ -175,13 +192,13 @@ 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` 的布尔变量将再次用于控制迭代。 +Listing 7 展示了完整的 `remove` 方法。第 2-3 行给这两个引用赋初始值。注意,`current` 在链表头处开始,和在其他遍历示例中一样。然而,`previous` 假定总是在 `current`之后一个节点。因此,由于在 `previous` 之前没有节点,所以之前的值将为 None(见 Figure 11)。`found` 的布尔变量将再次用于控制迭代。 在第 6-7 行中,我们检查存储在当前节点中的项是否是我们希望删除的项。如果是,`found` 设置为 True 。如果我们没有找到该项,则 `previous` 和 `current` 都必须向前移动一个节点。同样,这两个语句的顺序是至关重要的。`previous` 必须先将一个节点移动到 `current` 的位置。此时,才可以移动`current`。这个过程通常被称为“英寸蠕动”,因为 `previous` 必须赶上 `current`,然后 `current` 前进。Figure 12 展示了 `previous` 和`current` 的移动,它们沿着链表向下移动,寻找包含值 17 的节点。 -```` +```python def remove(self,item): current = self.head previous = None @@ -197,23 +214,26 @@ def remove(self,item): self.head = current.getNext() else: previous.setNext(current.getNext()) -```` +``` + *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()`。 经常出现的一个问题是,这里给出的两种情况是否也将处理要移除的项在链表的最后节点中的情况。我们留给你考虑。 +*Figure 14* +第 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..6a1c50b 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,6 @@ ## 3.22.有序列表抽象数据结构 -我们现在将考虑一种称为有序列表的列表类型。例如,如果上面所示的整数列表是有序列表(升序),则它可以写为`17,26,31,54,77和93`。由于 17 是最小项,它占据第一位置。同样,由于 93 是最大的,它占据最后的位置。 + +我们现在将考虑一种称为有序列表的列表类型。例如,如果上面所示的整数列表是有序列表(升序),则它可以写为 `17,26,31,54,77和93`。由于 17 是最小项,它占据第一位置。同样,由于 93 是最大的,它占据最后的位置。 有序列表的结构是项的集合,其中每个项保存基于项的一些潜在特性的相对位置。排序通常是升序或降序,并且我们假设列表项具有已经定义的有意义的比较运算。许多有序列表操作与无序列表的操作相同。 @@ -12,5 +13,3 @@ * index(item) 返回项在列表中的位置。它需要 item 作为参数并返回索引。假定该项在列表中。 * pop() 删除并返回列表中的最后一个项。假设该列表至少有一个项。 * pop(pos) 删除并返回位置 pos 处的项。它需要 pos 作为参数并返回项。假定该项在列表中。 - - 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..6b35235 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,15 +1,19 @@ ## 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)。 -```` +```python class OrderedList: def __init__(self): self.head = None -```` +``` + *Listing 8* 当我们考虑有序列表的操作时,我们应该注意,`isEmpty` 和`size` 方法可以与无序列表一样实现,因为它们只处理链表中的节点数量,而不考虑实际项值。同样,`remove` 方法将正常工作,因为我们仍然需要找到该项,然后删除它。剩下的两个方法,`search` 和 `add`,将需要一些修改。 @@ -17,12 +21,14 @@ class OrderedList: 搜索无序列表需要我们一次遍历一个节点,直到找到我们正在寻找的节点或者没找到节点(None)。事实证明,相同的方法在有序列表也有效。然而,在项不在链表中的情况下,我们可以利用该顺序来尽快停止搜索。 例如,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行)。其余行与无序列表搜索相同。 -```` +```python def search(self,item): current = self.head found = False @@ -37,21 +43,23 @@ def search(self,item): current = current.getNext() return found -```` +``` + *Listing 9* 最重要的需要修改的方法是 `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)允许迭代继续,只要有更多的节点,并且当前节点中的值不大于该项。 在任一种情况下,当迭代失败时,我们找到了新节点的位置。 该方法的其余部分完成 Figure17 所示的两步过程。一旦为该项创建了新节点,剩下的唯一问题是新节点是否将被添加在链表的开始处或某个中间位置。再次,`previous == None`(第13行)可以用来提供答案。 -```` +```python def add(self,item): current = self.head previous = None @@ -70,11 +78,12 @@ def add(self,item): else: temp.setNext(current) previous.setNext(temp) -```` +``` + *Listing 10* ### 3.23.1.链表分析 -为了分析链表操作的复杂性,我们需要考虑它们是否需要遍历。考虑具有 n 个节点的链表。 `isEmpty` 方法是 O(1),因为它需要一个步骤来检查头的引用为 `None`。另一方面,`size` 将总是需要 n 个步骤,因为不从头到尾地移动没法知道有多少节点在链表中。因此,长度为O(n)。将项添加到无序列表始终是O(1),因为我们只是将新节点放置在链表的头部。但是,搜索和删除,以及添加有序列表,都需要遍历过程。虽然平均他们可能只需要遍历节点的一半,这些方法都是O(n),因为在最坏的情况下,都将处理列表中的每个节点。 -你可能还注意到此实现的性能与早前针对 Python 列表给出的实际性能不同。这表明链表不是 Python 列表的实现方式。 Python 列表的实际实现基于数组的概念。我们在第 8 章中更详细地讨论这个问题。 +为了分析链表操作的复杂性,我们需要考虑它们是否需要遍历。考虑具有 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.24.\346\200\273\347\273\223/README.md" "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.24.\346\200\273\347\273\223/README.md" index 640dac1..899ff6f 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.24.\346\200\273\347\273\223/README.md" +++ "b/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.24.\346\200\273\347\273\223/README.md" @@ -15,4 +15,3 @@ * 列表是项的集合,其中每个项目保存相对位置。 * 链表实现保持逻辑顺序,而不需要物理存储要求。 * 修改链表头是一种特殊情况。 - 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..66ecb6f 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,7 +1,7 @@ ## 3.3.什么是栈 -栈(有时称为“后进先出栈”)是一个项的有序集合,其中添加移除新项目总发生在同一端。这一端通常称为“顶部”。与顶部对应的端称为“底部”。 +栈(有时称为“后进先出栈”)是一个项的有序集合,其中添加移除新项总发生在同一端。这一端通常称为“顶部”。与顶部对应的端称为“底部”。 栈的底部很重要,因为在栈中靠近底部的项是存储时间最长的。最近添加的项是最先会被移除的。这种排序原则有时被称为 LIFO,后进先出。它基于在集合内的时间长度做排序。较新的项靠近顶部,较旧的项靠近底部。 @@ -9,6 +9,7 @@ ![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* @@ -18,5 +19,3 @@ *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..38e6137 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" @@ -11,8 +11,4 @@ 例如,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..2ac98ee 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" @@ -1,4 +1,5 @@ ## 3.5.Python实现栈 + 现在我们已经将栈清楚地定义了抽象数据类型,我们将把注意力转向使用 Python 实现栈。回想一下,当我们给抽象数据类型一个物理实现时,我们将实现称为数据结构。 正如我们在第1章中所描述的,在 Python 中,与任何面向对象编程语言一样,抽象数据类型(如栈)的选择的实现是创建一个新类。栈操作实现为类的方法。此外,为了实现作为元素集合的栈,使用由 Python 提供的原语集合的能力是有意义的。 我们将使用列表作为底层实现。 @@ -7,7 +8,7 @@ 以下栈实现(ActiveCode 1)假定列表的结尾将保存栈的顶部元素。随着栈增长(push 操作),新项将被添加到列表的末尾。 pop 也操作列表末尾的元素。 -```` +```python class Stack: def __init__(self): self.items = [] @@ -27,16 +28,16 @@ class Stack: def size(self): return len(self.items) -```` -*ActiveCode 1* +``` -记住我们只定义类的实现,我们需要创建一个栈,然后使用它。ActiveCode 2 展示了我们通过实例化 Stack 类执行Table 1中的操作。注意,Stack 类的定义是从 pythonds 模块导入的。 +*ActiveCode 1* +记住我们只定义类的实现,我们需要创建一个栈,然后使用它。ActiveCode 2 展示了我们通过实例化 Stack 类执行 Table 1中的操作。注意,Stack 类的定义是从 pythonds 模块导入的。 > **Note** -pythonds 模块包含本书中讨论的所有数据结构的实现。它根据以下部分构造:基本数据类型,树和图形。 该模块可以从 [pythonworks.org](http://www.pythonworks.org/pythonds)下载。 +pythonds 模块包含本书中讨论的所有数据结构的实现。它根据以下部分构造:基本数据类型,树和图。 该模块可以从 [pythonworks.org](http://www.pythonworks.org/pythonds)下载。 -```` +```python from pythonds.basic.stack import Stack s=Stack() @@ -53,13 +54,6 @@ print(s.pop()) print(s.pop()) print(s.size()) -```` -*ActiveCode 2* - - - - - - - +``` +*ActiveCode 2* 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..41838d1 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,43 +1,47 @@ ## 3.6.简单括号匹配 -我们现在把注意力转向使用栈来解决真正的计算机问题。你会这么写算术表达式 -`(5+6)∗(7+8)/(4+3)` +我们现在把注意力转向使用栈解决真正的计算机问题。你会这么写算术表达式 + +`(5+6)*(7+8)/(4+3)` 其中括号用于命令操作的执行。你可能也有一些语言的经验,如 Lisp 的构造 -```` +```lisp (defun square(n) (* n n)) -```` +``` + 这段代码定义了一个名为 square 的函数,它将返回参数的 n 的平方。 Lisp 使用大量的圆括号是臭名昭著的。 在这两个例子中,括号必须以匹配的方式出现。括号匹配意味着每个开始符号具有相应的结束符号,并且括号能被正确嵌套。考虑下面正确匹配的括号字符串: -```` +``` (()()()()) (((()))) (()((())())) -```` +``` + 对比那些不匹配的括号: -```` +``` ((((((()) ())) (()()(() -```` +``` 区分括号是否匹配的能力是识别很多编程语言结构的重要部分。具有挑战的是如何编写一个算法,能够从左到右读取一串符号,并决定符号是否平衡。为了解决这个问题,我们需要做一个重要的观察。从左到右处理符号时,最近开始符号必须与下一个关闭符号相匹配(见 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。 -```` +```python from pythonds.basic.stack import Stack def parChecker(symbolString): @@ -64,10 +68,6 @@ def parChecker(symbolString): print(parChecker('((()))')) print(parChecker('(()')) -```` -*ActiveCode 1* - - - - +``` +*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.7.\347\254\246\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.7.\347\254\246\345\217\267\345\214\271\351\205\215/README.md" index 6505080..456e55a 100644 --- "a/3.\345\237\272\346\234\254\346\225\260\346\215\256\347\273\223\346\236\204/3.7.\347\254\246\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.7.\347\254\246\345\217\267\345\214\271\351\205\215/README.md" @@ -1,13 +1,15 @@ ## 3.7.符号匹配 -上面显示的匹配括号问题是许多编程语言都会出现的一般情况的特定情况。匹配和嵌套不同种类的开始和结束符号的情况经常发生。例如,在 Python 中,方括号 `[` 和 `]` 用于列表,花括号 `{` 和 `}` 用于字典。括号 `(` 和 `)` 用于元祖和算术表达式。只要每个符号都能保持自己的开始和结束关系,就可以混合符号。符号字符串如 -```` +上面显示的匹配括号问题是许多编程语言都会出现的一般情况的特定情况。匹配和嵌套不同种类的开始和结束符号的情况经常发生。例如,在 Python 中,方括号 `[` 和 `]` 用于列表,花括号 `{` 和 `}` 用于字典。括号 `(` 和 `)` 用于元组和算术表达式。只要每个符号都能保持自己的开始和结束关系,就可以混合符号。符号字符串如 + +``` { { ( [ ] [ ] ) } ( ) } [ [ { { ( ( ) ) } } ] ] [ ] [ ] [ ] ( ) { } -```` +``` + 这些被恰当的匹配了,因为不仅每个开始符号都有对应的结束符号,而且符号的类型也匹配。 相反这些字符串没法匹配: @@ -24,7 +26,7 @@ Python 程序见 ActiveCode 1。唯一的变化是 16 行,我们称之为辅助函数匹配。必须检查栈中每个删除的符号,以查看它是否与当前结束符号匹配。如果不匹配,则布尔变量 balanced 被设置为 False。 -```` +```python from pythonds.basic.stack import Stack def parChecker(symbolString): @@ -57,10 +59,8 @@ def matches(open,close): print(parChecker('{{([][])}()}')) print(parChecker('[{()]')) -```` +``` + *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.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..68bba88 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,22 +2,29 @@ 在你学习计算机的过程中,你可能已经接触了二进制。二进制在计算机科学中是很重要的,因为存储在计算机内的所有值都是以 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) +$$ +2\times10^{2} + 3\times10^{1} + 3\times10^{0} +$$ -![3.8.十进制转换成二进制.1](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.1.png) +和 -但是我们如何能够容易地将整数值转换为二进制呢?答案是 “除 2”算法,它用栈来跟踪二进制结果的数字。 +$$ +1\times2^{7} + 1\times2^{6} + 1\times2^{5} + 0\times2^{4} + 1\times2^{3} + 0\times2^{2} + 0\times2^{1} + 1\times2^{0} +$$ + +但是我们如何能够容易地将整数值转换为二进制呢?答案是“除 2”算法,它用栈来跟踪二进制结果的数字。 “除 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 行构造了一个二进制字符串。 -```` python +```python from pythonds.basic.stack import Stack def divideBy2(decNumber): @@ -36,19 +43,27 @@ def divideBy2(decNumber): print(divideBy2(42)) -```` +``` + *ActiveCode 1* -这个用于二进制转换的算法可以很容易的扩展以执行任何基数的转换。在计算可科学中,通常会使用很多不同的编码。其中最常见的是二级制,八进制和十六进制。 +这个用于二进制转换的算法可以很容易的扩展以执行任何基数的转换。在计算机科学中,通常会使用很多不同的编码。其中最常见的是二级制,八进制和十六进制。 + +十进制 $$233$$ 和它对应的八进制和十六进制 $$351_8$$, $$E9_{16}$$ -十进制 233 和它对应的八进制和十六进制 352^8, E9^16 +$$ +3\times8^{2} + 5\times8^{1} + 1\times8^{0} +$$ -![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) +和 -可以修改 divideBy2 函数,使它不仅能接受十进制参数,还能接受预期转换的基数。‘除 2’ 的概念被简单的替换成更通用的 ‘除基数’。在 ActiveCode2 展示的是一个名为 baseConverter 函数。采用十进制数和 2 到 16 之间的任何基数作为参数。余数部分仍然入栈,直到被转换的值为 0。我们创建一组数字,用来表示超过 9 的余数。 +$$ +14\times16^{1} + 9\times16^{0} +$$ -```` python +可以修改 `divideBy2` 函数,使它不仅能接受十进制参数,还能接受预期转换的基数。‘除 2’ 的概念被简单的替换成更通用的 ‘除基数’。在 ActiveCode2 展示的是一个名为 `baseConverter` 函数。采用十进制数和 2 到 16 之间的任何基数作为参数。余数部分仍然入栈,直到被转换的值为 0。我们创建一组数字,用来表示超过 9 的余数。 + +```python from pythonds.basic.stack import Stack def baseConverter(decNumber,base): @@ -69,12 +84,6 @@ def baseConverter(decNumber,base): print(baseConverter(25,2)) print(baseConverter(25,16)) -```` -*ActiveCode2* - - - - - - +``` +*ActiveCode2* 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 92% 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..5c8fe32 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" @@ -1,5 +1,6 @@ -## 3.9.中缀前缀和后缀表达式 -当你编写一个算术表达式如 `B*C` 时,表达式的形式使你能够正确理解它。在这种情况下,你知道 B 乘以 C, 因为乘法运算符 `*` 出现在表达式中。这种类型的符号称为中缀,因为运算符在它处理的两个操作数之间。看另外一个中缀示例,`A+B*C `,运算符 `+` 和 `*` 仍然出现在操作数之间。这里面有个问题是,他们分别作用于哪个运算数上,`+` 作用于 A 和 B , 还是 `*` 作用于 B 和 C?表达式似乎有点模糊。 +## 3.9.中缀,前缀和后缀表达式 + +当你编写一个算术表达式如 `B*C` 时,表达式的形式使你能够正确理解它。在这种情况下,你知道 B 乘以 C, 因为乘法运算符 `*` 出现在表达式中。这种类型的符号称为中缀,因为运算符在它处理的两个操作数之间。看另外一个中缀示例,`A+B*C`,运算符 `+` 和 `*` 仍然出现在操作数之间。这里面有个问题是,他们分别作用于哪个运算数上,`+` 作用于 A 和 B , 还是 `*` 作用于 B 和 C?表达式似乎有点模糊。 事实上,你已经读写过这些类型的表达式很长一段时间,所以它们对你不会导致什么问题。这是因为你知道运算符 `+`和 `*`。每个运算符都有一个优先级。优先级较高的运算符在优先级较低的运算符之前使用。唯一改变顺序的是括号的存在。算术运算符的优先顺序是将乘法和除法置于加法和减法之上。如果出现具有相等优先级的两个运算符,则使用从左到右的顺序排序或关联。 @@ -7,7 +8,7 @@ 虽然这一切对你来说都很明显。但请记住,计算机需要准确知道要执行的操作符和顺序。一种保证不会对操作顺序产生混淆的表达式的方法是创建一个称为完全括号表达式的表达式。这种类型的表达式对每个运算符都使用一对括号。括号没有歧义的指示操作的顺序。也没有必要记住任何优先规则。 -表达式 `A+B*C+D` 可以重写为 `((A + (B * C)) + D)` ,表明先乘法,然后是左边的加法,` A + B + C + D` 可以写为 `(((A + B) + C) + D)`,因为加法操作从左向右相关联。 +表达式 `A+B*C+D` 可以重写为 `((A + (B * C)) + D)` ,表明先乘法,然后是左边的加法,`A + B + C + D` 可以写为 `(((A + B) + C) + D)`,因为加法操作从左向右相关联。 有两种非常重要的表达式格式,你可能一开始不会很明显的看出来。中缀表达式 `A+B`, 如果我们移动两个操作数之间的运算符会发生什么?结果表达式变成 `+ A B`。同样,我们也可以将运算符移动到结尾,得到 `A B +` ,这样看起来有点奇怪。 @@ -16,33 +17,40 @@ `A+B*C` 将在前缀中写为 `+ A * B C` 。乘法运算符紧接在操作数 B 和 C 之前,表示 `*` 优先于 `+`。然后加法运算符出现在 A 和乘法的结果之前。 在后缀中,表达式将是 `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.中缀表达式转换前缀和后缀 + 到目前为止,我们已经使用特定方法在中缀表达式和等效前缀和后缀表达式符号之间进行转换。正如你可能期望的,有一些算法来执行转换,允许任何复杂表达式转换。 我们考虑的第一种技术使用前面讨论的完全括号表达式的概念。回想一下,`A + B * C`可以写成`(A +(B * C))`,以明确标识乘法优先于加法。然而,仔细观察,你可以看到每个括号对还表示操作数对的开始和结束,中间有相应的运算符。 -看上面的子表达式`(B * C)`中的右括号。 如果我们将乘法符号移动到那个位置,并删除匹配的左括号,得到 `B C * `,我们实际上已经将子表达式转换为后缀符号。 如果加法运算符也被移动到其相应的右括号位置并且匹配的左括号被去除,则将得到完整的后缀表达式(见 Figure 6)。 +看上面的子表达式`(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* 所以为了转换表达式,无论是对前缀还是后缀符号,先根据操作的顺序把表达式转换成完全括号表达式。然后将包含的运算符移动到左或右括号的位置,具体取决于需要前缀或后缀符号。 @@ -54,11 +62,12 @@ Table 4 展示了一些其他的例子 *Figure 8* ### 3.9.2.中缀转后缀通用法 + 我们需要开发一个算法来将任何中缀表达式转换为后缀表达式。 为了做到这一点,我们仔细看看转换过程。 再次考虑表达式 `A + B * C`。如上所示,`A B C * +`是等价的后缀表达式。 我们已经注意到,操作数 A,B 和 C 保持在它们的相对位置。只有操作符改变位置。再看中缀表达式中的运算符。从左到右出现的第一个运算符为 +。 然而,在后缀表达式中,+ 在结束位置,因为下一个运算符 * 的优先级高于加法。 原始表达式中的运算符的顺序在生成的后缀表达式中相反。 -当我们处理表达式时,操作符必须保存在某处,因为它们相应的右操作数还没有看到。 此外,这些保存的操作符的顺序可能由于它们的优先级而需要反转。这是在该示例中的加法和乘法的情况,由于加法运算符在乘法运算符之前,并且具有较低的优先级,因此需要在使用乘法运算符之后出现。 由于这种顺序的反转,考虑使用栈来保持运算符直到用到它们是有意义的。 +当我们处理表达式时,操作符必须保存在某处,因为它们相应的右操作数还没有看到。 此外,这些保存的操作符的顺序可能由于它们的优先级而需要反转。这是在该示例中的加法和乘法的情况,由于加法运算符在乘法运算符之前,并且具有较低的优先级,因此需要在使用乘法运算符之后出现。 由于这种顺序的反转,考虑使用栈来保存运算符直到用到它们是有意义的。 `(A + B)* C`的情况会是什么样呢? 回想一下,`A B + C *`是等价的后缀表达式。从左到右处理此中缀表达式,我们先看到 `+`。 在这种情况下,当我们看到 `*`,` + `已经放置在结果表达式中,由于括号它的优先级高于`*`。 我们现在可以开始看看转换算法如何工作。当我们看到左括号时,我们保存它,表示高优先级的另一个运算符将出现。该操作符需要等到相应的右括号出现以表示其位置(回忆完全括号的算法)。 当右括号出现时,可以从栈中弹出操作符。 @@ -83,7 +92,7 @@ Figure 9 展示了对表达式 `A * B + C * D` 的转换算法。注意,第一 为了在 Python 中编写算法,我们使用一个名为 prec 的字典来保存操作符的优先级。这个字典将每个运算符映射到一个整数,可以与其他运算符的优先级(我们使用整数3,2和1)进行比较。左括号将赋予最低的值。这样,与其进行比较的任何运算符将具有更高的优先级,将被放置在它的顶部。第15行将操作数定义为任何大写字符或数字。完整的转换函数见 ActiveCode 1。 -```` python +```python from pythonds.basic.stack import Stack def infixToPostfix(infixexpr): @@ -120,11 +129,11 @@ def infixToPostfix(infixexpr): print(infixToPostfix("A * B + C * D")) print(infixToPostfix("( A + B ) * C - ( D - E ) * ( F + G )")) -```` +``` 执行结果如下 -```` +```python >>> infixtopostfix("( A + B ) * ( C + D )") 'A B + C D + *' >>> infixtopostfix("( A + B ) * C") @@ -132,22 +141,23 @@ print(infixToPostfix("( A + B ) * C - ( D - E ) * ( F + G )")) >>> infixtopostfix("A + B * C") 'A B C * +' >>> -```` +``` ### 3.9.3.后缀表达式求值 作为最后栈的示例,我们考虑对后缀符号中的表达式求值。在这种情况下,栈再次是我们选择的数据结构。但是,在扫描后缀表达式时,它必须等待操作数,而不像上面的转换算法中的运算符。 解决问题的另一种方法是,每当在输入上看到运算符时,计算两个最近的操作数。 -要详细的了解这一点,考虑后缀表达式 ` 4 5 6 * +`, 首先遇到操作数 `4` 和 `5`,此时,你还不确定如何处理它们,直到看到下一个符号。将它们放置到栈上,确保它们在下一个操作符出现时可用。 +要详细的了解这一点,考虑后缀表达式 `4 5 6 * +`, 首先遇到操作数 `4` 和 `5`,此时,你还不确定如何处理它们,直到看到下一个符号。将它们放置到栈上,确保它们在下一个操作符出现时可用。 在这种情况下,下一个符号是另一个操作数。所以,像先前一样,压入栈中。并检查下一个符号。现在我们看到了操作符 `*`,这意味着需要将两个最近的操作数相乘。通过弹出栈两次,我们可以得到正确的两个操作数,然后执行乘法(这种情况下结果为 30)。 我们现在可以通过将其放回栈中来处理此结果,以便它可以表示为表达式后面的运算符的操作数。当处理最后一个操作符时,栈上只有一个值,弹出并返回它作为表达式的结果。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` 不同,因此我们必须保证操作数的顺序不会交换。 +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) @@ -164,7 +174,7 @@ Figure 11 是个稍微复杂的示例,`7 8 + 3 2 + /` 。在这个例子中有 用于计算后缀表达式的完整函数见 ActiveCode 2,为了辅助计算,定义了一个函数 doMath, 它将获取两个操作数和运算符,执行相应的计算。 -```` python +```python from pythonds.basic.stack import Stack def postfixEval(postfixExpr): @@ -193,7 +203,4 @@ def doMath(op, op1, op2): print(postfixEval('7 8 + 3 2 + /')) -```` - - - +``` \ No newline at end of file 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.1.\347\233\256\346\240\207/README.md" "b/4.\351\200\222\345\275\222/4.1.\347\233\256\346\240\207/README.md" index bfc060f..7957ef6 100644 --- "a/4.\351\200\222\345\275\222/4.1.\347\233\256\346\240\207/README.md" +++ "b/4.\351\200\222\345\275\222/4.1.\347\233\256\346\240\207/README.md" @@ -8,4 +8,3 @@ * 将递归理解为一种迭代形式。 * 实现问题的递归公式化。 * 了解计算机系统如何实现递归。 - 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 81% 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..a256799 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,45 +1,44 @@ -## 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` 年!。 +虽然传说是有趣的,你不必担心世界不久的将来会消失。移动 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 代码。 -```` +```python def moveTower(height,fromPole, toPole, withPole): if height >= 1: moveTower(height-1,fromPole,withPole,toPole) moveDisk(fromPole,toPole) moveTower(height-1,withPole,toPole,fromPole) -```` +``` + *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` 程序,你可以看到它给你一个非常有效的解决方案。 -```` +```python def moveDisk(fp,tp): print("moving disk from",fp,"to",tp) -```` -*Listing 2* - -现在你已经看到了 `moveTower` 和 `moveDisk` 的代码,你可能会想知道为什么我们没有明确记录什么盘子在什么杆上的数据结构。这里有一个提示:如果你要明确地跟踪盘子,你会使用三个 Stack 对象,每个杆一个。 答案是 Python 提供了我们需要调用隐含的栈。 - - +``` +*Listing 2* +现在你已经看到了 `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..6c37106 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" @@ -2,19 +2,19 @@ 在这一节中,我们将讨论一个与扩展机器人世界相关的问题:你如何找到自己的迷宫? 如果你在你的宿舍有一个扫地机器人(不是所有的大学生?)你希望你可以使用你在本节中学到的知识重新给它编程。 我们要解决的问题是帮助我们的乌龟在虚拟迷宫中找到出路。 迷宫问题的根源与希腊的神话有关,传说忒修斯被送入迷宫中以杀死人身牛头怪。忒修斯用了一卷线帮助他找到回去的退路,当他完成杀死野兽的任务。在我们的问题中,我们将假设我们的乌龟在迷宫中间的某处,必须找到出路。 看看 Figure 2,了解我们将在本节中做什么。 - ![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. 如果这些方向都没有成功,那么没有办法离开迷宫,我们失败。 -现在,这听起来很容易,但有几个细节先谈谈。假设我们第一步是向北走。按照我们的程序,我们的下一步也将是向北。但如果北面被一堵墙阻挡,我们必须看看程序的下一步,并试着向南。不幸的是,向南使我们回到我们原来的起点。如果我们应用从那里的递归过程,我们将又回到向北一格,并陷入无限循环。所以,我们必须有一个策略来记住我们去过哪。在这种情况下,我们假设有一袋面包屑可以撒在我们走过的路上。如果我们沿某个方向迈出一步,发现那个位置上已经有面包屑,我们应该立即后退并尝试我们的程序中的下一个方向。我们看看这个算法的代码,就像从递归函数调用返回一样简单。 +现在,这听起来很容易,但有几个细节先谈谈。假设我们第一步是向北走。按照我们的程序,我们的下一步也将是向北。但如果北面被一堵墙阻挡,我们必须看看程序的下一步,并试着向南。不幸的是,向南使我们回到原来的起点。如果我们从那里再次应用递归过程,我们将又回到向北一格,并陷入无限循环。所以,我们必须有一个策略来记住我们去过哪。在这种情况下,我们假设有一袋面包屑可以撒在我们走过的路上。如果我们沿某个方向迈出一步,发现那个位置上已经有面包屑,我们应该立即后退并尝试程序中的下一个方向。我们看看这个算法的代码,就像从递归函数调用返回一样简单。 正如我们对所有递归算法所做的一样,让我们回顾一下基本情况。其中一些你可能已经根据前一段的描述猜到了。在这种算法中,有四种基本情况要考虑: @@ -34,7 +34,7 @@ Maze 类还重载索引运算符 `[]` ,以便我们的算法可以轻松访问 让我们来查看称为 `searchFrom` 的搜索函数的代码。代码如 Listing 3 所示。请注意,此函数需要三个参数:迷宫对象,起始行和起始列。 这很重要,因为作为递归函数,搜索在每次递归调用时开始。 -```` +```python def searchFrom(maze, startRow, startColumn): maze.updatePosition(startRow, startColumn) # Check for base cases: @@ -61,16 +61,17 @@ def searchFrom(maze, startRow, startColumn): else: maze.updatePosition(startRow, startColumn, DEAD_END) return found -```` +``` + *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 中的数据文件,内部表示如下所示: -```` +```python [ ['+','+','+','+',...,'+','+','+','+','+','+','+'], ['+',' ',' ',' ',...,' ',' ',' ','+',' ',' ',' '], ['+',' ','+',' ',...,'+','+',' ','+',' ','+','+'], @@ -82,13 +83,13 @@ def searchFrom(maze, startRow, startColumn): ['+',' ','+','+',...,' ',' ','+',' ',' ',' ','+'], ['+',' ',' ',' ',...,' ',' ','+',' ','+','+','+'], ['+','+','+','+',...,'+','+','+',' ','+','+','+']] -```` +``` `drawMaze` 方法使用这个内部表示在屏幕上绘制迷宫的初始视图。 Figure 3:示例迷宫数据文件 -```` +```python ++++++++++++++++++++++ + + ++ ++ + + + + +++ + ++ @@ -100,14 +101,15 @@ Figure 3:示例迷宫数据文件 + +++++++ S + + + + +++ ++++++++++++++++++ +++ -```` +``` + *Figure 3* 如 Listing 5 所示,`updatePosition` 方法使用相同的内部表示来查看乌龟是否遇到了墙。它还用 `.` 或 `-` 更新内部表示,以表示乌龟已经访问了特定格子或者格子是死角。 此外,`updatePosition` 方法使用两个辅助方法`moveTurtle` 和 `dropBreadCrumb` 来更新屏幕上的视图。 最后,`isExit` 方法使用乌龟的当前位置来检测退出条件。退出条件是当乌龟已经到迷宫的边缘时,即行零或列零,或者在最右边列或底部行。 -```` +```python class Maze: def __init__(self,mazeFileName): rowsInMaze = 0 @@ -138,10 +140,11 @@ class Maze: -(rowsInMaze-1)/2-.5, (columnsInMaze-1)/2+.5, (rowsInMaze-1)/2+.5) -```` +``` + *Listing 4* -```` +```python def drawMaze(self): for y in range(self.rowsInMaze): for x in range(self.columnsInMaze): @@ -193,10 +196,11 @@ def updatePosition(self,row,col,val=None): if color: self.dropBreadcrumb(color) -```` +``` + *Listing 5* -```` +```python def isExit(self,row,col): return (row == 0 or row == self.rowsInMaze-1 or @@ -205,9 +209,6 @@ def isExit(self,row,col): def __getitem__(self,idx): return self.mazelist[idx] -```` -*Listing 6* - - - +``` +*Listing 6* 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..9e23bda 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,19 +2,19 @@ 计算机科学中的许多程序是为了优化一些值而编写的; 例如,找到两个点之间的最短路径,找到最适合一组点的线,或找到满足某些标准的最小对象集。计算机科学家使用许多策略来解决这些问题。本书的目标之一是向你展示几种不同的解决问题的策略。`动态规划` 是这些类型的优化问题的一个策略。 -优化问题的典型例子包括使用最少的硬币找零。假设你是一个自动售货机制造商的程序员。你的公司希望通过给每个交易的最少硬币来简化工作。假设客户放入 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) 执行我们刚才描述的算法如 Listing 7 所示。在第 3 行,我们检查基本情况;也就是说,我们正试图支付硬币的确切金额。如果我们没有等于找零数量的硬币,我们递归调用每个小于找零额的不同的硬币值。第 6 行显示了我们如何使用列表推导将硬币列表过滤到小于当前找零的硬币列表。递归调用也减少了由所选硬币的值所需要的总找零量。递归调用在第 7 行。注意在同一行,我们将硬币数 `+1` ,以说明我们正在使用一个硬币的事实。 -```` python +```python def recMC(coinValueList,change): minCoins = change if change in coinValueList: @@ -27,19 +27,21 @@ def recMC(coinValueList,change): return minCoins print(recMC([1,5,10,25],63)) -```` +``` + *Listing 7* 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 显示了一个修改的算法,以合并我们的表查找方案。 -```` python +```python def recDC(coinValueList,change,knownResults): minCoins = change if change in coinValueList: @@ -57,7 +59,8 @@ def recDC(coinValueList,change,knownResults): return minCoins print(recDC([1,5,10,25],63,[0]*64)) -```` +``` + *ActiveCode 1* 注意,在第 6 行中,我们添加了一个测试,看看我们的列表是否包含此找零的最小硬币数量。如果没有,我们递归计算最小值,并将计算出的最小值存储在列表中。使用这个修改的算法减少了我们需要为四个硬币递归调用的数量,63美分问题只需 221 次调用! @@ -66,18 +69,18 @@ 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) Listing 8 用一个动态规划算法来解决我们的找零问题。 `dpMakeChange` 有三个参数:一个有效硬币值的列表,我们要求的找零额,以及一个包含每个值所需最小硬币数量的列表。 当函数完成时,`minCoins` 将包含从 0 到找零值的所有值的解。 -```` +```python def dpMakeChange(coinValueList,change,minCoins): for cents in range(change+1): coinCount = cents @@ -86,18 +89,19 @@ def dpMakeChange(coinValueList,change,minCoins): coinCount = minCoins[cents-j]+1 minCoins[cents] = coinCount return minCoins[change] -```` +``` + *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。 -```` +```python def dpMakeChange(coinValueList,change,minCoins,coinsUsed): for cents in range(change+1): coinCount = cents @@ -131,10 +135,11 @@ def main(): print(coinsUsed) main() -```` +``` + *AcitveCode 2* -```` +```python Making change for 63 requires 3 coins They are: @@ -143,6 +148,4 @@ They are: 21 The used list is as follows: [1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 10, 1, 1, 1, 1, 5, 1, 1, 1, 1, 10, 21, 1, 1, 1, 25, 1, 1, 1, 1, 5, 10, 1, 1, 1, 10, 1, 1, 1, 1, 5, 10, 21, 1, 1, 10, 21, 1, 1, 1, 25, 1, 10, 1, 1, 5, 10, 1, 1, 1, 10, 1, 10, 21] -```` - - +``` 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..d8d2710 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,8 @@ 在本章中,我们讨论了几个递归算法的例子。 选择这些算法来揭示几个不同的问题,其中递归是一种有效的问题解决技术。 本章要记住的要点如下: * 所有递归算法都必须具有基本情况。 -* 递归算法必须改变其状态并朝向基本情况进展。 +* 递归算法必须改变其状态并朝基本情况发展。 * 递归算法必须调用自身(递归)。 * 递归在某些情况下可以代替迭代。 * 递归算法通常可以自然地映射到你尝试解决的问题的表达式。 -* 递归并不总是答案。有时,递归解决方案可能比替代算法在计算上更昂贵。 - +* 递归并不总是答案。有时,递归解决方案可能比迭代算法在计算上更昂贵。 diff --git "a/4.\351\200\222\345\275\222/4.2.\344\273\200\344\271\210\346\230\257\351\200\222\345\275\222/README.md" "b/4.\351\200\222\345\275\222/4.2.\344\273\200\344\271\210\346\230\257\351\200\222\345\275\222/README.md" index 7b1990b..bbe1fe8 100644 --- "a/4.\351\200\222\345\275\222/4.2.\344\273\200\344\271\210\346\230\257\351\200\222\345\275\222/README.md" +++ "b/4.\351\200\222\345\275\222/4.2.\344\273\200\344\271\210\346\230\257\351\200\222\345\275\222/README.md" @@ -1,5 +1,3 @@ ## 4.2.什么是递归 **递归**是一种解决问题的方法,将问题分解为更小的子问题,直到得到一个足够小的问题可以被很简单的解决。通常递归涉及函数调用自身。递归允许我们编写优雅的解决方案,解决可能很难编程的问题。 - - 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..f43fae2 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,8 +1,8 @@ ## 4.3.计算整数列表和 -我们将以一个简单的问题开始,你已经知道如何不使用递归解决。 假设您想计算整数列表的总和,例如:`[1,3,5,7,9]`。 计算总和的迭代函数 见ActiveCode 1。函数使用累加器变量(`theSum`)来计算列表中所有整数的和,从 0 开始,并加上列表中的每个数字。 +我们将以一个简单的问题开始,你已经知道如何不使用递归解决。 假设你想计算整数列表的总和,例如:`[1,3,5,7,9]`。 计算总和的迭代函数见ActiveCode 1。函数使用累加器变量(`theSum`)来计算列表中所有整数的和,从 0 开始,加上列表中的每个数字。 -```` +```python def listsum(numList): theSum = 0 for i in numList: @@ -11,24 +11,43 @@ def listsum(numList): print(listsum([1,3,5,7,9])) -```` +``` + *Activecode 1* -假设没有 `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) +假设没有 `while` 循环或 `for` 循环。你将如何计算整数列表的总和?如果你是一个数学家,你可能开始回忆加法是一个函数,这个函数定义了两个整数类型的参数。故将列表和问题从加一个列表重新定义为加一对整数,我们可以把列表重写为一个完全括号表达式。如下所示: + +$$ +((((1+3)+5)+7)+9) +$$ 我们也可以把表达式用另一种方式括起来 -![4.3.计算整数列表和.2](assets/4.3.%E8%AE%A1%E7%AE%97%E6%95%B4%E6%95%B0%E5%88%97%E8%A1%A8%E5%92%8C.2.png) + +$$ +(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:]` 之和的总和。 以函数形式表述: -![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) +$$ +\begin{aligned} +total=(1+(3+(5+(7+9))))&\\ +total=(1+(3+(5+16)))&\\ +total=(1+(3+21))&\\ +total=(1+24)&\\ +total=25& +\end{aligned} +$$ -在这个方程式中,`first(numList)` 返回列表的第一个元素,`rest(numList)` 返回除第一个元素之外的所有列表。这很容易在 Python 中表示,如 ActiveCode 2 中所示。 +我们如何能把这个想法变成一个 Python 程序? 首先,让我们以 Python 列表的形式重述求和问题。 我们可以说列表 `numList` 的和是列表的第一个元素`numList[0]` 和列表其余部分`numList[1:]` 之和的总和。 以函数形式表述: -```` +$$ +listSum(numList)=first(numList)+listSum(numList) +$$ + +在这个方程式中,`first(numList)` 返回列表的第一个元素,`rest(numList)` 返回除第一个元素之外的所有元素列表。这很容易在 Python 中表示,如 ActiveCode 2 中所示。 + +```python def listsum(numList): if len(numList) == 1: return numList[0] @@ -36,19 +55,20 @@ def listsum(numList): return numList[0] + listsum(numList[1:]) 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* - - +*Figure 2* 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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..a3a9d23 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,17 +1,15 @@ ## 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..438db94 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" @@ -20,7 +20,7 @@ ActiveCode 1 展示了实现上述算法的 Python 代码, 以 2 到 16 之间的任何基数为参数。 -```` +```python def toStr(n,base): convertString = "0123456789ABCDEF" if n < base: @@ -29,18 +29,14 @@ def toStr(n,base): return toStr(n//base,base) + convertString[n%base] print(toStr(1453,16)) -```` +``` 请注意,在第 3 行中,我们检查基本情况,其中 n 小于我们要转换的基数。 当我们检测到基本情况时,我们停止递归,并简单地从 convertString 序列返回字符串。 在第 6 行中,我们满足第二和第三定律 - 递归调用和减少除法问题大小。 让我们再次跟踪算法; 这次我们将数字 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 调用,则生成的字符串将是反向的!通过延后连接操作直到递归调用返回,我们可以得到正确顺序的结果。这应该能使你想起你在上一章中讨论的栈。 - - - +*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..2d18922 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" @@ -1,7 +1,7 @@ ## 4.6.栈帧:实现递归 假设不是将递归调用的结果与来自 convertString 的字符串拼接到 toStr,我们修改了算法,以便在进行递归调用之前将字符串入栈。此修改的算法的代码展示在 ActiveCode 1 中。 -```` python +```python from pythonds.basic.stack import Stack rStack = Stack() @@ -20,22 +20,22 @@ def toStr(n,base): return res print(toStr(1453,16)) +``` -```` *ActiveCode 1* 每次我们调用 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* +*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..c2eaff0 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" @@ -2,11 +2,11 @@ 在上一节中,我们讨论了一些使用递归很容易解决的问题; 然而,我们可能很难找到一个模型或一种可视化方法知道在递归函数中发生了什么。这使得递归难以让人掌握。在本节中,我们将看到几个使用递归绘制一些有趣图片的例子。当你看到这些图片的形状,你会对递归过程有新的认识,可能有助于巩固你对递归理解。 -我们使用的插图的工具是 Python 的 `turtle` 模块称为 `turtle`。` turtle` 是 Python 所有版本的标准库,并且非常易于使用。比喻很简单。你可以创建一只乌龟,乌龟能前进,后退,左转,右转等。乌龟可以让它的尾巴或上或下。当乌龟的尾巴向下,它移动时会画一条线。为了增加乌龟的艺术价值,你可以改变尾巴的宽度以及尾巴浸入的墨水的颜色。 +我们使用的插图的工具是 Python 的 `turtle` 模块称为 `turtle`。`turtle` 是 Python 所有版本的标准库,并且非常易于使用。比喻很简单。你可以创建一只乌龟,乌龟能前进,后退,左转,右转等。乌龟可以让它的尾巴或上或下。当乌龟的尾巴向下,它移动时会画一条线。为了增加乌龟的艺术价值,你可以改变尾巴的宽度以及尾巴浸入的墨水的颜色。 这里有一个简单的例子来说明龟图形基础。我们将使用 `turtle` 模块递归绘制螺旋。 见 ActiveCode 1。导入 `turtle` 模块后,我们创建一个乌龟。当乌龟被创建时,它也创建一个窗口来绘制。接下来我们定义 `drawSpir​​al` 函数。这个简单函数的基本情况是当我们想要绘制的线的长度(由 len 参数给出)减小到零或更小时。如果线的长度大于零,我们让乌龟以 len 单位前进,然后向右转 90 度。当我们再次调用 `drawSpir​​al` 并缩短长度时递归。在ActiveCode 1 结束时,你会注意到我们调用函数 `myWin.exitonclick()`,这是一个方便的缩小窗口的方法,使乌龟进入等待模式,直到你单击窗口,然后程序清理并退出。 -```` python +```python import turtle myTurtle = turtle.Turtle() @@ -20,15 +20,15 @@ def drawSpiral(myTurtle, lineLen): drawSpiral(myTurtle,100) 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 +```python def tree(branchLen,t): if branchLen > 5: t.forward(branchLen) @@ -38,12 +38,13 @@ def tree(branchLen,t): tree(branchLen-10,t) t.right(20) t.backward(branchLen) -```` +``` + *Listing 1* -此树示例的完整程序在 ActiveCode 2 中。在运行代码之前,请考虑你希望看到的树形状。看着递归调用,并想想这棵树将如何展开。它会对称地绘制树的右半边和左半边吗? 它会先绘制右侧然后左侧? +此树示例的完整程序在 ActiveCode 2 中。在运行代码之前,请思考你希望看到的树形状。看着递归调用,并想想这棵树将如何展开。它会对称地绘制树的右半边和左半边吗? 它会先绘制右侧然后左侧? -```` +```python import turtle def tree(branchLen,t): @@ -69,19 +70,19 @@ def main(): main() -```` +``` + *Activecode 2* ![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..ed817b9 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,13 +1,14 @@ ## 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 +```python import turtle def drawTriangle(points,color,myTurtle): @@ -51,21 +52,18 @@ def main(): 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/4.\351\200\222\345\275\222/4.9.\345\244\215\346\235\202\351\200\222\345\275\222\351\227\256\351\242\230/README.md" "b/4.\351\200\222\345\275\222/4.9.\345\244\215\346\235\202\351\200\222\345\275\222\351\227\256\351\242\230/README.md" index e5312a5..e4cea4d 100644 --- "a/4.\351\200\222\345\275\222/4.9.\345\244\215\346\235\202\351\200\222\345\275\222\351\227\256\351\242\230/README.md" +++ "b/4.\351\200\222\345\275\222/4.9.\345\244\215\346\235\202\351\200\222\345\275\222\351\227\256\351\242\230/README.md" @@ -1,4 +1,3 @@ ## 4.9。 复杂递归问题 在前面的章节中,我们讨论了一些相对容易解决的问题,以及一些有趣的问题,可以帮助我们理解递归算法中运用到的模型以及发生了什么。在本节中,我们将看到一些使用迭代编程风格难以解决的问题,但是它们能非常优雅,并且很容易使用递归来解决。我们将通过查看一个欺骗性的问题,最初看起来好像有一个优雅的递归解决方案,但实际上没有。 - 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..2aaa751 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 53% 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..0df9488 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,24 +1,26 @@ -## 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 展示了我们使用此增量的示例的第一个子列表。 -```` +```python def shellSort(alist): sublistcount = len(alist)//2 while sublistcount > 0: @@ -46,11 +48,10 @@ def gapInsertionSort(alist,start,gap): alist = [54,26,93,17,77,31,44,55,20] shellSort(alist) print(alist) -```` -*Activecode 1* +``` +*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^{\frac{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..ee091ac 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" @@ -2,15 +2,16 @@ 我们现在将注意力转向使用分而治之策略作为提高排序算法性能的一种方法。 我们将研究的第一个算法是归并排序。归并排序是一种递归算法,不断将列表拆分为一半。 如果列表为空或有一个项,则按定义(基本情况)进行排序。如果列表有多个项,我们分割列表,并递归调用两个半部分的合并排序。 一旦对这两半排序完成,就执行称为合并的基本操作。合并是获取两个较小的排序列表并将它们组合成单个排序的新列表的过程。 Figure 10 展示了我们熟悉的示例列表,它被mergeSort 分割。 Figure 11 展示了归并后的简单排序列表。 - ![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 切片操作来提取左右两半。 重要的是要注意,列表可能没有偶数个项。这并不重要,因为长度最多相差一个。 -```` python +```python def mergeSort(alist): print("Splitting ",alist) if len(alist)>1: @@ -47,17 +48,16 @@ def mergeSort(alist): alist = [54,26,93,17,77,31,44,55,20] mergeSort(alist) print(alist) -```` +``` + *Activecode1* 一旦在左半部分和右半部分(行8-9)上调用 mergeSort 函数,就假定它们已被排序。函数的其余部分(行11-31)负责将两个较小的排序列表合并成一个较大的排序列表。请注意,合并操作通过重复从排序列表中取最小的项目,将项目逐个放回原始列表(alist)。 mergeSort 函数已经增加了一个打印语句(行2),以显示在每次调用开始时排序的列表的内容。 还有一个打印语句(第32行)来显示合并过程。 脚本显示了在我们的示例列表中执行函数的结果。 请注意,`44,55` 和 `20 `的列表不会均匀分配。第一个分割出 [44],第二个 [55,20]。 很容易看到分割过程最终产生可以立即与其他排序列表合并的列表。 -为了分析 mergeSort 函数,我们需要考虑组成其实现的两个不同的过程。首先,列表被分成两半。我们已经计算过(在二分查找中)将列表划分为一半需要 log^n 次,其中 n 是列表的长度。第二个过程是合并。列表中的每个项将最终被处理并放置在排序的列表上。因此,大小为 n 的列表的合并操作需要 n 个操作。此分析的结果是 log^n 的拆分,其中每个操作花费 n,总共 nlog^n 。归并排序是一种 O(nlogn) 算法。 +为了分析 mergeSort 函数,我们需要考虑组成其实现的两个不同的过程。首先,列表被分成两半。我们已经计算过(在二分查找中)将列表划分为一半需要 log^n 次,其中 n 是列表的长度。第二个过程是合并。列表中的每个项将最终被处理并放置在排序的列表上。因此,大小为 n 的列表的合并操作需要 n 个操作。此分析的结果是 $$logn$$ 的拆分,其中每个操作花费 n,总共 $$nlogn$$ 。归并排序是一种 $$O(nlogn)$$ 算法。 -回想切片 是 O(k),其中 k 是切片的大小。为了保证 mergeSort 是 O(nlog^n ),我们将需要删除 slice 运算符。这是可能的,如果当我们进行递归调用,我们简单地传递开始和结束索引与列表。我们把这作为一个练习。 +回想切片 是 $$O(k)$$,其中 k 是切片的大小。为了保证 mergeSort 是 $$O(nlogn)$$,我们将需要删除 slice 运算符。这是可能的,如果当我们进行递归调用,我们简单地传递开始和结束索引与列表。我们把这作为一个练习。 重要的是注意,mergeSort 函数需要额外的空间来保存两个半部分,因为它们是使用切片操作提取的。如果列表很大,这个额外的空间可能是一个关键因素,并且在处理大型数据集时可能会导致此类问题。 - - 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..b27c2da 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" @@ -6,13 +6,14 @@ Figure 12 展示 54 将作为我们的第一个枢纽值。由于我们已经看过这个例子几次,我们知道 54 最终将会在当前持有 31 的位置。接下来将发生分区过程。它将找到拆分点,同时将其他项移动到列表的适当侧,小于或大于枢轴值。 - ![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,11 +21,12 @@ 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` 以与合并排序相同的基本情况开始。如果列表的长度小于或等于一,它已经排序。 如果它更大,那么它可以被分区和递归排序。 分区函数实现前面描述的过程。 -``` +```python def quickSort(alist): quickSortHelper(alist,0,len(alist)-1) @@ -73,7 +75,6 @@ print(alist) 要分析 `quickSort` 函数,请注意,对于长度为 n 的列表,如果分区总是出现在列表中间,则会再次出现 log⁡n 分区。为了找到分割点,需要针对枢轴值检查 n 个项中的每一个。结果是 nlog⁡n。此外,在归并排序过程中不需要额外的存储器。 -不幸的是,在最坏的情况下,分裂点可能不在中间,并且可能非常偏向左边或右边,留下非常不均匀的分割。在这种情况下,对 n 个项的列表进行排序划分为对0 个项的列表和 n-1 个项目的列表进行排序。然后将 n-1 个划分的列表排序为大小为0的列表和大小为 n-2 的列表,以此类推。结果是具有递归所需的所有开销的 O(n) 排序。 +不幸的是,在最坏的情况下,分裂点可能不在中间,并且可能非常偏向左边或右边,留下非常不均匀的分割。在这种情况下,对 n 个项的列表进行排序划分为对0 个项的列表和 n-1 个项目的列表进行排序。然后将 n-1 个划分的列表排序为大小为0的列表和大小为 n-2 的列表,以此类推。结果是具有递归所需的所有开销的 $$O(n^2)$$ 排序。 我们之前提到过,有不同的方法来选择枢纽值。特别地,我们可以尝试通过使用称为中值三的技术来减轻一些不均匀分割的可能性。要选择枢轴值,我们将考虑列表中的第一个,中间和最后一个元素。在我们的示例中,这些是54,77和20.现在选择中值,在我们的示例中为54,并将其用于枢轴值(当然,这是我们最初使用的枢轴值)。想法是,在列表中的第一项不属于列表的中间的情况下,中值三将选择更好的“中间”值。当原始列表部分有序时,这将特别有用。我们将此枢轴值选择的实现作为练习。 - diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.13.\346\200\273\347\273\223/README.md" "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.13.\346\200\273\347\273\223/README.md" index cfd5379..247f908 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.13.\346\200\273\347\273\223/README.md" +++ "b/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.13.\346\200\273\347\273\223/README.md" @@ -1,10 +1,9 @@ ## 5.13.总结 -* 对于有序和无序列表,顺序搜索是 O(n)。 -* 在最坏的情况下,有序列表的二分查找是 O(log^n )。 +* 对于有序和无序列表,顺序搜索是 $$O(n)$$。 +* 在最坏的情况下,有序列表的二分查找是 $$O(logn)$$。 * 哈希表可以提供恒定时间搜索。 -* 冒泡排序,选择排序和插入排序是 O(n^2 )算法。 -* shell排序通过排序增量子列表来改进插入排序。它落在 O(n) 和 O(n^2 ) 之间。 -* 归并排序是 O(nlog^n ),但是合并过程需要额外的空间。 -* 快速排序是 O(nlog^n ),但如果分割点不在列表中间附近,可能会降级到O(n^2 ) 。它不需要额外的空间。 - +* 冒泡排序,选择排序和插入排序是 $$O(n^2)$$算法。 +* 希尔排序通过排序增量子列表来改进插入排序。它落在 $$O(n)$$ 和 $$O(n^2)$$ 之间。 +* 归并排序是 $$O(nlogn)$$,但是合并过程需要额外的空间。 +* 快速排序是 $$O(nlogn)$$,但如果分割点不在列表中间附近,可能会降级到$$O(n^2)$$ 。它不需要额外的空间。 diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.2.\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/5.2.\346\220\234\347\264\242/README.md" index 0bc6a4e..8f53302 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.2.\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/5.2.\346\220\234\347\264\242/README.md" @@ -4,15 +4,12 @@ 在 Python 中,有一个非常简单的方法来询问一个项是否在一个项列表中。我们使用 in 运算符。 -```` +```bash >>> 15 in [3,5,2,4,1] False >>> 3 in [3,5,2,4,1] True >>> -```` +``` 这很容易写,一个底层的操作替我们完成这个工作。事实证明,有很多不同的方法来搜索。我们在这里感兴趣的是这些算法如何工作以及它们如何相互比较。 - - - 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..46cc2fb 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,39 +5,41 @@ 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。 -```` +```python def sequentialSearch(alist, item): - pos = 0 - found = False - - while pos < len(alist) and not found: - if alist[pos] == item: - found = True - else: - pos = pos+1 - - return found - + pos = 0 + found = False + + while pos < len(alist) and not found: + if alist[pos] == item: + found = True + else: + pos = pos+1 + + return found + testlist = [1, 2, 32, 8, 17, 19, 42, 13, 0] print(sequentialSearch(testlist, 3)) print(sequentialSearch(testlist, 13)) -```` -*CodeLens 1* +``` +*CodeLens 1* ### 5.3.1.顺序查找分析 为了分析搜索算法,我们需要定一个基本计算单位。回想一下,这通常是为了解决问题要重复的共同步骤。对于搜索,计算比较操作数是有意义的。每个比较都有可能找到我们正在寻找的项目。此外,我们在这里做另一个假设。项列表不以任何方式排序。项随机放置到列表中。换句话说,项在列表任何位置的概率是一样的。 -如果项目不在列表中,知道它的唯一方法是将其与存在的每个项进行比较。如果有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,33 +47,35 @@ 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* -```` +```python def orderedSequentialSearch(alist, item): - pos = 0 - found = False - stop = False - while pos < len(alist) and not found and not stop: - if alist[pos] == item: - found = True - else: - if alist[pos] > item: - stop = True - else: - pos = pos+1 - - return found - + pos = 0 + found = False + stop = False + while pos < len(alist) and not found and not stop: + if alist[pos] == item: + found = True + else: + if alist[pos] > item: + stop = True + else: + pos = pos+1 + + return found + testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42,] print(orderedSequentialSearch(testlist, 3)) print(orderedSequentialSearch(testlist, 13)) -```` +``` + *CodeLens 2* -Table 2 总结了这些结果。 请注意,在最好的情况下,我们通过只查看一项会发现该项不在列表中。 平均来说,我们将只了解 n/2 项就知道。然而,这种复杂度仍然是 O(n)。 总之,只有在我们没有找到该项的情况下,才通过对列表排序来改进顺序查找。 +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* - +*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..a81780b 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,76 +1,77 @@ ## 5.4.二分查找 -有序列表对于我们的比较是很有用的。在顺序查找中,当我们与第一个项进行比较时,如果第一个项不是我们要查找的,则最多还有 n-1 个项目。 二分查找从中间项开始,而不是按顺序查找列表。 如果该项是我们正在寻找的项,我们就完成了查找。 如果它不是,我们可以使用列表的有序性质来消除剩余项的一半。如果我们正在查找的项大于中间项,就可以消除中间项以及比中间项小的一半元素。如果该项在列表中,肯定在大的那半部分。 -然后我们可以用大的半部分重复这个过程。从中间项开始,将其与我们正在寻找的内容进行比较。再次,我们找到元素或将列表分成两半,消除我们可能的搜索空间的另一部分。Figure 3 展示了该算法如何快速找到值 54 。完整的函数见CodeLens 3中。 +有序列表对于我们的比较是很有用的。在顺序查找中,当我们与第一个项进行比较时,如果第一个项不是我们要查找的,则最多还有 `n-1` 个项目。 二分查找从中间项开始,而不是按顺序查找列表。 如果该项是我们正在寻找的项,我们就完成了查找。 如果它不是,我们可以使用列表的有序性质来消除剩余项的一半。如果我们正在查找的项大于中间项,就可以消除中间项以及比中间项小的一半元素。如果该项在列表中,肯定在大的那半部分。 + +然后我们可以用大的半部分重复这个过程。从中间项开始,将其与我们正在寻找的内容进行比较。再次,我们找到元素或将列表分成两半,消除可能的搜索空间的另一部分。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 +```python def binarySearch(alist, item): - first = 0 - last = len(alist)-1 - found = False - - while first<=last and not found: - midpoint = (first + last)//2 - if alist[midpoint] == item: - found = True - else: - if item < alist[midpoint]: - last = midpoint-1 - else: - first = midpoint+1 - - return found - + first = 0 + last = len(alist)-1 + found = False + + while first<=last and not found: + midpoint = (first + last)//2 + if alist[midpoint] == item: + found = True + else: + if item < alist[midpoint]: + last = midpoint-1 + else: + first = midpoint+1 + + return found + testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42,] print(binarySearch(testlist, 3)) print(binarySearch(testlist, 13)) -```` -*CodeLens 3* +``` +*CodeLens 3* -在我们继续分析之前,我们应该注意到,这个算法是分而治之策略的一个很好的例子。 分和治意味着我们将问题分成更小的部分,以某种方式解决更小的部分,然后重新组合整个问题以获得结果。 当我们执行列表的二分查找时,我们首先检查中间项。如果我们正在搜索的项小于中间项,我们可以简单地对原始列表的左半部分执行二分查找。同样,如果项大,我们可以执行右半部分的二分查找。 无论哪种方式,都是递归调用二分查找函数。 CodeLens 4 展示了这个递归版本。 +在我们继续分析之前,我们应该注意到,这个算法是分而治之策略的一个很好的例子。分和治意味着我们将问题分成更小的部分,以某种方式解决更小的部分,然后重新组合整个问题以获得结果。 当我们执行列表的二分查找时,我们首先检查中间项。如果我们正在搜索的项小于中间项,我们可以简单地对原始列表的左半部分执行二分查找。同样,如果项大,我们可以执行右半部分的二分查找。 无论哪种方式,都是递归调用二分查找函数。 CodeLens 4 展示了这个递归版本。 -```` python +```python def binarySearch(alist, item): - if len(alist) == 0: - return False - else: - midpoint = len(alist)//2 - if alist[midpoint]==item: - return True - else: - if item>> ord('c') 99 >>> ord('a') 97 >>> ord('t') 116 -```` +``` 然后,我们可以获取这三个 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) +![5.5.Hash查找.figure6](assets/5.5.Hash%E6%9F%A5%E6%89%BE.figure6.png) + *Figure 6* -```` python +```python def hash(astring, tablesize): sum = 0 for pos in range(len(astring)): sum = sum + ord(astring[pos]) return sum%tablesize -```` +``` + *Listing 1* 有趣的是,当使用此散列函数时,字符串总是返回相同的散列值。 为了弥补这一点,我们可以使用字符的位置作为权重。 Figure 7 展示了使用位置值作为加权因子的一种可能的方式。 ![5.5.Hash查找.figure7](assets/5.5.Hash%E6%9F%A5%E6%89%BE.figure7.png) + *Figure 7* 你可以思考一些其他方法来计算集合中项的散列值。重要的是要记住,哈希函数必须是高效的,以便它不会成为存储和搜索过程的主要部分。如果哈希函数太复杂,则计算槽名称的程序要比之前所述的简单地进行基本的顺序或二分搜索更耗时。 这将打破散列的目的。 ### 5.5.2.冲突解决 + 我们现在回到碰撞的问题。当两个项散列到同一个槽时,我们必须有一个系统的方法将第二个项放在散列表中。这个过程称为冲突解决。如前所述,如果散列函数是完美的,冲突将永远不会发生。然而,由于这通常是不可能的,所以冲突解决成为散列非常重要的部分。 解决冲突的一种方法是查找散列表,尝试查找到另一个空槽以保存导致冲突的项。一个简单的方法是从原始哈希值位置开始,然后以顺序方式移动槽,直到遇到第一个空槽。注意,我们可能需要回到第一个槽(循环)以查找整个散列表。这种冲突解决过程被称为开放寻址,因为它试图在散列表中找到下一个空槽或地址。通过系统地一次访问每个槽,我们执行称为线性探测的开放寻址技术。 @@ -80,6 +88,7 @@ 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,23 +96,27 @@ 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 的原因。 +在冲突后寻找另一个槽的过程叫 `重新散列`。使用简单的线性探测,rehash 函数是 `newhashvalue = rehash(oldhashvalue)`其中 `rehash(pos)=(pos + 1)%sizeoftable`。 `加3`rehash 可以定义为`rehash(pos)=(pos + 3)%sizeoftable`。一般来说,`rehash(pos)=(pos + skip)%sizeoftable`。重要的是要注意,“跳过”的大小必须使得表中的所有槽最终都被访问。否则,表的一部分将不被使用。为了确保这一点,通常建议表大小是素数。这是我们在示例中使用 11 的原因。 线性探测思想的一个变种称为二次探测。代替使用常量 “跳过” 值,我们使用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* 当我们要搜索一个项时,我们使用散列函数来生成它应该在的槽。由于每个槽都有一个集合,我们使用一种搜索技术来查找该项是否存在。优点是,平均来说,每个槽中可能有更少的项,因此搜索可能更有效。我们将在本节结尾处查看散列的分析。 @@ -121,22 +134,23 @@ map 抽象数据类型定义如下。该结构是键与值之间的关联的无 * len() 返回存储在 map 中的键值对的数量。 * in 返回 True 对于 `key in map` 语句,如果给定的键在 map 中,否则为False。 -字典一个很大的好处是,给定一个键,我们可以非常快速地查找相关的值。为了提供这种快速查找能力,我们需要一个支持高效搜索的实现。我们可以使用具有顺序或二分查找的列表,但是使用如上所述的哈希表将更好,因为查找哈希表中的项可以接近 O(1) 性能。 +字典一个很大的好处是,给定一个键,我们可以非常快速地查找相关的值。为了提供这种快速查找能力,我们需要一个支持高效搜索的实现。我们可以使用具有顺序或二分查找的列表,但是使用如上所述的哈希表将更好,因为查找哈希表中的项可以接近 $$O(1)$$ 性能。 在 Listing 2 中,我们使用两个列表来创建一个实现 Map 抽象数据类型的HashTable 类。一个名为 `slots` 的列表将保存键项,一个称 `data` 的并行列表将保存数据值。当我们查找一个键时,`data` 列表中的相应位置将保存相关的数据值。我们将使用前面提出的想法将键列表视为哈希表。注意,哈希表的初始大小已经被选择为 11。尽管这是任意的,但是重要的是,大小是质数,使得冲突解决算法可以尽可能高效。 -```` python +```python class HashTable: def __init__(self): self.size = 11 self.slots = [None] * self.size self.data = [None] * self.size -```` +``` + *Listing 2* hash 函数实现简单的余数方法。冲突解决技术是 `加1` rehash 函数的线性探测。 put 函数(见 Listing 3)假定最终将有一个空槽,除非 key 已经存在于 `self.slots` 中。 它计算原始哈希值,如果该槽不为空,则迭代 rehash 函数,直到出现空槽。如果非空槽已经包含 key,则旧数据值将替换为新数据值。 -```` python +```python def put(self,key,data): hashvalue = self.hashfunction(key,len(self.slots)) @@ -163,14 +177,15 @@ def hashfunction(self,key,size): def rehash(self,oldhash,size): return (oldhash+1)%size -```` +``` + *Listing 3* 同样,get 函数(见 Listing 4)从计算初始哈希值开始。如果值不在初始槽中,则 rehash 用于定位下一个可能的位置。注意,第 15 行保证搜索将通过检查以确保我们没有返回到初始槽来终止。如果发生这种情况,我们已用尽所有可能的槽,并且项不存在。 HashTable 类提供了附加的字典功能。我们重载 `__getitem__` 和`__setitem__` 方法以允许使用 `[]` 访问。 这意味着一旦创建了HashTable,索引操作符将可用。 -```` +```python def get(self,key): startslot = self.hashfunction(key,len(self.slots)) @@ -194,12 +209,13 @@ def __getitem__(self,key): def __setitem__(self,key,data): self.put(key,data) -```` +``` + *Listing 4* 下面的会话展示了 HashTable 类的操作。首先,我们将创建一个哈希表并存储一些带有整数键和字符串数据值的项。 -```` +```bash >>> H=HashTable() >>> H[54]="cat" >>> H[26]="dog" @@ -215,11 +231,11 @@ def __setitem__(self,key,data): >>> H.data ['bird', 'goat', 'pig', 'chicken', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat'] -```` +``` 接下来,我们将访问和修改哈希表中的一些项。注意,正替换键 20 的值。 -```` +```bash >>> H[20] 'chicken' >>> H[17] @@ -233,17 +249,12 @@ def __setitem__(self,key,data): >> print(H[99]) None -```` +``` ### 5.5.4.hash法分析 -我们之前说过,在最好的情况下,散列将提供O(1),恒定时间搜索。然而,由于冲突,比较的数量通常不是那么简单。即使对散列的完整分析超出了本文的范围,我们可以陈述一些近似搜索项所需的比较数量的已知结果。 +我们之前说过,在最好的情况下,散列将提供 $$O(1)$$,恒定时间搜索。然而,由于冲突,比较的数量通常不是那么简单。即使对散列的完整分析超出了本文的范围,我们可以陈述一些近似搜索项所需的比较数量的已知结果。 我们需要分析散列表的使用的最重要的信息是负载因子 λ。概念上,如果 λ 小,则碰撞的机会较低,这意味着项更可能在它们所属的槽中。如果 λ 大,意味着表正在填满,则存在越来越多的冲突。这意味着冲突解决更困难,需要更多的比较来找到一个空槽。使用链接,增加的碰撞意味着每个链上的项数量增加。 和以前一样,我们将有一个成功的搜索结果和不成功的。对于使用具有线性探测的开放寻址的成功搜索,平均比较数大约为`1/2(1 + 1/(1-λ))`,不成功的搜索为 1/2(1+(1/1-λ)^2 ) 如果我们使用链接,则对于成功的情况,平均比较数目是 1+λ/2,如果搜索不成功,则简单地是 λ 比较次数。 - - - - - 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/assets/5.5.Hash\346\237\245\346\211\276.figure6.png" "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/assets/5.5.Hash\346\237\245\346\211\276.figure6.png" new file mode 100644 index 0000000..a4287b1 Binary files /dev/null and "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/assets/5.5.Hash\346\237\245\346\211\276.figure6.png" differ diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.6.\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.6.\346\216\222\345\272\217/README.md" index 28b96de..e595e3a 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.6.\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.6.\346\216\222\345\272\217/README.md" @@ -5,4 +5,3 @@ 有许多开发和分析的排序算法。表明排序是计算机科学的一个重要研究领域。对大量项进行排序可能需要大量的计算资源。与搜索一样,排序算法的效率与正在处理的项的数量有关。对于小集合,复杂的排序方法可能更麻烦,开销太高。另一方面,对于更大的集合,我们希望利用尽可能多的改进。在本节中,我们将讨论几种排序技术,并对它们的运行时间进行比较。 在分析特定算法之前,我们应该考虑可用于分析排序过程的操作。首先,必须比较两个值以查看哪个更小(或更大)。为了对集合进行排序,需要一些系统的方法来比较值,以查看是否有问题。比较的总数将是测量排序过程的最常见方法。第二,当值相对于彼此不在正确的位置时,可能需要交换它们。这种交换是一种昂贵的操作,并且交换的总数对于评估算法的整体效率也将是很重要的。 - 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..0d13562 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,17 +5,18 @@ 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` 函数。它将列表作为参数,并根据需要交换项来修改它。 交换操作,有时称为 `swap`,在 Python 中与在大多数其他编程语言略有不同。通常,交换列表中的两个元素需要临时存储位置(额外的内存位置)。 代码片段如下 -```` +```python temp = alist[i] alist[i] = alist[j] alist[j] = temp -```` +``` 将交换列表中的第 i 项和第 j 项。没有临时存储,其中一个值将被覆盖。 @@ -24,9 +25,10 @@ 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 +```python def bubbleSort(alist): for passnum in range(len(alist)-1,0,-1): for i in range(passnum): @@ -38,17 +40,19 @@ def bubbleSort(alist): alist = [54,26,93,17,77,31,44,55,20] bubbleSort(alist) print(alist) -```` +``` + *ActiveCode 1* -为了分析气泡排序,我们应该注意,不管项如何在初始列表中排列,将进行 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 )比较。在最好的情况下,如果列表已经排序,则不会进行交换。 但是,在最坏的情况下,每次比较都会导致交换元素。 平均来说,我们交换了一半时间。 +为了分析气泡排序,我们应该注意,不管项如何在初始列表中排列,将进行 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 展示了这种修改,通常称为 `短冒泡排序`。 -```` +```python def shortBubbleSort(alist): exchanges = True passnum = len(alist)-1 @@ -65,6 +69,6 @@ def shortBubbleSort(alist): alist=[20,30,40,90,50,60,70,80,100,110] shortBubbleSort(alist) print(alist) -```` -*Activecode 2* +``` +*Activecode 2* diff --git "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.8.\351\200\211\346\213\251\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.8.\351\200\211\346\213\251\346\216\222\345\272\217/README.md" index db27820..9b79d83 100644 --- "a/5.\346\216\222\345\272\217\345\222\214\346\220\234\347\264\242/5.8.\351\200\211\346\213\251\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.8.\351\200\211\346\213\251\346\216\222\345\272\217/README.md" @@ -8,7 +8,7 @@ Figure 3 展示了整个排序过程。在每次遍历时,选择最大的剩 *Activecode 1* -```` python +```python def selectionSort(alist): for fillslot in range(len(alist)-1,0,-1): positionOfMax=0 @@ -24,8 +24,6 @@ alist = [54,26,93,17,77,31,44,55,20] selectionSort(alist) print(alist) -```` - -你可能会看到选择排序与冒泡排序有相同数量的比较,因此也是 O(n^2 )。 然而,由于交换数量的减少,选择排序通常在基准研究中执行得更快。 事实上,对于我们的列表,冒泡排序有 20 次交换,而选择排序只有 8 次。 - +``` +你可能会看到选择排序与冒泡排序有相同数量的比较,因此也是 $$O(n^2)$$。 然而,由于交换数量的减少,选择排序通常在基准研究中执行得更快。 事实上,对于我们的列表,冒泡排序有 20 次交换,而选择排序只有 8 次。 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..50e910c 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" @@ -1,8 +1,9 @@ ## 5.9.插入排序 -插入排序,尽管仍然是 O(n^2 ),工作方式略有不同。它始终在列表的较低位置维护一个排序的子列表。然后将每个新项 “插入” 回先前的子列表,使得排序的子列表称为较大的一个项。Figure 4 展示了插入排序过程。 阴影项表示算法进行每次遍历时的有序子列表。 +插入排序,尽管仍然是 $$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,16 +11,16 @@ 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 行执行移位操作,将值向上移动到列表中的一个位置,在其后插入。请记住,这不是像以前的算法中的完全交换。 -插入排序的最大比较次数是 n-1 个整数的总和。同样,是 O(n^2 )。然而,在最好的情况下,每次通过只需要进行一次比较。这是已经排序的列表的情况。 +插入排序的最大比较次数是 n-1 个整数的总和。同样,是 $$O(n^2)$$。然而,在最好的情况下,每次通过只需要进行一次比较。这是已经排序的列表的情况。 关于移位和交换的一个注意事项也很重要。通常,移位操作只需要交换大约三分之一的处理工作,因为仅执行一次分配。在基准研究中,插入排序有非常好的性能。 - -```` python +```python def insertionSort(alist): for index in range(1,len(alist)): @@ -36,7 +37,6 @@ alist = [54,26,93,17,77,31,44,55,20] insertionSort(alist) print(alist) -```` -*ActiveCode 1* - +``` +*ActiveCode 1* 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.1.\347\233\256\346\240\207/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.1.\347\233\256\346\240\207/README.md" index 28a0fc0..583def2 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.1.\347\233\256\346\240\207/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.1.\347\233\256\346\240\207/README.md" @@ -1,9 +1,8 @@ ## 6.1.目标 + * 要理解树数据结构是什么,以及如何使用它。 * 查看树如何用于实现 map 数据结构。 * 使用列表实现树。 * 使用类和引用来实现树。 * 实现树作为递归数据结构。 * 使用堆实现优先级队列。 - - 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..9ff323c 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" @@ -4,7 +4,6 @@ 为了使我们的堆有效地工作,我们将利用二叉树的对数性质来表示我们的堆。 为了保证对数性能,我们必须保持树平衡。平衡二叉树在根的左和右子树中具有大致相同数量的节点。 在我们的堆实现中,我们通过创建一个 `完整二叉树` 来保持树平衡。 一个完整的二叉树是一个树,其中每个层都有其所有的节点,除了树的最底层,从左到右填充。 Figure 1 展示了完整二叉树的示例。 - ![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* @@ -13,8 +12,7 @@ ### 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) @@ -24,15 +22,16 @@ 我们将开始实现一个二叉堆的构造函数。由于整个二叉堆可以由单个列表表示,所以构造函数将初始化列表和一个 `currentSize` 属性来跟踪堆的当前大小。 Listing 1 展示了构造函数的 Python 代码。 你会注意到,一个空的二叉堆有一个单一的零作为 `heapList` 的第一个元素,这个零只是放那里,用于以后简单的整数除法。 -``` +```python class BinHeap: def __init__(self): self.heapList = [0] self.currentSize = 0 ``` + *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) @@ -42,7 +41,7 @@ class BinHeap: 我们现在可以编写 `insert` 方法了(见 Listing 3)。 插入方法中的大部分工作都是由 `percUp` 完成的。 一旦一个新项被追加到树上,`percUp` 接管并正确定位新项。 -``` +```python def percUp(self,i): while i // 2 > 0: if self.heapList[i] < self.heapList[i // 2]: @@ -51,24 +50,27 @@ def percUp(self,i): self.heapList[i] = tmp i = i // 2 ``` + *Listing 2* -``` +```python def insert(self,k): self.heapList.append(k) self.currentSize = self.currentSize + 1 self.percUp(self.currentSize) ``` + *Listing 3* 使用正确定义的 `insert` 方法,我们现在可以看 `delMin` 方法。 因为堆属性要求树的根是树中的最小项,所以找到最小项很容易。`delMin` 的难点在根被删除后恢复堆结构和堆顺序属性。 我们可以分两步恢复我们的堆。首先,我们将通过获取列表中的最后一个项并将其移动到根位置来恢复根项,保持我们的堆结构属性。 但是,我们可能已经破坏了我们的二叉堆的堆顺序属性。 第二,我们通过将新的根节点沿着树向下推到其正确位置来恢复堆顺序属性。 Figure 3展示了将新的根节点移动到堆中的正确位置所需的交换序列。 ![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` 方法中找到。 -``` +```python def percDown(self,i): while (i * 2) <= self.currentSize: mc = self.minChild(i) @@ -87,11 +89,12 @@ def minChild(self,i): else: return i * 2 + 1 ``` + *Listing 4* `delmin` 操作的代码在 Listing 5 中。注意,有难度的工作由辅助函数处理,在这种情况下是 `percDown` 。 -``` +```python def delMin(self): retval = self.heapList[1] self.heapList[1] = self.heapList[self.currentSize] @@ -100,11 +103,12 @@ def delMin(self): self.percDown(1) return retval ``` + *Listing 5* -为了完成我们对二叉堆的讨论,我们将看从一个列表构建整个堆的方法。你可能想到的第一种方法如下所示。给定一个列表,通过一次插入一个键轻松地构建一个堆。由于你从一个项的列表开始,该列表是有序的,可以使用二分查找找到正确的位置,以大约 O(log^n ) 操作的成本插入下一个键。 但是,请记住,在列表中间插入项可能需要 O(n) 操作来移动列表的其余部分,为新项腾出空间。 因此,要在堆中插入 n 个键,将需要总共 O(nlogn) 操作。 然而,如果我们从整个列表开始,那么我们可以在 O(n) 操作中构建整个堆。Listing 6 展示了构建整个堆的代码。 +为了完成我们对二叉堆的讨论,我们将看从一个列表构建整个堆的方法。你可能想到的第一种方法如下所示。给定一个列表,通过一次插入一个键轻松地构建一个堆。由于你从一个项的列表开始,该列表是有序的,可以使用二分查找找到正确的位置,以大约 $$O(logn)$$ 操作的成本插入下一个键。 但是,请记住,在列表中间插入项可能需要 $$O(n)$$ 操作来移动列表的其余部分,为新项腾出空间。 因此,要在堆中插入 n 个键,将需要总共 $$O(nlogn)$$ 操作。 然而,如果我们从整个列表开始,那么我们可以在 $$O(n)$$ 操作中构建整个堆。Listing 6 展示了构建整个堆的代码。 -```` +```python def buildHeap(self,alist): i = len(alist) // 2 self.currentSize = len(alist) @@ -112,16 +116,17 @@ def buildHeap(self,alist): while (i > 0): self.percDown(i) i = i - 1 -```` -*Listing 6* +``` +*Listing 6* ![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 所示的这一系列交换的列表与树进行比较是有用的。 -``` +```bash i = 2 [0, 9, 5, 6, 2, 3] i = 1 [0, 9, 2, 6, 5, 3] i = 0 [0, 2, 3, 6, 5, 9] @@ -129,7 +134,7 @@ i = 0 [0, 2, 3, 6, 5, 9] 完整二叉堆代码实现见 activecode 1 -``` +```python class BinHeap: def __init__(self): self.heapList = [0] @@ -193,10 +198,9 @@ print(bh.delMin()) print(bh.delMin()) ``` -*ActiveCode 1* -我们可以在 O(n) 中构建堆的断言可能看起来有点神秘,证明超出了本书的范围。 然而,理解的关键是记住 log^n 因子是从树的高度派生的。 对于`buildHeap` 中的大部分工作,树比 log^n 短。 - -基于可以从 O(n) 时间构建堆的事实,你可以使用堆对列表在 O(nlog⁡n) 时间内排序,作为本章结尾的练习。 +*ActiveCode 1* +我们可以在 $$O(n)$$ 中构建堆的断言可能看起来有点神秘,证明超出了本书的范围。 然而,理解的关键是记住 $$logn$$ 因子是从树的高度派生的。 对于`buildHeap` 中的大部分工作,树比 $$logn$$ 短。 +基于可以从 $$O(n)$$ 时间构建堆的事实,你可以使用堆对列表在 $$O(nlogn)$$ 时间内排序,作为本章结尾的练习。 diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.11.\344\272\214\345\217\211\346\237\245\346\211\276\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.11.\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/README.md" index 0e4d182..9fae615 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.11.\344\272\214\345\217\211\346\237\245\346\211\276\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.11.\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/README.md" @@ -1,4 +1,3 @@ ## 6.11.二叉查找树 我们已经看到了两种不同的方法来获取集合中的键值对。回想一下,这些集合实现了 `map` 抽象数据类型。我们讨论的 `map` ADT 的两个实现是在列表和哈希表上的二分搜索。在本节中,我们将研究二叉查找树作为从键映射到值的另一种方法。 在这种情况下,我们对树中项的确切位置不感兴趣,但我们有兴趣使用二叉树结构来提供高效的搜索。 - diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.12.\346\237\245\346\211\276\346\240\221\346\223\215\344\275\234/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.12.\346\237\245\346\211\276\346\240\221\346\223\215\344\275\234/README.md" index 56afc35..08fb57c 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.12.\346\237\245\346\211\276\346\240\221\346\223\215\344\275\234/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.12.\346\237\245\346\211\276\346\240\221\346\223\215\344\275\234/README.md" @@ -8,4 +8,3 @@ * del 使用 del map[key] 形式的语句从 `map` 中删除键值对。 * len() 返回存储在映射中的键值对的数量。 * in 返回 True 如果给定的键在 `map` 中。 - 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..b2ebbe5 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,13 +3,14 @@ 二叉搜索树依赖于在左子树中找到的键小于父节点的属性,并且在右子树中找到的键大于父代。 我们将这个称为 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 的右孩子。 为了实现二叉搜索树,我们将使用类似于我们用于实现链表的节点和引用方法,以及表达式树。但是,因为我们必须能够创建和使用一个空的二叉搜索树,我们的实现将使用两个类。第一个类称为 `BinarySearchTree`,第二个类称为`TreeNode`。 `BinarySearchTree` 类具有对作为二叉搜索树的根的`TreeNode` 的引用。在大多数情况下,外部类中定义的外部方法只是检查树是否为空。如果树中有节点,请求只是传递到 `BinarySearchTree` 类中定义的私有方法,该方法以 root 作为参数。在树是空的或者我们想要删除树根的键的情况下,我们必须采取特殊的行动。 `BinarySearchTree` 类构造函数的代码以及一些其他杂项函数如Listing 1所示。 -``` +```python class BinarySearchTree: def __init__(self): @@ -25,13 +26,14 @@ class BinarySearchTree: def __iter__(self): return self.root.__iter__() ``` + *Listing 1* TreeNode 类提供了许多辅助函数,使得在 `BinarySearchTree` 类方法中完成的工作更容易。 `TreeNode` 的构造函数以及这些辅助函数如 Listing 2所示。你可以在列表中看到许多辅助函数根据自己的位置将节点分类为子节点(左或右)和节点具有的子节点类型。 `TreeNode` 类还将显式地跟踪父节点作为每个节点的属性。当我们讨论 del 操作符的实现时,你会看到为什么这很重要。 Listing 2 中 `TreeNode` 另一个有趣的方面是我们使用 Python 的可选参数。可选参数使我们能够在几种不同的情况下轻松创建 `TreeNode` 。有时我们想要构造一个已经同时具有父和子的新 `TreeNode` 。对于现有的父和子,我们可以传递父和子作为参数。在其他时候,我们将使用键值对创建一个`TreeNode`,我们不会为父或子传递任何参数。在这种情况下,将使用可选参数的默认值。 -``` python +```python class TreeNode: def __init__(self,key,val,left=None,right=None, parent=None): @@ -64,7 +66,7 @@ class TreeNode: def hasBothChildren(self): return self.rightChild and self.leftChild - + def replaceNodeData(self,key,value,lc,rc): self.key = key self.payload = value @@ -75,19 +77,20 @@ class TreeNode: if self.hasRightChild(): self.rightChild.parent = self ``` + *Listing 2* 现在我们有了 `BinarySearchTree` shell 和 `TreeNode`,现在是时候编写 `put` 方法,这将允许我们构建二叉搜索树。 `put` 方法是`BinarySearchTree` 类的一个方法。此方法将检查树是否已具有根。如果没有根,那么 `put` 将创建一个新的 `TreeNode` 并将其做为树的根。如果根节点已经就位,则 put 调用私有递归辅助函数 `_put` 根据以下算法搜索树: -* 从树的根开始,搜索二叉树,将新键与当前节点中的键进行比较。如果新键小于当前节点,则搜索左子树。如果新键大于当前节点,则搜索右侧子树。 +* 从树的根开始,搜索二叉树,将新键与当前节点中的键进行比较。如果新键小于当前节点,则搜索左子树。如果新键大于当前节点,则搜索右子树。 * 当没有左(或右)孩子要搜索时,我们在树中找到应该建立新节点的位置。 * 要向树中添加节点,请创建一个新的 `TreeNode` 对象,并将对象插入到上一步发现的节点。 Listing 3 展示了在树中插入一个新节点的 Python 代码。`_put` 函数按照上述步骤递归编写。请注意,当一个新的子节点插入到树中时,`currentNode` 将作为父节点传递给新的树节点。 -我们实现插入的一个重要问题是重复的键不能正确处理。当我们的树被实现时,重复键将在具有原始键的节点的右子树中创建具有相同键值的新节点。这样做的结果是,具有新键的节点将永远不会在搜索期间被找到。处理插入重复键的更好方法是将新键相关联的值替换旧值。我们将修复这个bug作为一个练习。 +我们实现插入的一个重要问题是重复的键不能正确处理。当我们的树被实现时,重复键将在具有原始键的节点的右子树中创建具有相同键值的新节点。这样做的结果是,具有新键的节点将永远不会在搜索期间被找到。处理插入重复键的更好方法是将新键相关联的值替换旧值。我们将修复这个bug作为练习。 -``` +```python def put(self,key,val): if self.root: self._put(key,val,self.root) @@ -107,19 +110,22 @@ def _put(self,key,val,currentNode): else: currentNode.rightChild = TreeNode(key,val,parent=currentNode) ``` + *Listing 3* -当 `put` 方法定义后,我们可以通过使用 `__setitem__` 方法调用(参见 Listing 4 )`put` 方法来重载赋值的 `[]` 运算符。这使得我们可以编写像` myZipTree['Plymouth'] = 55446` 这样的 Python 语句,就像 Python 字典一样。 +当 `put` 方法定义后,我们可以通过使用 `__setitem__` 方法调用(参见 Listing 4 )`put` 方法来重载赋值的 `[]` 运算符。这使得我们可以编写像`myZipTree['Plymouth'] = 55446` 这样的 Python 语句,就像 Python 字典一样。 -``` +```python def __setitem__(self,k,v): self.put(k,v) ``` + *Listing 4* 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` 方法更容易,因为它只是递归地搜索树,直到它到达不匹配的叶节点或找到匹配的键。当找到匹配的键时,返回存储在节点的有效载荷中的值。 @@ -128,7 +134,7 @@ Listing 5 展示了 `get` ,`_get` 和 `__getitem__` 的代码。 `_get` 方法 通过实现 `__getitem__` 方法,我们可以编写一个类似于访问字典的 Python 语句,而实际上我们使用的是二叉搜索树,例如 `z = myZipTree ['Fargo']` 。正如你所看到的,所有的 `__getitem__` 方法都是调用`get` 。 -``` +```python def get(self,key): if self.root: res = self._get(key,self.root) @@ -152,29 +158,31 @@ def _get(self,key,currentNode): def __getitem__(self,key): return self.get(key) ``` + *Listing 5* 使用 `get` ,我们可以通过为 `BinarySearchTree` 写一个`__contains__` 方法来实现 in 操作。 `__contains__` 方法将简单地调用 `get` 并在 `get` 返回值时返回 True,如果返回 None 则返回 False。 `__contains__` 的代码如Listing 6所示。 -``` +```python def __contains__(self,key): if self._get(key,self.root): return True else: return False ``` + *Listing 6* 回想一下,`__contains__` 重载了 `in` 操作符,允许我们写出如下语句: -``` +```python if 'Northfield' in myZipTree: print("oom ya ya") ``` 最后,我们将注意力转向二叉搜索树中最具挑战性的方法,删除一个键(参见 Listing 7)。 第一个任务是通过搜索树来找到要删除的节点。 如果树具有多个节点,我们使用 `_get` 方法搜索以找到需要删除的 `TreeNode`。 如果树只有一个节点,这意味着我们删除树的根,但是我们仍然必须检查以确保根的键匹配要删除的键。 在任一情况下,如果未找到键,`del` 操作符将引发错误。 -``` +```python def delete(self,key): if self.size > 1: nodeToRemove = self._get(key,self.root) @@ -192,6 +200,7 @@ def delete(self,key): def __delitem__(self,key): self.delete(key) ``` + *Listing 7* 一旦我们找到了我们要删除的键的节点,我们必须考虑三种情况: @@ -202,16 +211,18 @@ def __delitem__(self,key): 第一种情况很简单(见 Listing 8)。 如果当前节点没有子节点,我们需要做的是删除节点并删除对父节点中该节点的引用。 此处的代码如下所示。 -``` +```python if currentNode.isLeaf(): if currentNode == currentNode.parent.leftChild: currentNode.parent.leftChild = None else: currentNode.parent.rightChild = None ``` + *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)。如果一个节点只有一个孩子,那么我们可以简单地促进孩子取代其父。此案例的代码展示在下一个列表中。当你看这个代码,你会看到有六种情况要考虑。由于这些情况相对于左孩子或右孩子对称,我们将仅讨论当前节点具有左孩子的情况。决策如下: @@ -220,7 +231,7 @@ if currentNode.isLeaf(): 2. 如果当前节点是右子节点,则我们只需要更新左子节点的父引用以指向当前节点的父节点,然后更新父节点的右子节点引用以指向当前节点的左子节点。 3. 如果当前节点没有父级,则它是根。在这种情况下,我们将通过在根上调用`replaceNodeData` 方法来替换 `key`,`payload`,`leftChild` 和 `rightChild` 数据。 -``` +```python else: # this node has one child if currentNode.hasLeftChild(): if currentNode.isLeftChild(): @@ -247,25 +258,29 @@ else: # this node has one child currentNode.rightChild.leftChild, currentNode.rightChild.rightChild) ``` + *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` 的原因是它直接找到我们想要拼接的节点,并做出正确的更改。 我们可以递归调用删除,但是我们将浪费时间重新搜索关键节点。 -``` +```python elif currentNode.hasBothChildren(): #interior succ = currentNode.findSuccessor() succ.spliceOut() currentNode.key = succ.key currentNode.payload = succ.payload ``` + *Listing 10* 找到后继的代码如下所示(见 Listing 11),是 `TreeNode` 类的一个方法。此代码利用二叉搜索树的相同属性,采用中序遍历从最小到最大打印树中的节点。在寻找接班人时,有三种情况需要考虑: @@ -278,7 +293,7 @@ elif currentNode.hasBothChildren(): #interior 调用 `findMin` 方法来查找子树中的最小键。你应该说服自己,任何二叉搜索树中的最小值键是树的最左子节点。因此,`findMin` 方法简单地循环子树的每个节点中的 `leftChild` 引用,直到它到达没有左子节点的节点。 -``` +```python def findSuccessor(self): succ = None if self.hasRightChild(): @@ -319,6 +334,7 @@ def spliceOut(self): self.parent.rightChild = self.rightChild self.rightChild.parent = self.parent ``` + *Listing 11* 我们需要查看二叉搜索树的最后一个接口方法。假设我们想要按中序遍历树中的所有键。我们肯定用字典做,为什么不是树?你已经知道如何使用中序遍历算法按顺序遍历二叉树。然而,编写迭代器需要更多的工作,因为迭代器在每次调用迭代器时只返回一个节点。 @@ -327,12 +343,11 @@ Python 为我们提供了一个非常强大的函数,在创建迭代器时使 二叉树的 `inorder` 迭代器的代码展示在下一个列表中。仔细看看这段代码;乍一看,你可能认为代码不是递归的。但是,请记住,`__iter__` 覆盖 `for x in `操作,因此它是递归的!因为它是在 `TreeNode` 实例上递归的,所以`__iter__` 方法在 `TreeNode` 类中定义。 - -``` +```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(): @@ -342,7 +357,7 @@ def __iter__(self): 此时,你可能需要下载包含完整版本的 `BinarySearchTree` 和 `TreeNode` 类的整个文件。 -```` +```python class TreeNode: def __init__(self,key,val,left=None,right=None,parent=None): self.key = key @@ -555,5 +570,4 @@ mytree[2]="at" print(mytree[6]) print(mytree[2]) -```` - +``` 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..5d609c8 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,16 +1,15 @@ ## 6.14.查找树分析 +随着二叉搜索树的实现完成,我们将对已经实现的方法进行快速分析。让我们先来看看 `put` 方法。其性能的限制因素是二叉树的高度。从词汇部分回忆一下树的高度是根和最深叶节点之间的边的数量。高度是限制因素,因为当我们寻找合适的位置将一个节点插入到树中时,我们需要在树的每个级别最多进行一次比较。 -随着二叉搜索树的实现完成,我们将对我们已经实现的方法进行快速分析。让我们先来看看 `put` 方法。其性能的限制因素是二叉树的高度。从词汇部分回忆一下树的高度是根和最深叶节点之间的边的数量。高度是限制因素,因为当我们寻找合适的位置将一个节点插入到树中时,我们需要在树的每个级别最多进行一次比较。 +二叉树的高度可能是多少?这个问题的答案取决于如何将键添加到树。如果按照随机顺序添加键,树的高度将在 $$log2^n$$ 附近,其中 n 是树中的节点数。这是因为如果键是随机分布的,其中大约一半将小于根,一半大于根。请记住,在二叉树中,根节点有一个节点,下一级节点有两个节点,下一个节点有四个节点。任何特定级别的节点数为 $$2^d$$ ,其中 d 是级别的深度。完全平衡的二叉树中的节点总数为 $$2^{h+1} - 1$$,其中 h 表示树的高度。 -二叉树的高度可能是多少?这个问题的答案取决于如何将键添加到树。如果按照随机顺序添加键,树的高度将在 log2^⁡n 附近,其中 n 是树中的节点数。这是因为如果键是随机分布的,其中大约一半将小于根,一半大于根。请记住,在二叉树中,根节点有一个节点,下一级节点有两个节点,下一个节点有四个节点。任何特定级别的节点数为 2^d ,其中 d 是级别的深度。完全平衡的二叉树中的节点总数为 2^h+1 - 1,其中 h 表示树的高度。 +完全平衡的树在左子树中具有与右子树相同数量的节点。在平衡二叉树中,`put` 的最坏情况性能是 $$O(log2^n)$$,其中 n 是树中的节点数。注意,这是与前一段中的计算的反比关系。所以 log2^⁡n 给出了树的高度,并且表示了在适当的位置插入新节点时,需要做的最大比较次数。 -完全平衡的树在左子树中具有与右子树相同数量的节点。在平衡二叉树中,`put` 的最坏情况性能是 O(log2^⁡n ),其中 n 是树中的节点数。注意,这是与前一段中的计算的反比关系。所以 log2^⁡n 给出了树的高度,并且表示了在适当的位置插入新节点时,需要做的最大比较次数。 - -不幸的是,可以通过以排序顺序插入键来构造具有高度 n 的搜索树!这样的树的示例见 Figure 6。在这种情况下,put方法的性能是 O(n)。 +不幸的是,可以通过以排序顺序插入键来构造具有高度 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..4427c5f 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" @@ -1,16 +1,15 @@ ## 6.15.平衡二叉搜索树 -在上一节中,我们考虑构建一个二叉搜索树。正如我们所学到的,二叉搜索树的性能可以降级到 O(n) 的操作,如 `get` 和 `put` ,如果树变得不平衡。在本节中,我们将讨论一种特殊类型的二叉搜索树,它自动确保树始终保持平衡。这棵树被称为 AVL树,以其发明人命名:G.M. Adelson-Velskii 和E.M.Landis。 +在上一节中,我们考虑构建一个二叉搜索树。正如我们所学到的,二叉搜索树的性能可以降级到 $$O(n)$$ 的操作,如 `get` 和 `put` ,如果树变得不平衡。在本节中,我们将讨论一种特殊类型的二叉搜索树,它自动确保树始终保持平衡。这棵树被称为 AVL树,以其发明人命名:G.M. Adelson-Velskii 和E.M.Landis。 AVL树实现 Map 抽象数据类型就像一个常规的二叉搜索树,唯一的区别是树的执行方式。为了实现我们的 AVL树,我们需要跟踪树中每个节点的平衡因子。我们通过查看每个节点的左右子树的高度来做到这一点。更正式地,我们将节点的平衡因子定义为左子树的高度和右子树的高度之间的差。 ->balanceFactor = height(leftSubTree) - height(rightSubTree) - +$$ +balanceFactor = height(leftSubTree) - height(rightSubTree) +$$ 使用上面给出的平衡因子的定义,我们说如果平衡因子大于零,则子树是左重的。如果平衡因子小于零,则子树是右重的。如果平衡因子是零,那么树是完美的平衡。为了实现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* - - +*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..d8499f5 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,26 +3,45 @@ 在我们继续之前,我们来看看执行这个新的平衡因子要求的结果。我们的主张是,通过确保树总是具有 -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) 的树中的节点数量的模式是: -![6.16.AVL平衡二叉搜索树.1](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.1.png) + +$$ +N_h = 1 + N_{h-1} + N_{h-2} +$$ 这种可能看起来很熟悉,因为它非常类似于斐波纳契序列。 给定树中节点的数量,我们可以使用这个事实来导出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 的方程为: -![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) +$$ +\begin{aligned} +F_0 = 0 \\ +F_1 = 1 \\ +F_i = F_{i-1} + F_{i-2} \text{ for all } i \ge 2 +\end{aligned} +$$ -通过用其黄金比例近似替换斐波那契参考,我们得到: -![6.16.AVL平衡二叉搜索树.4](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.4.png) +一个重要的数学结果是,随着斐波纳契数列越来越大,$$F_i/F_{i-1}$$ 的比率越来越接近黄金比率 $$\Phi = \frac{(1 +\sqrt{5})}{2}$$。 如果要查看上一个方程的导数,可以查阅数学文本。 我们将简单地使用该方程来近似 Fi,如 $$Fi =\Phi^i / 5$$。 如果我们利用这个近似,我们可以重写 Nh 的方程为: -如果我们重新排列这些项,并取两边的底数为2的对数,然后求解 h,我们得到以下推导: -![6.16.AVL平衡二叉搜索树.5](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.5.png) - -这个推导告诉我们,在任何时候,我们的AVL树的高度等于树中节点数目的对数的常数(1.44)倍。 这是搜索我们的AVL树的好消息,因为它将搜索限制为O(logN)。 +$$ +N_h = F_{h+2} - 1, h \ge 1 +$$ +通过用其黄金比例近似替换斐波那契参考,我们得到: +$$ +N_h = \frac{\Phi^{h+2}}{\sqrt{5}} - 1 +$$ +如果我们重新排列这些项,并取两边的底数为2的对数,然后求解 h,我们得到以下推导: +$$ +\begin{aligned} +\log{N_h+1} = (H+2)\log{\Phi} - \frac{1}{2} \log{5} \\ +h = \frac{\log{N_h+1} - 2 \log{\Phi} + \frac{1}{2} \log{5}}{\log{\Phi}} \\ +h = 1.44 \log{N_h} +\end{aligned} +$$ +这个推导告诉我们,在任何时候,我们的AVL树的高度等于树中节点数目的对数的常数(1.44)倍。 这是搜索我们的AVL树的好消息,因为它将搜索限制为 $$O(logN)$$。 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/assets/6.13.\346\237\245\346\211\276\346\240\221\345\256\236\347\216\260.figure4.png" "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/assets/6.13.\346\237\245\346\211\276\346\240\221\345\256\236\347\216\260.figure4.png" deleted file mode 100644 index 20f1d9e..0000000 Binary files "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/assets/6.13.\346\237\245\346\211\276\346\240\221\345\256\236\347\216\260.figure4.png" and /dev/null differ 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/assets/6.13.\346\237\245\346\211\276\346\240\221\345\256\236\347\216\260.figure5.png" "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/assets/6.13.\346\237\245\346\211\276\346\240\221\345\256\236\347\216\260.figure5.png" deleted file mode 100644 index 72becf7..0000000 Binary files "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/assets/6.13.\346\237\245\346\211\276\346\240\221\345\256\236\347\216\260.figure5.png" and /dev/null differ 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/assets/6.14.\346\237\245\346\211\276\346\240\221\345\210\206\346\236\220.figure6.png" "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/assets/6.14.\346\237\245\346\211\276\346\240\221\345\210\206\346\236\220.figure6.png" deleted file mode 100644 index 976ce4e..0000000 Binary files "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/assets/6.14.\346\237\245\346\211\276\346\240\221\345\210\206\346\236\220.figure6.png" and /dev/null differ 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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.1.png" "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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.1.png" deleted file mode 100644 index 45ea52e..0000000 Binary files "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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.1.png" and /dev/null differ 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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.2.png" "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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.2.png" deleted file mode 100644 index ca94eb7..0000000 Binary files "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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.2.png" and /dev/null differ 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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.3.png" "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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.3.png" deleted file mode 100644 index 1a09c6e..0000000 Binary files "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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.3.png" and /dev/null differ 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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.4.png" "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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.4.png" deleted file mode 100644 index 56da5f0..0000000 Binary files "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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.4.png" and /dev/null differ 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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.5.png" "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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.5.png" deleted file mode 100644 index 8d0912a..0000000 Binary files "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/assets/6.16.AVL\345\271\263\350\241\241\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.5.png" and /dev/null differ 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..2935871 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,11 +3,11 @@ 现在我们已经证明保持 AVL树的平衡将是一个很大的性能改进,让我们看看如何增加过程来插入一个新的键到树。由于所有新的键作为叶节点插入到树中,并且我们知道新叶的平衡因子为零,所以刚刚插入的节点没有新的要求。但一旦添加新叶,我们必须更新其父的平衡因子。这个新叶如何影响父的平衡因子取决于叶节点是左孩子还是右孩子。如果新节点是右子节点,则父节点的平衡因子将减少1。如果新节点是左子节点,则父节点的平衡因子将增加1。这个关系可以递归地应用到新节点的祖父节点,并且应用到每个祖先一直到树的根。由于这是一个递归过程,我们来看一下用于更新平衡因子的两种基本情况: * 递归调用已到达树的根。 -* 母公司的平衡因子已调整为零。你应该说服自己,一旦一个子树的平衡因子为零,那么它的祖先节点的平衡不会改变。 +* 父节点的平衡因子已调整为零。你应该说服自己,一旦一个子树的平衡因子为零,那么它的祖先节点的平衡不会改变。 我们将实现 AVL 树作为 `BinarySearchTree` 的子类。首先,我们将覆盖`_put` 方法并编写一个新的 `updateBalance` 辅助方法。这些方法如Listing 1所示。你将注意到,`_put` 的定义与简单二叉搜索树中的完全相同,除了第 7 行和第 13 行上对 `updateBalance` 的调用的添加。 -```` +```python def _put(self,key,val,currentNode): if key < currentNode.key: if currentNode.hasLeftChild(): @@ -34,7 +34,8 @@ def updateBalance(self,node): if node.parent.balanceFactor != 0: self.updateBalance(node.parent) -```` +``` + *Listing 1* 新的 `updateBalance` 方法完成了大多数工作。这实现了我们刚才描述的递归过程。 `updateBalance` 方法首先检查当前节点是否不够平衡,需要重新平衡(第16行)。如果平衡,则重新平衡完成,并且不需要对父节点进行进一步更新。如果当前节点不需要重新平衡,则调整父节点的平衡因子。如果父的平衡因子不为零,那么算法通过递归调用父对象上的 `updateBalance`,继续沿树向根向上运行。 @@ -44,6 +45,7 @@ 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,14 +64,14 @@ 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* +*Figure 4* 现在你已经看到了旋转,并且有旋转的工作原理的基本概念,让我们看看代码。Listing 2展示了右旋转和左旋转的代码。在第2行中,我们创建一个临时变量来跟踪子树的新根。正如我们之前所说的,新的根是上一个根的右孩子。现在对这个临时变量存储了一个对右孩子的引用,我们用新的左孩子替换旧根的右孩子。 下一步是调整两个节点的父指针。如果 newRoot 有一个左子节点,那么左子节点的新父节点变成旧的根节点。新根的父节点设置为旧根的父节点。如果旧根是整个树的根,那么我们必须设置树的根以指向这个新根。否则,如果旧根是左孩子,则我们将左孩子的父节点更改为指向新根;否则我们改变右孩子的父亲指向新的根。(行10-13)。最后,我们将旧根的父节点设置为新根。这是一个很复杂的过程,所以我们鼓励你跟踪这个功能,同时看下 Figure 3。 `rotateRight` 方法是对称的 `rotateLeft`,所以我们将留给你来研究 `rotateRight` 的代码。 -```` +```python def rotateLeft(self,rotRoot): newRoot = rotRoot.rightChild rotRoot.rightChild = newRoot.leftChild @@ -87,43 +89,60 @@ def rotateLeft(self,rotRoot): rotRoot.parent = newRoot rotRoot.balanceFactor = rotRoot.balanceFactor + 1 - min(newRoot.balanceFactor, 0) newRoot.balanceFactor = newRoot.balanceFactor + 1 + max(rotRoot.balanceFactor, 0) -```` +``` + *Listing 2* 最后,第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 为根的特定子树的高度。 根据定义,我们知道以下: - -> newBal(B)=hA−hC -oldBal(B)=hA−hD +$$ +\begin{aligned} +newBal(B)&=hA-hC\\ +oldBal(B)&=hA-hD +\end{aligned} +$$ 但我们知道,D 的旧高度也可以由 `1 + max(hC,hE)`给出,也就是说,D 的高度比其两个孩子的最大高度大 1。 记住,`hC`和 `hE` 没有改变。 所以,让我们用第二个方程来代替它 -> oldBal(B)=hA−(1+max(hC,hE)) +$$ +oldBal(B)=hA-(1+max(hC,hE)) +$$ 然后减去这两个方程。 以下步骤进行减法并使用一些代数来简化 `newBal(B)` 的等式。 -> newBal(B)−oldBal(B)=hA−hC−(hA−(1+max(hC,hE))) -> newBal(B)−oldBal(B)=hA−hC−hA+(1+max(hC,hE)) -> newBal(B)−oldBal(B)=hA−hA+1+max(hC,hE)−hC -> newBal(B)−oldBal(B)=1+max(hC,hE)−hC +$$ +\begin{aligned} +newBal(B)-oldBal(B)&=hA-hC-(hA-(1+max(hC,hE)))\\ +newBal(B)-oldBal(B)&=hA-hC-hA+(1+max(hC,hE))\\ +newBal(B)-oldBal(B)&=hA-hA+1+max(hC,hE)-hC\\ +newBal(B)-oldBal(B)&=1+max(hC,hE)-hC +\end{aligned} +$$ 接下来我们将 oldBal(B) 移动到方程的右边,并利用 `max(a,b) -c = max(a-c,b-c)`。 -> newBal(B)=oldBal(B)+1+max(hC−hC,hE−hC) +$$ +newBal(B)=oldBal(B)+1+max(hC-hC,hE-hC) +$$ -但是,hE-hC 与 -oldBal(D) 相同。因此,我们可以使用另一个表示 max(-a,-b) = -min(a,b) 的标识。 因此,我们可以完成我们的 newBal(B) 的推导,具有以下步骤: +但是,$$hE-hC$$ 与 $$-oldBal(D)$$ 相同。因此,我们可以使用另一个表示 $$max(-a, -b) = -min(a, b)$$ 的标识。 因此,我们可以完成我们的 newBal(B) 的推导,具有以下步骤: -> newBal(B)=oldBal(B)+1+max(0,−oldBal(D)) -> newBal(B)=oldBal(B)+1−min(0,oldBal(D)) +$$ +\begin{aligned} +newBal(B)&=oldBal(B)+1+max(0,-oldBal(D))\\ +newBal(B)&=oldBal(B)+1-min(0,oldBal(D)) +\end{aligned} +$$ 现在我们有所有的部分,我们很容易知道。 我们记住 B 是 rotRoot 和 D 是newRoot 然后我们可以看到这正好对应第16行的语句,或者: -``` +```python rotRoot.balanceFactor = rotRoot.balanceFactor + 1 - min(0,newRoot.balanceFactor) ``` @@ -132,10 +151,12 @@ 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* 要纠正这个问题,我们必须使用以下规则集: @@ -147,11 +168,11 @@ 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语句实现的 。 - -``` +```python def rebalance(self,node): if node.balanceFactor < 0: if node.rightChild.balanceFactor > 0: @@ -168,10 +189,6 @@ def rebalance(self,node): ``` *Listing 3* - -通过保持树在所有时间的平衡,我们可以确保 get 方法将按 O(log2(n)) 时间运行。但问题是我们的 put 方法有什么成本?让我们将它分解为 put 执行的操作。由于将新节点作为叶子插入,更新所有父节点的平衡因子将需要最多log2^n 运算,树的每层一个运算。如果发现子树不平衡,则需要最多两次旋转才能使树重新平衡。但是,每个旋转在 O(1)时间中工作,因此我们的put操作仍然是O(log2^n )。 +通过保持树在所有时间的平衡,我们可以确保 get 方法将按 $$O(log2(n))$$ 时间运行。但问题是我们的 put 方法有什么成本?让我们将它分解为 put 执行的操作。由于将新节点作为叶子插入,更新所有父节点的平衡因子将需要最多 $$log2^n$$ 运算,树的每层一个运算。如果发现子树不平衡,则需要最多两次旋转才能使树重新平衡。但是,每个旋转在 $$O(1)$$ 时间中工作,因此我们的put操作仍然是 $$O(log2^n)$$。 在这一点上,我们已经实现了一个功能AVL树,除非你需要删除一个节点的能力。我们保留删除节点和随后的更新和重新平衡作为一个练习。 - - - 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..ada2207 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,8 +1,5 @@ ## 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.19.\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.19.\346\200\273\347\273\223/README.md" index 3dbf6b0..8165fac 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.19.\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.19.\346\200\273\347\273\223/README.md" @@ -7,4 +7,3 @@ * 用于实现 Map ADT的平衡二叉树(AVL树)。 * 一个二叉树实现一个最小堆。 * 用于实现优先级队列的最小堆。 - 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..5fb3594 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,7 @@ 在我们开始研究树形数据结构之前,让我们来看几个常见的例子。我们树的第一个例子是生物学的分类树。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,13 +17,14 @@ 你可能每天使用的树结构的另一个示例是文件系统。在文件系统中,目录或文件夹被构造为树。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 目录的内容或任何子级。 树的最后一个例子是网页。 以下是使用HTML编写的简单网页的示例。 Figure 3 展示了用于创建页面的每个 HTML 标记的树。 -``` +```html @@ -42,8 +44,7 @@ ``` ![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。 +*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..f523830 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" @@ -3,43 +3,66 @@ 我们已经看了树的示例,我们将正式定义树及其组件。 ##### 节点 + 节点是树的基本部分。它可以有一个名称,我们称之为“键”。节点也可以有附加信息。我们将这个附加信息称为“有效载荷”。虽然有效载荷信息不是许多树算法的核心,但在利用树的应用中通常是关键的。 + ##### 边 + 边是树的另一个基本部分。边连接两个节点以显示它们之间存在关系。每个节点(除根之外)都恰好从另一个节点的传入连接。每个节点可以具有多个输出边。 + ##### 根 + 树的根是树中唯一没有传入边的节点。在 Figure 2 中,/ 是树的根。 + ##### 路径 -路径是由边连接节点的有序列表。例如,Mammal→→Carnivora→→Felidae→→Felis→→Domestica是一条路径。 + +路径是由边连接节点的有序列表。例如, $$Mammal \to Carnivora \to Felidae \to Felis \to Domestica$$ 是一条路径。 + ##### 子节点 + 具有来自相同传入边的节点 c 的集合称为该节点的子节点。在 Figure 2中,节点 log/,spool/ 和 yp/ 是节点 var/ 的子节点。 + ##### 父节点 + 具有和它相同传入边的所连接的节点称为父节点。在 Figure 2 中,节点 var/ 是节点 log/,spool/ 和 yp/ 的父节点。 + ##### 兄弟 + 树中作为同一父节点的子节点的节点被称为兄弟节点。节点 etc/ 和 usr/ 是文件系统树中的兄弟节点。 + ##### 子树 + 子树是由父节点和该父节点的所有后代组成的一组节点和边。 + ##### 叶节点 + 叶节点是没有子节点的节点。 例如,人类和黑猩猩是 Figure 1 中的叶节点。 + ##### 层数 + 节点 n 的层数为从根结点到该结点所经过的分支数目。 例如,图1中的Felis节点的级别为五。根据定义,根节点的层数为零。 + ##### 高度 + 树的高度等于树中任何节点的最大层数。 Figure 2 中的树的高度是 2。 现在已经定义了基本词汇,我们可以继续对树的正式定义。 事实上,我们将提供一个树的两个定义。 一个定义涉及节点和边。 第二个定义,将被证明是非常有用的,是一个递归定义。 定义一:树由一组节点和一组连接节点的边组成。树具有以下属性: + * 树的一个节点被指定为根节点。 * 除了根节点之外,每个节点 n 通过一个其他节点 p 的边连接,其中 p 是 n 的父节点。 * 从根路径遍历到每个节点路径唯一。 * 如果树中的每个节点最多有两个子节点,我们说该树是一个二叉树。 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* - +*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..a3d2230 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,9 +3,10 @@ 在由列表表示的树中,我们将从 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 +```python myTree = ['a', #root ['b', #left subtree ['d', [], []], @@ -18,24 +19,26 @@ myTree = ['a', #root 注意,我们可以使用标准列表索引来访问列表的子树。树的根是 `myTree[0]`,根的左子树是 `myTree[1]`,右子树是 `myTree[2]`。 ActiveCode 1 说明了使用列表创建一个简单的树。一旦树被构建,我们可以访问根和左右子树。 该列表方法的一个非常好的属性是表示子树的列表的结构遵守树定义的结构; 结构本身是递归的!具有根值和两个空列表的子树是叶节点。列表方法的另一个很好的特性是它可以推广到一个有许多子树的树。在树超过二叉树的情况下,另一个子树只是另一个列表。 -``` +```python myTree = ['a', ['b', ['d',[],[]], ['e',[],[]] ], ['c', ['f',[],[]], []] ] print(myTree) print('left subtree = ', myTree[1]) print('root = ', myTree[0]) print('right subtree = ', myTree[2]) ``` + *Activecode 1* 让我们提供一些使我们能够使用列表作为树的函数来形式化树数据结构的这个定义。注意,我们不会定义一个二叉树类。我们写的函数只是帮助我们操纵一个标准列表,就像我们正在使用一棵树。 -``` +```python def BinaryTree(r): return [r, [], []] ``` + `BinaryTree` 函数简单地构造一个具有根节点和两个子列表为空的列表。要将左子树添加到树的根,我们需要在根列表的第二个位置插入一个新的列表。我们必须小心。如果列表已经在第二个位置有东西,我们需要跟踪它,并沿着树向下把它作为我们添加的列表的左子节点。Listing 1 展示了插入左子节点的 Python 代码。 -```` +```python def insertLeft(root,newBranch): t = root.pop(1) if len(t) > 1: @@ -43,12 +46,13 @@ def insertLeft(root,newBranch): else: root.insert(1,[newBranch, [], []]) return root -```` +``` + *Listing 1* 注意,要插入一个左子节点,我们首先获得与当前左子节点对应的(可能为空的)列表。然后我们添加新的左子树,添加旧的左子数作为新子节点的左子节点。这允许我们在任何位置将新节点拼接到树中。`insertRight` 的代码与`insertLeft` 类似,如 Listing 2所示。 -``` +```python def insertRight(root,newBranch): t = root.pop(2) if len(t) > 1: @@ -57,11 +61,12 @@ def insertRight(root,newBranch): root.insert(2,[newBranch,[],[]]) return root ``` + *Listing 2* 为了完成这组树形函数(见 Listing 3),让我们编写一些访问函数来获取和设置根节点的值,以及获取左或右子树。 -``` +```python def getRootVal(root): return root[0] @@ -74,6 +79,5 @@ def getLeftChild(root): def getRightChild(root): return root[2] ``` -*Listing 3* - +*Listing 3* 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..356ae04 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,24 +5,26 @@ 使用节点和引用,我们认为树结构类似于 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` 来引用新树节点。 -``` +```python class BinaryTree: def __init__(self,rootObj): self.key = rootObj self.leftChild = None self.rightChild = None ``` + *Listing 4* 请注意,在 Listing 4 中,构造函数希望获取某种对象存储在根中。 就像你可以在列表中存储任何你喜欢的对象一样,树的根对象可以是对任何对象的引用。 对于我们的先前示例,我们将存储节点的名称作为根值。使用节点和引用来表示 Figure 2 中的树,我们将创建 `BinaryTree` 类的六个实例。 接下来,我们来看看需要构建超出根节点的树的函数。要向树中添加一个左子树,我们将创建一个新的二叉树对象,并设置根的左边属性来引用这个新对象。 `insertLeft` 的代码如 Listing 5所示。 -``` +```python def insertLeft(self,newNode): if self.leftChild == None: self.leftChild = BinaryTree(newNode) @@ -31,13 +33,14 @@ def insertLeft(self,newNode): t.leftChild = self.leftChild self.leftChild = t ``` + *Listing 5* 我们必须考虑两种插入情况。 第一种情况的特征没有现有左孩子的节点。当没有左孩子时,只需向树中添加一个节点。 第二种情况的特征在于具有现有左孩子的节点。在第二种情况下,我们插入一个节点并将现有的子节点放到树中的下一个层。第二种情况由 Listing 5 第 4 行的 else 语句处理。 `insertRight` 的代码必须考虑一组对称的情况。没有右孩子,或者我们在根和现有右孩子之间插入节点。 插入代码如 Listing 6 所示。 -``` +```python def insertRight(self,newNode): if self.rightChild == None: self.rightChild = BinaryTree(newNode) @@ -46,11 +49,12 @@ def insertRight(self,newNode): t.rightChild = self.rightChild self.rightChild = t ``` + *Listing 6* 为了完成一个简单二叉树数据结构的定义,我们将实现获取 左和右孩子(见 Listing 7 )以及根值的方法。 -``` +```python def getRightChild(self): return self.rightChild @@ -63,11 +67,12 @@ def setRootVal(self,obj): def getRootVal(self): return self.key ``` + *Listing 7* 现在我们有了创建和操作二叉树的所有部分,让我们使用它们来检查结构。 我们使用节点 a 作为根的简单树,并将节点 b 和 c 添加为子节点。 ActiveCode 1 创建树并查看存储在 key,left 和 right 中的一些值。注意,根的左和右孩子本身是 BinaryTree 类的不同实例。正如我们在对树的原始递归定义中所说的,这允许我们将二叉树的任何子项视为二叉树本身。 -``` +```python class BinaryTree: def __init__(self,rootObj): self.key = rootObj @@ -117,4 +122,3 @@ r.getRightChild().setRootVal('hello') print(r.getRightChild().getRootVal()) ``` - 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..46ea3be 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,19 @@ 随着我们的树数据结构的实现完成,我们现在看一个例子,说明如何使用树来解决一些真正的问题。在本节中,我们将讨论分析树。 分析树可以用于表示诸如句子或数学表达式的真实世界构造。 ![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* 在本节的其余部分,我们将更详细地检查分析树。 特别的,我们会看 @@ -21,7 +24,6 @@ Figure 1 展示了一个简单句子的层次结构。 将句子表示为树结 * 如何评估存储在分析树中的表达式。 * 如何从分析树中恢复原始数学表达式。 - 构建分析树的第一步是将表达式字符串拆分成符号列表。 有四种不同的符号要考虑:左括号,右括号,运算符和操作数。 我们知道,每当我们读一个左括号,我们开始一个新的表达式,因此我们应该创建一个新的树来对应于该表达式。 相反,每当我们读一个右括号,我们就完成了一个表达式。 我们还知道操作数将是叶节点和它们的操作符的子节点。 最后,我们知道每个操作符都将有一个左和右孩子。 使用上面的信息,我们可以定义四个规则如下: @@ -34,27 +36,38 @@ Figure 1 展示了一个简单句子的层次结构。 将句子表示为树结 在编写 Python 代码之前,让我们看看上面列出的规则的一个例子。我们将使用表达式`(3+(4 * 5))`。 我们将把这个表达式解析成下面的字符标记列表 `['(','3','+','(','4','*','5',')',')']`。 最初,我们将使用由空根节点组成的分析树开始。 Figure 4 展示了当每个新符号被处理时分析树的结构和内容。 ![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` 方法获取节点的子节点的方法,但是我们如何跟踪父节点呢?当我们遍历树时,保持跟踪父对象的简单解决方案是使用栈。每当我们想下降到当前节点的子节点时,我们首先将当前节点入到栈上。当我们想要返回到当前节点的父节点时,我们将父节点从栈中弹出。 使用上述规则,以及 `Stack`和 `BinaryTree` 操作,我们现在可以编写一个Python 函数来创建一个分析树。我们的分析树生成器的代码见 ActiveCode 1。 -```` +```python from pythonds.basic.stack import Stack from pythonds.trees.binaryTree import BinaryTree @@ -87,7 +100,8 @@ def buildParseTree(fpexp): pt = buildParseTree("( ( 10 + 5 ) * 3 )") pt.postorder() #defined and explained in the next section -```` +``` + *Activecode1* 用于构建分析树的四个规则被编码为 ActiveCode 1 的行 11,15,19 和 24 上的 if 语句的前四个子句。在每种情况下,可以看到代码实现了如上所述的规则,与几个调用 BinaryTree 或 Stack 方法。我们在这个函数中唯一的错误检查是在 else 子句中,如果我们从列表中得到一个我们不认识的 token,我们引发一个ValueError异常。 @@ -102,7 +116,7 @@ pt.postorder() #defined and explained in the next section 为了实现算术,我们使用具有键 `'+',' - ','*'`和 `'/'` 的字典。存储在字典中的值是来自 Python 的运算符模块的函数。运算符模块为我们提供了许多常用操作符的功能。当我们在字典中查找一个运算符时,检索相应的函数对象。由于检索的对象是一个函数,我们可以用通常的方式 `function(param1,param2)` 调用它。因此,查找 `opers['+'](2,2)` 等效于`operator.add(2,2)`。 -``` +```python def evaluate(parseTree): opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv} @@ -115,11 +129,9 @@ def evaluate(parseTree): else: return parseTree.getRootVal() ``` + *Listing 1* 最后,我们将跟踪我们在 Figure 4 中创建的分析树上的求值函数。当我们首先调用 evaluate 时,我们将整个树的根作为参数 parseTree 传递。然后我们获得对左和右孩子的引用,以确保它们存在。递归调用发生在第 9 行。我们首先在树的根中查找运算符,它是`'+'`。 `'+'` 操作符映射到`operator.add`函数调用,它接受两个参数。像 Python 函数调用一样,Python 做的第一件事是计算传递给函数的参数。在这种情况下,两个参数都是对我们的 evaluate 函数的递归函数调用。使用从左到右的计算,第一个递归调用向左。在第一个递归调用中,赋值函数给出左子树。我们发现节点没有左或右孩子,所以我们得到一个叶节点。当我们在叶节点时,我们只是返回存储在叶节点中的值作为计算的结果。在这种情况下,我们返回整数 3 。 在这一点上,我们有一个参数对 `operator.add` 的顶层调用进行求值。但我们还没有完成。继续从左到右的参数计算,我们现在进行递归调用来评估根的右孩子。我们发现节点有一个左和右孩子,所以我们查找存储在这个节点 '*' 运算符,并使用左和右孩子作为参数调用此函数。你可以看到,两个递归调用都到了叶节点,分别计算结果为整数 4 和 5。使用两个参数求值,我们返回`operator.mul(4,5)` 的结果。在这一点上,我们已经计算了顶级 `“+”` 运算符的操作数,剩下的所有操作都完成对 `operator.add(3,20)` 的调用。对于`(3+(4 * 5))`的整个表达式树的计算结果是 23。 - - - 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..c606f8f 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,26 +12,28 @@ 让我们看一些例子,来说明这三种遍历。首先看前序遍历。作为遍历的树的示例,我们将把这本书表示为树。这本书是树的根,每一章都是根节点的一个孩子。章节中的每个章节都是章节的子节点,每个小节都是章节的子节点,依此类推。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`。 +假设你想从前到后读这本书。前序遍历给你正确的顺序。从树的根(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`。 编写树遍历的代码惊人地优雅,主要是因为遍历是递归写的。Listing 2 展示了用于二叉树的前序遍历的 Python 代码。 你可能想知道,编写像前序遍历算法的最好方法是什么?是一个简单地使用树作为数据结构的函数,还是树数据结构本身的方法?Listing 2 展示了作为外部函数编写的前序遍历的版本,它将二叉树作为参数。外部函数特别优雅,因为我们的基本情况只是检查树是否存在。如果树参数为 None,那么函数返回而不采取任何操作。 -```` +```python def preorder(tree): if tree: print(tree.getRootVal()) preorder(tree.getLeftChild()) preorder(tree.getRightChild()) -```` +``` + *Listing 2* 我们也可以实现 `preorder` 作为 `BinaryTree` 类的方法。Listing 3 中展示了将 `preorder` 实现为内部方法的代码。注意当我们将代码从内部移动到外部时会发生什么。 一般来说,我们只需用 `self` 替换 `tree`。 但是,我们还需要修改基本情况。内部方法必须在进行前序的递归调用之前检查左和右孩子的存在。 -``` +```python def preorder(self): print(self.key) if self.leftChild: @@ -39,24 +41,26 @@ def preorder(self): if self.rightChild: self.rightChild.preorder() ``` + *Listing 3* -以下哪两种方式实现前序最好? 答案是在这种情况下,实现前序作为外部函数可能更好。原因是你很少只是想遍历树。在大多数情况下,将要使用其中一个基本的遍历模式来完成其他任务。 事实上,我们将在下面的例子中看到后序遍历模式与我们前面编写的用于计算分析树的代码非常接近。 因此,我们用外部函数实现其余的遍历。 +以上哪种方式实现前序最好? 答案是在这种情况下,实现前序作为外部函数可能更好。原因是你很少只是想遍历树。在大多数情况下,将要使用其中一个基本的遍历模式来完成其他任务。 事实上,我们将在下面的例子中看到后序遍历模式与我们前面编写的用于计算分析树的代码非常接近。 因此,我们用外部函数实现其余的遍历。 Listing 4 中所示的后序遍历算法几乎与前序遍历顺序相同,只是将 print 调用移动到函数的末尾。 -``` +```python def postorder(tree): if tree != None: postorder(tree.getLeftChild()) postorder(tree.getRightChild()) print(tree.getRootVal()) ``` + *Listing 4* 我们已经看到了后序遍历的常见用法,即计算分析树。再次回到 Listing 1。 我们所做的是计算左子树,计算右子树,并通过对操作符的函数调用在根节点中组合它们。假设我们的二叉树只存储表达式树的数据,让我们重写计算函数,需要更仔细地对 Listing 4中的后序遍历代码进行建模(参见 Listing 5)。 -``` +```python def postordereval(tree): opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv} res1 = None @@ -69,24 +73,26 @@ def postordereval(tree): else: return tree.getRootVal() ``` + *Listing 5* -请注意,Listing 4中的形式与 Listing 5中的形式相同,只是不是在函数的末尾打印值,而是返回它。 这允许我们保存从第 6 行和第 7 行的递归调用返回的值。然后,我们使用这些保存的值以及第 9 行的运算符一起计算结果。 +请注意,Listing 4中的形式与 Listing 5中的形式相同,只是不在函数的末尾打印值,而是返回它。 这允许我们保存从第 6 行和第 7 行的递归调用返回的值。然后,我们使用这些保存的值以及第 9 行的运算符一起计算结果。 在本节中我们最终将看到中序遍历。 在中序遍历中,我们访问左子树,其次是根,最后是右子树。 Listing 6 展示了我们的中序遍历的代码。 注意,在所有三个遍历函数中,我们只是改变 print 语句相对于两个递归函数调用的位置。 -``` +```python def inorder(tree): if tree != None: inorder(tree.getLeftChild()) print(tree.getRootVal()) inorder(tree.getRightChild()) ``` + *Listing 6* 如果我们执行一个简单的中序遍历分析树,我们得到没有任何括号的原始表达式。 让我们修改基本的 `inorder` 算法,以允许我们恢复表达式的完全括号版本。 我们将对基本模板进行的唯一修改如下:在递归调用左子树之前打印左括号,并在递归调用右子树后打印右括号。 修改后的代码如 Listing 7所示。 -``` +```python def printexp(tree): sVal = "" if tree: @@ -96,5 +102,3 @@ def printexp(tree): return sVal ``` - - 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..56c01b2 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,8 +1,7 @@ ## 6.8.基于二叉堆的优先队列 -在前面的部分中,你了解了称为队列的先进先出数据结构。队列的一个重要变种称为优先级队列。优先级队列的作用就像一个队列,你可以通过从前面删除一个项目来出队。然而,在优先级队列中,队列中的项目的逻辑顺序由它们的优先级确定。最高优先级项在队列的前面,最低优先级的项在后面。因此,当你将项排入优先级队列时,新项可能会一直移动到前面。我们将在下一章中研究一些图算法看到优先级队列是有用的数据结构。 +在前面的部分中,你了解了称为队列的先进先出数据结构。队列的一个重要变种称为优先级队列。优先级队列的作用就像一个队列,你可以通过从前面删除一个项目来出队。然而,在优先级队列中,队列中的项的逻辑顺序由它们的优先级确定。最高优先级项在队列的前面,最低优先级的项在后面。因此,当你将项排入优先级队列时,新项可能会一直移动到前面。我们将在下一章中研究一些图算法看到优先级队列是有用的数据结构。 -你可能想到了几种简单的方法使用排序函数和列表实现优先级队列。然而,插入列表是 O(n) 并且排序列表是 O(nlogn)。我们可以做得更好。实现优先级队列的经典方法是使用称为二叉堆的数据结构。二叉堆将允许我们在 O(logn) 中排队和取出队列。 +你可能想到了几种简单的方法使用排序函数和列表实现优先级队列。然而,插入列表是 $$O(n)$$ 并且排序列表是 $$O(nlogn)$$。我们可以做得更好。实现优先级队列的经典方法是使用称为二叉堆的数据结构。二叉堆将允许我们在 $$O(logn)$$ 中排队和取出队列。 二叉堆是很有趣的研究,因为堆看起来很像一棵树,但是当我们实现它时,我们只使用一个单一的列表作为内部表示。二叉堆有两个常见的变体:最小堆(其中最小的键总是在前面)和最大堆(其中最大的键值总是在前面)。在本节中,我们将实现最小堆。我们将最大堆实现作为练习。 - diff --git "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.9.\344\272\214\345\217\211\345\240\206\346\223\215\344\275\234/README.md" "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.9.\344\272\214\345\217\211\345\240\206\346\223\215\344\275\234/README.md" index 9f5b4ed..531e650 100644 --- "a/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.9.\344\272\214\345\217\211\345\240\206\346\223\215\344\275\234/README.md" +++ "b/6.\346\240\221\345\222\214\346\240\221\347\232\204\347\256\227\346\263\225/6.9.\344\272\214\345\217\211\345\240\206\346\223\215\344\275\234/README.md" @@ -12,7 +12,7 @@ ActiveCode 1 展示了使用一些二叉堆方法。注意,无论我们向堆中添加项的顺序是什么,每次都删除最小的。我们现在将把注意力转向如何实现这个想法。 -``` python +```python from pythonds.trees.binheap import BinHeap bh = BinHeap() @@ -29,4 +29,3 @@ print(bh.delMin()) print(bh.delMin()) ``` - diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.1.\347\233\256\346\240\207/README.md" "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.1.\347\233\256\346\240\207/README.md" index cfc0075..ac93376 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.1.\347\233\256\346\240\207/README.md" +++ "b/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.1.\347\233\256\346\240\207/README.md" @@ -9,5 +9,5 @@ 虽然人们相对容易看路线图并且理解不同地点之间的关系,但计算机没有这样的知识。然而,我们也可以将路线图视为图。当我们这样做时,我们可以让我们的计算机为我们做有趣的事情。如果你曾经使用过一个互联网地图网站,你知道一台计算机可以找到从一个地方到另一个地方最短,最快或最简单的路径。 作为计算机科学的学生,你可能想知道你必须学习的课程,以获得一个学位。图是表示学该课程之前的先决条件和其他相互依存关系的好方法。Figure 1 展示了另一个图。这个代表了在路德学院完成计算机科学专业的课程和顺序。 -![7.1.目标.figure1](assets/7.1.%E7%9B%AE%E6%A0%87.figure1.png) +![7.1.目标.figure1](assets/7.1.%E7%9B%AE%E6%A0%87.figure1.png) diff --git "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.10.\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\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.10.\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\210\206\346\236\220/README.md" index d8afcda..e5b8720 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.10.\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\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.10.\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\210\206\346\236\220/README.md" @@ -1,8 +1,7 @@ ## 7.10.广度优先搜索分析 -在继续使用其他图算法之前,让我们分析广度优先搜索算法的运行时性能。首先要观察的是,对于图中的每个顶点 `|V|` 最多执行一次 while 循环。因为一个顶点必须是白色,才能被检查和添加到队列。这给出了用于 while 循环的 O(v)。嵌套在 while 内部的 for 循环对于图中的每个边执行最多一次,`|E|`。原因是每个顶点最多被出列一次,并且仅当节点 u 出队时,我们才检查从节点 u 到节点 v 的边。这给出了用于 for 循环的 O(E) 。组合这两个环路给出了 O(V+E)。 +在继续使用其他图算法之前,让我们分析广度优先搜索算法的运行时性能。首先要观察的是,对于图中的每个顶点 $$|V|$$ 最多执行一次 while 循环。因为一个顶点必须是白色,才能被检查和添加到队列。这给出了用于 while 循环的 $$O(V)$$。嵌套在 while 内部的 for 循环对于图中的每个边执行最多一次,$$|E|$$。原因是每个顶点最多被出列一次,并且仅当节点 u 出队时,我们才检查从节点 u 到节点 v 的边。这给出了用于 for 循环的 $$O(E)$$ 。组合这两个环路给出了 $$O(V+E)$$。 -当然做广度优先搜索只是任务的一部分。从起始节点到目标节点的链接之后是任务的另一部分。最糟糕的情况是,如果图是单个长链。在这种情况下,遍历所有顶点将是 O(V)。正常情况将是 |V| 的一小部分但我们仍然写 O(V)。 +当然做广度优先搜索只是任务的一部分。从起始节点到目标节点的链接之后是任务的另一部分。最糟糕的情况是,如果图是单个长链。在这种情况下,遍历所有顶点将是 $$O(V)$$。正常情况将是 $$|V|$$ 的一小部分但我们仍然写 $$O(V)$$。 最后,至少对于这个问题,存在构建初始图形所需的时间。我们把 `buildGraph` 函数的分析作为一个练习。 - 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..89ef801 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,9 +1,8 @@ ## 7.11.骑士之旅 -另一个经典问题,我们可以用来说明第二个通用图算法称为 “骑士之旅”。骑士之旅图是在一个棋盘上用一个棋子让骑士玩。图的目的是找到一系列的动作,让骑士访问板上的每格一次。一个这样的序列被称为“旅游”。骑士的旅游难题已经吸引了象棋玩家,数学家和计算机科学家多年。一个 8×8 棋盘的可能的游览次数的上限为 1.305×10^35 ;然而,还有更多可能的死胡同。显然,这是一个需要脑力,计算能力,或两者都需要的问题。 +另一个经典问题,我们可以用来说明第二个通用图算法称为 “骑士之旅”。骑士之旅图是在一个棋盘上用一个棋子当骑士玩。图的目的是找到一系列的动作,让骑士访问板上的每格一次。一个这样的序列被称为“旅游”。骑士的旅游难题已经吸引了象棋玩家,数学家和计算机科学家多年。一个 $$8 \times 8$$ 棋盘的可能的游览次数的上限为 $$1.305 \times 10^{35}$$ ;然而,还有更多可能的死胡同。显然,这是一个需要脑力,计算能力,或两者都需要的问题。 虽然研究人员已经研究了许多不同的算法来解决骑士的旅游问题,图搜索是最容易理解的程序之一。再次,我们将使用两个主要步骤解决问题: * 表示骑士在棋盘上作为图的动作。 -* 使用图算法来查找长度为 `rows×columns-1` 的路径,其中图上的每个顶点都被访问一次。 - +* 使用图算法来查找长度为 $$rows \times columns-1$$ 的路径,其中图上的每个顶点都被访问一次。 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..e1956b6 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,11 +3,12 @@ 为了将骑士的旅游问题表示为图,我们将使用以下两个点:棋盘上的每个正方形可以表示为图形中的一个节点。 骑士的每个合法移动可以表示为图形中的边。 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 所示的顶点数的线性顶点数。 -``` +```python from pythonds.graphs import Graph def knightGraph(bdSize): @@ -24,11 +25,12 @@ def knightGraph(bdSize): def posToNodeId(row, column, board_size): return (row * board_size) + column ``` + *Listing 1* `genLegalMoves` 函数(Listing 2)使用板上骑士的位置,并生成八个可能移动中的一个。 `legalCoord` 辅助函数(Listing 2)确保生成的特定移动仍在板上。 -``` +```python def genLegalMoves(x,y,bdSize): newMoves = [] moveOffsets = [(-1,-2),(-1,2),(-2,-1),(-2,1), @@ -47,13 +49,11 @@ def legalCoord(x,bdSize): else: return False ``` -*Listing 2* +*Listing 2* -Figure 2 展示了一个 8×8 板的可能移动的完整图。图中有正好 336 个边。 注意,与板的边相对应的顶点具有比板中间的顶点更少的连接(移动数)。 再次我们可以看到图的稀疏。 如果图形完全连接,则会有 4,096 个边。 由于只有336 个边,邻接矩阵只有 8.2% 填充率。 +Figure 2 展示了一个 $$8 \times 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* - - +*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..5b39fbe 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" @@ -4,11 +4,11 @@ 图的深度优先搜索正是我们需要的,来找到有 63 个边的路径。我们将看到,当深度优先搜索算法找到死角(图中没有可移动的地方)时,它将回到下一个最深的顶点,允许它进行移动。 -`knightTour` 函数有四个参数:`n` ,搜索树中的当前深度; `path`,到此为止访问的顶点的列表; `u`,图中我们希望探索的顶点; `limit ` 路径中的节点数。 `knightTour` 函数是递归的。当调用 `knightTour` 函数时,它首先检查基本情况。如果我们有一个包含 `64` 个顶点的路径,我们状态为 `True` 的 `knightTour` 返回,表示我们找到了一个成功的线路。如果路径不够长,我们继续通过选择一个新的顶点来探索一层,并对这个顶点递归调用knightTour。 +`knightTour` 函数有四个参数:`n` ,搜索树中的当前深度; `path`,到此为止访问的顶点的列表; `u`,图中我们希望探索的顶点; `limit` 路径中的节点数。 `knightTour` 函数是递归的。当调用 `knightTour` 函数时,它首先检查基本情况。如果我们有一个包含 `64` 个顶点的路径,我们状态为 `True` 的 `knightTour` 返回,表示我们找到了一个成功的线路。如果路径不够长,我们继续通过选择一个新的顶点来探索一层,并对这个顶点递归调用knightTour。 DFS 还使用颜色来跟踪图中的哪些顶点已被访问。未访问的顶点是白色的,访问的顶点是灰色的。如果已经探索了特定顶点的所有邻居,并且我们尚未达到64个顶点的目标长度,我们已经到达死胡同。当我们到达死胡同时,我们必须回溯。当我们从状态为 False 的 knightTour 返回时,发生回溯。在广度优先搜索中,我们使用一个队列来跟踪下一个要访问的顶点。由于深度优先搜索是递归的,我们隐式使用一个栈来帮助我们回溯。当我们从第 11 行的状态为 False 的`knightTour` 调用返回时,我们保持在 while 循环中,并查看 `nbrList` 中的下一个顶点。 -``` +```python from pythonds.graphs import Graph, Vertex def knightTour(n,path,u,limit): u.setColor('gray') @@ -28,18 +28,21 @@ def knightTour(n,path,u,limit): done = True return done ``` + *Listing 3* 让我们看看一个简单的例子 `knightTour` 。你可以按照搜索的步骤参考下面的图。对于这个例子,我们假设对第 6 行的 `getConnections` 方法的调用按字母顺序对节点排序。我们首先调用 `knightTour(0,path,A,6)` -Figure 中 `knightTour` 从节点 A 开始.与 A 相邻的节点是 B 和 D。由于 B 在字母 D 之前,DFS选择 B 展开下一个,如 Figure 4 所示。当`knightTour` 被递归调用时,开始从 B 开始探寻。 B 与 C 和 D 相邻,所以 knightTour 选择接下来探索 C。然而,如 Figure 5 所示,节点 C 是没有相邻节点的死胡同。此时,我们将节点 C 的颜色更改为白色。对 knightTour 的调用返回值 False。从递归调用的返回有效地将搜索回溯到顶点B(参见Figure 6)。列表中要探索的下一个顶点是顶点 D,因此 `knightTour` 使递归调用移动到节点 D(参见 Figure 7)。从顶点 D 开始,`knightTour` 可以继续进行递归调用,直到我们再次到达节点 C(参见Figure 8,Figure 9和 Figure 10)。然而,当我们到达节点C时,测试 `n >> g = Graph() >>> for i in range(6): ... g.addVertex(i) @@ -101,6 +103,5 @@ class Graph: ( 5 , 4 ) ( 5 , 2 ) ``` -*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.7.\345\255\227\346\242\257\347\232\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.7.\345\255\227\346\242\257\347\232\204\351\227\256\351\242\230/README.md" index 54b74e2..0122e1b 100644 --- "a/7.\345\233\276\345\222\214\345\233\276\347\232\204\347\256\227\346\263\225/7.7.\345\255\227\346\242\257\347\232\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.7.\345\255\227\346\242\257\347\232\204\351\227\256\351\242\230/README.md" @@ -18,4 +18,3 @@ SAGE * 将字之间的关系表示为图。 * 使用称为广度优先搜索的图算法来找到从起始字到结束字的有效路径。 - 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..c91a45b 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,19 +3,20 @@ 我们的第一个问题是弄清楚如何将大量的单词集合转换为图。 如果两个词只有一个字母不同,我们就创建从一个词到另一个词的边。如果我们可以创建这样的图,则从一个词到另一个词的任意路径就是词梯子拼图的解决方案。 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* +*Figure 1* -我们可以使用几种不同的方法来创建解决这个问题的图。假设我们有一个长度相同的单词列表。作为起点,我们可以在图中为列表中的每个单词创建一个顶点。为了弄清楚如何连接单词,我们可以比较列表中的每个单词。比较时我们看有多少字母是不同的。如果所讨论的两个字只有一个字母不同,我们可以在图中创建它们之间的边。对于小的列表,这种方法会正常工作;然而假设我们有一个 `5,110` 词的列表。粗略地说,将一个字与列表上的每个其他词进行比较是 O(n^2 )。对于5110 个词,n^2 是超过2600万的比较。 +我们可以使用几种不同的方法来创建解决这个问题的图。假设我们有一个长度相同的单词列表。作为起点,我们可以在图中为列表中的每个单词创建一个顶点。为了弄清楚如何连接单词,我们可以比较列表中的每个单词。比较时我们看有多少字母是不同的。如果所讨论的两个字只有一个字母不同,我们可以在图中创建它们之间的边。对于小的列表,这种方法会正常工作;然而假设我们有一个 `5,110` 词的列表。粗略地说,将一个字与列表上的每个其他词进行比较是 $$O(n^2)$$。对于5110 个词,$$n^2$$ 是超过2600万的比较。 -我们可以通过以下方法做得更好。假设我们有大量的桶,每个桶在外面有一个四个字母的单词,除了标签中的一个字母已经被下划线替代。例如,看 Figure 2,我们可能有一个标记为 “pop_” 的桶。当我们处理列表中的每个单词时,我们使用 “_” 作为通配符比较每个桶的单词,所以 “pope” 和 “pops “ 将匹配 ”pop_“。每次我们找到一个匹配的桶,我们就把单词放在那个桶。一旦我们把所有单词放到适当的桶中,就知道桶中的所有单词必须连接。 +我们可以通过以下方法做得更好。假设我们有大量的桶,每个桶在外面有一个四个字母的单词,除了标签中的一个字母已经被下划线替代。例如,看 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 代码。 -``` +```python from pythonds.graphs import Graph def buildGraph(wordFile): @@ -39,7 +40,7 @@ def buildGraph(wordFile): g.addEdge(word1,word2) return g ``` + *Listing 1* 因为这是我们的第一个真实世界图问题,你可能想知道图是如何稀疏?这个问题的四个字母的单词列表是 `5,110` 字长。 如果我们使用邻接矩阵,则矩阵将具有`5,110 * 5,110 = 26,112,100` 个格。 由 `buildGraph` 函数构造的图正好有 `53,286` 个边,所以矩阵只有 0.20% 的单元格填充! 这是一个非常稀疏的矩阵。 - 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..590ae7d 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" @@ -17,7 +17,7 @@ BFS 从起始顶点开始,颜色从灰色开始,表明它正在被探索。 3. 到 nbr 的距离设置为到 `currentVert + 1` 的距离 4. nbr 被添加到队列的末尾。 将 nbr 添加到队列的末尾有效地调度此节点以进行进一步探索,但不是直到 `currentVert` 的邻接表上的所有其他顶点都被探索。 -``` python +```python from pythonds.graphs import Graph, Vertex from pythonds.basic import Queue @@ -36,27 +36,30 @@ def bfs(g,start): vertQueue.enqueue(nbr) currentVert.setColor('black') ``` + *Listing 2* 让我们看看 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)展示了如何按前导链接打印出字梯。 -``` python +```python def traverse(y): x = y while (x.getPred()): @@ -66,7 +69,5 @@ def traverse(y): traverse(g.getVertex('sage')) ``` -*Listing 3* - - +*Listing 3* diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 8864d4a..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 432f16b..46a18eb 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,2 @@ -# 介绍 - -#### problem-solving-with-algorithms-and-data-structure-using-python 中文版 - -#### 目的 - -数据结构作为计算机从业人员的必备基础,Java, c 之类的语言有很多这方面的书籍,Python 相对较少, -其中比较著名的一本 [problem-solving-with-algorithms-and-data-structure-using-python](http://interactivepython.org/runestone/static/pythonds/index.html),所以我在学习的过程中将其翻译了中文版,希望对大家有点帮助。 - -* 由于本人英语能力不佳,本书部分翻译参考谷歌,但每句话都经过个人理解后调整修改,尽量保证语句畅通。 -* 由于翻译比较仓促,难以避免有些排版错别字等问题,后续会润色。如你也有兴趣参与,可 pull request 到 [github 仓库](https://github.com/facert/python-data-structure-cn) -* 默认大家有一定的 Python 基础,故暂未翻译 Python 语法的几个章节。后续考虑书的完整性会加上这几节。 -* 本书未加上课后练习,如有兴趣,可上原书网站练习。 - -#### 地址 -* github 地址: https://github.com/facert/python-data-structure-cn -* gitbook 在线浏览: https://facert.gitbooks.io/python-data-structure-cn - - -#### 联系作者 -* 邮箱: zhangcr1992@163.com -* 博客: https://facert.github.io - -#### 许可证 -本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。传播此文档时请注意遵循以上许可协议。 关于本许可证的更多详情可参考 http://creativecommons.org/licenses/by-sa/4.0/ - - - - - +已废弃 -> 其他译本: http://gis4g.pku.edu.cn/download/sessdsa-textbook/ diff --git a/SUMMARY.md b/SUMMARY.md index f36bd65..0748e5b 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) @@ -8,17 +8,23 @@ * [1.5.为什么要学习数据结构和抽象数据类型](1.介绍/1.5.为什么要学习数据结构和抽象数据类型/README.md) * [1.6.为什么要学习算法](1.介绍/1.6.为什么要学习算法/README.md) * [1.7.回顾Python基础](1.介绍/1.7.回顾Python基础/README.md) - -* [2.算法分析](README.md) + * [1.8.数据入门](1.介绍/1.8.数据入门/README.md) + * [1.9.输入和输出](1.介绍/1.9.输入和输出/README.md) + * [1.10.控制结构](1.介绍/1.10.控制结构/README.md) + * [1.11.处理异常](1.介绍/1.11.处理异常/README.md) + * [1.12.定义函数](1.介绍/1.12.定义函数/README.md) + * [1.13.面向对象编程-定义类](1.介绍/1.13.面向对象编程-定义类/README.md) + * [1.14.总结](1.介绍/1.14.总结/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 +33,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 +49,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 +58,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 +72,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 +98,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 diff --git a/book.json b/book.json new file mode 100644 index 0000000..cdec539 --- /dev/null +++ b/book.json @@ -0,0 +1,3 @@ +{ + "plugins": ["katex"] +} \ No newline at end of file diff --git a/python-data-structure-cn.pdf b/python-data-structure-cn.pdf new file mode 100644 index 0000000..93bc0e3 Binary files /dev/null and b/python-data-structure-cn.pdf differ