C++抽象编程——算法分析(5)——标准复杂度类

合并算法的计算复杂度

现在,我们可以根据分治法实施排序功能。效率如何? 我们当然可以通过对vector的进行排序并对结果进行测试所需时间来测量其效率。但现在开始,我们要从计算复杂度的角度考虑该算法。

当你调用合并排序算法来对一组含N个数的vector进行排序的时候,运行时间可以分为下面两个部分:

  1. 执行当前递归分解操作所需的时间量(The amount of time required to execute the operations at the current level of the recursive decomposition
  2. 执行递归调用所需的时间(The time required to execute the recursive calls

在递归分解的顶层,执行非递归操作的时间花费与N成比例。填充子vector的循环占N个周期(即分成两个vector的操作),并且调用合并具有在向量中重新填充原始N个位置的作用。 如果添加这些操作并忽略常数因子,则会发现任何单次调用的复杂度(不对其中的递归调用进行计数)**都需要**O(N)操作。

但是递归操作的成本呢? 要排序大小为N的vector,我们就必须递归排序大小为N / 2的两个vector。这些操作中的每一个都需要一定的时间。 如果应用相同的逻辑,你可以快速确定对这些较小向量中的每一个进行排序需要与该级别的N / 2成正比,再加上其自身递归调用所需的时间。然后,相同的过程将持续到达到simple case(vector由单个元素组成或根本没有元素)。
所以解决问题所需的总时间是递归分解的每个级别所需时间的总和。当我们向下移动递归层次结构时,vector变小,但变得更多。然而,在每个层次上所做的工作总是与N成正比。因此,确定总工作量是一个发现有多少级别的问题。分解如下图所示:

在层次结构的每个级别,N的值除以2.因此,总数等于在下降到1之前可以将N除以2的次数。以数学方式重写此问题,就是需要找到一个这样的k值

显然:

因为级数为 并且每个级别完成的工作量与N成正比,所以总的花费时间与 成正比。
对数通常以10(常用对数)或数学常数e(自然对数)的幂表示,但是与其他科学学科不同,计算机科学倾向于使用基于2的幂的二进制对数。因为使用不同的幂计算的对数 基数只有一个常数因子不同,因此在讨论计算复杂度时,我们通常省略对数基数。 因此,合并排序的计算复杂度通常写为:

比较N2和N log N的性能

但是O(N log N)有多好? 评估改进水平的一个方法是查看实验数据,以了解选择和合并排序算法的运行时间如何。 该时序信息如下图所示。

对于10个项目,合并排序的这种实现比选择排序慢五倍多。 在100个项目,选择排序仍然更快,但不是很多。 当您获得最多100,000个项目时,合并排序比选择排序快几百倍。在我的电脑上,选择排序算法需要两分半钟以上的时间来排序100,000个项目,而合并排序在不到半秒钟内完成作业。对于大型vector,合并排序显然是一个显著的改进。
另外,我们还可以通过比较两种算法的计算复杂度公式获得相同的信息,如下所示:

我们可以获得相同的信息: 随着N变大,两列中的数字都在增长,但N平方列的增长速度比N*log N列快得多。 因此,基于N log N算法的排序算法对于更大范围的vector是很有效果的。

标准复杂度类

在编程中,大多数算法属于几种常见的复杂类。最重要的复杂性类别下表所示,它给出了类的通用名称以及相应的big-O表达式和该类中的代表性算法。

类的通用名称big-O表达式代表性算法
常量类(Constant)O(1)在vector中返回第一个元素
对数类(Logarithmic)O(log N)在排序好的vector中进行折半查找
线性类(Linear)O(N)vector中的线性查找
N log N类O(N log N)合并排序
二次类(Quadratic)O(N^2)选择排序
立方类(Cubic)O(N^3)传统的矩阵乘法算法
指数类(Exponential)O(2^N)Hanoi塔

这些类之间的效率差异其实是明显的的。我们可以通过查看下图中的图形来了解不同复杂性函数之间的相互关系,其中将这些复杂度函数绘制在传统的坐标图上。

不幸的是,这个图表说明了一个不完整的,甚至有点误导的理论,因为N的值都很小。毕竟,复杂性分析是相关的,因为N的值会逐渐变大。下图显示了以对数刻度绘制的相同数据,可以更好地了解这些函数在更广泛的值范围内如何发展:

属于常数,线性,二次和立方复杂度类以及其他更为一般的算法都是一般称为多项式算法(polynomial algorithms)。它们都在时间N的k次方中执行(k是常数)。从图中可以看出,随着N值的增加,函数N^k 无论k多大,都将始终比由2^N表示的指数函数变得更慢。这个属性在找到现实世界问题的实际算法方面具有重要的意义。尽管选择排序示例清楚地表明,二次算法对于N的值变大也具有实质的潜能问题(如我们的分开再合并),但是其复杂度为O(2^N)的算法都相当差。作为一般的经验法则,计算机科学家将可以通过多项式时间运算的算法解决的问题分类为易于解决的问题(intractable),因为它们可以在计算机上实现。没有多项式时间算法存在的问题都被认为是棘手的(intractable)(如2^N这一类问题)。

不幸的是,存在许多商业上重要的问题,所有已知算法都需要指数时间。其中之一是我们之前讨论过的,子集和问题(子集和问题)。另一个是旅行推销员的问题,寻求找到一个可以通过一些交通系统连接的一组N个城市的最短路线,然后回到起点。(就是离散数中的邮递员问题),就任何人都知道,在多项式时间中不可能解决子集合问题或旅行推销员问题。最有名的方法在最坏的情况下都具有指数性能,并且在产生所有可能的路和比较成本方面效率相当。一般来说,每个这些问题最有效的解决方案是尝试所有可能性,这需要指数时间。但是另一方面,没有人能够最终证明不存在这个问题的多项式时间算法。可能会有一些聪明的算法使这些问题易于处理。如果真的是这样,那么目前认为很困难的许多问题也将进入易处理的范围。

关于像子集和问题跟邮递员问题的这一类问题,计算机科学家将它称为NP-完全问题(* NP-complete problems*),是否可以在多项式时间内解决问题是计算机科学中最重要的开放性问题之一,同样这也疏于数学领域的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值