程序员必会的40种算法
上QQ阅读APP看书,第一时间看更新

4.1 算法设计基本概念

根据《美国传统词典》(American Heritage Dictionary),算法的定义如下:

“算法是由无歧义指令构成的有限集合,它在给定的一组初始条件下按预定顺序执行,直到满足给定的可识别的结束条件以实现某种目的。”

设计算法就是要给出这个“由无歧义指令构成的有限集合”,以最有效的方式“实现某种目标”。为复杂的实际问题设计算法是一项烦琐的任务。要构想得到一个好的设计,需要先充分了解待求解问题。我们首先要弄清楚需要做什么(即了解需求),然后再研究如何做(即设计算法)。理解问题包括找出待求解问题的功能性需求和非功能性需求,让我们看看这些需求分别是什么:

  • 功能性需求正式规定了待求解问题的输入和输出接口,以及与之相关的功能,帮助我们理解数据处理、数据操作,以及生成结果所需要执行的计算。
  • 非功能性需求设定了对算法的性能和安全性方面的预期。

需要注意的是,设计算法就是要在给定的环境下以最佳方式同时满足功能性需求和非功能性需求,并且还要考虑到用于运行所设计算法的资源集。

为了得到一个能够满足功能性需求和非功能性需求的算法,我们的设计应该考虑以下三个关注点(正如第一章所讲):

  • 第一点:所设计算法是否能产生我们预期的结果?
  • 第二点:所设计算法是获取预期结果的最佳方法吗?
  • 第三点:所设计算法在更大的数据集上表现怎么样?

我们逐一讨论这些问题。

4.1.1 第一点——所设计算法是否能产生预期的结果

算法是实际问题的数学求解方案,一个有效的算法应该能够产生准确的结果。如何验证算法的正确性,这不应该是事后才需要的想法,而应该是在算法的设计中就已经考虑到了。在制定验证算法的策略之前,我们需要考虑以下两个方面:

  • 定义真实值:为了验证算法,对于给定的一组输入,我们需要已知的正确结果。这些已知的正确结果在待求解问题中称为真实值真实值是很重要的,因为当我们不断地改进我们的算法,以获得更好的求解方案时,它可以作为参考。
  • 选择度量标准:我们还需要考虑如何量化运行结果与真实值之间的差距。选择合适的度量标准将有助于我们准确地量化算法的质量。

例如,对于机器学习算法,我们可以将已标记的现有数据用作真实值,可以选择一个或多个度量标准(例如准确率、召回率或精度)来量化与真实值之间的差距。需要注意的是,在某些用例中,正确的输出不是一个单一的值,而是被定义为一组给定的范围。在设计和开发算法的过程中,我们的目标是反复改进算法,直到其运行结果位于需求所明确的范围之内。

4.1.2 第二点——所设计算法是否是获取结果的最佳方法

第二个关注点是找到以下问题的答案:

所设计算法是最佳求解方案吗?我们是否能够证明该问题不存在更好的求解方案?

乍一看,这个问题似乎很容易回答。然而,对于某一类算法,研究人员花了几十年的时间来验证由某个算法产生的特定解是否也是最佳解,以及是否不存在可以给出更好结果的其他求解方案,但都没有成功。所以,我们首先要了解问题、问题的需求以及运行算法的资源,这一点非常重要。我们需要确认以下说法:

我们是否应该以找到该问题的最优解为目标?寻找和验证最优解是非常耗时且复杂的,所以基于启发式方法的可行方案是我们最好的选择。

因此,理解问题及其复杂性很重要,它可以帮助我们估算对资源的需求。

在开始深入讨论之前,我们先在这里定义几个术语:

  • 多项式算法(polynomial algorithm):如果算法的时间复杂度为O(nk),则我们将其称为多项式算法,其中k为常数。
  • 可行解(certificate):迭代结束时产生的一个候选解称为可行解。随着我们在解决特定问题上的不断迭代,我们通常会产生一系列的可行解。如果解收敛,则每一个生成的可行解都会比前一个更好。在某些时候,当我们的可行解满足需求时,我们会选择该可行解作为最终的解。

第1章介绍了大O记号,用以分析算法的时间复杂度。在分析时间复杂度的背景下,我们考虑以下不同的时间区间:

  • 一个算法产生一个解(即可行解)所需要的时间tr
  • 验证给出的解(可行解)所需的时间ts

刻画问题复杂度

多年来,学术界根据问题的复杂度将问题分为不同的类。在我们尝试设计问题的求解方案之前,先尝试确定它属于哪一类问题是有意义的。一般来说,存在三种类型的问题:

  • 类型1:肯定存在求解该问题的多项式算法。
  • 类型2:可以证明这类问题无法用多项式算法求解。
  • 类型3:无法找到求解该问题的多项式算法,也无法证明不可能找到该问题的多项式求解方案。

我们来看看各类问题:

  • 非确定性多项式问题(NP):一个问题要成为NP问题,必须满足以下条件:
    • 肯定存在多项式算法用于验证候选解(可行解)是否最优。
  • 多项式问题(P):这类问题可以视为NP问题的子集。除满足NP问题的条件以外,P问题还需要满足如下额外的条件:
    • 肯定存在至少一种多项式算法来求解它们。

图4-1展示了P问题和NP问题之间的关系。

图 4-1

007-1a 如果一个问题是NP问题,那么它也是P问题吗?这是计算机科学领域中尚未解决的最大问题之一。它是由克莱数学研究所(Clay Mathematics Institute)评选的千禧年大奖难题(Millennium Prize Problems),并已宣布为这个问题的解决提供100万美元的奖金,因为它将对人工智能、密码学和理论计算机科学等领域产生重大影响:

下面继续列出各类问题:

  • NP完全问题:NP完全类包含了所有NP问题中最难的问题。一个NP完全问题需要满足以下两个条件:
    • 没有已知的多项式算法来生成可行解。
    • 有已知的多项式算法来验证提出的可行解是否最优。
  • NP难问题:NP难类包含的问题至少与NP类中的任何问题一样难,但是这些问题本身并不需要属于NP类。

现在,我们绘制一张图(图4-2)来说明各类问题之间的关系。

图 4-2

注意,P=NP的正确性仍有待学术界证明。尽管这尚未得到证实,但很有可能P≠NP。在此种情况下,不存在求解NP完全问题的多项式求解方案。请注意,图4-2是基于这个假设所画的。

4.1.3 第三点——所设计算法在更大的数据集上表现如何

算法以规定的方式处理数据以产生结果。一般来说,随着数据规模的增加,处理数据和计算所需结果的时间越来越长。术语大数据有时用于粗略地标识由于数据的体积、多样性和速度太大而预计对所使用的基础结构和算法构成挑战的数据集。一个设计良好的算法应该是可扩展的,这意味着,它应该尽可能高效地运行,利用可用的资源在合理时间内产生正确的结果。在处理大数据时,算法的设计变得更加重要。为了量化算法的可扩展性,我们需要注意以下两个方面的问题:

  • 随着输入数据的增加,资源需求增加:对此进行的估计称为空间复杂度分析。
  • 随着输入数据的增加,运行时间增加:对此进行的估计称为时间复杂度分析。

请注意,我们正生活在一个被定义为数据爆炸的时代。“大数据”一词已经成为主流,因为它抓住了现代算法通常需要处理的数据的规模和复杂性。

在开发和测试阶段,许多算法仅使用少量的数据样本。在设计算法时,研究算法的可扩展性是很重要的。特别是随着数据集规模的增加,仔细分析(即测试或预测)算法性能的变化非常重要。