持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情
一些简单的算法和数据结构
虽然我们在本书中花了相当多的篇幅来讨论效率,但目标不是让你成为设计高效程序的专家。有许多长书(甚至一些很好的长书)专门讨论这个主题.68 在第11章中,我们介绍了复杂性分析背后的一些基本概念。在本章中,我们将使用这些概念来研究一些经典算法的复杂性。本章的目标是帮助你培养一些关于如何处理效率问题的一般直觉。当你读完这一章时,你应该明白为什么有些程序在眨眼之间就完成了,为什么有些程序需要一夜之间运行,为什么有些程序在你的一生中不会完成。
我们在本书中看到的第一个算法是基于蛮力穷举枚举的。我们认为,现代计算机的速度如此之快,以至于通常采用聪明的算法是浪费时间。编写简单且明显正确的代码通常是正确的方法。
然后,我们研究了一些问题(例如,找到多项式根的近似值),其中搜索空间太大而无法使蛮力变得实用。这导致我们考虑更有效的算法,如平分搜索和牛顿-拉夫森。重点是效率的关键是一个好的算法,而不是聪明的编码技巧。
在科学(物理、生活和社会)中,程序员通常从快速编码一个简单的算法开始,以测试关于数据集的假设的合理性,然后在少量的数据。如果这产生了令人鼓舞的结果,那么生成可以在大型数据集上运行(也许一遍又一遍)的实现的艰苦工作就开始了。这样的实现需要基于有效的算法。
高效的算法很难发明。成功的专业计算机科学家可能会在他们的整个职业生涯中发明一种算法——如果他们幸运的话。我们大多数人从未发明过一种新颖的算法。相反,我们所做的是学会将我们面临的问题中最复杂的方面减少到以前解决的问题。
更具体地说,我们
了解问题的固有复杂性。
想想如何将这个问题分解成子问题。将这些子问题与已经存在有效算法的其他问题相关联。
本章包含一些示例,旨在为您提供有关算法设计的一些直觉。许多其他算法出现在本书的其他地方。
请记住,最有效的算法并不总是首选的算法。一个以最有效的方式做所有事情的程序通常不必要地难以理解。通常,从以最直接的方式解决手头的问题开始,对其进行检测以找到任何计算瓶颈,然后寻找方法来改善程序中导致瓶颈的那些部分的计算复杂性,这通常是一个好的策略。
搜索算法
搜索算法是一种用于在项目集合中查找具有特定属性的项或项组的方法。我们将项目集合称为搜索空间。搜索空间可以是具体的东西,如一组电子病历,也可以是抽象的东西,如所有整数的集合。大量在实践中出现的问题可以表述为搜索问题。
本书前面介绍的许多算法都可以看作是搜索算法。在第3章中,我们将查找多项式根的近似值作为搜索问题,并研究了三种算法 - 穷举枚举,平分搜索和牛顿 - 拉夫森 - 用于搜索可能答案的空间。
在本节中,我们将研究两种用于搜索列表的算法。每个都符合规范
def Search(L, e):
“”“假定 L 是一个列表。
如果 e 在 L 中,则返回 True,否则返回 False“””
精明的读者可能会怀疑这是否在语义上与L中的Python表达式e等价。答案是肯定的,是的。如果你不关心发现e是否在L中的效率,你应该简单地写这个表达式。
线性搜索和使用间接访问元素
Python使用以下算法来确定元素是否在列表中:
如果元素 e 不在列表中,则算法将执行 θ(1en(L) ) 测试,即复杂度在 L 的长度上充其量是线性的。为什么“充其量”是线性的?只有当循环内的每个操作都可以在恒定时间内完成时,它才会是线性的。这就提出了一个问题,即Python是否在常量时间内检索列表的第i个元素。由于我们的计算模型假设获取地址的内容是一个常量时间操作,因此问题就变成了我们是否可以在常量时间内计算列表的第i个元素的地址。
让我们首先考虑列表的每个元素都是一个整数的简单情况。这意味着列表的每个元素都是相同的大小,例如,四个内存单元(四个8位字节9)。假设列表的元素是连续存储的,则列表第 i 个元素在内存中的地址只是 start + 4*i,其中 start 是列表开头的地址。因此,我们可以假设Python可以在常量时间内计算整数列表中第i个元素的地址。
当然,我们知道Python列表可以包含int以外的类型的对象,并且同一列表可以包含许多类型和大小的对象。您可能会认为这会带来问题,但事实并非如此。
在Python中,列表表示为长度(列表中的对象数)和一系列固定大小的指向对象的指针7°。图 12-1 说明了这些指针的用法。
阴影区域表示包含四个元素的列表。最左侧的阴影框包含一个指向整数的指针,该整数指示列表的长度。其他每个阴影框都包含一个指向列表中某个对象的指针。
如果 length 字段占用四个内存单位,并且每个指针(地址)占用四个内存单位,则列表的第 i 个元素的地址存储在地址开始 + 4 + 4*i 处。同样,可以在常量时间内找到此地址,然后存储在该地址的值可用于访问 ith 元素。此访问也是一个常量时间操作。
此示例说明了计算中使用的最重要的实现技术之一:间接.71 一般来说,间接涉及通过首先访问包含对最初寻求的事物的引用的其他事物来访问某些内容。每当我们使用变量来引用该变量绑定到的对象时,都会发生这种情况。当我们使用变量访问列表,然后使用存储在该列表中的引用来访问另一个对象时,我们将经历两个级别的间接寻址。