zoukankan      html  css  js  c++  java
  • 《程序员实用算法》试读:1.1评估算法

    除了最直观的应用之外,算法是所有程序的核心和灵魂。算法一般被设计用于以最小的代价高效地解决特定的问题。算法的价值一般取决于两方面因素:如何恰当地解决问题以及如何高效地实现解决方案。这些是算法分析的定性和定量方面。

    对于许多算法,质量不是一个问题。例如,对于排序算法,必须保证每次都对所有元素正确地进行了排序。一旦出错,就必须丢弃它并且严格说来不能将其视为一种算法。在其他领域,不能基于这种简单的通过/失败测试来度量质量。例如,在第4章中介绍的Soundex算法允许检索听起来相同的单词或名字。与排序算法不同,可以调整Soundex算法,以寻找接近的匹配或者相当宽泛的匹配;这取决于实现算法的方式和开发人员的需求。在这种情况下,质量是可度量的并且是算法的重要方面,并且指导我们认真选择不同的解决方案。

    算法设计的定量方面尝试确定算法所需的资源。一般来说,最重要的度量标准是时间:即算法运行得有多快?偶尔,计算机资源(比如可用的内存)也是一个重要因素。

    度量性能

    与基准的性能不同,算法的性能很少依据时间来加以说明。在论及排序例程时,你几乎从未听到它完成排序要花费862秒这样的说明。这有一个很好的理由:这种计时难以复制,并且通常依赖于正在处理的数据的具体特征。算法不依赖于计时,而是依赖于一个直观的方程,以显示输入的大小与性能之间的关系。用于显示这种关系的传统方法是使用符号O,称为大O表示法(bigoh notation)。其工作方式是:假定你有一个算法,它简单地通读一个文本文件,从中查找单词flea。一种合理的方法是寻找字母f的每个实例(参见第4章)。当找到一个f,该算法就测试4个字母的序列,看看它是不是单词flea。在这个示例中,显然执行时间直接与文本文件的大小成正比。如果给定的文件包含N个字符,那么我们就说该算法的执行时间的界限是O(N)。你会注意到这种表述没有考虑到可能影响性能的其他因素——例如,字母f在文本中出现的频繁程度。在查找字符串时(比如fleas rarely wear collars),字符串的长度以及其中相似字符串(比如fleas rarely wear colors)出现的频率也会影响性能。不过,严格来讲,这些因素是要处理的数据的函数,而不是算法的函数。因此,在大O表示法中,它们不会出现在公式中。该表示法只是简单地说明数据规模(一般用N表示,偶尔也用n表示)与算法的典型性能之间的关系。

    另一个示例也许更能说明问题:在著名的冒泡排序算法中(参见第5章),对于一个给定的项目列表,通过遍历一次列表对其中的每个项目进行排序。每次遍历,都会减少一个要检查的元素。在遍历列表中的项目时将比较相邻的元素,如果它们的排序顺序不正确,就交换它们的位置。在这种排序中,对性能影响最大的是排序的次数。对于3个项目的列表,3次遍历将产生3次比较。10个项目的列表则需要45次比较。因此,很明显,比较次数总是N(N-1)/2。在这个表示法中,数据规模与执行之间的关系(将这种关系称为性能(performance))将记作O((N2-N)/2)。除非N的值较小,否则这样的性能将慢得令人不可接受。

    算法科学考虑的是通过分析和改进算法,从而稳定地增强性能方程。在设计和实现算法时,对这个算法方程有一定的感性认识是很重要的。其原因非常有说服力。如果一种算法的性能比N2更差,我们通常认为它是无用的。如果在运算速度为1纳秒的计算机上运行算法,那么性能为3N3的算法需要花费95年的时间来处理1 000 000个项目的输入,而阶为19 500 000N的算法仅需要花费54小时来处理同样数量的元素。很明显,造成这些巨大差异的原因是:N的阶是至关重要的,而它前面的常数则不是。在前面的示例中,即使该算法使用的是N3而不是3N3,它仍将是无用的。因此,大O表示法不使用常数;它仅使用N的阶。这样,用于冒泡排序的大O表示法是O(N2-N)。

    在本书中,我们对性能方程的介绍稍有不同。如以前所解释的那样使用性能方程;不过,我们将保留这些常数,因为我们觉得它们的存在也很重要。为了不给计算机科学的一些漫不经心的学生造成混淆,我们不使用大O表示法,因为对于受过训练的学生,常数的存在看起来很奇怪。

    1平均情况与最坏情况

    只根据单个方程或度量标准来讨论性能是不够的。某些算法的表现在大多数情况下相当不错,但在最坏情况下却令人无法接受。由于很少能够对通用算法将应用的数据加以限制,因此在设计算法时考虑到最坏情况是很重要的。最坏情况下的性能有时可能很差,以致于采用一种能够自如地处理最坏情况的通常慢一些的算法可能是一个更好的选择。让我们来看看著名的快速排序(quicksort)算法(参见第5章),它在C标准库中被实现为函数qsort()。它在大多数情况下都表现得非常好。其平均情况下的性能为NlgN(缩写lg是指以2为底的对数。对数是指将底数(这里是2)提升为n的指数值。例如,lg8是3,因为23的值是8)。这意味着快速排序需要8×3个时间单位对8个项目进行排序,或者说需要32×5个时间单位对32个项目进行排序。结果是,当输入从8个项目到32个项目增加了4倍时,排序时间将从24个单位时间增加到160个时间单位。同样,1 024个项目则需要10 240个时间单位。这样的性能被认为是高效的。

    不过,在将快速排序算法应用于已经排好序的输入文件时,该算法将表现出最差的性能。性能方程突然变成N2。如表11所示,如果这种唯一的最坏情况完全有可能发生,那么就不能使用快速排序算法(幸运的是,对于大多数C程序员而言,编译器供应商在他们的qsort()实现中提防了这种情况,所以这个问题通常不会出现)。表11快速排序算法的平均情况和最坏情况下的性能,以针对N个项目的时间单位作为度量标准

    N平均情况:NlgN最坏情况:N282464321601 0241 02410 2401 048 576

    2最坏情况下的性能

    当分析算法在最坏情况下的行为时,试图去简单地估计最坏情况发生的可能性是不够的。还应当考虑万一发生最坏情况时的后果。在快速排序算法的示例中,很明显,算法最主要的代价是性能惩罚;也就是说,当发生最坏情况时,排序将缓慢地继续进行。不过,对于某些算法,最坏情况下的性能惩罚可能很大,出于实用性考虑,该算法会挂起系统(例如,假定在最坏情况下数据排序需要15天的时间)。

    某些类别的问题具有如此严重的最坏情况下的后果,以致于在编写算法时必须确保已经检测到最坏情况并进行了妥善处理。这类问题经常出现在调度领域。在本书中没有对调度算法加以介绍;不过,由于它们能够很好地用于说明该问题,我们将简要介绍其中的一个经典类别:“哲学家进餐”问题。图11摆放有面条的桌子,可能会使哲学家

    因无法进餐而饿死

    该问题由EWDijkstra于1965年首次提出,现在已经有了很多变体。它实质上可以表述如下:5个哲学家坐在圆餐桌旁享用一顿面条(参见图11)。任意两个盘子之间只有一根筷子。吃面条的时候,每个哲学家必须使用两根筷子:从她们两边的盘子中各取一根筷子。因此,如果任何一个相邻的哲学家在吃面条,这个哲学家自己就不能吃面条,而必须等待。她可以在任何时刻尝试吃面条,方法是首先拿起一根筷子,然后再看看另一根筷子是否也可以使用。如果另一根筷子不可用,那么她必须放下刚才拿起的那一根筷子。在大多数场合(按照我们的说法,即平均情况下),所有的哲学家都能吃到面条,尽管他们中的一些需要等待轮到自己才能用餐。不过,在最坏情况下,所有的哲学家都会被饿死(如果你不熟悉这个问题,可以暂时停下来,推算一下最坏的情况应该是什么)。假定所有的哲学家都在同一时刻决定开始吃面条。他们全都拿起右边的那一根筷子,然后再转过来看左边的那根筷子,当然,这根筷子已经被别人拿走了。于是他们放下筷子,然后立刻再次开始进行下一轮尝试。刚才的情形又重复发生了。只要他们同时决定开始进餐(即最坏情况),他们将会一致地拿起再放下右边的那根筷子,直到他们被饿死为止。按照算法的术语,称程序将开始一个无限循环。这种最坏的情况杀死了哲学家以及算法本身。

    该问题的要点是:在考虑算法时,应该从其可能性和后果两方面研究最坏情况。如果发现后果是灾难性的而你又无法改变算法本身,那么就必须为最坏情况事先做好准备。

    3最好情况下的性能

    人们通常很少分析算法在最好情况下的性能。这是因为一般在最好情况下不会导致应用程序完全停止,因此,它没有什么危险。此外,与最坏情况一样,它极少发生,并且极度依赖于要处理的数据本身。但是,这并不是说对最好情况就不需要进行分析。许多算法在运行最好情况的数据时,性能有惊人的提升。经典的示例是前面讨论过的冒泡排序算法。它的典型性能是O(N2-N),而在最好情况下的性能是O(N)。这意味着对于大多数应用程序,冒泡排序是非常慢的,而在最好情况下这个排序例程运行得足够快。这对于应用程序开发人员具有一些隐含意义,因为在某些场合下可以预先知道要处理的数据的质量。一种典型的情况是将一个算法的输出作为另一个算法的输入。在后面将看到,在这种情况下,可以有效地使用最好情况下的性能(甚至对于冒泡排序也是如此)。
  • 相关阅读:
    第二周作业
    20172331 《Java程序设计》第1周学习总结
    20172316《程序设计与数据结构》(下)课程总结
    哈夫曼编码测试博客
    20172316 2017-2018-2 《程序设计与数据结构》实验三报告
    20172316 2018-2019-1《程序设计与数据结构》第九周学习总结
    20172316 2018-2019-1 《程序设计与数据结构》实验二报告
    2018-2019-1 20172316《程序设计与数据结构》第八周学习总结
    20172316 2018-2019-1《程序设计与数据结构》第七周学习总结
    20172316 《程序设计与数据结构》第六周学习总结
  • 原文地址:https://www.cnblogs.com/shihao/p/2146476.html
Copyright © 2011-2022 走看看