挑战408——数据结构(3)——Big-O表示法

对比上篇的文章,通常对于数值型的操作,通常可以用数值的本身来反映问题的规模。对于大多数的关于数组或者vector的算法,通常参与运算的数的多少本身就代表了问题的规模。在评估算法效率过程中,我们通常用字母N来表示问题规模的大小,当N逐渐增大时,N与算法性能之间的关系称为算法复杂度The relationship between N and the performance of an algorithm as N becomes large is called the computational complexity of that algorithm
通常,算法最重要的性能指标就是执行时间,尽管算法分析的时候,也要考虑到运行时所需的内存空间,但是因为随着科技的发展,现在的空间已经不是首要的考虑因素。所以我们考虑问题首先考虑时间复杂性。
通常我们使用一种称为大O符号的特殊速记符号来表示算法的计算复杂度。该符号是德国数学家保罗·巴赫曼(Paul Bachmann)在1892年前在计算机开发之前提出的。符号本身很简单,由字母O组成,后跟括号括起来的公式。当用于指定计算复杂度时,公式通常是涉及问题大小N的简单函数。比如下面的表示方法:
在这里插入图片描述
记为O(N^2),(英文读作:big-oh of N squared)

Big-O的简化

使用大O符号来估计算法的计算复杂度时,其目的是提供一个对问题的定性观察,以便观察当N中的变化(如N变大)是怎么影响算法性能的。因为大O表示法并不是一个量化措施,所以要简化括号内的公式,以便以最简单的形式捕获算法的定性行为。使用big-O表示法时可以使用的最常见的简化如下:

  1. 去除当N变大时,任何对算法性能的影响不再显著的部分(Eliminate any term whose contribution to the total ceases to be significant as N becomes large)。当一个公式涉及多个部分时,其中一个部分通常比其他部分增长得更快,并且随着N变大而最终导致影响整个表达式。对于N值较大的值,此部分单独控制算法的运行时间,你可以完全忽略公式中的其他部分。(类似求极限,N->无穷,可以忽略低次幂部分)。
  2. 去除常数因素。(Eliminate any constant factors.) 当计算复杂度时,主要关注的是运行时间如何随着问题大小N的变化而变化。常数因子对整体模式没有影响。

例如:
在这里插入图片描述
可以简化为:
在这里插入图片描述
此表达式抓住了选择排序的性能的本质。随着问题的大小增加,运行时间的增长趋向于问题规模的平方。 因此,如果你将vector的大小加倍,则运行时间将增加四倍。如果你将输入值的数量乘以10,则运行时间将会增加100倍。

计算算法复杂度

根据之前的算法,算法的复杂度肯定是越小越好的。那么如何从代码中降低算法的复杂度呢?
我们先看一段代码,这段代码是用来求vector中的数据的平均值的:

double average(vector<double> & vec) {
    int n = vec.size();
    double total = 0;
    for (int i = 0; i < n; i++) {
    total += vec[i];
}
    return total / n;
}

现在我们来分析一下这个算法的复杂度。
当调用此函数时,代码的某些部分只会执行一次,例如初始化total为0,并在返回语句中进行除法运算。这些计算需要一定的时间,但是他们不依赖于vector大小的意义上(即无论vector多大,他们运行的时间和次数都是确定的),运行时间是恒定的。对于这种执行时间不依赖于问题大小的代码被称为以恒定时间运行(constant time),以大O表示法表示为O(1)。
有些人可能会对O(1)的心存疑惑,因为O记法是要依赖于N的。但是实际上,这种不依赖于N的符号,是O(1)符号的整体。因为增加问题的大小时,这部分代码的运行时间根本不增加(因为它是确定的,就像数学函数中的Y = 1的一条线,它斜率为0,表示随着问题规模的增大,运行时间不变。)。
现在再来分析一下for循环里面的语句:


for (int i = 0; i < n; i++) {
    total += vec[i];
}

对于for循环来说,的每个循环都是执行 一次。这些部分包括在for循环语句中的表达式i ++还有下面的语句,它们构成循环体:

total += vec[i];

虽然这里这部分操作的每执行一次执行都花费了固定的时间t(假设这部分操作为 i 自增一次,total加一次),那么具体要执行多少次就由for循环中的n决定。也就是说这些语句执行n次的意味着它们的总执行时间T与vector的大小N成正比。也就是T = O(tN),根据O记法,这里应该省略常数。即该函数的算法复杂度为O(N),这种复杂度通常称为线性时间(linear time)
当然我们还可以通过查看代码的循环结构来估算算法复杂度。在大多数情况下,单个表达式和语句,除非它们涉及必须单独考虑的函数调用,否则会在固定的时间里运行。在计算复杂性方面重要的是这些语句的执行频率。对于许多程序,可以通过找到最常执行的代码片段来确定计算复杂度,并确定其作为N的函数被执行了多少次。
例如在average函数的情况下,循环体是执行n次 因为代码的任何部分都不会比这更频繁地执行,所以我们可以预测计算复杂度将为O(N)。
这个时候我们可以用同样的方式对选择排序函数进行分析。代码中最常执行的部分是语句中的比较两个数的大小:

if (vec[i] < vec[rh]) rh = i;

该语句嵌套在两个for循环中,并且都受N的值的限制。内循环运行的次数跟外循环一样频繁,也就意味着内循环中的语句的运行时间为O(N^2).

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值