绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
数据结构——算法初步(2)——big-O记法(1)
2020-05-20 12:08:38

对比上篇的文章,通常对于数值型的操作,通常可以用数值的本身来反映问题的规模。对于大多数的关于数组或者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 = ;
    for (int i = ; 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).

分享好友

分享这个小栈给你的朋友们,一起进步吧。

数据架构
创建时间:2020-05-20 11:23:41
有关数据架构的小栈里面全都有
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 小雨滴
    专家
戳我,来吐槽~